Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
22ea2c4
Support mat_type="is"
pbrubeck Jun 24, 2025
9042f45
MatIS: support DirichletBC, add a test
pbrubeck Jun 24, 2025
642d18b
MatIS: Fix parallel LGMap
pbrubeck Jun 24, 2025
2d06f08
MatNest + MatIS
pbrubeck Jun 26, 2025
7bca3e6
More comprehensive tests
pbrubeck Jun 26, 2025
d0b6469
Propagate sub_mat_type, fix MatNest + MatIS
pbrubeck Jun 26, 2025
6b7c7f1
Only set sub_mat_type on the diagonal blocks
pbrubeck Jun 26, 2025
f9e0bd4
style
pbrubeck Jun 27, 2025
5c715e1
Test BDDC, setCoordinates
pbrubeck Jun 28, 2025
7956efd
Fix VectorFunctionSpace
pbrubeck Jun 29, 2025
8248cf9
refine BDDC customization
stefanozampini Jul 23, 2025
f6c4778
fix lint
stefanozampini Jul 24, 2025
32b2c4d
Simplify coordinates handling of bddcpc driver
stefanozampini Jul 24, 2025
3f33eff
Don't know why this fixes the mapping for me
stefanozampini Jul 27, 2025
b945f38
test tabulate_exterior_derivative
pbrubeck Jul 28, 2025
e4fc071
add failing test
stefanozampini Jul 28, 2025
f701ec3
Fix vertex dofs
pbrubeck Jul 28, 2025
baa8da5
Add permutation test
pbrubeck Jul 28, 2025
245362d
Trivial case for get_restriction_indices
pbrubeck Jul 29, 2025
5397bcf
DROP BEFORE MERGE
pbrubeck Jul 29, 2025
8979c96
mark parellel test
pbrubeck Jul 29, 2025
79314b5
bddc: use callables for gradient and divergence
stefanozampini Jul 30, 2025
72b782c
minor
stefanozampini Jul 30, 2025
0808ccd
tabulate_exterior_derivative as MatIS
pbrubeck Jul 31, 2025
fb3dda7
Fix tabulate_exterior_derivative in parallel
pbrubeck Aug 1, 2025
052bedd
BDDC get_divergence_mat
pbrubeck Aug 1, 2025
1ec8faa
Fix high-order divergence tabulation
pbrubeck Aug 1, 2025
671261b
Cleanup
pbrubeck Aug 1, 2025
6926ad1
cleanup
pbrubeck Aug 1, 2025
f5e1651
Fix Q1
pbrubeck Aug 2, 2025
9ff5cf6
Test tabulate_exterior_derivative
pbrubeck Aug 5, 2025
de9240b
H(curl) constraints for BDDC
pbrubeck Aug 5, 2025
b8bcb0f
cleanup
pbrubeck Aug 6, 2025
6e216b4
Refactor
pbrubeck Aug 6, 2025
3e5bac3
FDMPC: Support other variants
pbrubeck Aug 7, 2025
e8be078
bddc: allow view of preconditioning matrix
stefanozampini Aug 8, 2025
c98ea74
bddc: attach constants of the H(grad) space to discrete gradient
stefanozampini Aug 19, 2025
4312274
ImplicitMatrixContext: allow for an arbitrary submatrix extraction
stefanozampini Aug 21, 2025
2616dc0
bddc nedelec: attach constants as nullspace
stefanozampini Sep 2, 2025
0b3d6e7
Update .github/workflows/core.yml
pbrubeck Sep 8, 2025
b16c0f6
Remove unrelated test
pbrubeck Sep 8, 2025
6a1938b
Update pyop2/types/mat.py
pbrubeck Sep 24, 2025
3d54f50
Merge branch 'main' into pbrubeck/matis
pbrubeck Sep 24, 2025
508c4bb
Fix mixed IS bcs
pbrubeck Sep 25, 2025
7bd0b51
Merge branch 'main' into pbrubeck/matis
pbrubeck Sep 25, 2025
f413448
Merge branch 'main' into pbrubeck/matis
pbrubeck Sep 25, 2025
c94cfea
Merge branch 'main' into pbrubeck/matis
pbrubeck Sep 29, 2025
e3c1dd7
cleanup
pbrubeck Sep 29, 2025
508c540
remove negative index workaround
pbrubeck Sep 29, 2025
60d26d0
Harder test with MixedFunctionSpace(Vector, Scalar)
pbrubeck Sep 29, 2025
a5d5bbe
Fix BC for MATIS block
stefanozampini Oct 2, 2025
fc9ecb2
add mat_type kawrg to local_to_global_map
pbrubeck Oct 2, 2025
320a273
DROP BEFORE MERGE
pbrubeck Oct 2, 2025
98444be
merge conflict
pbrubeck Oct 6, 2025
4cb8404
Fix unghosted_lgmap on ExtrudedMesh
pbrubeck Oct 15, 2025
77be0ec
Merge branch 'main' into pbrubeck/matis
pbrubeck Oct 15, 2025
c433530
Merge branch 'main' into pbrubeck/matis
pbrubeck Oct 17, 2025
cf1bd88
Fix test
pbrubeck Oct 17, 2025
7978e8a
Merge branch 'main' into pbrubeck/matis
pbrubeck Oct 22, 2025
74a52ec
Drop unrelated change
pbrubeck Oct 22, 2025
4c50572
Update pyop2/types/mat.py
pbrubeck Oct 22, 2025
cb83725
Apply suggestions from code review
pbrubeck Oct 27, 2025
46e3354
Update firedrake/matrix_free/operators.py
pbrubeck Oct 27, 2025
81ec989
Merge branch 'main' into pbrubeck/matis
pbrubeck Oct 27, 2025
a0e01cd
topological_dimension
pbrubeck Oct 27, 2025
53b22e1
Apply suggestions from code review
pbrubeck Oct 29, 2025
810e595
Merge branch 'main' into pbrubeck/matis
pbrubeck Oct 29, 2025
09dec97
Test BDDC for H(curl) and H(div)
pbrubeck Oct 30, 2025
8939fc4
Update firedrake/preconditioners/bddc.py
pbrubeck Oct 30, 2025
31c80d8
docs
pbrubeck Oct 30, 2025
7c31da6
implement is_lagrange
pbrubeck Oct 30, 2025
20b49dc
remove unrelated change
pbrubeck Oct 30, 2025
f356c0f
test for correctness
pbrubeck Oct 31, 2025
dde83a8
test mesh independence
pbrubeck Oct 31, 2025
a87093e
cleanup
pbrubeck Oct 31, 2025
b10e62a
cleanup
pbrubeck Oct 31, 2025
a2d8f30
Apply suggestions from code review
pbrubeck Oct 31, 2025
af3b325
Merge branch 'main' into pbrubeck/matis
pbrubeck Nov 3, 2025
73cdcc2
add more tests
pbrubeck Nov 3, 2025
4800570
fix complex
pbrubeck Nov 3, 2025
c88b5e4
Extruded periodic
pbrubeck Nov 3, 2025
39a9395
FunctionSpace: introduce extruded_periodic attribute
pbrubeck Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions firedrake/assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,9 @@ def allocate(self):
else:
test, trial = self._form.arguments()
sparsity = ExplicitMatrixAssembler._make_sparsity(test, trial, self._mat_type, self._sub_mat_type, self.maps_and_regions)
return matrix.Matrix(self._form, self._bcs, self._mat_type, sparsity, ScalarType, options_prefix=self._options_prefix)
return matrix.Matrix(self._form, self._bcs, self._mat_type, sparsity, ScalarType,
sub_mat_type=self._sub_mat_type,
options_prefix=self._options_prefix)
else:
raise NotImplementedError("Only implemented for rank = 2 and diagonal = False")

Expand Down Expand Up @@ -1324,12 +1326,12 @@ def _get_mat_type(mat_type, sub_mat_type, arguments):
for arg in arguments
for V in arg.function_space()):
mat_type = "nest"
if mat_type not in {"matfree", "aij", "baij", "nest", "dense"}:
if mat_type not in {"matfree", "aij", "baij", "nest", "dense", "is"}:
raise ValueError(f"Unrecognised matrix type, '{mat_type}'")
if sub_mat_type is None:
sub_mat_type = parameters.parameters["default_sub_matrix_type"]
if sub_mat_type not in {"aij", "baij"}:
raise ValueError(f"Invalid submatrix type, '{sub_mat_type}' (not 'aij' or 'baij')")
if sub_mat_type not in {"aij", "baij", "is"}:
raise ValueError(f"Invalid submatrix type, '{sub_mat_type}' (not 'aij', 'baij', or 'is')")
return mat_type, sub_mat_type


Expand Down Expand Up @@ -1373,6 +1375,7 @@ def allocate(self):
self._sub_mat_type,
self._make_maps_and_regions())
return matrix.Matrix(self._form, self._bcs, self._mat_type, sparsity, ScalarType,
sub_mat_type=self._sub_mat_type,
options_prefix=self._options_prefix,
fc_params=self._form_compiler_params)

Expand Down Expand Up @@ -1490,8 +1493,10 @@ def _apply_bc(self, tensor, bc, u=None):
# Set diagonal entries on bc nodes to 1 if the current
# block is on the matrix diagonal and its index matches the
# index of the function space the bc is defined on.
if op2tensor.handle.getType() == "is":
# Flag the entire matrix as assembled before indexing the diagonal block
op2tensor.handle.assemble()
op2tensor[index, index].set_local_diagonal_entries(bc.nodes, idx=component, diag_val=self.weight)

# Handle off-diagonal block involving real function space.
# "lgmaps" is correctly constructed in _matrix_arg, but
# is ignored by PyOP2 in this case.
Expand Down Expand Up @@ -2067,16 +2072,18 @@ def collect_lgmaps(self):
row_bcs, col_bcs = self._filter_bcs(i, j)
# the tensor is already indexed
rlgmap, clgmap = self._tensor.local_to_global_maps
rlgmap = self.test_function_space[i].local_to_global_map(row_bcs, rlgmap)
clgmap = self.trial_function_space[j].local_to_global_map(col_bcs, clgmap)
mat_type = self._tensor.handle.getType()
rlgmap = self.test_function_space[i].local_to_global_map(row_bcs, rlgmap, mat_type=mat_type)
clgmap = self.trial_function_space[j].local_to_global_map(col_bcs, clgmap, mat_type=mat_type)
return ((rlgmap, clgmap),)
else:
lgmaps = []
for i, j in self.get_indicess():
row_bcs, col_bcs = self._filter_bcs(i, j)
rlgmap, clgmap = self._tensor[i, j].local_to_global_maps
rlgmap = self.test_function_space[i].local_to_global_map(row_bcs, rlgmap)
clgmap = self.trial_function_space[j].local_to_global_map(col_bcs, clgmap)
mat_type = self._tensor[i, j].handle.getType()
rlgmap = self.test_function_space[i].local_to_global_map(row_bcs, rlgmap, mat_type=mat_type)
clgmap = self.trial_function_space[j].local_to_global_map(col_bcs, clgmap, mat_type=mat_type)
lgmaps.append((rlgmap, clgmap))
return tuple(lgmaps)
else:
Expand Down
3 changes: 2 additions & 1 deletion firedrake/functionspacedata.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ class FunctionSpaceData(object):
__slots__ = ("real_tensorproduct", "map_cache", "entity_node_lists",
"node_set", "cell_boundary_masks",
"interior_facet_boundary_masks", "offset", "offset_quotient",
"extruded", "mesh", "global_numbering", "boundary_set")
"extruded", "extruded_periodic", "mesh", "global_numbering", "boundary_set")

@PETSc.Log.EventDecorator()
def __init__(self, mesh, ufl_element, boundary_set=None):
Expand Down Expand Up @@ -458,6 +458,7 @@ def __init__(self, mesh, ufl_element, boundary_set=None):
self.cell_boundary_masks = get_boundary_masks(mesh, (edofs_key, "cell"), finat_element)
self.interior_facet_boundary_masks = get_boundary_masks(mesh, (edofs_key, "interior_facet"), finat_element)
self.extruded = mesh.cell_set._extruded
self.extruded_periodic = mesh.cell_set._extruded_periodic
self.mesh = mesh
self.global_numbering = global_numbering

Expand Down
45 changes: 37 additions & 8 deletions firedrake/functionspaceimpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ def set_shared_data(self):
self._shared_data = sdata
self.real_tensorproduct = sdata.real_tensorproduct
self.extruded = sdata.extruded
self.extruded_periodic = sdata.extruded_periodic
self.offset = sdata.offset
self.offset_quotient = sdata.offset_quotient
self.cell_boundary_masks = sdata.cell_boundary_masks
Expand Down Expand Up @@ -856,10 +857,31 @@ def boundary_nodes(self, sub_domain):
return self._shared_data.boundary_nodes(self, sub_domain)

@PETSc.Log.EventDecorator()
def local_to_global_map(self, bcs, lgmap=None):
def local_to_global_map(self, bcs, lgmap=None, mat_type=None):
r"""Return a map from process local dof numbering to global dof numbering.

If BCs is provided, mask out those dofs which match the BC nodes."""
Parameters
----------
bcs: [firedrake.bcs.BCBase]
If provided, mask out those dofs which match the BC nodes.
lgmap: PETSc.LGMap
The base local-to-global map, which might be partially masked.
mat_type: str
The matrix assembly type. This is required as different matrix types
handle the LGMap differently for MixedFunctionSpace.

Note
----
For a :func:`.VectorFunctionSpace` or :func:`.TensorFunctionSpace` the returned
LGMap will be the scalar one, unless the bcs are imposed on a particular component.
For a :class:`MixedFunctionSpace` the returned LGMap is unblocked,
unless mat_type == "is".

Returns
-------
PETSc.LGMap
A local-to-global map with masked BC dofs.
"""
# Caching these things is too complicated, since it depends
# not just on the bcs, but also the parent space, and anything
# this space has been recursively split out from [e.g. inside
Expand All @@ -884,10 +906,16 @@ def local_to_global_map(self, bcs, lgmap=None):
bsize = lgmap.getBlockSize()
assert bsize == self.block_size
else:
# MatBlock case, LGMap is already unrolled.
indices = lgmap.block_indices.copy()
# MatBlock case, the LGMap is implementation dependent
bsize = lgmap.getBlockSize()
unblocked = True
assert bsize == self.block_size
if mat_type == "is":
indices = lgmap.indices.copy()
unblocked = False
else:
# LGMap is already unrolled
indices = lgmap.block_indices.copy()
unblocked = True
nodes = []
for bc in bcs:
if bc.function_space().component is not None:
Expand Down Expand Up @@ -977,6 +1005,7 @@ def set_shared_data(self):
# sdata carries real_tensorproduct.
self.real_tensorproduct = sdata.real_tensorproduct
self.extruded = sdata.extruded
self.extruded_periodic = sdata.extruded_periodic
self.offset = sdata.offset
self.offset_quotient = sdata.offset_quotient
self.cell_boundary_masks = sdata.cell_boundary_masks
Expand All @@ -997,7 +1026,7 @@ def __hash__(self):
return hash((self.mesh(), self.dof_dset, self.ufl_element(),
self.boundary_set))

def local_to_global_map(self, bcs, lgmap=None):
def local_to_global_map(self, bcs, lgmap=None, mat_type=None):
return lgmap or self.dof_dset.lgmap

def collapse(self):
Expand Down Expand Up @@ -1204,7 +1233,7 @@ def exterior_facet_node_map(self):
function space nodes."""
return op2.MixedMap(s.exterior_facet_node_map() for s in self)

def local_to_global_map(self, bcs):
def local_to_global_map(self, bcs, lgmap=None, mat_type=None):
r"""Return a map from process local dof numbering to global dof numbering.

If BCs is provided, mask out those dofs which match the BC nodes."""
Expand Down Expand Up @@ -1452,7 +1481,7 @@ def top_nodes(self):
":class:`RealFunctionSpace` objects have no bottom nodes."
return None

def local_to_global_map(self, bcs, lgmap=None):
def local_to_global_map(self, bcs, lgmap=None, mat_type=None):
assert len(bcs) == 0
return None

Expand Down
160 changes: 111 additions & 49 deletions firedrake/preconditioners/bddc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
from firedrake.petsc import PETSc
from firedrake.dmhooks import get_function_space, get_appctx
from firedrake.ufl_expr import TestFunction, TrialFunction
from ufl import curl, div, HCurl, HDiv, inner, dx
from firedrake.function import Function
from firedrake.functionspace import FunctionSpace, VectorFunctionSpace, TensorFunctionSpace
from firedrake.preconditioners.fdm import tabulate_exterior_derivative
from firedrake.preconditioners.hiptmair import curl_to_grad
from ufl import H1, H2, inner, dx, JacobianDeterminant
from pyop2.utils import as_tuple
import gem
import numpy

__all__ = ("BDDCPC",)
Expand All @@ -22,17 +27,13 @@ class BDDCPC(PCBase):
- ``'bddc_pc_bddc_dirichlet'`` to set sub-KSPs on subdomain interiors,
- ``'bddc_pc_bddc_coarse'`` to set the coarse solver KSP.

This PC also inspects optional arguments supplied in the application context:
- ``'discrete_gradient'`` for problems in H(curl), this sets the arguments
(a Mat tabulating the gradient of the auxiliary H1 space) and
This PC also inspects optional callbacks supplied in the application context:
- ``'get_discrete_gradient'`` for 3D problems in H(curl), this is a callable that
provide the arguments (a Mat tabulating the gradient of the auxiliary H1 space) and
keyword arguments supplied to ``PETSc.PC.setBDDCDiscreteGradient``.
- ``'divergence_mat'`` for 3D problems in H(div), this sets the Mat with the
assembled bilinear form testing the divergence against an L2 space.

Notes
-----
Currently the Mat type IS is only supported by FDMPC.

- ``'get_divergence_mat'`` for problems in H(div) (resp. 2D H(curl)), this is
provide the arguments (a Mat with the assembled bilinear form testing the divergence
(curl) against an L2 space) and keyword arguments supplied to ``PETSc.PC.setDivergenceMat``.
"""

_prefix = "bddc_"
Expand All @@ -44,6 +45,8 @@ def initialize(self, pc):
self.prefix = (pc.getOptionsPrefix() or "") + self._prefix

V = get_function_space(dm)
variant = V.ufl_element().variant()
sobolev_space = V.ufl_element().sobolev_space

# Create new PC object as BDDC type
bddcpc = PETSc.PC().create(comm=pc.comm)
Expand All @@ -53,13 +56,16 @@ def initialize(self, pc):
bddcpc.setType(PETSc.PC.Type.BDDC)

opts = PETSc.Options(bddcpc.getOptionsPrefix())
if V.ufl_element().variant() == "fdm" and "pc_bddc_use_local_mat_graph" not in opts:
# Disable computation of disconected components of subdomain interfaces
# Do not use CSR of local matrix to define dofs connectivity unless requested
# Using the CSR only makes sense for H1/H2 problems
is_h1h2 = sobolev_space in [H1, H2]
if "pc_bddc_use_local_mat_graph" not in opts and (not is_h1h2 or not is_lagrange(V.finat_element)):
opts["pc_bddc_use_local_mat_graph"] = False

# Handle boundary dofs
ctx = get_appctx(dm)
bcs = tuple(ctx._problem.bcs)
if V.extruded:
bcs = tuple(ctx._problem.dirichlet_bcs())
if V.extruded and not V.extruded_periodic:
boundary_nodes = numpy.unique(numpy.concatenate(list(map(V.boundary_nodes, ("on_boundary", "top", "bottom")))))
else:
boundary_nodes = V.boundary_nodes("on_boundary")
Expand All @@ -69,46 +75,44 @@ def initialize(self, pc):
dir_nodes = numpy.unique(numpy.concatenate([bcdofs(bc, ghost=False) for bc in bcs]))
neu_nodes = numpy.setdiff1d(boundary_nodes, dir_nodes)

V.dof_dset.lgmap.apply(dir_nodes, result=dir_nodes)
dir_nodes = V.dof_dset.lgmap.apply(dir_nodes)
dir_bndr = PETSc.IS().createGeneral(dir_nodes, comm=pc.comm)
bddcpc.setBDDCDirichletBoundaries(dir_bndr)

V.dof_dset.lgmap.apply(neu_nodes, result=neu_nodes)
neu_nodes = V.dof_dset.lgmap.apply(neu_nodes)
neu_bndr = PETSc.IS().createGeneral(neu_nodes, comm=pc.comm)
bddcpc.setBDDCNeumannBoundaries(neu_bndr)

appctx = self.get_appctx(pc)
sobolev_space = V.ufl_element().sobolev_space
degree = max(as_tuple(V.ufl_element().degree()))

# Set coordinates only if corner selection is requested
# There's no API to query from PC
if "pc_bddc_corner_selection" in opts:
W = VectorFunctionSpace(V.mesh(), "Lagrange", degree, variant=variant)
coords = Function(W).interpolate(V.mesh().coordinates)
bddcpc.setCoordinates(coords.dat.data_ro.repeat(V.block_size, axis=0))

tdim = V.mesh().topological_dimension
degree = max(as_tuple(V.ufl_element().degree()))
if tdim >= 2 and V.finat_element.formdegree == tdim-1:
B = appctx.get("divergence_mat", None)
if B is None:
from firedrake.assemble import assemble
d = {HCurl: curl, HDiv: div}[sobolev_space]
Q = V.reconstruct(family="DG", degree=degree-1)
b = inner(d(TrialFunction(V)), TestFunction(Q)) * dx(degree=2*(degree-1))
B = assemble(b, mat_type="matfree")
bddcpc.setBDDCDivergenceMat(B.petscmat)
elif sobolev_space == HCurl:
gradient = appctx.get("discrete_gradient", None)
if gradient is None:
from firedrake.preconditioners.fdm import tabulate_exterior_derivative
from firedrake.preconditioners.hiptmair import curl_to_grad
Q = V.reconstruct(element=curl_to_grad(V.ufl_element()))
gradient = tabulate_exterior_derivative(Q, V)
corners = get_vertex_dofs(Q)
gradient.compose('_elements_corners', corners)
allow_repeated = P.getISAllowRepeated()
get_divergence = appctx.get("get_divergence_mat", get_divergence_mat)
divergence = get_divergence(V, mat_type="is", allow_repeated=allow_repeated)
try:
div_args, div_kwargs = divergence
except ValueError:
div_args = (divergence,)
div_kwargs = dict()
bddcpc.setBDDCDivergenceMat(*div_args, **div_kwargs)

elif tdim >= 3 and V.finat_element.formdegree == 1:
get_gradient = appctx.get("get_discrete_gradient", get_discrete_gradient)
gradient = get_gradient(V)
try:
grad_args, grad_kwargs = gradient
except ValueError:
grad_args = (gradient,)
grad_kwargs = {'order': degree}
else:
try:
grad_args, grad_kwargs = gradient
except ValueError:
grad_args = (gradient,)
grad_kwargs = dict()

grad_kwargs = dict()
bddcpc.setBDDCDiscreteGradient(*grad_args, **grad_kwargs)

bddcpc.setFromOptions()
Expand All @@ -127,9 +131,67 @@ def applyTranspose(self, pc, x, y):
self.pc.applyTranspose(x, y)


def get_vertex_dofs(V):
W = V.reconstruct(element=restrict(V.ufl_element(), "vertex"))
def get_restricted_dofs(V, domain):
W = FunctionSpace(V.mesh(), restrict(V.ufl_element(), domain))
indices = get_restriction_indices(V, W)
V.dof_dset.lgmap.apply(indices, result=indices)
vertex_dofs = PETSc.IS().createGeneral(indices, comm=V.comm)
return vertex_dofs
indices = V.dof_dset.lgmap.apply(indices)
return PETSc.IS().createGeneral(indices, comm=V.comm)


def get_divergence_mat(V, mat_type="is", allow_repeated=False):
from firedrake import assemble
degree = max(as_tuple(V.ufl_element().degree()))
Q = TensorFunctionSpace(V.mesh(), "DG", 0, variant=f"integral({degree-1})", shape=V.value_shape[:-1])
B = tabulate_exterior_derivative(V, Q, mat_type=mat_type, allow_repeated=allow_repeated)

Jdet = JacobianDeterminant(V.mesh())
s = assemble(inner(TrialFunction(Q)*(1/Jdet), TestFunction(Q))*dx(degree=0), diagonal=True)
with s.dat.vec as svec:
B.diagonalScale(svec, None)
return (B,), {}


def get_discrete_gradient(V):
from firedrake import Constant
from firedrake.nullspace import VectorSpaceBasis

Q = FunctionSpace(V.mesh(), curl_to_grad(V.ufl_element()))
gradient = tabulate_exterior_derivative(Q, V)
basis = Function(Q)
try:
basis.interpolate(Constant(1))
except NotImplementedError:
basis.project(Constant(1))
nsp = VectorSpaceBasis([basis])
nsp.orthonormalize()
gradient.setNullSpace(nsp.nullspace())
if not is_lagrange(Q.finat_element):
vdofs = get_restricted_dofs(Q, "vertex")
gradient.compose('_elements_corners', vdofs)

degree = max(as_tuple(Q.ufl_element().degree()))
grad_args = (gradient,)
grad_kwargs = {'order': degree}
return grad_args, grad_kwargs


def is_lagrange(finat_element):
"""Returns whether finat_element.dual_basis consists only of point evaluation dofs."""
try:
Q, ps = finat_element.dual_basis
except NotImplementedError:
return False
# Inspect the weight matrix
# Lagrange elements have gem.Delta as the only terminal nodes
children = [Q]
while children:
nodes = []
for c in children:
if isinstance(c, gem.Delta):
pass
elif isinstance(c, gem.gem.Terminal):
return False
else:
nodes.extend(c.children)
children = nodes
return True
Loading
Loading