Matrices for graph traversal

In [1]:
import numpy as np
In [2]:
n = 5

# Make a sparsely populated random matrix
A = np.zeros((n, n))

from random import randrange, uniform
for i in range(n*n//2):
    i, j = randrange(n), randrange(n)
    w = round(uniform(0, 1), 1)
    A[i, j] = w
    
A
Out[2]:
array([[ 0.5,  0.8,  0. ,  0.5,  0.4],
       [ 0. ,  0.7,  0.7,  0.3,  0. ],
       [ 0.5,  0. ,  0. ,  0. ,  0. ],
       [ 0. ,  0. ,  0. ,  0. ,  0. ],
       [ 0. ,  0. ,  0.7,  0. ,  0. ]])

For a reason that will become clear in a minute, we need the columns of $A$ to be normalized to sum to 1:

In [3]:
A_cols = np.sum(A, axis=0)
A_cols[A_cols == 0] = 1
A = A/A_cols

print(A)
print(np.sum(A, axis=0))
[[ 0.5         0.53333333  0.          0.625       1.        ]
 [ 0.          0.46666667  0.5         0.375       0.        ]
 [ 0.5         0.          0.          0.          0.        ]
 [ 0.          0.          0.          0.          0.        ]
 [ 0.          0.          0.5         0.          0.        ]]
[ 1.  1.  1.  1.  1.]

This short piece of code exports the matrix in a format that's readable to the dot graph drawing tool.

In [4]:
def to_dot(A, vec=None):
    lines = ['digraph mygraph { size="2,2";']
    for i in range(n):
        for j in range(n):
            if A[i, j]:
                lines.append("%d -> %d [label=\"%0.1f\"];" % (j, i, A[i, j]))
    if vec is not None:
        for i, vec_i in enumerate(vec):
            assert 0<=vec_i<=1
            lines.append(
                '%d [style="filled", fillcolor="#ff%02xff"];'
                % (i, int(255*(1-vec_i))))
    lines.append("}")
    return "\n".join(lines)

See?

In [5]:
print(to_dot(A))
digraph mygraph { size="2,2";
0 -> 0 [label="0.5"];
1 -> 0 [label="0.5"];
3 -> 0 [label="0.6"];
4 -> 0 [label="1.0"];
1 -> 1 [label="0.5"];
2 -> 1 [label="0.5"];
3 -> 1 [label="0.4"];
0 -> 2 [label="0.5"];
2 -> 4 [label="0.5"];
}

In [6]:
%load_ext gvmagic
In [7]:
%dotstr to_dot(A)
mygraph 0 0 0->0 0.5 2 2 0->2 0.5 1 1 1->0 0.5 1->1 0.5 3 3 3->0 0.6 3->1 0.4 4 4 4->0 1.0 2->1 0.5 2->4 0.5

Another thing we can do is plot distributions on the graph:

In [9]:
d = np.zeros((n,))
d[0] = 1
%dotstr to_dot(A, d)
mygraph 0 0 0->0 0.5 2 2 0->2 0.5 1 1 1->0 0.5 1->1 0.5 3 3 3->0 0.6 3->1 0.4 4 4 4->0 1.0 2->1 0.5 2->4 0.5
In [10]:
d
Out[10]:
array([ 1.,  0.,  0.,  0.,  0.])

Now, how would we model the spread of this distribution across the graph?

In [73]:
d = A.dot(d)
print(d)
%dotstr to_dot(A, d)
[ 0.45070423  0.21126761  0.22535211  0.          0.11267606]

mygraph 0 0 0->0 0.5 2 2 0->2 0.5 1 1 1->0 0.5 1->1 0.5 3 3 3->0 0.6 3->1 0.4 4 4 4->0 1.0 2->1 0.5 2->4 0.5
In [47]:
d
Out[47]:
array([ 0.45070426,  0.2112676 ,  0.2253521 ,  0.        ,  0.11267604])

More questions:

  • How would you find the steady state of this traversal?
  • Any predictions about np.sum(d)?