def nullspace(self, comm=None): r"""The PETSc NullSpace object for this :class:`.VectorSpaceBasis`. :kwarg comm: Communicator to create the nullspace on.""" if hasattr(self, "_nullspace"): return self._nullspace comm = comm or COMM_WORLD self._nullspace = PETSc.NullSpace().create(constant=self._constant, vectors=self._petsc_vecs, comm=comm) return self._nullspace
def create_sc_nullspace(P, V, V_facet, comm): """Gets the nullspace vectors corresponding to the Schur complement system. :arg P: The H1 operator from the ImplicitMatrixContext. :arg V: The H1 finite element space. :arg V_facet: The finite element space of H1 basis functions restricted to the mesh skeleton. Returns: A nullspace (if there is one) for the Schur-complement system. """ from firedrake import Function nullspace = P.getNullSpace() if nullspace.handle == 0: # No nullspace return None vecs = nullspace.getVecs() tmp = Function(V) scsp_tmp = Function(V_facet) new_vecs = [] # Transfer the trace bit (the nullspace vector restricted # to facet nodes is the nullspace for the condensed system) kernel = """ for (int i=0; i<%(d)d; ++i){ for (int j=0; j<%(s)d; ++j){ x_facet[i*%(s)d + j] = x_h[i*%(s)d + j]; } }""" % { "d": V_facet.finat_element.space_dimension(), "s": np.prod(V_facet.shape) } for v in vecs: with tmp.dat.vec_wo as t: v.copy(t) par_loop(kernel, ufl.dx, { "x_facet": (scsp_tmp, WRITE), "x_h": (tmp, READ) }) # Map vecs to the facet space with scsp_tmp.dat.vec_ro as v: new_vecs.append(v.copy()) # Normalize for v in new_vecs: v.normalize() sc_nullspace = PETSc.NullSpace().create(vectors=new_vecs, comm=comm) return sc_nullspace
def __init__(self, vecs=None, constant=False): if vecs is None and not constant: raise RuntimeError( "Must either provide a list of null space vectors, or constant keyword (or both)" ) self._vecs = vecs or [] self._petsc_vecs = [] for v in self._vecs: with v.dat.vec_ro as v_: self._petsc_vecs.append(v_) if not self.is_orthonormal(): raise RuntimeError("Provided vectors must be orthonormal") self._nullspace = PETSc.NullSpace().create(constant=constant, vectors=self._petsc_vecs) self._constant = constant
def create_schur_nullspace(P, forward, V, V_d, TraceSpace, comm): """Gets the nullspace vectors corresponding to the Schur complement system for the multipliers. :arg P: The mixed operator from the ImplicitMatrixContext. :arg forward: A Slate expression denoting the forward elimination operator. :arg V: The original "unbroken" space. :arg V_d: The broken space. :arg TraceSpace: The space of approximate traces. Returns: A nullspace (if there is one) for the Schur-complement system. """ from firedrake import assemble, Function, project nullspace = P.getNullSpace() if nullspace.handle == 0: # No nullspace return None vecs = nullspace.getVecs() tmp = Function(V) tmp_b = Function(V_d) tnsp_tmp = Function(TraceSpace) forward_action = forward * tmp_b new_vecs = [] for v in vecs: with tmp.dat.vec_wo as t: v.copy(t) project(tmp, tmp_b) assemble(forward_action, tensor=tnsp_tmp) with tnsp_tmp.dat.vec_ro as v: new_vecs.append(v.copy()) # Normalize for v in new_vecs: v.normalize() schur_nullspace = PETSc.NullSpace().create(vectors=new_vecs, comm=comm) return schur_nullspace
def _build_monolithic_basis(self): """Build a basis for the complete mixed space. The monolithic basis is formed by the cartesian product of the bases forming each sub part. """ from itertools import product bvecs = [[None] for _ in self] # Get the complete list of basis vectors for each component in # the mixed basis. for idx, basis in enumerate(self): if isinstance(basis, VectorSpaceBasis): v = [] if basis._constant: v = [ function.Function(self._function_space[idx]).assign(1) ] bvecs[idx] = basis._vecs + v # Basis for mixed space is cartesian product of all the basis # vectors we just made. allbvecs = [x for x in product(*bvecs)] vecs = [function.Function(self._function_space) for _ in allbvecs] # Build the functions representing the monolithic basis. for vidx, bvec in enumerate(allbvecs): for idx, b in enumerate(bvec): if b: vecs[vidx].sub(idx).assign(b) for v in vecs: v /= v.dat.norm self._vecs = vecs self._petsc_vecs = [] for v in self._vecs: with v.dat.vec_ro as v_: self._petsc_vecs.append(v_) self._nullspace = PETSc.NullSpace().create(constant=False, vectors=self._petsc_vecs)
def _build_monolithic_basis(self): r"""Build a basis for the complete mixed space. The monolithic basis is formed by the cartesian product of the bases forming each sub part. """ self._vecs = [] for idx, basis in enumerate(self): if isinstance(basis, VectorSpaceBasis): vecs = basis._vecs if basis._constant: vecs = vecs + (function.Function( self._function_space[idx]).assign(1), ) for vec in vecs: mvec = function.Function(self._function_space) mvec.sub(idx).assign(vec) self._vecs.append(mvec) self._petsc_vecs = [] for v in self._vecs: with v.dat.vec_ro as v_: self._petsc_vecs.append(v_) # orthonormalize: basis = self._petsc_vecs for i, vec in enumerate(basis): alphas = [] for vec_ in basis[:i]: alphas.append(vec.dot(vec_)) for alpha, vec_ in zip(alphas, basis[:i]): vec.axpy(-alpha, vec_) vec.normalize() self._nullspace = PETSc.NullSpace().create(constant=False, vectors=self._petsc_vecs, comm=self.comm)
def initialize(self, pc): _, P = pc.getOperators() assert P.type == "python" context = P.getPythonContext() (self.J, self.bcs) = (context.a, context.row_bcs) test, trial = self.J.arguments() if test.function_space() != trial.function_space(): raise NotImplementedError("test and trial spaces must be the same") Pk = test.function_space() element = Pk.ufl_element() shape = element.value_shape() mesh = Pk.ufl_domain() if len(shape) == 0: P1 = firedrake.FunctionSpace(mesh, "CG", 1) elif len(shape) == 2: P1 = firedrake.VectorFunctionSpace(mesh, "CG", 1, dim=shape[0]) else: P1 = firedrake.TensorFunctionSpace(mesh, "CG", 1, shape=shape, symmetry=element.symmetry()) # TODO: A smarter low-order operator would also interpolate # any coefficients to the coarse space. mapper = ArgumentReplacer({ test: firedrake.TestFunction(P1), trial: firedrake.TrialFunction(P1) }) self.lo_J = map_integrands.map_integrand_dags(mapper, self.J) lo_bcs = [] for bc in self.bcs: # Don't actually need the value, since it's only used for # killing parts of the restriction matrix. lo_bcs.append( firedrake.DirichletBC(P1, firedrake.zero(P1.shape), bc.sub_domain, method=bc.method)) self.lo_bcs = tuple(lo_bcs) mat_type = PETSc.Options().getString( pc.getOptionsPrefix() + "lo_mat_type", firedrake.parameters["default_matrix_type"]) self.lo_op = firedrake.assemble(self.lo_J, bcs=self.lo_bcs, mat_type=mat_type) self.lo_op.force_evaluation() A, P = pc.getOperators() nearnullsp = P.getNearNullSpace() if nearnullsp.handle != 0: # Actually have a near nullspace tmp = firedrake.Function(Pk) low = firedrake.Function(P1) vecs = [] for vec in nearnullsp.getVecs(): with tmp.dat.vec as v: vec.copy(v) low.interpolate(tmp) with low.dat.vec_ro as v: vecs.append(v.copy()) nullsp = PETSc.NullSpace().create(vectors=vecs, comm=pc.comm) self.lo_op.petscmat.setNearNullSpace(nullsp) lo = PETSc.PC().create(comm=pc.comm) lo.incrementTabLevel(1, parent=pc) lo.setOperators(self.lo_op.petscmat, self.lo_op.petscmat) lo.setOptionsPrefix(pc.getOptionsPrefix() + "lo_") lo.setFromOptions() self.lo = lo self.restriction = restriction_matrix(Pk, P1, self.bcs, self.lo_bcs) self.work = self.lo_op.petscmat.createVecs() if len(self.bcs) > 0: bc_nodes = numpy.unique( numpy.concatenate([bc.nodes for bc in self.bcs])) bc_nodes = bc_nodes[bc_nodes < Pk.dof_dset.size] bc_iset = PETSc.IS().createBlock(numpy.prod(shape), bc_nodes, comm=PETSc.COMM_SELF) self.bc_indices = bc_iset.getIndices() bc_iset.destroy() else: self.bc_indices = numpy.empty(0, dtype=numpy.int32)
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 ufl.algorithms.map_integrands import map_integrand_dags from firedrake import (FunctionSpace, TrialFunction, TrialFunctions, TestFunction, Function, BrokenElement, MixedElement, FacetNormal, Constant, DirichletBC, Projector) from firedrake.assemble import (allocate_matrix, create_assembly_callable) from firedrake.formmanipulation import ArgumentReplacer, split_form # Extract the problem context prefix = pc.getOptionsPrefix() _, P = pc.getOperators() context = P.getPythonContext() test, trial = context.a.arguments() V = test.function_space() if V.mesh().cell_set._extruded: # TODO: Merge FIAT branch to support TPC trace elements raise NotImplementedError("Not implemented on extruded meshes.") # Break the function spaces and define fully discontinuous spaces broken_elements = [BrokenElement(Vi.ufl_element()) for Vi in V] elem = MixedElement(broken_elements) V_d = FunctionSpace(V.mesh(), elem) arg_map = {test: TestFunction(V_d), trial: TrialFunction(V_d)} # Replace the problems arguments with arguments defined # on the new discontinuous spaces replacer = ArgumentReplacer(arg_map) new_form = map_integrand_dags(replacer, context.a) # Create the space of approximate traces. # The vector function space will have a non-empty value_shape W = next(v for v in V if bool(v.ufl_element().value_shape())) if W.ufl_element().family() in ["Raviart-Thomas", "RTCF"]: tdegree = W.ufl_element().degree() - 1 else: tdegree = W.ufl_element().degree() # NOTE: Once extruded is ready, we will need to be aware of this # and construct the appropriate trace space for the HDiv element TraceSpace = FunctionSpace(V.mesh(), "HDiv Trace", tdegree) # NOTE: For extruded, we will need to add "on_top" and "on_bottom" trace_conditions = [ DirichletBC(TraceSpace, Constant(0.0), "on_boundary") ] # Set up the functions for the original, hybridized # and schur complement systems self.broken_solution = Function(V_d) self.broken_rhs = Function(V_d) self.trace_solution = Function(TraceSpace) self.unbroken_solution = Function(V) self.unbroken_rhs = Function(V) # Create the symbolic Schur-reduction Atilde = Tensor(new_form) gammar = TestFunction(TraceSpace) n = FacetNormal(V.mesh()) # Vector trial function will have a non-empty ufl_shape sigma = next(f for f in TrialFunctions(V_d) if bool(f.ufl_shape)) # NOTE: Once extruded is ready, this will change slightly # to include both horizontal and vertical interior facets K = Tensor(gammar('+') * ufl.dot(sigma, n) * ufl.dS) # 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_rhs, tensor=self.schur_rhs, form_compiler_parameters=context.fc_params) schur_comp = K * Atilde.inv * K.T self.S = allocate_matrix(schur_comp, bcs=trace_conditions, form_compiler_parameters=context.fc_params) self._assemble_S = create_assembly_callable( schur_comp, tensor=self.S, bcs=trace_conditions, form_compiler_parameters=context.fc_params) self._assemble_S() self.S.force_evaluation() Smat = self.S.petscmat # Nullspace for the multiplier problem nullsp = P.getNullSpace() if nullsp.handle != 0: new_vecs = get_trace_nullspace_vecs(K * Atilde.inv, nullsp, V, V_d, TraceSpace) tr_nullsp = PETSc.NullSpace().create(vectors=new_vecs, comm=pc.comm) Smat.setNullSpace(tr_nullsp) # Set up the KSP for the system of Lagrange multipliers ksp = PETSc.KSP().create(comm=pc.comm) ksp.setOptionsPrefix(prefix + "trace_") ksp.setTolerances(rtol=1e-13) ksp.setOperators(Smat) ksp.setUp() ksp.setFromOptions() self.ksp = ksp # Now we construct the local tensors for the reconstruction stage # TODO: Add support for mixed tensors and these variables # become unnecessary split_forms = split_form(new_form) A = Tensor(next(sf.form for sf in split_forms if sf.indices == (0, 0))) B = Tensor(next(sf.form for sf in split_forms if sf.indices == (1, 0))) C = Tensor(next(sf.form for sf in split_forms if sf.indices == (1, 1))) trial = TrialFunction( FunctionSpace(V.mesh(), BrokenElement(W.ufl_element()))) K_local = Tensor(gammar('+') * ufl.dot(trial, n) * ufl.dS) # Split functions and reconstruct each bit separately sigma_h, u_h = self.broken_solution.split() g, f = self.broken_rhs.split() # Pressure reconstruction M = B * A.inv * B.T + C u_sol = M.inv * f + M.inv * ( B * A.inv * K_local.T * self.trace_solution - B * A.inv * g) self._assemble_pressure = create_assembly_callable( u_sol, tensor=u_h, form_compiler_parameters=context.fc_params) # Velocity reconstruction sigma_sol = A.inv * g + A.inv * (B.T * u_h - K_local.T * self.trace_solution) self._assemble_velocity = create_assembly_callable( sigma_sol, tensor=sigma_h, form_compiler_parameters=context.fc_params) # Set up the projector for projecting the broken solution # into the unbroken finite element spaces # NOTE: Tolerance here matters! sigma_b, _ = self.broken_solution.split() sigma_u, _ = self.unbroken_solution.split() self.projector = Projector(sigma_b, sigma_u, solver_parameters={ "ksp_type": "cg", "ksp_rtol": 1e-13 })