```
In [1]:
```import numpy as np

Now, assuming we have the following matrix structure

```
In [2]:
```A = np.array([[1, 1, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0],
[0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 1, 1]])

```
In [3]:
``````
A
```

```
Out[3]:
```

```
In [4]:
```dof = 3

All we need is to define a blow matrix as follow

```
In [5]:
```B = np.ones((dof, dof))

```
In [6]:
``````
B
```

```
Out[6]:
```

`kron`

) of the matrix `A`

with this matrix `B`

will result in a block matrix with similar shape as `A`

```
In [7]:
```A_block = np.kron(A, B)

For more info about the Kronecker product, check this link.

Now let's view the resulting structure of our matrix by defining the following auxiliary plot method

```
In [8]:
```%matplotlib inline
import matplotlib.pyplot as plt
def spy(A, precision=0.1, markersize=5, figsize=None):
if figsize:
fig = plt.figure(figsize=figsize)
else:
fig = plt.figure()
plt.spy(A, precision=precision, markersize=markersize)
plt.show()

```
In [9]:
```spy(A_block)

```
```

```
In [10]:
```import scipy.sparse as sp

Let's create a simple sparse diagonal matrix.

```
In [11]:
```N = 10

```
In [12]:
```A = sp.diags([1, 1, 1], [-1, 0, 1], shape=(N, N))

The matrix `B`

can be the same (dense) matrix.

```
In [13]:
```A_block_sparse = sp.kron(A, B)

```
In [14]:
```spy(A_block_sparse)

```
```

```
In [15]:
```B = [[1, 0], [0, 1]]
A_new = sp.kronsum(A_block_sparse, B)
#A_new = sp.kronsum(A_block_sparse, [[1, 1, 1, 1], [1, 1, 1, 1 ], [1, 1, 1, 1], [1, 1, 1, 1]])
spy(A_new)

```
```

Inverting the product order

```
In [16]:
```B = [[1, 1], [1, 1]]
A_new = sp.kronsum(B, A_block_sparse)
spy(A_new)

```
```

We can check the definition of the `kronsum`

here. I am not sure if it matches the `scipy`

definition.

```
In [17]:
```import networkx as nx
G = nx.Graph()
G.add_edge(0,1)
G.add_edge(1,2)
G.add_edge(1,3)

`networkx`

, the matrix structure associated with the graph above is easily obtained by using the `laplacian_matrix`

method.

```
In [18]:
```nx.laplacian_matrix(G).toarray()

```
Out[18]:
```

`to_scipy_sparse_matrix`

(with a few extras sums), i.e.,

```
In [19]:
```nx.to_scipy_sparse_matrix(G).toarray()

```
Out[19]:
```

```
In [20]:
```N = 3
A = sp.diags([1, 1, 1], [-1, 0, 1], shape=(N, N))
A.toarray()

```
Out[20]:
```

```
In [21]:
```G = nx.Graph()
G.add_edge(0,1)
G.add_edge(1,2)
J = []
for pipe in G.edges():
A = sp.diags([1, 1, 1], [-1, 0, 1], shape=(N, N))
J.append(A)
J = sp.block_diag(J)
spy(J)

```
```

```
In [22]:
```n_x = 3
connec = np.array([0, 1], dtype=int) + n_x - 1
B = np.zeros(J.shape[1])
B[connec] = 1
C = np.zeros((J.shape[0] + 1, 1))
C[connec, :] = 1
C[-1, :] = 1
J = sp.vstack([J, B])
J = sp.hstack([J, C])
spy(J)

```
```

`kron`

product

```
In [23]:
```dof = 3
block = np.ones((dof, dof))
J_block = sp.kron(J, block)
spy(J_block)

```
```

```
In [24]:
```from collections import OrderedDict
class OrderedDiGraph(nx.DiGraph):
node_dict_factory = OrderedDict
adjlist_dict_factory = OrderedDict
edge_attr_dict_factory = OrderedDict
G = nx.OrderedDiGraph()
G.add_edge(0,1)
G.add_edge(1,2)
G.add_edge(1,3)
G.add_edge(3,4)
G.add_edge(4,5)
G.add_edge(3,6)

Let's use `draw`

method from `networkx`

to view our (pipe) network graph

```
In [25]:
```pos = nx.spring_layout(G, k=2)
plt.figure()
nx.draw(G, pos,node_color='g', node_size=250, with_labels=True, width=6)
plt.show()

```
```

The first step is to create the base matrix structure without including the internal nodes.

```
In [26]:
```N = 10
J = []
edges_idx = {}
for i, pipe in enumerate(G.edges()):
edges_idx[pipe] = i
A = sp.diags([1, 1, 1], [-1, 0, 1], shape=(N, N))
J.append(A)
J = sp.block_diag(J)

```
In [27]:
```connections = []
# Add internal node matrix structures
for n in G.nodes():
edges = G.in_edges(n) + G.out_edges(n)
if len(edges) > 1: # if len(edges) > 1, then n is an internal node
connec = []
for i, e in enumerate(edges):
j = edges_idx[e]
if e[0] == n:
matrix_idx = j*N
else:
assert e[1] == n, 'node must match edge idx here!'
matrix_idx = (j+1)*N - 1
connec.append(matrix_idx)
connections.append(connec)

The method defined below will help us to modify the shape of `J`

by including the **nodes** in the end.

```
In [28]:
```def append_connection_nodes_to_sparse_matrix(connec, J):
B = np.zeros(J.shape[1])
B[connec] = 1
C = np.zeros((J.shape[0] + 1, 1))
C[connec, :] = 1
C[-1, :] = 1
J = sp.vstack([J, B])
J = sp.hstack([J, C])
return J

Now let's modify our matrix with the method we have just implemented above

```
In [29]:
```for connec in connections:
J = append_connection_nodes_to_sparse_matrix(connec, J)

Nice, now it's time to view some results a see how it looks like.

```
In [30]:
```spy(J, markersize=3, figsize=(5,5))

```
```

`kron`

(not so magic after you understand the mathematical definition) product to create the block matrix structure.

```
In [31]:
```dof = 3
block = np.ones((dof, dof))
J_block = sp.kron(J, block)
spy(J_block, markersize=2, figsize=(5,5))

```
```

Bigger version (zoom) of the plot above...

```
In [32]:
```spy(J_block, markersize=5.05, figsize=(12,12))

```
```