def test_mixed_element_interpolation(): mesh = create_unit_cube(MPI.COMM_WORLD, 3, 3, 3) el = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 1) V = FunctionSpace(mesh, ufl.MixedElement([el, el])) u = Function(V) with pytest.raises(RuntimeError): u.interpolate(lambda x: np.ones(2, x.shape[1]))
def test_mixed_element(ElementType, space, cell, order): if cell == ufl.triangle: mesh = UnitSquareMesh(MPI.COMM_WORLD, 1, 1, CellType.triangle, dolfinx.cpp.mesh.GhostMode.shared_facet) else: mesh = UnitCubeMesh(MPI.COMM_WORLD, 1, 1, 1, CellType.tetrahedron, dolfinx.cpp.mesh.GhostMode.shared_facet) norms = [] U_el = ufl.FiniteElement(space, cell, order) for i in range(3): U = FunctionSpace(mesh, U_el) u = ufl.TrialFunction(U) v = ufl.TestFunction(U) a = ufl.inner(u, v) * ufl.dx A = dolfinx.fem.assemble_matrix(a) A.assemble() norms.append(A.norm()) U_el = ufl.MixedElement(U_el) for i in norms[1:]: assert np.isclose(norms[0], i)
def test_mixed_constant_bc(mesh_factory): """Test that setting a dirichletbc with on a component of a mixed function yields the same result as setting it with a function""" func, args = mesh_factory mesh = func(*args) tdim, gdim = mesh.topology.dim, mesh.geometry.dim boundary_facets = locate_entities_boundary( mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool)) TH = ufl.MixedElement([ ufl.VectorElement("Lagrange", mesh.ufl_cell(), 2), ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 1) ]) W = FunctionSpace(mesh, TH) U = Function(W) # Apply BC to component of a mixed space using a Constant c = Constant(mesh, (PETSc.ScalarType(2), PETSc.ScalarType(2))) dofs0 = locate_dofs_topological(W.sub(0), tdim - 1, boundary_facets) bc0 = dirichletbc(c, dofs0, W.sub(0)) u = U.sub(0) set_bc(u.vector, [bc0]) # Apply BC to component of a mixed space using a Function ubc1 = u.collapse() ubc1.interpolate(lambda x: np.full((gdim, x.shape[1]), 2.0)) dofs1 = locate_dofs_topological((W.sub(0), ubc1.function_space), tdim - 1, boundary_facets) bc1 = dirichletbc(ubc1, dofs1, W.sub(0)) U1 = Function(W) u1 = U1.sub(0) set_bc(u1.vector, [bc1]) # Check that both approaches yield the same vector assert np.allclose(u.x.array, u1.x.array)
def test_mixed_interpolation(cell_type, order): """Test that interpolation is correct in a MixedElement.""" mesh = one_cell_mesh(cell_type) tdim = mesh.topology.dim A = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), order) B = ufl.VectorElement("Lagrange", mesh.ufl_cell(), order) V = FunctionSpace(mesh, ufl.MixedElement([A, B])) v = Function(V) if tdim == 1: def f(x): return (x[0]**order, 2 * x[0]) elif tdim == 2: def f(x): return (x[1], 2 * x[0]**order, 3 * x[1]) else: def f(x): return (x[1], 2 * x[0]**order, 3 * x[2], 4 * x[0]) v.interpolate(f) points = [random_point_in_cell(cell_type) for count in range(5)] cells = [0 for count in range(5)] values = v.eval(points, cells) for p, v in zip(points, values): assert np.allclose(v, f(p))
def test_mixed_interpolation(): """Test that interpolation raised an exception.""" mesh = one_cell_mesh(CellType.triangle) A = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 1) B = ufl.VectorElement("Lagrange", mesh.ufl_cell(), 1) v = Function(FunctionSpace(mesh, ufl.MixedElement([A, B]))) with pytest.raises(RuntimeError): v.interpolate(lambda x: (x[1], 2 * x[0], 3 * x[1]))
def __init__(self, spaces, name=None): super(MixedFunctionSpace, self).__init__() self._spaces = tuple( IndexedFunctionSpace(i, s, self) for i, s in enumerate(spaces)) self._ufl_element = ufl.MixedElement( *[s.ufl_element() for s in spaces]) self.name = name or "_".join(str(s.name) for s in spaces) self._subspaces = {} self._mesh = spaces[0].mesh()
def __new__(cls, spaces, name=None): """ :param spaces: a list (or tuple) of :class:`FunctionSpace`\s The function space may be created as :: V = MixedFunctionSpace(spaces) ``spaces`` may consist of multiple occurances of the same space: :: P1 = FunctionSpace(mesh, "CG", 1) P2v = VectorFunctionSpace(mesh, "Lagrange", 2) ME = MixedFunctionSpace([P2v, P1, P1, P1]) """ # Check that function spaces are on the same mesh meshes = [space.mesh() for space in spaces] for i in xrange(1, len(meshes)): if meshes[i] is not meshes[0]: raise ValueError( "All function spaces must be defined on the same mesh!") # Select mesh mesh = meshes[0] # Get topological spaces spaces = flatten(spaces) if mesh is mesh.topology: spaces = tuple(spaces) else: spaces = tuple(space.topological for space in spaces) # Ask object from cache self = ObjectCached.__new__(cls, mesh, spaces, name) if not self._initialized: self._spaces = [ IndexedFunctionSpace(s, i, self) for i, s in enumerate(spaces) ] self._mesh = mesh.topology self._ufl_element = ufl.MixedElement( *[fs.ufl_element() for fs in spaces]) self.name = name or '_'.join(str(s.name) for s in spaces) self._initialized = True dm = PETSc.DMShell().create() with self.make_dat().vec_ro as v: dm.setGlobalVector(v.duplicate()) dm.setAttr('__fs__', weakref.ref(self)) dm.setCreateFieldDecomposition(self.create_field_decomp) dm.setCreateSubDM(self.create_subdm) self._dm = dm self._ises = self.dof_dset.field_ises self._subspaces = [] if mesh is not mesh.topology: self = WithGeometry(self, mesh) return self
def test_mixed_element_interpolation(): def f(x): return np.ones(2, x.shape[1]) mesh = UnitCubeMesh(MPI.COMM_WORLD, 3, 3, 3) el = ufl.FiniteElement("CG", mesh.ufl_cell(), 1) V = dolfinx.FunctionSpace(mesh, ufl.MixedElement([el, el])) u = dolfinx.Function(V) with pytest.raises(RuntimeError): u.interpolate(f)
def __init__(self, spaces, name=None): super(MixedFunctionSpace, self).__init__() self._spaces = tuple(IndexedFunctionSpace(i, s, self) for i, s in enumerate(spaces)) mesh, = set(s.mesh() for s in spaces) self._ufl_function_space = ufl.FunctionSpace(mesh.ufl_mesh(), ufl.MixedElement(*[s.ufl_element() for s in spaces])) self.name = name or "_".join(str(s.name) for s in spaces) self._subspaces = {} self._mesh = mesh self.comm = self.node_set.comm
def reconstruct_element(element, cell=None): """Rebuild element with a new cell.""" if cell is None: return element if isinstance(element, ufl.FiniteElement): family = element.family() degree = element.degree() return ufl.FiniteElement(family, cell, degree) if isinstance(element, ufl.VectorElement): family = element.family() degree = element.degree() dim = len(element.sub_elements()) return ufl.VectorElement(family, cell, degree, dim) if isinstance(element, ufl.TensorElement): family = element.family() degree = element.degree() shape = element.value_shape() symmetry = element.symmetry() return ufl.TensorElement(family, cell, degree, shape, symmetry) if isinstance(element, ufl.EnrichedElement): eles = [ reconstruct_element(sub, cell=cell) for sub in element._elements ] return ufl.EnrichedElement(*eles) if isinstance(element, ufl.RestrictedElement): return ufl.RestrictedElement( reconstruct_element(element.sub_element(), cell=cell), element.restriction_domain()) if isinstance(element, (ufl.TraceElement, ufl.InteriorElement, ufl.HDivElement, ufl.HCurlElement, ufl.BrokenElement, ufl.FacetElement)): return type(element)(reconstruct_element(element._element, cell=cell)) if isinstance(element, ufl.OuterProductElement): return ufl.OuterProductElement(element._A, element._B, cell=cell) if isinstance(element, ufl.OuterProductVectorElement): dim = len(element.sub_elements()) return ufl.OuterProductVectorElement(element._A, element._B, cell=cell, dim=dim) if isinstance(element, ufl.OuterProductTensorElement): return element.reconstruct(cell=cell) if isinstance(element, ufl.MixedElement): eles = [ reconstruct_element(sub, cell=cell) for sub in element.sub_elements() ] return ufl.MixedElement(*eles) raise NotImplementedError( "Don't know how to reconstruct element of type %s" % type(element))
def __init__(self, spaces): """ Create mixed finite element function space. *Arguments* spaces a list (or tuple) of :py:class:`FunctionSpaces <dolfin.functions.functionspace.FunctionSpace>`. *Examples of usage* The function space may be created by .. code-block:: python V = MixedFunctionSpace(spaces) ``spaces`` may consist of multiple occurances of the same space: .. code-block:: python P1 = FunctionSpace(mesh, "CG", 1) P2v = VectorFunctionSpace(mesh, "Lagrange", 2) ME = MixedFunctionSpace([P2v, P1, P1, P1]) """ # Check arguments if not len(spaces) > 0: cpp.dolfin_error("functionspace.py", "create mixed function space", "Need at least one subspace") if not all(isinstance(V, FunctionSpaceBase) for V in spaces): cpp.dolfin_error("functionspace.py", "create mixed function space", "Invalid subspaces: " + str(spaces)) #if not all(V.mesh() == spaces[0].mesh() for V in spaces): # cpp.dolfin_error("functionspace.py", "Nonmatching meshes for mixed function space: " \ # + str([V.mesh() for V in spaces])) # Check that all spaces share same constrained_domain map # Create UFL element element = ufl.MixedElement(*[V.ufl_element() for V in spaces]) # Initialize base class using mesh from first space FunctionSpaceBase.__init__(self, spaces[0].mesh(), element, constrained_domain=spaces[0].dofmap().constrained_domain)
def __init__(self, spaces): """ Create mixed finite element function space. *Arguments* spaces a list (or tuple) of :py:class:`FunctionSpaces <dolfin.functions.functionspace.FunctionSpace>`. *Examples of usage* The function space may be created by .. code-block:: python V = MixedFunctionSpace(spaces) ``spaces`` may consist of multiple occurances of the same space: .. code-block:: python P1 = FunctionSpace(mesh, "CG", 1) P2v = VectorFunctionSpace(mesh, "Lagrange", 2) ME = MixedFunctionSpace([P2v, P1, P1, P1]) """ # Check arguments if not len(spaces) > 0: cpp.dolfin_error("functionspace.py", "create mixed function space", "Need at least one subspace") if not all(isinstance(V, FunctionSpaceBase) for V in spaces): cpp.dolfin_error("functionspace.py", "create mixed function space", "Invalid subspaces: " + str(spaces)) # Create UFL element element = ufl.MixedElement(*[V.ufl_element() for V in spaces]) # Get common mesh and constrained_domain, must all be the same mesh, constrained_domain \ = _get_common_mesh_and_constrained_domain(spaces, "mixed") # Initialize base class using mesh from first space FunctionSpaceBase.__init__(self, mesh, element, \ constrained_domain=constrained_domain)
def derivative(form, u, du=None, coefficient_derivatives=None): if du is None: # Get existing arguments from form and position the new one with the next argument number form_arguments = form.arguments() number = max([-1] + [arg.number() for arg in form_arguments]) + 1 if any(arg.part() is not None for arg in form_arguments): cpp.dolfin_error( "formmanipulation.py", "compute derivative of form", "Cannot automatically create new Argument using " "parts, please supply one") part = None if isinstance(u, Function): V = u.function_space() du = Argument(V, number, part) elif isinstance(u, (list, tuple)) and all( isinstance(w, Function) for w in u): cpp.deprecation( "derivative of form w.r.t. a tuple of Coefficients", "1.7.0", "2.0.0", "Take derivative w.r.t. a single Coefficient on " "a mixed space instead.") # Get mesh mesh = u[0].function_space().mesh() if not all(mesh.id() == v.function_space().mesh().id() for v in u): cpp.dolfin_error( "formmanipulation.py", "compute derivative of form", "Don't know how to compute derivative with " "respect to Coefficients on different meshes yet") # Build mixed element, space and argument element = ufl.MixedElement( [v.function_space().ufl_element() for v in u]) V = FunctionSpace(mesh, element) du = ufl.split(Argument(V, number, part)) else: cpp.dolfin_error("formmanipulation.py", "compute derivative of form w.r.t. '%s'" % u, "Supply Function as a Coefficient") return ufl.derivative(form, u, du, coefficient_derivatives)
def MixedFunctionSpace(spaces): """ Create mixed finite element function space. *Arguments* spaces a list (or tuple) of :py:class:`FunctionSpaces <dolfin.functions.functionspace.FunctionSpace>`. *Examples of usage* The function space may be created by .. code-block:: python V = MixedFunctionSpace(spaces) ``spaces`` may consist of multiple occurances of the same space: .. code-block:: python P1 = FunctionSpace(mesh, "CG", 1) P2v = VectorFunctionSpace(mesh, "Lagrange", 2) ME = MixedFunctionSpace([P2v, P1, P1, P1]) """ cpp.deprecation("'MixedFunctionSpace'", "1.7.0", "2.0.0", "Use 'FunctionSpace(mesh, MixedElement(...))'.") # Check arguments if not len(spaces) > 0: cpp.dolfin_error("functionspace.py", "create mixed function space", "Need at least one subspace") if not all(isinstance(V, FunctionSpace) for V in spaces): cpp.dolfin_error("functionspace.py", "create mixed function space", "Invalid subspaces: " + str(spaces)) # Get common mesh and constrained_domain, must all be the same mesh, constrained_domain = _get_common_mesh_and_constrained_domain(spaces) # Create UFL element element = ufl.MixedElement(*[V.ufl_element() for V in spaces]) return FunctionSpace(mesh, element, constrained_domain=constrained_domain)
def test_monolithic(V1, V2, squaremesh_5): mesh = squaremesh_5 Wel = ufl.MixedElement([V1.ufl_element(), V2.ufl_element()]) W = dolfinx.FunctionSpace(mesh, Wel) u = dolfinx.Function(W) u0, u1 = ufl.split(u) v = ufl.TestFunction(W) v0, v1 = ufl.split(v) Phi = (ufl.sin(u0) - 0.5)**2 * ufl.dx(mesh) + (4.0 * u0 - u1)**2 * ufl.dx(mesh) F = ufl.derivative(Phi, u, v) opts = PETSc.Options("monolithic") opts.setValue('snes_type', 'newtonls') opts.setValue('snes_linesearch_type', 'basic') opts.setValue('snes_rtol', 1.0e-10) opts.setValue('snes_max_it', 20) opts.setValue('ksp_type', 'preonly') opts.setValue('pc_type', 'lu') opts.setValue('pc_factor_mat_solver_type', 'mumps') problem = dolfiny.snesblockproblem.SNESBlockProblem([F], [u], prefix="monolithic") sol, = problem.solve() u0, u1 = sol.split() u0 = u0.collapse() u1 = u1.collapse() assert np.isclose((u0.vector - np.arcsin(0.5)).norm(), 0.0) assert np.isclose((u1.vector - 4.0 * np.arcsin(0.5)).norm(), 0.0)
def __init__(self, spaces, name=None): """ :param spaces: a list (or tuple) of :class:`FunctionSpace`\s The function space may be created as :: V = MixedFunctionSpace(spaces) ``spaces`` may consist of multiple occurances of the same space: :: P1 = FunctionSpace(mesh, "CG", 1) P2v = VectorFunctionSpace(mesh, "Lagrange", 2) ME = MixedFunctionSpace([P2v, P1, P1, P1]) """ if self._initialized: return self._spaces = [ IndexedFunctionSpace(s, i, self) for i, s in enumerate(flatten(spaces)) ] self._mesh = self._spaces[0].mesh() self._ufl_element = ufl.MixedElement( *[fs.ufl_element() for fs in self._spaces]) self.name = name or '_'.join(str(s.name) for s in self._spaces) self.rank = 1 self._index = None self._initialized = True dm = PETSc.DMShell().create() from firedrake.function import Function with Function(self).dat.vec_ro as v: dm.setGlobalVector(v.duplicate()) dm.setAttr('__fs__', weakref.ref(self)) dm.setCreateFieldDecomposition(self.create_field_decomp) dm.setCreateSubDM(self.create_subdm) self._dm = dm self._ises = self.dof_dset.field_ises self._subspaces = []
def initialize(self, pc): """Set up the problem context. Take the original mixed problem and reformulate the problem as a hybridized mixed system. A KSP is created for the Lagrange multiplier system. """ from firedrake import (FunctionSpace, Function, Constant, TrialFunction, TrialFunctions, TestFunction, DirichletBC, assemble) from firedrake.assemble import (allocate_matrix, create_assembly_callable) from firedrake.formmanipulation import split_form from ufl.algorithms.replace import replace # Extract the problem context prefix = pc.getOptionsPrefix() + "hybridization_" _, P = pc.getOperators() self.cxt = P.getPythonContext() if not isinstance(self.cxt, ImplicitMatrixContext): raise ValueError("The python context must be an ImplicitMatrixContext") test, trial = self.cxt.a.arguments() V = test.function_space() mesh = V.mesh() if len(V) != 2: raise ValueError("Expecting two function spaces.") if all(Vi.ufl_element().value_shape() for Vi in V): raise ValueError("Expecting an H(div) x L2 pair of spaces.") # Automagically determine which spaces are vector and scalar for i, Vi in enumerate(V): if Vi.ufl_element().sobolev_space().name == "HDiv": self.vidx = i else: assert Vi.ufl_element().sobolev_space().name == "L2" self.pidx = i # Create the space of approximate traces. W = V[self.vidx] if W.ufl_element().family() == "Brezzi-Douglas-Marini": tdegree = W.ufl_element().degree() else: try: # If we have a tensor product element h_deg, v_deg = W.ufl_element().degree() tdegree = (h_deg - 1, v_deg - 1) except TypeError: tdegree = W.ufl_element().degree() - 1 TraceSpace = FunctionSpace(mesh, "HDiv Trace", tdegree) # Break the function spaces and define fully discontinuous spaces broken_elements = ufl.MixedElement([ufl.BrokenElement(Vi.ufl_element()) for Vi in V]) V_d = FunctionSpace(mesh, broken_elements) # Set up the functions for the original, hybridized # and schur complement systems self.broken_solution = Function(V_d) self.broken_residual = Function(V_d) self.trace_solution = Function(TraceSpace) self.unbroken_solution = Function(V) self.unbroken_residual = Function(V) # Set up the KSP for the hdiv residual projection hdiv_mass_ksp = PETSc.KSP().create(comm=pc.comm) hdiv_mass_ksp.setOptionsPrefix(prefix + "hdiv_residual_") # HDiv mass operator p = TrialFunction(V[self.vidx]) q = TestFunction(V[self.vidx]) mass = ufl.dot(p, q)*ufl.dx # TODO: Bcs? M = assemble(mass, bcs=None, form_compiler_parameters=self.cxt.fc_params) M.force_evaluation() Mmat = M.petscmat hdiv_mass_ksp.setOperators(Mmat) hdiv_mass_ksp.setUp() hdiv_mass_ksp.setFromOptions() self.hdiv_mass_ksp = hdiv_mass_ksp # Storing the result of A.inv * r, where A is the HDiv # mass matrix and r is the HDiv residual self._primal_r = Function(V[self.vidx]) tau = TestFunction(V_d[self.vidx]) self._assemble_broken_r = create_assembly_callable( ufl.dot(self._primal_r, tau)*ufl.dx, tensor=self.broken_residual.split()[self.vidx], form_compiler_parameters=self.cxt.fc_params) # Create the symbolic Schur-reduction: # Original mixed operator replaced with "broken" # arguments arg_map = {test: TestFunction(V_d), trial: TrialFunction(V_d)} Atilde = Tensor(replace(self.cxt.a, arg_map)) gammar = TestFunction(TraceSpace) n = ufl.FacetNormal(mesh) sigma = TrialFunctions(V_d)[self.vidx] # We zero out the contribution of the trace variables on the exterior # boundary. Extruded cells will have both horizontal and vertical # facets if mesh.cell_set._extruded: trace_bcs = [DirichletBC(TraceSpace, Constant(0.0), "on_boundary"), DirichletBC(TraceSpace, Constant(0.0), "bottom"), DirichletBC(TraceSpace, Constant(0.0), "top")] K = Tensor(gammar('+') * ufl.dot(sigma, n) * ufl.dS_h + gammar('+') * ufl.dot(sigma, n) * ufl.dS_v) else: trace_bcs = [DirichletBC(TraceSpace, Constant(0.0), "on_boundary")] K = Tensor(gammar('+') * ufl.dot(sigma, n) * ufl.dS) # If boundary conditions are contained in the ImplicitMatrixContext: if self.cxt.row_bcs: raise NotImplementedError("Strong BCs not currently handled. Try imposing them weakly.") # Assemble the Schur complement operator and right-hand side self.schur_rhs = Function(TraceSpace) self._assemble_Srhs = create_assembly_callable( K * Atilde.inv * self.broken_residual, tensor=self.schur_rhs, form_compiler_parameters=self.cxt.fc_params) schur_comp = K * Atilde.inv * K.T self.S = allocate_matrix(schur_comp, bcs=trace_bcs, form_compiler_parameters=self.cxt.fc_params) self._assemble_S = create_assembly_callable(schur_comp, tensor=self.S, bcs=trace_bcs, form_compiler_parameters=self.cxt.fc_params) self._assemble_S() self.S.force_evaluation() Smat = self.S.petscmat # Nullspace for the multiplier problem nullspace = create_schur_nullspace(P, -K * Atilde, V, V_d, TraceSpace, pc.comm) if nullspace: Smat.setNullSpace(nullspace) # Set up the KSP for the system of Lagrange multipliers trace_ksp = PETSc.KSP().create(comm=pc.comm) trace_ksp.setOptionsPrefix(prefix) trace_ksp.setOperators(Smat) trace_ksp.setUp() trace_ksp.setFromOptions() self.trace_ksp = trace_ksp split_mixed_op = dict(split_form(Atilde.form)) split_trace_op = dict(split_form(K.form)) # Generate reconstruction calls self._reconstruction_calls(split_mixed_op, split_trace_op) # NOTE: The projection stage *might* be replaced by a Fortin # operator. We may want to allow the user to specify if they # wish to use a Fortin operator over a projection, or vice-versa. # In a future add-on, we can add a switch which chooses either # the Fortin reconstruction or the usual KSP projection. # Set up the projection KSP hdiv_projection_ksp = PETSc.KSP().create(comm=pc.comm) hdiv_projection_ksp.setOptionsPrefix(prefix + 'hdiv_projection_') # Reuse the mass operator from the hdiv_mass_ksp hdiv_projection_ksp.setOperators(Mmat) # Construct the RHS for the projection stage self._projection_rhs = Function(V[self.vidx]) self._assemble_projection_rhs = create_assembly_callable( ufl.dot(self.broken_solution.split()[self.vidx], q)*ufl.dx, tensor=self._projection_rhs, form_compiler_parameters=self.cxt.fc_params) # Finalize ksp setup hdiv_projection_ksp.setUp() hdiv_projection_ksp.setFromOptions() self.hdiv_projection_ksp = hdiv_projection_ksp
def initialize(self, pc): """Set up the problem context. Take the original mixed problem and reformulate the problem as a hybridized mixed system. A KSP is created for the Lagrange multiplier system. """ from firedrake import (FunctionSpace, Function, Constant, TrialFunction, TrialFunctions, TestFunction, DirichletBC) from firedrake.assemble import (allocate_matrix, create_assembly_callable) from firedrake.formmanipulation import split_form from ufl.algorithms.replace import replace # Extract the problem context prefix = pc.getOptionsPrefix() + "hybridization_" _, P = pc.getOperators() self.ctx = P.getPythonContext() if not isinstance(self.ctx, ImplicitMatrixContext): raise ValueError( "The python context must be an ImplicitMatrixContext") test, trial = self.ctx.a.arguments() V = test.function_space() mesh = V.mesh() if len(V) != 2: raise ValueError("Expecting two function spaces.") if all(Vi.ufl_element().value_shape() for Vi in V): raise ValueError("Expecting an H(div) x L2 pair of spaces.") # Automagically determine which spaces are vector and scalar for i, Vi in enumerate(V): if Vi.ufl_element().sobolev_space().name == "HDiv": self.vidx = i else: assert Vi.ufl_element().sobolev_space().name == "L2" self.pidx = i # Create the space of approximate traces. W = V[self.vidx] if W.ufl_element().family() == "Brezzi-Douglas-Marini": tdegree = W.ufl_element().degree() else: try: # If we have a tensor product element h_deg, v_deg = W.ufl_element().degree() tdegree = (h_deg - 1, v_deg - 1) except TypeError: tdegree = W.ufl_element().degree() - 1 TraceSpace = FunctionSpace(mesh, "HDiv Trace", tdegree) # Break the function spaces and define fully discontinuous spaces broken_elements = ufl.MixedElement( [ufl.BrokenElement(Vi.ufl_element()) for Vi in V]) V_d = FunctionSpace(mesh, broken_elements) # Set up the functions for the original, hybridized # and schur complement systems self.broken_solution = Function(V_d) self.broken_residual = Function(V_d) self.trace_solution = Function(TraceSpace) self.unbroken_solution = Function(V) self.unbroken_residual = Function(V) shapes = (V[self.vidx].finat_element.space_dimension(), np.prod(V[self.vidx].shape)) domain = "{[i,j]: 0 <= i < %d and 0 <= j < %d}" % shapes instructions = """ for i, j w[i,j] = w[i,j] + 1 end """ self.weight = Function(V[self.vidx]) par_loop((domain, instructions), ufl.dx, {"w": (self.weight, INC)}, is_loopy_kernel=True) instructions = """ for i, j vec_out[i,j] = vec_out[i,j] + vec_in[i,j]/w[i,j] end """ self.average_kernel = (domain, instructions) # Create the symbolic Schur-reduction: # Original mixed operator replaced with "broken" # arguments arg_map = {test: TestFunction(V_d), trial: TrialFunction(V_d)} Atilde = Tensor(replace(self.ctx.a, arg_map)) gammar = TestFunction(TraceSpace) n = ufl.FacetNormal(mesh) sigma = TrialFunctions(V_d)[self.vidx] if mesh.cell_set._extruded: Kform = (gammar('+') * ufl.jump(sigma, n=n) * ufl.dS_h + gammar('+') * ufl.jump(sigma, n=n) * ufl.dS_v) else: Kform = (gammar('+') * ufl.jump(sigma, n=n) * ufl.dS) # Here we deal with boundaries. If there are Neumann # conditions (which should be enforced strongly for # H(div)xL^2) then we need to add jump terms on the exterior # facets. If there are Dirichlet conditions (which should be # enforced weakly) then we need to zero out the trace # variables there as they are not active (otherwise the hybrid # problem is not well-posed). # If boundary conditions are contained in the ImplicitMatrixContext: if self.ctx.row_bcs: # Find all the subdomains with neumann BCS # These are Dirichlet BCs on the vidx space neumann_subdomains = set() for bc in self.ctx.row_bcs: if bc.function_space().index == self.pidx: raise NotImplementedError( "Dirichlet conditions for scalar variable not supported. Use a weak bc" ) if bc.function_space().index != self.vidx: raise NotImplementedError( "Dirichlet bc set on unsupported space.") # append the set of sub domains subdom = bc.sub_domain if isinstance(subdom, str): neumann_subdomains |= set([subdom]) else: neumann_subdomains |= set( as_tuple(subdom, numbers.Integral)) # separate out the top and bottom bcs extruded_neumann_subdomains = neumann_subdomains & { "top", "bottom" } neumann_subdomains = neumann_subdomains - extruded_neumann_subdomains integrand = gammar * ufl.dot(sigma, n) measures = [] trace_subdomains = [] if mesh.cell_set._extruded: ds = ufl.ds_v for subdomain in sorted(extruded_neumann_subdomains): measures.append({ "top": ufl.ds_t, "bottom": ufl.ds_b }[subdomain]) trace_subdomains.extend( sorted({"top", "bottom"} - extruded_neumann_subdomains)) else: ds = ufl.ds if "on_boundary" in neumann_subdomains: measures.append(ds) else: measures.extend((ds(sd) for sd in sorted(neumann_subdomains))) markers = [int(x) for x in mesh.exterior_facets.unique_markers] dirichlet_subdomains = set(markers) - neumann_subdomains trace_subdomains.extend(sorted(dirichlet_subdomains)) for measure in measures: Kform += integrand * measure trace_bcs = [ DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains ] else: # No bcs were provided, we assume weak Dirichlet conditions. # We zero out the contribution of the trace variables on # the exterior boundary. Extruded cells will have both # horizontal and vertical facets trace_subdomains = ["on_boundary"] if mesh.cell_set._extruded: trace_subdomains.extend(["bottom", "top"]) trace_bcs = [ DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains ] # Make a SLATE tensor from Kform K = Tensor(Kform) # Assemble the Schur complement operator and right-hand side self.schur_rhs = Function(TraceSpace) self._assemble_Srhs = create_assembly_callable( K * Atilde.inv * AssembledVector(self.broken_residual), tensor=self.schur_rhs, form_compiler_parameters=self.ctx.fc_params) mat_type = PETSc.Options().getString(prefix + "mat_type", "aij") schur_comp = K * Atilde.inv * K.T self.S = allocate_matrix(schur_comp, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type, options_prefix=prefix) self._assemble_S = create_assembly_callable( schur_comp, tensor=self.S, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type) with timed_region("HybridOperatorAssembly"): self._assemble_S() Smat = self.S.petscmat nullspace = self.ctx.appctx.get("trace_nullspace", None) if nullspace is not None: nsp = nullspace(TraceSpace) Smat.setNullSpace(nsp.nullspace(comm=pc.comm)) # Set up the KSP for the system of Lagrange multipliers trace_ksp = PETSc.KSP().create(comm=pc.comm) trace_ksp.setOptionsPrefix(prefix) trace_ksp.setOperators(Smat) trace_ksp.setUp() trace_ksp.setFromOptions() self.trace_ksp = trace_ksp split_mixed_op = dict(split_form(Atilde.form)) split_trace_op = dict(split_form(K.form)) # Generate reconstruction calls self._reconstruction_calls(split_mixed_op, split_trace_op)
import ufl from firedrake import * mesh = UnitSquareMesh(1, 1) Vhat = FunctionSpace(mesh, 'CG', 2) broken_elements = ufl.MixedElement( [ufl.BrokenElement(Vi.ufl_element()) for Vi in Vhat]) V = FunctionSpace(mesh, broken_elements) u = TrialFunction(V) v = TestFunction(V) mass = inner(u, v) * dx breakpoint() M = assemble(mass).M
def white_noise(V, rng): ''' ''' # Create broken space for independent samples mesh = V.mesh() broken_elements = ufl.MixedElement( [ufl.BrokenElement(Vi.ufl_element()) for Vi in V]) Vbrok = FunctionSpace(mesh, broken_elements) iid_normal = rng.normal(Vbrok, 0.0, 1.0) wnoise = Function(V) # Create mass expression, assemble and extract kernel u = TrialFunction(V) v = TestFunction(V) mass = inner(u, v) * dx mass_ker, *stuff = tsfc_interface.compile_form(mass, "mass", coffee=False) mass_code = loopy.generate_code_v2( mass_ker.kinfo.kernel.code).device_code() mass_code = mass_code.replace("void " + mass_ker.kinfo.kernel.name, "static void " + mass_ker.kinfo.kernel.name) # Add custom code for doing "Cholesky" decomp and applying to broken vector blocksize = mass_ker.kinfo.kernel.code.args[0].shape[0] cholesky_code = f""" extern void dpotrf_(char *UPLO, int *N, double *A, int *LDA, int *INFO); extern void dgemv_(char *TRANS, int *M, int *N, double *ALPHA, double *A, int *LDA, double *X, int *INCX, double *BETA, double *Y, int *INCY); {mass_code} void apply_cholesky(double *__restrict__ z, double *__restrict__ b, double const *__restrict__ coords) {{ char uplo[1]; int32_t N = {blocksize}, LDA = {blocksize}, INFO = 0; int32_t i=0, j=0; uplo[0] = 'u'; double H[{blocksize}*{blocksize}] = {{{{ 0.0 }}}}; char trans[1]; int32_t stride = 1; double one = 1.0; double zero = 0.0; {mass_ker.kinfo.kernel.name}(H, coords); uplo[0] = 'u'; dpotrf_(uplo, &N, H, &LDA, &INFO); for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) if (j>i) H[i*N + j] = 0.0; trans[0] = 'T'; dgemv_(trans, &N, &N, &one, H, &LDA, z, &stride, &zero, b, &stride); }} """ cholesky_kernel = op2.Kernel(cholesky_code, "apply_cholesky", ldargs=["-llapack", "-lblas"]) # Construct arguments for par loop def get_map(x): return x.cell_node_map() i, _ = mass_ker.indices z_arg = vector_arg(op2.READ, get_map, i, function=iid_normal, V=Vbrok) b_arg = vector_arg(op2.INC, get_map, i, function=wnoise, V=V) coords = mesh.coordinates op2.par_loop(cholesky_kernel, mesh.cell_set, z_arg, b_arg, coords.dat(op2.READ, get_map(coords))) return wnoise
def white_noise(V, rng=None): ''' ''' # If no random number generator provided make a new one if rng is None: pcg = _default_pcg rng = RandomGenerator(pcg) # Create broken space for independent samples mesh = V.mesh() broken_elements = ufl.MixedElement([ufl.BrokenElement(Vi.ufl_element()) for Vi in V]) Vbrok = FunctionSpace(mesh, broken_elements) iid_normal = rng.normal(Vbrok, 0.0, 1.0) wnoise = Function(V) # We also need cell volumes for correction DG0 = FunctionSpace(mesh, 'DG', 0) vol = Function(DG0) vol.interpolate(CellVolume(mesh)) # Create mass expression, assemble and extract kernel u = TrialFunction(V) v = TestFunction(V) mass = inner(u,v)*dx mass_ker, *stuff = tsfc_interface.compile_form(mass, "mass", coffee=False) mass_code = loopy.generate_code_v2(mass_ker.kinfo.kernel.code).device_code() mass_code = mass_code.replace("void " + mass_ker.kinfo.kernel.name, "static void " + mass_ker.kinfo.kernel.name) # Add custom code for doing "Cholesky" decomp and applying to broken vector blocksize = mass_ker.kinfo.kernel.code.args[0].shape[0] cholesky_code = f""" extern void dpotrf_(char *UPLO, int *N, double *A, int *LDA, int *INFO); extern void dgemv_(char *TRANS, int *M, int *N, double *ALPHA, double *A, int *LDA, double *X, int *INCX, double *BETA, double *Y, int *INCY); {mass_code} void apply_cholesky(double *__restrict__ z, double *__restrict__ b, double const *__restrict__ coords, double const *__restrict__ volume) {{ char uplo[1]; int32_t N = {blocksize}, LDA = {blocksize}, INFO = 0; int32_t i=0, j=0; uplo[0] = 'u'; double H[{blocksize}*{blocksize}] = {{{{ 0.0 }}}}; char trans[1]; int32_t stride = 1; //double one = 1.0; double scale = 1.0/volume[0]; double zero = 0.0; {mass_ker.kinfo.kernel.name}(H, coords); uplo[0] = 'u'; dpotrf_(uplo, &N, H, &LDA, &INFO); for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) if (j>i) H[i*N + j] = 0.0; trans[0] = 'T'; dgemv_(trans, &N, &N, &scale, H, &LDA, z, &stride, &zero, b, &stride); }} """ # Get the BLAS and LAPACK compiler parameters to compile the kernel if COMM_WORLD.rank == 0: petsc_variables = get_petsc_variables() BLASLAPACK_LIB = petsc_variables.get("BLASLAPACK_LIB", "") BLASLAPACK_LIB = COMM_WORLD.bcast(BLASLAPACK_LIB, root=0) BLASLAPACK_INCLUDE = petsc_variables.get("BLASLAPACK_INCLUDE", "") BLASLAPACK_INCLUDE = COMM_WORLD.bcast(BLASLAPACK_INCLUDE, root=0) else: BLASLAPACK_LIB = COMM_WORLD.bcast(None, root=0) BLASLAPACK_INCLUDE = COMM_WORLD.bcast(None, root=0) cholesky_kernel = op2.Kernel(cholesky_code, "apply_cholesky", include_dirs=BLASLAPACK_INCLUDE.split(), ldargs=BLASLAPACK_LIB.split()) # Construct arguments for par loop def get_map(x): return x.cell_node_map() i, _ = mass_ker.indices z_arg = vector_arg(op2.READ, get_map, i, function=iid_normal, V=Vbrok) b_arg = vector_arg(op2.INC, get_map, i, function=wnoise, V=V) coords = mesh.coordinates volumes = vector_arg(op2.READ, get_map, i, function=vol, V=DG0) op2.par_loop(cholesky_kernel, mesh.cell_set, z_arg, b_arg, coords.dat(op2.READ, get_map(coords)), volumes) return wnoise
def initialize(self, pc): """Set up the problem context. Take the original mixed problem and reformulate the problem as a hybridized mixed system. A KSP is created for the Lagrange multiplier system. """ from firedrake import (FunctionSpace, Function, Constant, TrialFunction, TrialFunctions, TestFunction, DirichletBC, assemble) from firedrake.assemble import (allocate_matrix, create_assembly_callable) from firedrake.formmanipulation import split_form from ufl.algorithms.replace import replace # Extract the problem context prefix = pc.getOptionsPrefix() + "hybridization_" _, P = pc.getOperators() self.cxt = P.getPythonContext() if not isinstance(self.cxt, ImplicitMatrixContext): raise ValueError("The python context must be an ImplicitMatrixContext") test, trial = self.cxt.a.arguments() V = test.function_space() mesh = V.mesh() if len(V) != 2: raise ValueError("Expecting two function spaces.") if all(Vi.ufl_element().value_shape() for Vi in V): raise ValueError("Expecting an H(div) x L2 pair of spaces.") # Automagically determine which spaces are vector and scalar for i, Vi in enumerate(V): if Vi.ufl_element().sobolev_space().name == "HDiv": self.vidx = i else: assert Vi.ufl_element().sobolev_space().name == "L2" self.pidx = i # Create the space of approximate traces. W = V[self.vidx] if W.ufl_element().family() == "Brezzi-Douglas-Marini": tdegree = W.ufl_element().degree() else: try: # If we have a tensor product element h_deg, v_deg = W.ufl_element().degree() tdegree = (h_deg - 1, v_deg - 1) except TypeError: tdegree = W.ufl_element().degree() - 1 TraceSpace = FunctionSpace(mesh, "HDiv Trace", tdegree) # Break the function spaces and define fully discontinuous spaces broken_elements = ufl.MixedElement([ufl.BrokenElement(Vi.ufl_element()) for Vi in V]) V_d = FunctionSpace(mesh, broken_elements) # Set up the functions for the original, hybridized # and schur complement systems self.broken_solution = Function(V_d) self.broken_residual = Function(V_d) self.trace_solution = Function(TraceSpace) self.unbroken_solution = Function(V) self.unbroken_residual = Function(V) # Set up the KSP for the hdiv residual projection hdiv_mass_ksp = PETSc.KSP().create(comm=pc.comm) hdiv_mass_ksp.setOptionsPrefix(prefix + "hdiv_residual_") # HDiv mass operator p = TrialFunction(V[self.vidx]) q = TestFunction(V[self.vidx]) mass = ufl.dot(p, q)*ufl.dx # TODO: Bcs? M = assemble(mass, bcs=None, form_compiler_parameters=self.cxt.fc_params) M.force_evaluation() Mmat = M.petscmat hdiv_mass_ksp.setOperators(Mmat) hdiv_mass_ksp.setUp() hdiv_mass_ksp.setFromOptions() self.hdiv_mass_ksp = hdiv_mass_ksp # Storing the result of A.inv * r, where A is the HDiv # mass matrix and r is the HDiv residual self._primal_r = Function(V[self.vidx]) tau = TestFunction(V_d[self.vidx]) self._assemble_broken_r = create_assembly_callable( ufl.dot(self._primal_r, tau)*ufl.dx, tensor=self.broken_residual.split()[self.vidx], form_compiler_parameters=self.cxt.fc_params) # Create the symbolic Schur-reduction: # Original mixed operator replaced with "broken" # arguments arg_map = {test: TestFunction(V_d), trial: TrialFunction(V_d)} Atilde = Tensor(replace(self.cxt.a, arg_map)) gammar = TestFunction(TraceSpace) n = ufl.FacetNormal(mesh) sigma = TrialFunctions(V_d)[self.vidx] if mesh.cell_set._extruded: Kform = (gammar('+') * ufl.dot(sigma, n) * ufl.dS_h + gammar('+') * ufl.dot(sigma, n) * ufl.dS_v) else: Kform = (gammar('+') * ufl.dot(sigma, n) * ufl.dS) # Here we deal with boundaries. If there are Neumann # conditions (which should be enforced strongly for # H(div)xL^2) then we need to add jump terms on the exterior # facets. If there are Dirichlet conditions (which should be # enforced weakly) then we need to zero out the trace # variables there as they are not active (otherwise the hybrid # problem is not well-posed). # If boundary conditions are contained in the ImplicitMatrixContext: if self.cxt.row_bcs: # Find all the subdomains with neumann BCS # These are Dirichlet BCs on the vidx space neumann_subdomains = set() for bc in self.cxt.row_bcs: if bc.function_space().index == self.pidx: raise NotImplementedError("Dirichlet conditions for scalar variable not supported. Use a weak bc") if bc.function_space().index != self.vidx: raise NotImplementedError("Dirichlet bc set on unsupported space.") # append the set of sub domains subdom = bc.sub_domain if isinstance(subdom, str): neumann_subdomains |= set([subdom]) else: neumann_subdomains |= set(as_tuple(subdom, int)) # separate out the top and bottom bcs extruded_neumann_subdomains = neumann_subdomains & {"top", "bottom"} neumann_subdomains = neumann_subdomains.difference(extruded_neumann_subdomains) integrand = gammar * ufl.dot(sigma, n) measures = [] trace_subdomains = [] if mesh.cell_set._extruded: ds = ufl.ds_v for subdomain in extruded_neumann_subdomains: measures.append({"top": ufl.ds_t, "bottom": ufl.ds_b}[subdomain]) trace_subdomains.extend(sorted({"top", "bottom"} - extruded_neumann_subdomains)) else: ds = ufl.ds if "on_boundary" in neumann_subdomains: measures.append(ds) else: measures.append(ds(tuple(neumann_subdomains))) dirichlet_subdomains = set(mesh.exterior_facets.unique_markers) - neumann_subdomains trace_subdomains.append(sorted(dirichlet_subdomains)) for measure in measures: Kform += integrand*measure trace_bcs = [DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains] else: # No bcs were provided, we assume weak Dirichlet conditions. # We zero out the contribution of the trace variables on # the exterior boundary. Extruded cells will have both # horizontal and vertical facets trace_subdomains = ["on_boundary"] if mesh.cell_set._extruded: trace_subdomains.extend(["bottom", "top"]) trace_bcs = [DirichletBC(TraceSpace, Constant(0.0), subdomain) for subdomain in trace_subdomains] # Make a SLATE tensor from Kform K = Tensor(Kform) # Assemble the Schur complement operator and right-hand side self.schur_rhs = Function(TraceSpace) self._assemble_Srhs = create_assembly_callable( K * Atilde.inv * AssembledVector(self.broken_residual), tensor=self.schur_rhs, form_compiler_parameters=self.cxt.fc_params) schur_comp = K * Atilde.inv * K.T self.S = allocate_matrix(schur_comp, bcs=trace_bcs, form_compiler_parameters=self.cxt.fc_params) self._assemble_S = create_assembly_callable(schur_comp, tensor=self.S, bcs=trace_bcs, form_compiler_parameters=self.cxt.fc_params) self._assemble_S() self.S.force_evaluation() Smat = self.S.petscmat # Nullspace for the multiplier problem nullspace = create_schur_nullspace(P, -K * Atilde, V, V_d, TraceSpace, pc.comm) if nullspace: Smat.setNullSpace(nullspace) # Set up the KSP for the system of Lagrange multipliers trace_ksp = PETSc.KSP().create(comm=pc.comm) trace_ksp.setOptionsPrefix(prefix) trace_ksp.setOperators(Smat) trace_ksp.setUp() trace_ksp.setFromOptions() self.trace_ksp = trace_ksp split_mixed_op = dict(split_form(Atilde.form)) split_trace_op = dict(split_form(K.form)) # Generate reconstruction calls self._reconstruction_calls(split_mixed_op, split_trace_op) # NOTE: The projection stage *might* be replaced by a Fortin # operator. We may want to allow the user to specify if they # wish to use a Fortin operator over a projection, or vice-versa. # In a future add-on, we can add a switch which chooses either # the Fortin reconstruction or the usual KSP projection. # Set up the projection KSP hdiv_projection_ksp = PETSc.KSP().create(comm=pc.comm) hdiv_projection_ksp.setOptionsPrefix(prefix + 'hdiv_projection_') # Reuse the mass operator from the hdiv_mass_ksp hdiv_projection_ksp.setOperators(Mmat) # Construct the RHS for the projection stage self._projection_rhs = Function(V[self.vidx]) self._assemble_projection_rhs = create_assembly_callable( ufl.dot(self.broken_solution.split()[self.vidx], q)*ufl.dx, tensor=self._projection_rhs, form_compiler_parameters=self.cxt.fc_params) # Finalize ksp setup hdiv_projection_ksp.setUp() hdiv_projection_ksp.setFromOptions() self.hdiv_projection_ksp = hdiv_projection_ksp
encoding=XDMFFile.Encoding.HDF5) as file: file.write_mesh(mesh) # Function spaces r=1 # r=2 # r=3 # x_extents = mesh_bounding_box(mesh, 0) # y_extents = mesh_bounding_box(mesh, 1) # Measures dx = ufl.Measure("dx", domain=mesh) ds = ufl.Measure("ds", domain=mesh) element = ufl.MixedElement([ufl.FiniteElement("Regge", ufl.triangle, r), ufl.FiniteElement("Lagrange", ufl.triangle, r+1)]) V = FunctionSpace(mesh, element) V_1 = V.sub(1).collapse() sigma, u = ufl.TrialFunctions(V) tau, v = ufl.TestFunctions(V) def S(tau): return tau - ufl.Identity(2) * ufl.tr(tau) # Discrete duality inner product # cf. eq. 4.5 Lizao Li's PhD thesis def b(tau_S, v): n = FacetNormal(mesh) return inner(tau_S, grad(grad(v))) * dx \
def test_biharmonic(): """Manufactured biharmonic problem. Solved using rotated Regge mixed finite element method. This is equivalent to the Hellan-Herrmann-Johnson (HHJ) finite element method in two-dimensions.""" mesh = RectangleMesh(MPI.COMM_WORLD, [np.array([0.0, 0.0, 0.0]), np.array([1.0, 1.0, 0.0])], [32, 32], CellType.triangle) element = ufl.MixedElement([ufl.FiniteElement("Regge", ufl.triangle, 1), ufl.FiniteElement("Lagrange", ufl.triangle, 2)]) V = FunctionSpace(mesh, element) sigma, u = ufl.TrialFunctions(V) tau, v = ufl.TestFunctions(V) x = ufl.SpatialCoordinate(mesh) u_exact = ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1]) * ufl.sin(ufl.pi * x[1]) f_exact = div(grad(div(grad(u_exact)))) sigma_exact = grad(grad(u_exact)) # sigma and tau are tangential-tangential continuous according to the # H(curl curl) continuity of the Regge space. However, for the biharmonic # problem we require normal-normal continuity H (div div). Theorem 4.2 of # Lizao Li's PhD thesis shows that the latter space can be constructed by # the former through the action of the operator S: def S(tau): return tau - ufl.Identity(2) * ufl.tr(tau) sigma_S = S(sigma) tau_S = S(tau) # Discrete duality inner product eq. 4.5 Lizao Li's PhD thesis def b(tau_S, v): n = FacetNormal(mesh) return inner(tau_S, grad(grad(v))) * dx \ - ufl.dot(ufl.dot(tau_S('+'), n('+')), n('+')) * jump(grad(v), n) * dS \ - ufl.dot(ufl.dot(tau_S, n), n) * ufl.dot(grad(v), n) * ds # Non-symmetric formulation a = inner(sigma_S, tau_S) * dx - b(tau_S, u) + b(sigma_S, v) L = inner(f_exact, v) * dx V_1 = V.sub(1).collapse() zero_u = Function(V_1) with zero_u.vector.localForm() as zero_u_local: zero_u_local.set(0.0) # Strong (Dirichlet) boundary condition boundary_facets = locate_entities_boundary( mesh, mesh.topology.dim - 1, lambda x: np.full(x.shape[1], True, dtype=bool)) boundary_dofs = locate_dofs_topological((V.sub(1), V_1), mesh.topology.dim - 1, boundary_facets) bcs = [DirichletBC(zero_u, boundary_dofs, V.sub(1))] A = assemble_matrix(a, bcs=bcs) A.assemble() b = assemble_vector(L) apply_lifting(b, [a], [bcs]) b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE) # Solve solver = PETSc.KSP().create(MPI.COMM_WORLD) PETSc.Options()["ksp_type"] = "preonly" PETSc.Options()["pc_type"] = "lu" # PETSc.Options()["pc_factor_mat_solver_type"] = "mumps" solver.setFromOptions() solver.setOperators(A) x_h = Function(V) solver.solve(b, x_h.vector) x_h.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) # Recall that x_h has flattened indices. u_error_numerator = np.sqrt(mesh.mpi_comm().allreduce(assemble_scalar( inner(u_exact - x_h[4], u_exact - x_h[4]) * dx(mesh, metadata={"quadrature_degree": 5})), op=MPI.SUM)) u_error_denominator = np.sqrt(mesh.mpi_comm().allreduce(assemble_scalar( inner(u_exact, u_exact) * dx(mesh, metadata={"quadrature_degree": 5})), op=MPI.SUM)) assert(np.absolute(u_error_numerator / u_error_denominator) < 0.05) # Reconstruct tensor from flattened indices. # Apply inverse transform. In 2D we have S^{-1} = S. sigma_h = S(ufl.as_tensor([[x_h[0], x_h[1]], [x_h[2], x_h[3]]])) sigma_error_numerator = np.sqrt(mesh.mpi_comm().allreduce(assemble_scalar( inner(sigma_exact - sigma_h, sigma_exact - sigma_h) * dx(mesh, metadata={"quadrature_degree": 5})), op=MPI.SUM)) sigma_error_denominator = np.sqrt(mesh.mpi_comm().allreduce(assemble_scalar( inner(sigma_exact, sigma_exact) * dx(mesh, metadata={"quadrature_degree": 5})), op=MPI.SUM)) assert(np.absolute(sigma_error_numerator / sigma_error_denominator) < 0.005)