Source code for felupe.assembly.expression._basis

# -*- coding: utf-8 -*-
"""
This file is part of FElupe.

FElupe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

FElupe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with FElupe.  If not, see <http://www.gnu.org/licenses/>.
"""

import numpy as np

try:
    from einsumt import einsumt
except ModuleNotFoundError:
    from numpy import einsum as einsumt


[docs] class BasisArray(np.ndarray): """Add the grad- and hess-attributes to an existing array [1]_, [2]_. Parameters ---------- input_array : array_like The input array. grad : array_like or None, optional The array for the grad-attribute (default is None). hess : array_like or None, optional The array for the hess-attribute (default is None). Examples -------- .. pyvista-plot:: >>> import numpy as np >>> import felupe as fem >>> >>> x = fem.assembly.expression.BasisArray( >>> np.ones(3), grad=np.zeros((3, 3)), hess=np.zeros((3, 3, 3)) >>> ) >>> x BasisArray([1., 1., 1.]) >>> x.grad array([[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]) >>> x.hess array([[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]], <BLANKLINE> [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]], <BLANKLINE> [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]]) References ---------- .. [1] https://numpy.org/doc/stable/user/basics.subclassing.html .. [2] https://numpy.org/doc/stable/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array """ def __new__(cls, input_array, grad=None, hess=None): obj = np.asarray(input_array).view(cls) obj.grad = grad obj.hess = hess return obj def __array_finalize__(self, obj): if obj is None: return self.grad = getattr(obj, "grad", None) self.hess = getattr(obj, "hess", None)
[docs] class BasisField: r"""Basis and its gradient for a field. Parameters ---------- field : Field A field on which the basis is created. parallel : bool, optional (default is False) Flag to activate parallel (threaded) basis evaluation. Attributes ---------- basis : ndarray The evaluated basis functions at quadrature points. grad : ndarray The evaluated gradient of the basis (if provided by the region). hess : ndarray The evaluated hessian of the basis (if provided by the region). Notes ----- *Basis* refers to the trial and test field, either values or gradients evaluated at quadrature points. The first two indices of a basis are used for looping over the element shape functions ``a`` and its components ``i``. The third index represents the vector component ``j`` of the field. The two trailing axes ``(q, c)`` contain the evaluated element shape functions at quadrature points ``q`` per cell ``c``. .. math:: \varphi_{ai~j~qc} = \delta_{ij} \left( h_a \right)_{qc} For gradients, the fourth index is used for the vector component of the partial derivative ``k``. .. math:: \text{grad}(\varphi)_{ai~jK~qc} = \delta_{ij} \left( \frac{\partial h_a}{\partial X_K} \right)_{qc} Examples -------- .. pyvista-plot:: >>> import numpy as np >>> import felupe as fem >>> >>> mesh = fem.Rectangle() >>> region = fem.RegionQuad(mesh) >>> displacement = fem.Field(region, dim=2) >>> >>> bf = fem.assembly.expression.BasisField(displacement) >>> bf.basis.shape (4, 2, 2, 4, 1) >>> bf.basis.grad.shape (4, 2, 2, 2, 4, 1) >>> bf.basis.hess.shape (4, 2, 2, 2, 2, 4, 1) See Also -------- felupe.assembly.expression.Basis : Bases and their gradients for the fields of a field container. """ def __init__(self, field, parallel=False): self.field = field einsum = einsumt if parallel else np.einsum basis = einsum( "ij,aqc->aijqc", np.eye(self.field.dim), self.field.region.h, ) if self.field.region.evaluate_gradient: grad = einsum( "ij,akqc->aijkqc", np.eye(self.field.dim), self.field.region.dhdX ) else: grad = np.full(basis.shape[:2], None) if self.field.region.evaluate_hessian: hess = einsum( "ij,aklqc->aijklqc", np.eye(self.field.dim), self.field.region.d2hdXdX ) else: hess = np.full(basis.shape[:2], None) self.basis = BasisArray(basis, grad=grad, hess=hess)
[docs] class Basis: r"""Bases and their gradients for the fields of a field container. Parameters ---------- field : FieldContainer A field container on which the basis should be created. Attributes ---------- basis : ndarray The list of bases. Examples -------- .. pyvista-plot:: >>> import numpy as np >>> import felupe as fem >>> >>> mesh = fem.Rectangle() >>> region = fem.RegionQuad(mesh) >>> displacement = fem.Field(region, dim=2) >>> field = fem.FieldContainer([displacement]) >>> >>> bases = fem.assembly.expression.Basis(field) >>> len(bases[:]) >>> 1 >>> bases[0].basis.shape (4, 2, 2, 4, 1) >>> bases[0].basis.grad.shape (4, 2, 2, 2, 4, 1) >>> bases[0].basis.hess.shape (4, 2, 2, 2, 2, 4, 1) See Also -------- felupe.assembly.expression.BasisField : Basis and its gradient for a field. """ def __init__(self, field, parallel=False): self.field = field self.basis = [BasisField(f, parallel=parallel) for f in self.field] def __getitem__(self, idx): "Slice-based access to underlying bases." return self.basis[idx]