Source code for firedrake.matrix

from typing import Any
from collections.abc import Iterable
import itertools

import ufl
from pyop2.utils import as_tuple
from pyop2 import op2
from pyop2.types.mat import _GlobalMatPayload, _DatMatPayload
from firedrake.petsc import PETSc
from firedrake.bcs import DirichletBC
from firedrake.matrix_free import ImplicitMatrixContext
from firedrake.slate import slate

__all__ = ("MatrixBase", "Matrix", "ImplicitMatrix", "AssembledMatrix")


class DummyOP2Mat:
    """A hashable implementation of M.handle"""
    def __init__(self, handle):
        self.handle = handle


def _get_mat_type(petscmat: PETSc.Mat) -> str:
    """Maps PETSc matrix types to Firedrake notation"""
    from firedrake.interpolation import VomOntoVomMatContext

    mat_type = petscmat.getType()
    if mat_type == "python":
        ctx = petscmat.getPythonContext()
        if isinstance(ctx, ImplicitMatrixContext):
            return "matfree"
        elif isinstance(ctx, _GlobalMatPayload):
            return "global"
        elif isinstance(ctx, _DatMatPayload):
            return "dat"
        elif isinstance(ctx, VomOntoVomMatContext):
            return "vomtovom"
        else:
            raise NotImplementedError(
                f"Python matrix context type '{type(ctx).__name__}' not recognised"
            )
    elif mat_type.startswith("seq") or mat_type.startswith("mpi"):
        return mat_type[3:]
    else:
        return mat_type


[docs] class MatrixBase(ufl.Matrix): """A representation of the linear operator associated with a bilinear form and bcs. Explicitly assembled matrices and matrix-free .matrix classes will derive from this. Parameters ---------- a A UFL BaseForm (with two arguments) that this MatrixBase represents, or a tuple of the arguments it represents, or a slate TensorBase. bcs An optional iterable of boundary conditions to apply to this :class:`MatrixBase`. Empty tuple by default. fc_params A dictionary of form compiler parameters for this matrix. """ def __init__( self, a: ufl.BaseForm | slate.TensorBase | tuple[ufl.Argument | ufl.Coargument, ufl.Argument | ufl.Coargument], bcs: Iterable[DirichletBC] = (), fc_params: dict[str, Any] | None = None, ): """Initialise a :class:`MatrixBase`.""" if isinstance(a, tuple): self.a = None test, trial = a arguments = a elif isinstance(a, ufl.BaseForm | slate.TensorBase): self.a = a test, trial = a.arguments() arguments = None else: raise TypeError(f"'a' must be a UFL BaseForm, slate TensorBase, or tuple of arguments, not {type(a).__name__}.") # Iteration over bcs must be in a parallel consistent order # (so we can't use a set, since the iteration order may differ # on different processes) super().__init__(test.function_space(), trial.function_space()) # ufl.Matrix._analyze_form_arguments sets the _arguments attribute to # non-Firedrake objects, which breaks things. To avoid this we overwrite # this property after the fact. self._analyze_form_arguments() self._arguments = arguments self.comm = test.function_space().comm self.block_shape = (len(test.function_space()), len(trial.function_space())) self.bcs = bcs self.form_compiler_parameters = {} if fc_params is None else fc_params
[docs] def arguments(self): if self.a: return self.a.arguments() else: return self._arguments
[docs] def ufl_domains(self): return self._domains
@property def has_bcs(self): """Return True if this :class:`MatrixBase` has any boundary conditions attached to it.""" return self._bcs != () @property def bcs(self): """The set of boundary conditions attached to this :class:`.MatrixBase` (may be empty).""" return self._bcs @bcs.setter def bcs(self, bcs): """Attach some boundary conditions to this :class:`MatrixBase`. :arg bcs: a boundary condition (of type :class:`.DirichletBC`), or an iterable of boundary conditions. If bcs is None, erase all boundary conditions on the :class:`.MatrixBase`. """ if bcs is not None: self._bcs = tuple(itertools.chain(*(as_tuple(bc) for bc in bcs))) else: self._bcs = () def __repr__(self): return f"{type(self).__name__}(a={self.a!r}, bcs={self.bcs!r})" def __str__(self): return f"assembled {type(self).__name__}(a={self.a}, bcs={self.bcs})"
[docs] def assign(self, val): """Set matrix entries.""" if isinstance(val, MatrixBase): val.petscmat.copy(self.petscmat) else: raise TypeError(f"Cannot assign a {type(val).__name__} to a {type(self).__name__}.") return self
[docs] def zero(self): """Set all matrix entries to zero.""" self.petscmat.zeroEntries() return self
[docs] class Matrix(MatrixBase): """A representation of an assembled bilinear form. Parameters ---------- a The bilinear form this :class:`Matrix` represents. mat The underlying matrix object. Either a PyOP2 Mat or a PETSc Mat. bcs An iterable of boundary conditions to apply to this :class:`Matrix`. Empty tuple by default. fc_params A dictionary of form compiler parameters for this matrix, by default None. options_prefix PETSc options prefix to apply, by default None. """ def __init__( self, a: ufl.BaseForm, mat: op2.Mat | PETSc.Mat, bcs: Iterable[DirichletBC] = (), fc_params: dict[str, Any] | None = None, options_prefix: str | None = None, ): """Initialise a :class:`Matrix`.""" super().__init__(a, bcs=bcs, fc_params=fc_params) if isinstance(mat, op2.Mat): self.M = mat elif isinstance(mat, PETSc.Mat): self.M = DummyOP2Mat(mat) else: raise TypeError(f"'mat' must be a PyOP2 Mat or PETSc Mat, not {type(mat).__name__}.") self.petscmat = self.M.handle if options_prefix is not None: self.petscmat.setOptionsPrefix(options_prefix) self.mat_type = _get_mat_type(self.petscmat)
[docs] class ImplicitMatrix(MatrixBase): """A representation of the action of bilinear form operating without explicitly assembling the associated matrix. This class wraps the relevant information for Python PETSc matrix. Parameters ---------- a The bilinear form this :class:`ImplicitMatrix` represents. ctx An :class:`firedrake.matrix_free.operators.ImplicitMatrixContext` that defines the operations of the matrix. bcs An iterable of boundary conditions to apply to this :class:`Matrix`. May be `None` if there are no boundary conditions to apply. Empty tuple by default. fc_params A dictionary of form compiler parameters for this matrix, by default None. options_prefix PETSc options prefix to apply, by default None. """ def __init__( self, a: ufl.BaseForm, ctx: ImplicitMatrixContext, bcs: Iterable[DirichletBC] = (), fc_params: dict[str, Any] | None = None, options_prefix: str | None = None, ): """Initialise a :class:`ImplicitMatrix`.""" super().__init__(a, bcs=bcs, fc_params=fc_params) self.petscmat = PETSc.Mat().create(comm=self.comm) self.petscmat.setType("python") self.petscmat.setSizes((ctx.row_sizes, ctx.col_sizes), bsize=ctx.block_size) self.petscmat.setPythonContext(ctx) if options_prefix is not None: self.petscmat.setOptionsPrefix(options_prefix) self.petscmat.setUp() self.petscmat.assemble() self.mat_type = "matfree"
[docs] def assemble(self): # Bump petsc matrix state by assembling it. # Ensures that if the matrix changed, the preconditioner is # updated if necessary. self.petscmat.assemble()
[docs] class AssembledMatrix(MatrixBase): """A representation of a matrix that doesn't require knowing the underlying form. Parameters ---------- args A tuple of the arguments the matrix represents. petscmat The PETSc matrix this object wraps. bcs an iterable of boundary conditions to apply to this :class:`Matrix`. May be `None` if there are no boundary conditions to apply. Empty tuple by default. options_prefix PETSc options prefix to apply, by default None. """ def __init__( self, args: tuple[ufl.Argument | ufl.Coargument, ufl.Argument | ufl.Coargument], petscmat: PETSc.Mat, bcs: Iterable[DirichletBC] = (), options_prefix: str | None = None, ): """Initialise an :class:`AssembledMatrix`.""" super().__init__(args, bcs=bcs) self.petscmat = petscmat if options_prefix is not None: self.petscmat.setOptionsPrefix(options_prefix) self.mat_type = _get_mat_type(self.petscmat) # this mimics op2.Mat.handle self.M = DummyOP2Mat(self.petscmat)