Source code for firedrake.ensemble.ensemble_mat

from typing import Iterable
from firedrake.petsc import PETSc
from firedrake.ensemble.ensemble_function import EnsembleFunction, EnsembleFunctionBase
from firedrake.ensemble.ensemble_functionspace import EnsembleFunctionSpaceBase


[docs] class EnsembleMatCtxBase: """ Base class for python type Mats defined over an :class:`~.ensemble.Ensemble`. Parameters ---------- row_space : The function space that the matrix acts on. Must have the same number of subspaces on each ensemble rank as col_space. col_space : The function space for the result of the matrix action. Must have the same number of subspaces on each ensemble rank as row_space. Notes ----- The main use of this base class is to enable users to implement the matrix action as acting on and resulting in an :class:`~.ensemble_function.EnsembleFunction`. This is done by implementing the ``mult_impl`` method. See Also -------- .ensemble_pc.EnsemblePCBase """ def __init__(self, row_space: EnsembleFunctionSpaceBase, col_space: EnsembleFunctionSpaceBase): name = type(self).__name__ if not isinstance(row_space, EnsembleFunctionSpaceBase): raise ValueError( f"{name} row_space must be EnsembleFunctionSpace not {type(row_space).__name__}") if not isinstance(col_space, EnsembleFunctionSpaceBase): raise ValueError( f"{name} col_space must be EnsembleFunctionSpace not {type(col_space).__name__}") if row_space.ensemble != col_space.ensemble: raise ValueError( f"{name} row and column spaces must have the same Ensemble") self.ensemble = row_space.ensemble self.row_space = row_space self.col_space = col_space # input/output Vecs will be copied in/out of these # so that base classes can implement mult only in # terms of Ensemble objects not Vecs. self.x = EnsembleFunction(self.row_space) self.y = EnsembleFunction(self.col_space)
[docs] def mult(self, A, x, y): """Apply the action of the matrix to x, putting the result in y. This method will be called by PETSc with x and y as Vecs, and acts as a wrapper around the ``mult_impl`` method which has x and y as EnsembleFunction for convenience. y is not guaranteed to be zero on entry. Parameters ---------- A : PETSc.Mat The PETSc matrix that self is the python context of. x : PETSc.Vec The vector acted on by the matrix. y : PETSc.Vec The result of the matrix action. See Also -------- EnsembleMatCtxBase.mult_impl """ with self.x.vec_wo() as xvec: x.copy(result=xvec) self.mult_impl(A, self.x, self.y) with self.y.vec_ro() as yvec: yvec.copy(result=y)
[docs] def mult_impl(self, A, x: EnsembleFunctionBase, y: EnsembleFunctionBase): """Apply the action of the matrix to x, putting the result in y. y is not guaranteed to be zero on entry. This is a convenience method allowing the matrix action to be implemented in terms of EnsembleFunction input and outputs by inheriting classes. Parameters ---------- A : PETSc.Mat The PETSc matrix that self is the python context of. x : The vector acted on by the matrix. y : The result of the matrix action. See Also -------- EnsembleMatCtxBase.mult """ raise NotImplementedError
[docs] class EnsembleBlockDiagonalMatCtx(EnsembleMatCtxBase): """ A python Mat context for a block diagonal matrix defined over an :class:`~.ensemble.Ensemble`. Each block acts on a single subspace of an :class:`~.ensemble_functionspace.EnsembleFunctionSpace`. Parameters ---------- block_mats : Iterable[PETSc.Mat] The PETSc Mats for each block. On each ensemble rank there must be as many Mats as there are local subspaces of ``row_space`` and ``col_space``, and the Mat sizes must match the sizes of the corresponding subspaces. row_space : The function space that the matrix acts on. Must have the same number of subspaces on each ensemble rank as col_space. col_space : The function space for the result of the matrix action. Must have the same number of subspaces on each ensemble rank as row_space. Notes ----- This is a python context, not an actual PETSc.Mat. To create the corresponding PETSc.Mat users should call :func:`~.EnsembleBlockDiagonalMat`. See Also -------- EnsembleBlockDiagonalMat ~.ensemble_pc.EnsembleBJacobiPC """ def __init__(self, block_mats: Iterable, row_space: EnsembleFunctionSpaceBase, col_space: EnsembleFunctionSpaceBase): super().__init__(row_space, col_space) self.block_mats = block_mats if self.row_space.nlocal_spaces != self.col_space.nlocal_spaces: raise ValueError( "EnsembleBlockDiagonalMat row and col spaces must be the same length," f" not {row_space.nlocal_spaces=} and {col_space.nlocal_spaces=}") if len(self.block_mats) != self.row_space.nlocal_spaces: raise ValueError( f"EnsembleBlockDiagonalMat requires one submatrix for each of the" f" {self.row_space.nlocal_spaces} local subfunctions of the EnsembleFunctionSpace," f" but only {len(self.block_mats)} provided.") for i, (Vrow, Vcol, block) in enumerate(zip(self.row_space.local_spaces, self.col_space.local_spaces, self.block_mats)): if not isinstance(block, PETSc.Mat): raise TypeError( f"Block {i} must be a PETSc.Mat not a {type(block).__name__}.\n" "Did you mean to use assemble(block).petscmat instead?") # number of columns is row length, and vice-versa vr_sizes = Vrow.dof_dset.layout_vec.sizes vc_sizes = Vcol.dof_dset.layout_vec.sizes mc_sizes, mr_sizes = block.sizes if (vr_sizes[0] != mr_sizes[0]) or (vr_sizes[1] != mr_sizes[1]): raise ValueError( f"Row sizes {mr_sizes} of block {i} and {vr_sizes} of row_space {i} of EnsembleBlockDiagonalMat must match.") if (vc_sizes[0] != mc_sizes[0]) or (vc_sizes[1] != mc_sizes[1]): raise ValueError( f"Col sizes of block {i} and col_space {i} of EnsembleBlockDiagonalMat must match.")
[docs] def mult_impl(self, A, x, y): for block, xsub, ysub in zip(self.block_mats, x.subfunctions, y.subfunctions): with xsub.dat.vec_ro as xvec, ysub.dat.vec_wo as yvec: block.mult(xvec, yvec)
[docs] def setUp(self, mat): for bmat in self.block_mats: bmat.setUp()
[docs] def view(self, mat, viewer=None): if viewer is None: return if viewer.getType() != PETSc.Viewer.Type.ASCII: return viewer.printfASCII(f" firedrake block diagonal Ensemble matrix: {type(self).__name__}\n") viewer.printfASCII(f" Number of blocks = {self.col_space.nglobal_spaces}, Number of ensemble ranks = {self.ensemble.ensemble_size}\n") if viewer.getFormat() != PETSc.Viewer.Format.ASCII_INFO_DETAIL: viewer.printfASCII(" Local information for first block is in the following Mat objects on rank 0:\n") prefix = mat.getOptionsPrefix() or "" viewer.printfASCII(f" Use -{prefix}ksp_view ::ascii_info_detail to display information for all blocks\n") subviewer = viewer.getSubViewer(self.ensemble.comm) if self.ensemble.ensemble_rank == 0: subviewer.pushASCIITab() self.block_mats[0].view(subviewer) subviewer.popASCIITab() viewer.restoreSubViewer(subviewer) # Comment taken from PCView_BJacobi in https://petsc.org/release/src/ksp/pc/impls/bjacobi/bjacobi.c.html#PCBJACOBI # extra call needed because of the two calls to PetscViewerASCIIPushSynchronized() in PetscViewerGetSubViewer() viewer.popASCIISynchronized() else: viewer.pushASCIISynchronized() viewer.printfASCII(" Local information for each block is in the following Mat objects:\n") viewer.pushASCIITab() subviewer = viewer.getSubViewer(self.ensemble.comm) r = self.ensemble.ensemble_rank offset = self.col_space.global_spaces_offset subviewer.printfASCII(f"[{r}] number of local blocks = {self.col_space.nlocal_spaces}, first local block number = {offset}\n") for i, submat in enumerate(self.block_mats): subviewer.printfASCII(f"[{r}] local block number {i}, global block number {offset + i}\n") submat.view(subviewer) subviewer.printfASCII("- - - - - - - - - - - - - - - - - -\n") viewer.restoreSubViewer(subviewer) viewer.popASCIITab() viewer.popASCIISynchronized()
[docs] def EnsembleBlockDiagonalMat(block_mats: Iterable, row_space: EnsembleFunctionSpaceBase, col_space: EnsembleFunctionSpaceBase): """ A Mat for a block diagonal matrix defined over an :class:`~.ensemble.Ensemble`. Each block acts on a single subspace of an :class:`~.ensemble_functionspace.EnsembleFunctionSpace`. This is a convenience function to create a PETSc.Mat with a :class:`.EnsembleBlockDiagonalMatCtx` Python context. Parameters ---------- block_mats : Iterable[PETSc.Mat] The PETSc Mats for each block. On each ensemble rank there must be as many Mats as there are local subspaces of ``row_space`` and ``col_space``, and the Mat sizes must match the sizes of the corresponding subspaces. row_space : The function space that the matrix acts on. Must have the same number of subspaces on each ensemble rank as col_space. col_space : The function space for the result of the matrix action. Must have the same number of subspaces on each ensemble rank as row_space. Returns ------- PETSc.Mat : The PETSc.Mat with an :class:`.EnsembleBlockDiagonalMatCtx` Python context. See Also -------- EnsembleBlockDiagonalMatCtx ~.ensemble_pc.EnsembleBJacobiPC """ ctx = EnsembleBlockDiagonalMatCtx(block_mats, row_space, col_space) # number of columns is row length, and vice-versa ncols = ctx.col_space.layout_vec.getSizes() nrows = ctx.row_space.layout_vec.getSizes() mat = PETSc.Mat().createPython( (ncols, nrows), ctx, comm=ctx.ensemble.global_comm) mat.setUp() mat.assemble() return mat