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