Assembling matrices with sparseutils#
The matrices passed to the solvers are SciPy sparse matrices. The module
sparseutils helps assembling them from the contributions of the
elements, of the integration points or of the pairs of approximation
functions of a model.
Symmetric matrices from the upper triangle#
The stiffness, geometric stiffness and mass matrices of conservative
problems are symmetric. Only their upper triangle, row \(\le\) column, needs
to be computed and stored, as three arrays of row indices, column indices
and values of a scipy.sparse.coo_matrix, in which repeated entries
are summed when the matrix is converted to another format.
sparseutils.make_symmetric() completes the lower triangle from the
upper one, ignoring the terms already below the diagonal, and
sparseutils.finalize_symmetric_matrix() also checks that no stored
value is nan or inf and returns a CSR matrix, the format used by the
solvers.
Unsymmetric matrices#
A matrix with a skew-symmetric part, e.g. a load stiffness matrix, is the
sum of its symmetric part, \((\boldsymbol{A} + \boldsymbol{A}^\top)/2\), and of its skew-symmetric
part, \((\boldsymbol{A} - \boldsymbol{A}^\top)/2\), assembled from their upper triangles with
sparseutils.make_symmetric() and
sparseutils.make_skew_symmetric(). Unsymmetric matrices may also be
assembled in full. linear_buckling.is_symmetric() and
sparseutils.is_symmetric() check the symmetry of the result, with a
relative norm and entry by entry, respectively.
Constrained degrees of freedom#
The rows and columns of the constrained dofs may be left null, and the
solvers remove them with sparseutils.remove_null_cols(), see
Boundary conditions and vector sizes.
Example#
A chain of springs assembled from the upper triangles of its element matrices, and a general matrix from its symmetric and skew-symmetric parts:
def test_assembly_upper_triangle():
"""Assembly of a symmetric matrix from the upper triangle of element
matrices, with repeated entries summed, and of an unsymmetric matrix
from its symmetric and skew-symmetric parts"""
from structsolve.sparseutils import finalize_symmetric_matrix
# chain of springs, element e connects the dofs e and e + 1
k = np.array([1., 2., 3., 4.])
n = k.size + 1
rows, cols, vals = [], [], []
for e, ke in enumerate(k):
# upper triangle of ke*[[1, -1], [-1, 1]]
rows += [e, e, e + 1]
cols += [e, e + 1, e + 1]
vals += [ke, -ke, ke]
K_upper = coo_matrix((vals, (rows, cols)), shape=(n, n))
K = finalize_symmetric_matrix(K_upper)
assert isinstance(K, csr_matrix)
K_ref = np.zeros((n, n))
for e, ke in enumerate(k):
K_ref[e:e+2, e:e+2] += ke*np.array([[1., -1.], [-1., 1.]])
np.testing.assert_allclose(K.toarray(), K_ref)
# the terms below the diagonal are ignored
np.testing.assert_allclose(make_symmetric(csr_matrix(K_ref)).toarray(),
K_ref)
# general matrix from the upper triangles of its two parts
rng = np.random.default_rng(0)
A = rng.standard_normal((n, n))
sym = np.triu(0.5*(A + A.T))
skew = np.triu(0.5*(A - A.T))
A_rebuilt = make_symmetric(coo_matrix(sym)) + make_skew_symmetric(
coo_matrix(skew))
np.testing.assert_allclose(A_rebuilt.toarray(), A)
# nan values are detected
K_upper.data[0] = np.nan
with pytest.raises(AssertionError):
finalize_symmetric_matrix(K_upper)