import matplotlib
if not hasattr(matplotlib.RcParams, "_get"):
    matplotlib.RcParams._get = dict.get

6.1. Assembly and np._ix#

Large matrices for PDE simulations are usually constructed from smaller matrices. Particularly in the finite element method, this is usually done element-by-element. The mesh is defined with two arrays, one containing the coordinates of the nodes, and another containing information on how the nodes are connected in elements. In a real finite element code, the element matrix would depend on thecoordinates, but below we work with hard-coded element matrix, so we do not need to define node coordinates. The purpose is just to illustrate how the global matrix is assembled on a system that is small enough to be able to print the whole thing.

Example code for finite element matrix assembly#

import numpy as np

# Connectivity matrix
elements = np.array([ 
    [0, 1, 4], 
    [1, 2, 4], 
    [2, 3, 4], 
    [3, 0, 4] 
    ])

# Element matrix (simplified)
K_elem = np.array([
        [2.0, -1.0, -1.0],
        [-1.0, 2.0, -1.0],
        [-1.0, -1.0, 2.0]
    ])

n_nodes = np.max(elements)+1

# METHOD 1: Naive approach with double loop over the element matrix
K_naive = np.zeros((n_nodes, n_nodes))
for elem_nodes in elements:
    for i in range(3):
        for j in range(3):
            K_naive[elem_nodes[i], elem_nodes[j]] += K_elem[i, j]

# METHOD 2: Efficient approach using numpy.ix_
K_efficient = np.zeros((n_nodes, n_nodes))
for elem_nodes in elements:
    K_efficient[np.ix_(elem_nodes, elem_nodes)] += K_elem    


print("Global matrix from naive implementation")
print(K_naive)

print("\nGlobal matrix from efficient implementation")
print(K_efficient)

As you can see when running the cell above, the two assembly methods lead to the same matrix.

Questions

The global matrix contains four 0’s. Can you explain their position?

The diagonal of the global matrix contains all 4’s and one 8. Can you explain why the values are as they are?

Vector assembly#

In the context of this lesson it is good to recall that for indexing in vectors, we do not need np._ix. As an example, consider that we are just interested in the diagonal of the global matrix. This can be represented as a vector, which we can assemble by every time adding the diagonal vector of the element matrix to the correct entries.

d_elem = np.diag(K_elem)
d_global = np.zeros(n_nodes)

for elem_nodes in elements:
    d_global[elem_nodes] += d_elem

print("Global vector assembled without loop over nodes and without np._ix")
print(d_global)

Attribution

This chapter is written by Frans van der Meer. Find out more here.