Source code for firedrake.preconditioners.hiptmair

import abc
import numpy

from pyop2.utils import as_tuple
from firedrake.bcs import DirichletBC
from firedrake.petsc import PETSc
from firedrake.preconditioners.base import PCBase
from firedrake.functionspace import FunctionSpace
from firedrake.ufl_expr import TestFunction, TrialFunction
from firedrake.preconditioners.hypre_ams import chop
from firedrake.preconditioners.facet_split import restrict
from firedrake.parameters import parameters
from firedrake_citations import Citations
from firedrake.interpolation import Interpolator
from ufl.algorithms.ad import expand_derivatives
import firedrake.dmhooks as dmhooks
import firedrake.utils as utils
import ufl
import finat.ufl


__all__ = ("TwoLevelPC", "HiptmairPC")


[docs] class TwoLevelPC(PCBase): """ PC for two-level methods should implement: - :meth:`coarsen` """
[docs] @abc.abstractmethod def coarsen(self, pc): """Return a tuple with coarse bilinear form, coarse boundary conditions, and coarse-to-fine interpolation matrix """ raise NotImplementedError
[docs] def initialize(self, pc): from firedrake.assemble import get_assembler A, P = pc.getOperators() appctx = self.get_appctx(pc) fcp = appctx.get("form_compiler_parameters") prefix = pc.getOptionsPrefix() options_prefix = prefix + self._prefix opts = PETSc.Options() coarse_operator, coarse_space_bcs, interp_petscmat = self.coarsen(pc) # Handle the coarse operator coarse_options_prefix = options_prefix + "mg_coarse_" coarse_mat_type = opts.getString(coarse_options_prefix + "mat_type", parameters["default_matrix_type"]) form_assembler = get_assembler(coarse_operator, bcs=coarse_space_bcs, form_compiler_parameters=fcp, mat_type=coarse_mat_type, options_prefix=coarse_options_prefix) self.coarse_op = form_assembler.allocate() self._assemble_coarse_op = form_assembler.assemble self._assemble_coarse_op(tensor=self.coarse_op) coarse_opmat = self.coarse_op.petscmat # We set up a PCMG object that uses the constructed interpolation # matrix to generate the restriction/prolongation operators. # This is a two-level multigrid preconditioner. pcmg = PETSc.PC().create(comm=pc.comm) pcmg.incrementTabLevel(1, parent=pc) pcmg.setType(pc.Type.MG) pcmg.setOptionsPrefix(options_prefix) pcmg.setMGLevels(2) pcmg.setMGType(pc.MGType.ADDITIVE) pcmg.setMGCycleType(pc.MGCycleType.V) pcmg.setMGInterpolation(1, interp_petscmat) # FIXME the default for MGRScale is created with the wrong shape when dim(coarse) > dim(fine) # FIXME there is no need for injection in a KSP context, probably this comes from the snes_ctx below # as workaround define injection as the restriction of the solution times a zero vector pcmg.setMGRScale(1, interp_petscmat.createVecRight()) pcmg.setOperators(A=A, P=P) coarse_solver = pcmg.getMGCoarseSolve() coarse_solver.setOperators(A=coarse_opmat, P=coarse_opmat) # coarse space dm coarse_space = coarse_operator.arguments()[-1].function_space() coarse_dm = coarse_space.dm coarse_solver.setDM(coarse_dm) coarse_solver.setDMActive(False) pcmg.setDM(pc.getDM()) pcmg.setFromOptions() self.pc = pcmg self._dm = coarse_dm prefix = coarse_solver.getOptionsPrefix() # Create new appctx self._ctx_ref = self.new_snes_ctx(pc, coarse_operator, coarse_space_bcs, coarse_mat_type, fcp, options_prefix=prefix) with dmhooks.add_hooks(coarse_dm, self, appctx=self._ctx_ref, save=False): coarse_solver.setFromOptions()
[docs] def update(self, pc): self._assemble_coarse_op(tensor=self.coarse_op) self.pc.setUp()
[docs] def apply(self, pc, X, Y): dm = self._dm with dmhooks.add_hooks(dm, self, appctx=self._ctx_ref): self.pc.apply(X, Y)
[docs] def applyTranspose(self, pc, X, Y): dm = self._dm with dmhooks.add_hooks(dm, self, appctx=self._ctx_ref): self.pc.applyTranspose(X, Y)
[docs] def view(self, pc, viewer=None): super(TwoLevelPC, self).view(pc, viewer) if hasattr(self, "pc"): viewer.printfASCII("Two level PC\n") self.pc.view(viewer)
[docs] class HiptmairPC(TwoLevelPC): """A two-level method for H(curl) or H(div) problems with an auxiliary potential space in H^1 or H(curl), respectively. Internally this creates a PETSc PCMG object that can be controlled by options using the extra options prefix ``hiptmair_mg_``. This allows for effective multigrid relaxation methods with patch solves centered around vertices for H^1, edges for H(curl), or faces for H(div). For the lowest-order spaces this corresponds to point-Jacobi. The H(div) auxiliary vector potential problem in H(curl) is singular for high-order. This can be overcome by pertubing the problem by a multiple of the mass matrix. The scaling factor can be provided (defaulting to 0) by providing a scalar in the application context, keyed on ``"hiptmair_shift"``. """ _prefix = "hiptmair_"
[docs] def coarsen(self, pc): Citations().register("Hiptmair1998") appctx = self.get_appctx(pc) a, bcs = self.form(pc) V = a.arguments()[-1].function_space() mesh = V.mesh() element = V.ufl_element() formdegree = V.finat_element.formdegree if formdegree == 1: celement = curl_to_grad(element) elif formdegree == 2: celement = div_to_curl(element) else: raise ValueError("Hiptmair decomposition not available for", element) prefix = pc.getOptionsPrefix() options_prefix = prefix + self._prefix opts = PETSc.Options(options_prefix) domain = opts.getString("mg_coarse_restriction_domain", "") if domain: celement = restrict(celement, domain) coarse_space = FunctionSpace(mesh, celement) assert coarse_space.finat_element.formdegree + 1 == formdegree coarse_space_bcs = [bc.reconstruct(V=coarse_space, g=0) for bc in bcs] if element.sobolev_space == ufl.HDiv: G_callback = appctx.get("get_curl", None) dminus = ufl.curl if V.shape: dminus = lambda u: ufl.as_vector([ufl.curl(u[k, ...]) for k in range(u.ufl_shape[0])]) else: G_callback = appctx.get("get_gradient", None) dminus = ufl.grad # Get only the zero-th order term of the form replace_dict = {ufl.grad(t): ufl.zero(ufl.grad(t).ufl_shape) for t in a.arguments()} beta = ufl.replace(expand_derivatives(a), replace_dict) test = TestFunction(coarse_space) trial = TrialFunction(coarse_space) coarse_operator = beta(dminus(test), dminus(trial)) zero_beta = opts.getBool("zero_beta_poisson", True) if zero_beta: from firedrake.assemble import assemble # Remove coarse nodes where beta is zero coarse_diagonal = assemble(coarse_operator, diagonal=True) diag = numpy.abs(coarse_diagonal.dat.data_ro_with_halos) atol = numpy.max(diag) * 1E-10 bc_nodes = numpy.flatnonzero(diag <= atol).astype(PETSc.IntType) coarse_space_bcs.append(BCFromNodes(coarse_space, 0, bc_nodes)) cdegree = max(as_tuple(celement.degree())) if formdegree > 1 and cdegree > 1: shift = appctx.get("hiptmair_shift", None) if shift is not None: b = beta(test, shift * trial) coarse_operator += ufl.Form(b.integrals_by_type("cell")) coarse_space_bcs = tuple(coarse_space_bcs) if G_callback is None: interp_petscmat = chop(Interpolator(dminus(test), V, bcs=bcs + coarse_space_bcs).callable().handle) else: interp_petscmat = G_callback(coarse_space, V, coarse_space_bcs, bcs) return coarse_operator, coarse_space_bcs, interp_petscmat
def curl_to_grad(ele): if isinstance(ele, finat.ufl.VectorElement): return type(ele)(curl_to_grad(ele._sub_element), dim=ele.num_sub_elements) elif isinstance(ele, finat.ufl.TensorElement): return type(ele)(curl_to_grad(ele._sub_element), shape=ele.value_shape, symmetry=ele.symmetry()) elif isinstance(ele, finat.ufl.MixedElement): return type(ele)(*(curl_to_grad(e) for e in ele.sub_elements)) elif isinstance(ele, finat.ufl.RestrictedElement): return finat.ufl.RestrictedElement(curl_to_grad(ele._element), ele.restriction_domain()) else: cell = ele.cell family = ele.family() variant = ele.variant() degree = ele.degree() if family.startswith("Sminus"): family = "S" else: if family in ["Nedelec 2nd kind H(curl)", "Brezzi-Douglas-Marini"]: degree = degree + 1 family = "CG" if isinstance(degree, tuple) and isinstance(cell, ufl.TensorProductCell): cells = ele.cell.sub_cells() elems = [finat.ufl.FiniteElement(family, cell=c, degree=d, variant=variant) for c, d in zip(cells, degree)] return finat.ufl.TensorProductElement(*elems, cell=cell) return finat.ufl.FiniteElement(family, cell=cell, degree=degree, variant=variant) def div_to_curl(ele): if isinstance(ele, finat.ufl.VectorElement): return type(ele)(div_to_curl(ele._sub_element), dim=ele.num_sub_elements) elif isinstance(ele, finat.ufl.TensorElement): return type(ele)(div_to_curl(ele._sub_element), shape=ele.value_shape, symmetry=ele.symmetry()) elif isinstance(ele, finat.ufl.MixedElement): return type(ele)(*(div_to_curl(e) for e in ele.sub_elements)) elif isinstance(ele, finat.ufl.RestrictedElement): return finat.ufl.RestrictedElement(div_to_curl(ele._element), ele.restriction_domain()) elif isinstance(ele, finat.ufl.EnrichedElement): return type(ele)(*(div_to_curl(e) for e in reversed(ele._elements))) elif isinstance(ele, finat.ufl.TensorProductElement): return type(ele)(*(div_to_curl(e) for e in ele.sub_elements), cell=ele.cell) elif isinstance(ele, finat.ufl.WithMapping): return type(ele)(div_to_curl(ele.wrapee), ele.mapping()) elif isinstance(ele, finat.ufl.BrokenElement): return type(ele)(div_to_curl(ele._element)) elif isinstance(ele, finat.ufl.HDivElement): return finat.ufl.HCurlElement(div_to_curl(ele._element)) elif isinstance(ele, finat.ufl.HCurlElement): raise ValueError("Expecting an H(div) element") else: degree = ele.degree() family = ele.family() if family in ["Lagrange", "CG", "Q"]: family = "DG" if ele.cell.is_simplex() else "DQ" degree = degree - 1 elif family in ["Discontinuous Lagrange", "DG", "DQ"]: family = "CG" degree = degree + 1 else: if family == "Brezzi-Douglas-Marini": degree = degree + 1 replace_dict = { "Raviart-Thomas": "N1curl", "Brezzi-Douglas-Marini": "N2curl", "RTCF": "RTCE", "NCF": "NCE", "SminusF": "SminusE", "SminusDiv": "SminusCurl", } family = replace_dict.get(family, None) if family is None: raise ValueError("Unexpected family %s" % family) return ele.reconstruct(degree=degree, family=family) class BCFromNodes(DirichletBC): def __init__(self, V, g, nodes): self._nodes = nodes super(BCFromNodes, self).__init__(V, g, tuple()) @utils.cached_property def nodes(self): return self._nodes