Source code for jax_sn.operators.mass_operator.mass_operator_base
import jax
import jax.numpy as jnp
from jax_sn.solution_domain import PrecomputedGeometricData, FEMMatrices
import lineax as lx
import equinox as eqx
from jax_sn.operators.base_linear_operator import (
BaseOperatorData, SymmetricDataStrategyLinearOperator, SymmetricStrategy,
)
from typing import Tuple
from abc import abstractmethod
[docs]
class MassData(BaseOperatorData):
fem_matrices : FEMMatrices
precomputed_geometric_data : PrecomputedGeometricData
# ---------------------------------------------------------------------------
# Mass strategies
# ---------------------------------------------------------------------------
[docs]
class MassStrategy(SymmetricStrategy):
'''How to apply the mass operator. Symmetric, so ``transpose`` == ``forward``.'''
[docs]
@abstractmethod
def forward(self, data: MassData, x):
...
[docs]
class MassOperator(SymmetricDataStrategyLinearOperator):
r'''
Mass operator.
Computes for a given vector :math:`\{ v_j \}` the mass matrix map:
.. math::
a_i = \sum_{j} \int H_i H_j dV v_j
Is arbitrarily batched over additional leading dimensions.
Mathematical description
---------------------------
The non-batched version computes the mass matrix for each element and basis function:
.. math::
\overline{\mathbf{H}} : F \to F
where in this case:
.. math::
F : \{ \text{all elements} \} \otimes \{ \text{all basis functions per element} \}
and since all elements are independent, the mass matrix is block-diagonal with each block representing the mass matrix for one element.
Its definition is (:math:`e,p` is element index, `i,j` basis function index):
.. math::
\overline{\mathbf{H}}[\{ v_{ei} \}] = a_{ej} = \sum_{i} J_{vq} \int_{B} H_i H_j dV v_{ei}
where :math:`J_{vq}` is the volume Jacobian determinant for element :math:`e`, and :math:`B` is the reference element.
Note this integral is already computed in :attr:`jax_sn.solution_domain.FEMMatrices.mass_matrix`, and the Jacobian determinants are precomputed and stored :attr:`jax_sn.solution_domain.PrecomputedGeometricData.vol_jacobians_det`.
Transposition
---------------------------
This operator is symmetric since the mass matrix is symmetric. Transposition thus returns itself.
The strategy therefore implements a single :meth:`~.mass_operator_strategies.MassStrategy.apply`
(forward == transpose); there is no ``transpose_bool``.
Structure
---------------------------
Batched versions operate over additional leading dimensions, e.g.:
.. math::
&\mathbf{H} : \mathcal{K} \otimes \mathcal{F} \to \mathcal{K} \otimes \mathcal{F}\\
&\mathbf{H} = \mathbf{I}_{\mathcal{K}} \otimes \overline{\mathbf{H}}
where :math:`\mathcal{K}` is an arbitrary additional space (e.g. angles).
In essence, this just is a batched matrix product (and is implemented like that as well).
'''
data : MassData
'''MassData containing FEM matrices and precomputed geometric data. [differentiable]'''
additional_shape : Tuple[int]
'''Additional leading shape to batch over. Used for consistent in/out structure definitions.'''
strategy : MassStrategy
'''How to apply the mass contraction. See :mod:`~jax_sn.operators.mass_operator.mass_operator_strategies`.'''
def _structure(self):
n_elements = self.data.precomputed_geometric_data.n_elements
n_basis = self.data.fem_matrices.n_basis
return jax.ShapeDtypeStruct(self.additional_shape + (n_elements, n_basis), self.data.fem_matrices.mass_matrix.dtype)
@lx.is_symmetric.register(MassOperator)
def _(operator : MassOperator):
return True