Usage¶
In this tutorial we use Braess network as an example.
Import Modules¶
Import grapharray module.
[1]:
import grapharray as ga
Define Variables¶
First, create a BaseGraph object, which describes the network on that variables are defined. It plays two roles when we define arrays:
Identifying on which network the variable is defined
Memory of the network structure.
[2]:
BG = ga.BaseGraph()
BaseGraph class is a subclass of networkx.DiGraph so you can add edges and nodes to BaseGraph instance in the same way as to DiGraph.Note that GraphArray accepts any hashable objects as nodes, as does NetworkX.
[3]:
BG.add_edges_from([
('start', 'A'),
('start', 'B') ,
('A', 'B'),
('A', 'end'),
('B', 'end')
])
Then, freeze BaseGraph object to prevent being modified after defining variables.
[4]:
BG.freeze()
Finally, create NodeArray instance to define node variables or EdgeArray instance to define edge variables. We must pass a frozen BaseGraph object to array classes.
[5]:
od_flow = ga.NodeArray(BG)
print(repr(od_flow))
edge_cost = ga.EdgeArray(BG)
print(repr(edge_cost))
index value
start 0.0
A 0.0
B 0.0
end 0.0
index value
('start', 'A') 0.0
('start', 'B') 0.0
('A', 'B') 0.0
('A', 'end') 0.0
('B', 'end') 0.0
These codes make variables defined on all nodes or edges of BG, all of whose values are zero.
You can set initial values of variables as you want by giving a keyword argument init_val
. The argument init_val
accepts several types of variables. if you want to set all initial values as the same value, simply give a scalar:
[6]:
od_flow = ga.NodeArray(BG, init_val=10)
print(repr(od_flow))
edge_cost = ga.EdgeArray(BG, init_val=10)
print(repr(edge_cost))
index value
start 10.0
A 10.0
B 10.0
end 10.0
index value
('start', 'A') 10.0
('start', 'B') 10.0
('A', 'B') 10.0
('A', 'end') 10.0
('B', 'end') 10.0
or if you want to set each value in detail, give * a dictionary that has node- or edge- indexes as keys and initial values as values:
[7]:
od_flow = ga.NodeArray(BG, init_val={
'start': -6,
'A': 0,
'B': 0,
'end': 6
})
print(repr(od_flow))
edge_cost = ga.EdgeArray(BG, init_val={
('start', 'A'): 0,
('start', 'B'): 50 ,
('A', 'B'): 10,
('A', 'end'): 50,
('B', 'end'): 0
})
print(repr(edge_cost))
index value
start -6.0
A 0.0
B 0.0
end 6.0
index value
('start', 'A') 0.0
('start', 'B') 50.0
('A', 'B') 10.0
('A', 'end') 50.0
('B', 'end') 0.0
a NodeArray or EdgeArray object (initializing by them is faster than that by dictionary)
[8]:
new_od_flow = ga.NodeArray(BG, init_val=od_flow)
print(repr(new_od_flow))
new_edge_cost = ga.EdgeArray(BG, init_val=edge_cost)
print(repr(new_edge_cost))
index value
start -6.0
A 0.0
B 0.0
end 6.0
index value
('start', 'A') 0.0
('start', 'B') 50.0
('A', 'B') 10.0
('A', 'end') 50.0
('B', 'end') 0.0
Update array values¶
You can modify values after creating instances as we show below.
[9]:
new_od_flow['A'] = 100
print(repr(new_od_flow))
new_edge_cost['A', 'B'] = 100
print(repr(new_edge_cost))
index value
start -6.0
A 100.0
B 0.0
end 6.0
index value
('start', 'A') 0.0
('start', 'B') 50.0
('A', 'B') 100.0
('A', 'end') 50.0
('B', 'end') 0.0
Mathematical Operations¶
NodeArray and EdgeArray objects can be added to, subtracted from, multiplied by and divided by another objects of the same classes.
[11]:
print(repr(new_od_flow + od_flow))
print(repr(new_od_flow - od_flow))
print(repr(new_od_flow * od_flow))
print(repr(new_od_flow / od_flow)) # this raises warnings because of the zero division
index value
start -12.0
A 100.0
B 0.0
end 12.0
index value
start 0.0
A 100.0
B 0.0
end 0.0
index value
start 36.0
A 0.0
B 0.0
end 36.0
index value
start 1.0
A inf
B nan
end 1.0
NodeArray and EdgeArray objects also operated with scalar values.
[13]:
print(repr(new_od_flow + 5))
print(repr(new_od_flow - 5))
print(repr(new_od_flow * 5))
print(repr(new_od_flow / 5))
index value
start -1.0
A 105.0
B 5.0
end 11.0
index value
start -11.0
A 95.0
B -5.0
end 1.0
index value
start -30.0
A 500.0
B 0.0
end 30.0
index value
start -1.2
A 20.0
B 0.0
end 1.2
Visualizing Values¶
(Coming soon…)
Computational Efficiency¶
NodeArray and EdgeArray stores variables’ values as np.ndarray and the mathematical operations shown above are operated with these arrays.
[14]:
print(new_od_flow.array) # You can see the array by .array property.
new_od_flow.array[1] = 5 # .array is read-only
print(new_od_flow.array)
[ -6. 100. 0. 6.]
[ -6. 100. 0. 6.]
Thus, these operation is as fast as that of np.ndarray. The larger the network is, the smaller the difference between the speed of these two methods are.
[15]:
# Create a huge graph to show computational efficiency.
import random
import numpy as np
import time
import timeit
BG = ga.BaseGraph()
BG.add_nodes_from(list(range(10000)))
for i in range(20000):
edge = random.sample(BG.nodes, 2)
BG.add_edge(*edge)
BG.freeze()
timeit_args = {
'timer': time.process_time, 'number': 100000, 'globals': globals()
}
[16]:
print("calculation with NodeArray ============")
e1 = ga.NodeArray(BG, init_val = 1)
e2 = ga.NodeArray(BG, init_val = 2.5739)
print(timeit.timeit("e1 + e2", **timeit_args))
print("calculation with np.ndarray =========")
a1 = e1.array
a2 = e2.array
print(timeit.timeit("a1 + a2", **timeit_args))
calculation with NodeArray ============
0.5805510000000003
calculation with np.ndarray =========
0.36343000000000014
[17]:
print("calculation with EdgeArray ============")
e1 = ga.EdgeArray(BG, init_val = 1)
e2 = ga.EdgeArray(BG, init_val = 2.5739)
print(timeit.timeit("e1 + e2", **timeit_args))
print("calculation with np.ndarray =========")
a1 = e1.array
a2 = e2.array
print(timeit.timeit("a1 + a2", **timeit_args))
calculation with EdgeArray ============
0.887826
calculation with np.ndarray =========
0.6614020000000003
[18]:
print("calculation with graphvar ============")
e = ga.EdgeArray(BG, init_val = 1)
A = ga.IncidenceMatrix(BG)
print(timeit.timeit("A @ e", **timeit_args))
print("calculation with np.ndarray =========")
e = e.array
A = A.array
print(timeit.timeit("A @ e", **timeit_args))
calculation with graphvar ============
5.138795000000001
calculation with np.ndarray =========
4.9411369999999994