44from firedrake .petsc import PETSc
55from firedrake .dmhooks import get_function_space , get_appctx
66from firedrake .ufl_expr import TestFunction , TrialFunction
7- from ufl import curl , div , HCurl , HDiv , inner , dx
7+ from firedrake .function import Function
8+ from firedrake .functionspace import FunctionSpace , VectorFunctionSpace , TensorFunctionSpace
9+ from firedrake .preconditioners .fdm import tabulate_exterior_derivative
10+ from firedrake .preconditioners .hiptmair import curl_to_grad
11+ from ufl import H1 , H2 , inner , dx , JacobianDeterminant
812from pyop2 .utils import as_tuple
13+ import gem
914import numpy
1015
1116__all__ = ("BDDCPC" ,)
@@ -22,17 +27,13 @@ class BDDCPC(PCBase):
2227 - ``'bddc_pc_bddc_dirichlet'`` to set sub-KSPs on subdomain interiors,
2328 - ``'bddc_pc_bddc_coarse'`` to set the coarse solver KSP.
2429
25- This PC also inspects optional arguments supplied in the application context:
26- - ``'discrete_gradient '`` for problems in H(curl), this sets the arguments
27- (a Mat tabulating the gradient of the auxiliary H1 space) and
30+ This PC also inspects optional callbacks supplied in the application context:
31+ - ``'get_discrete_gradient '`` for 3D problems in H(curl), this is a callable that
32+ provide the arguments (a Mat tabulating the gradient of the auxiliary H1 space) and
2833 keyword arguments supplied to ``PETSc.PC.setBDDCDiscreteGradient``.
29- - ``'divergence_mat'`` for 3D problems in H(div), this sets the Mat with the
30- assembled bilinear form testing the divergence against an L2 space.
31-
32- Notes
33- -----
34- Currently the Mat type IS is only supported by FDMPC.
35-
34+ - ``'get_divergence_mat'`` for problems in H(div) (resp. 2D H(curl)), this is
35+ provide the arguments (a Mat with the assembled bilinear form testing the divergence
36+ (curl) against an L2 space) and keyword arguments supplied to ``PETSc.PC.setDivergenceMat``.
3637 """
3738
3839 _prefix = "bddc_"
@@ -44,6 +45,8 @@ def initialize(self, pc):
4445 self .prefix = (pc .getOptionsPrefix () or "" ) + self ._prefix
4546
4647 V = get_function_space (dm )
48+ variant = V .ufl_element ().variant ()
49+ sobolev_space = V .ufl_element ().sobolev_space
4750
4851 # Create new PC object as BDDC type
4952 bddcpc = PETSc .PC ().create (comm = pc .comm )
@@ -53,13 +56,17 @@ def initialize(self, pc):
5356 bddcpc .setType (PETSc .PC .Type .BDDC )
5457
5558 opts = PETSc .Options (bddcpc .getOptionsPrefix ())
56- if V .ufl_element ().variant () == "fdm" and "pc_bddc_use_local_mat_graph" not in opts :
57- # Disable computation of disconected components of subdomain interfaces
59+ # Do not use CSR of local matrix to define dofs connectivity unless requested
60+ # Using the CSR only makes sense for H1/H2 problems
61+ is_h1h2 = sobolev_space in [H1 , H2 ]
62+ if "pc_bddc_use_local_mat_graph" not in opts and (not is_h1h2 or not is_lagrange (V .finat_element )):
5863 opts ["pc_bddc_use_local_mat_graph" ] = False
5964
65+ # Handle boundary dofs
6066 ctx = get_appctx (dm )
61- bcs = tuple (ctx ._problem .bcs )
62- if V .extruded :
67+ bcs = tuple (ctx ._problem .dirichlet_bcs ())
68+ mesh = V .mesh ().unique ()
69+ if mesh .extruded and not mesh .extruded_periodic :
6370 boundary_nodes = numpy .unique (numpy .concatenate (list (map (V .boundary_nodes , ("on_boundary" , "top" , "bottom" )))))
6471 else :
6572 boundary_nodes = V .boundary_nodes ("on_boundary" )
@@ -69,46 +76,44 @@ def initialize(self, pc):
6976 dir_nodes = numpy .unique (numpy .concatenate ([bcdofs (bc , ghost = False ) for bc in bcs ]))
7077 neu_nodes = numpy .setdiff1d (boundary_nodes , dir_nodes )
7178
72- V .dof_dset .lgmap .apply (dir_nodes , result = dir_nodes )
79+ dir_nodes = V .dof_dset .lgmap .apply (dir_nodes )
7380 dir_bndr = PETSc .IS ().createGeneral (dir_nodes , comm = pc .comm )
7481 bddcpc .setBDDCDirichletBoundaries (dir_bndr )
7582
76- V .dof_dset .lgmap .apply (neu_nodes , result = neu_nodes )
83+ neu_nodes = V .dof_dset .lgmap .apply (neu_nodes )
7784 neu_bndr = PETSc .IS ().createGeneral (neu_nodes , comm = pc .comm )
7885 bddcpc .setBDDCNeumannBoundaries (neu_bndr )
7986
8087 appctx = self .get_appctx (pc )
81- sobolev_space = V .ufl_element ().sobolev_space
88+ degree = max (as_tuple (V .ufl_element ().degree ()))
89+
90+ # Set coordinates only if corner selection is requested
91+ # There's no API to query from PC
92+ if "pc_bddc_corner_selection" in opts :
93+ W = VectorFunctionSpace (V .mesh (), "Lagrange" , degree , variant = variant )
94+ coords = Function (W ).interpolate (V .mesh ().coordinates )
95+ bddcpc .setCoordinates (coords .dat .data_ro .repeat (V .block_size , axis = 0 ))
8296
8397 tdim = V .mesh ().topological_dimension
84- degree = max (as_tuple (V .ufl_element ().degree ()))
8598 if tdim >= 2 and V .finat_element .formdegree == tdim - 1 :
86- B = appctx .get ("divergence_mat" , None )
87- if B is None :
88- from firedrake .assemble import assemble
89- d = {HCurl : curl , HDiv : div }[sobolev_space ]
90- Q = V .reconstruct (family = "DG" , degree = degree - 1 )
91- b = inner (d (TrialFunction (V )), TestFunction (Q )) * dx (degree = 2 * (degree - 1 ))
92- B = assemble (b , mat_type = "matfree" )
93- bddcpc .setBDDCDivergenceMat (B .petscmat )
94- elif sobolev_space == HCurl :
95- gradient = appctx .get ("discrete_gradient" , None )
96- if gradient is None :
97- from firedrake .preconditioners .fdm import tabulate_exterior_derivative
98- from firedrake .preconditioners .hiptmair import curl_to_grad
99- Q = V .reconstruct (element = curl_to_grad (V .ufl_element ()))
100- gradient = tabulate_exterior_derivative (Q , V )
101- corners = get_vertex_dofs (Q )
102- gradient .compose ('_elements_corners' , corners )
99+ allow_repeated = P .getISAllowRepeated ()
100+ get_divergence = appctx .get ("get_divergence_mat" , get_divergence_mat )
101+ divergence = get_divergence (V , mat_type = "is" , allow_repeated = allow_repeated )
102+ try :
103+ div_args , div_kwargs = divergence
104+ except ValueError :
105+ div_args = (divergence ,)
106+ div_kwargs = dict ()
107+ bddcpc .setBDDCDivergenceMat (* div_args , ** div_kwargs )
108+
109+ elif tdim >= 3 and V .finat_element .formdegree == 1 :
110+ get_gradient = appctx .get ("get_discrete_gradient" , get_discrete_gradient )
111+ gradient = get_gradient (V )
112+ try :
113+ grad_args , grad_kwargs = gradient
114+ except ValueError :
103115 grad_args = (gradient ,)
104- grad_kwargs = {'order' : degree }
105- else :
106- try :
107- grad_args , grad_kwargs = gradient
108- except ValueError :
109- grad_args = (gradient ,)
110- grad_kwargs = dict ()
111-
116+ grad_kwargs = dict ()
112117 bddcpc .setBDDCDiscreteGradient (* grad_args , ** grad_kwargs )
113118
114119 bddcpc .setFromOptions ()
@@ -127,9 +132,67 @@ def applyTranspose(self, pc, x, y):
127132 self .pc .applyTranspose (x , y )
128133
129134
130- def get_vertex_dofs ( V ):
131- W = V . reconstruct ( element = restrict (V .ufl_element (), "vertex" ))
135+ def get_restricted_dofs ( V , domain ):
136+ W = FunctionSpace ( V . mesh (), restrict (V .ufl_element (), domain ))
132137 indices = get_restriction_indices (V , W )
133- V .dof_dset .lgmap .apply (indices , result = indices )
134- vertex_dofs = PETSc .IS ().createGeneral (indices , comm = V .comm )
135- return vertex_dofs
138+ indices = V .dof_dset .lgmap .apply (indices )
139+ return PETSc .IS ().createGeneral (indices , comm = V .comm )
140+
141+
142+ def get_divergence_mat (V , mat_type = "is" , allow_repeated = False ):
143+ from firedrake import assemble
144+ degree = max (as_tuple (V .ufl_element ().degree ()))
145+ Q = TensorFunctionSpace (V .mesh (), "DG" , 0 , variant = f"integral({ degree - 1 } )" , shape = V .value_shape [:- 1 ])
146+ B = tabulate_exterior_derivative (V , Q , mat_type = mat_type , allow_repeated = allow_repeated )
147+
148+ Jdet = JacobianDeterminant (V .mesh ())
149+ s = assemble (inner (TrialFunction (Q )* (1 / Jdet ), TestFunction (Q ))* dx (degree = 0 ), diagonal = True )
150+ with s .dat .vec as svec :
151+ B .diagonalScale (svec , None )
152+ return (B ,), {}
153+
154+
155+ def get_discrete_gradient (V ):
156+ from firedrake import Constant
157+ from firedrake .nullspace import VectorSpaceBasis
158+
159+ Q = FunctionSpace (V .mesh (), curl_to_grad (V .ufl_element ()))
160+ gradient = tabulate_exterior_derivative (Q , V )
161+ basis = Function (Q )
162+ try :
163+ basis .interpolate (Constant (1 ))
164+ except NotImplementedError :
165+ basis .project (Constant (1 ))
166+ nsp = VectorSpaceBasis ([basis ])
167+ nsp .orthonormalize ()
168+ gradient .setNullSpace (nsp .nullspace ())
169+ if not is_lagrange (Q .finat_element ):
170+ vdofs = get_restricted_dofs (Q , "vertex" )
171+ gradient .compose ('_elements_corners' , vdofs )
172+
173+ degree = max (as_tuple (Q .ufl_element ().degree ()))
174+ grad_args = (gradient ,)
175+ grad_kwargs = {'order' : degree }
176+ return grad_args , grad_kwargs
177+
178+
179+ def is_lagrange (finat_element ):
180+ """Returns whether finat_element.dual_basis consists only of point evaluation dofs."""
181+ try :
182+ Q , ps = finat_element .dual_basis
183+ except NotImplementedError :
184+ return False
185+ # Inspect the weight matrix
186+ # Lagrange elements have gem.Delta as the only terminal nodes
187+ children = [Q ]
188+ while children :
189+ nodes = []
190+ for c in children :
191+ if isinstance (c , gem .Delta ):
192+ pass
193+ elif isinstance (c , gem .gem .Terminal ):
194+ return False
195+ else :
196+ nodes .extend (c .children )
197+ children = nodes
198+ return True
0 commit comments