예제 #1
0
    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
예제 #2
0
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
예제 #3
0
 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
예제 #4
0
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
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    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
                                   })