Exemple #1
0
    def test_decoratedmap_cache_hit(self, base_map):
        sm = op2.DecoratedMap(base_map, [op2.ALL])

        sm2 = op2.DecoratedMap(base_map, [op2.ALL])

        assert sm is sm2
        assert not sm != sm2
        assert sm == sm2
Exemple #2
0
    def test_decoratedmap_change_bcs(self, base_map):
        sm = op2.DecoratedMap(base_map, [op2.ALL])
        smbc = op2.DecoratedMap(base_map, [op2.ALL], implicit_bcs=["top"])

        assert "top" in smbc.implicit_bcs
        assert "top" not in sm.implicit_bcs

        smbc = op2.DecoratedMap(sm, implicit_bcs=["top"])

        assert "top" in smbc.implicit_bcs
        assert op2.ALL in smbc.iteration_region

        assert len(sm.implicit_bcs) == 0
        assert op2.ALL in smbc.iteration_region
Exemple #3
0
    def test_decoratedmap_le(self, base_map):
        sm = op2.DecoratedMap(base_map, [op2.ALL])

        assert base_map <= sm
        assert sm <= base_map

        smbc = op2.DecoratedMap(base_map, [op2.ALL], implicit_bcs=["top"])

        assert base_map <= smbc
        assert smbc <= base_map

        sm2 = op2.DecoratedMap(base_map, [op2.ON_BOTTOM])

        assert not base_map <= sm2
        assert not sm2 <= base_map
Exemple #4
0
    def test_decoratedmap_cache_miss(self, base_map, base_map2):
        sm = op2.DecoratedMap(base_map, [op2.ALL])
        sm2 = op2.DecoratedMap(base_map2, [op2.ALL])

        assert sm is not sm2
        assert sm != sm2
        assert not sm == sm2

        sm3 = op2.DecoratedMap(base_map, [op2.ON_BOTTOM])
        assert sm is not sm3
        assert sm != sm3
        assert not sm == sm3

        assert sm2 is not sm3
        assert sm2 != sm3
        assert not sm2 == sm3
Exemple #5
0
    def test_sparsity_cache_miss(self, base_set, base_set2, base_map,
                                 base_map2):
        dsets = (base_set, base_set)
        maps = (base_map, base_map)
        sp = op2.Sparsity(dsets, maps)

        dsets2 = op2.MixedSet([base_set, base_set])
        maps2 = op2.MixedMap([base_map, base_map])
        maps2 = op2.DecoratedMap(maps2, [op2.ALL])
        sp2 = op2.Sparsity(dsets2, maps2)
        assert sp is not sp2
        assert sp != sp2
        assert not sp == sp2

        dsets2 = (base_set, base_set2)
        maps2 = (base_map, base_map2)

        sp2 = op2.Sparsity(dsets2, maps2)
        assert sp is not sp2
        assert sp != sp2
        assert not sp == sp2
Exemple #6
0
    def get_map(self,
                V,
                entity_set,
                map_arity,
                bcs,
                name,
                offset,
                parent,
                kind=None):
        """Return a :class:`pyop2.Map` from some topological entity to
        degrees of freedom.

        :arg V: The :class:`FunctionSpace` to create the map for.
        :arg entity_set: The :class:`pyop2.Set` of entities to map from.
        :arg map_arity: The arity of the resulting map.
        :arg bcs: An iterable of :class:`~.DirichletBC` objects (may
            be ``None``.
        :arg name: A name for the resulting map.
        :arg offset: Map offset (for extruded).
        :arg parent: The parent map (used when bcs are provided)."""
        # V is only really used for error checking and "name".
        assert len(
            V) == 1, "get_map should not be called on MixedFunctionSpace"
        entity_node_list = self.entity_node_lists[entity_set]
        cache = self.map_caches[entity_set]

        if bcs is not None:
            # Separate explicit bcs (we just place negative entries in
            # the appropriate map values) from implicit ones (extruded
            # top and bottom) that require PyOP2 code gen.
            explicit_bcs = [
                bc for bc in bcs if bc.sub_domain not in ['top', 'bottom']
            ]
            implicit_bcs = [(bc.sub_domain, bc.method) for bc in bcs
                            if bc.sub_domain in ['top', 'bottom']]
            if len(explicit_bcs) == 0:
                # Implicit bcs are not part of the cache key for the
                # map (they only change the generated PyOP2 code),
                # hence rewrite bcs here.
                bcs = ()
            if len(implicit_bcs) == 0:
                implicit_bcs = None
        else:
            # Empty tuple if no bcs found.  This is so that matrix
            # assembly, which uses a set to keep track of the bcs
            # applied to matrix hits the cache when that set is
            # empty.  tuple(set([])) == tuple().
            bcs = ()
            implicit_bcs = None

        for bc in bcs:
            fs = bc.function_space()
            # Unwind proxies for ComponentFunctionSpace, but not
            # IndexedFunctionSpace.
            while fs.component is not None and fs.parent is not None:
                fs = fs.parent
            if fs.topological != V:
                raise RuntimeError(
                    "DirichletBC defined on a different FunctionSpace!")
        # Ensure bcs is a tuple in a canonical order for the hash key.
        lbcs = tuple(sorted(bcs, key=lambda bc: bc.__hash__()))

        cache = self.map_caches[entity_set]
        try:
            # Cache hit
            val = cache[lbcs]
            # In the implicit bc case, we decorate the cached map with
            # the list of implicit boundary conditions so PyOP2 knows
            # what to do.
            if implicit_bcs:
                val = op2.DecoratedMap(val, implicit_bcs=implicit_bcs)
            return val
        except KeyError:
            # Cache miss.
            # Any top and bottom bcs (for the extruded case) are handled elsewhere.
            nodes = [
                bc.nodes for bc in lbcs
                if bc.sub_domain not in ['top', 'bottom']
            ]
            decorate = any(bc.function_space().component is not None
                           for bc in lbcs)
            if nodes:
                bcids = reduce(numpy.union1d, nodes)
                negids = numpy.copy(bcids)
                for bc in lbcs:
                    if bc.sub_domain in ["top", "bottom"]:
                        continue
                    nbits = IntType.itemsize * 8 - 2
                    if decorate and bc.function_space().component is None:
                        # Some of the other entries will be marked
                        # with high bits, so we need to set all the
                        # high bits for these bcs
                        idx = numpy.searchsorted(bcids, bc.nodes)
                        if bc.function_space().dim > 3:
                            raise ValueError(
                                "Can't have component BCs with more than three components (have %d)",
                                bc.function_space().dim)
                        for cmp in range(bc.function_space().dim):
                            negids[idx] |= (1 << (nbits - cmp))

                    # FunctionSpace with component is IndexedVFS
                    if bc.function_space().component is not None:
                        # For indexed VFS bcs, we encode the component
                        # in the high bits of the map value.
                        # That value is then negated to indicate to
                        # the generated code to discard the values
                        #
                        # So here we do:
                        #
                        # node = -(node + 2**(nbits-cmpt) + 1)
                        #
                        # And in the generated code we can then
                        # extract the information to discard the
                        # correct entries.
                        # bcids is sorted, so use searchsorted to find indices
                        idx = numpy.searchsorted(bcids, bc.nodes)
                        # Set appropriate bit
                        negids[idx] |= (
                            1 << (nbits - bc.function_space().component))
                node_list_bc = numpy.arange(self.node_set.total_size,
                                            dtype=IntType)
                # Fix up for extruded, doesn't commute with indexedvfs
                # for now
                if self.extruded:
                    node_list_bc[bcids] = -10000000
                else:
                    node_list_bc[bcids] = -(negids + 1)
                new_entity_node_list = node_list_bc.take(entity_node_list)
            else:
                new_entity_node_list = entity_node_list

            if kind == "interior_facet" and self.bt_masks is not None:
                bt_masks = {}
                off = map_arity // 2
                for method, (bottom, top) in iteritems(self.bt_masks):
                    b = []
                    t = []
                    for i in bottom:
                        b.append(i)
                        b.append(i + off)
                    for i in top:
                        t.append(i)
                        t.append(i + off)
                    bt_masks[method] = (b, t)
            else:
                bt_masks = self.bt_masks
            val = op2.Map(entity_set,
                          self.node_set,
                          map_arity,
                          new_entity_node_list, ("%s_" + name) % (V.name),
                          offset=offset,
                          parent=parent,
                          bt_masks=bt_masks)

            if decorate:
                val = op2.DecoratedMap(val, vector_index=True)
            cache[lbcs] = val
            if implicit_bcs:
                return op2.DecoratedMap(val, implicit_bcs=implicit_bcs)
            return val
Exemple #7
0
 def get_map(x, bcs=None, decoration=None):
     map_ = x.cell_node_map(bcs)
     if decoration is not None:
         return op2.DecoratedMap(map_, decoration)
     return map_
Exemple #8
0
def _assemble(f,
              tensor=None,
              bcs=None,
              form_compiler_parameters=None,
              inverse=False,
              mat_type=None,
              sub_mat_type=None,
              appctx={},
              options_prefix=None,
              collect_loops=False,
              allocate_only=False):
    """Assemble the form or Slate expression f and return a Firedrake object
    representing the result. This will be a :class:`float` for 0-forms/rank-0
    Slate tensors, a :class:`.Function` for 1-forms/rank-1 Slate tensors and
    a :class:`.Matrix` for 2-forms/rank-2 Slate tensors.

    :arg bcs: A tuple of :class`.DirichletBC`\s to be applied.
    :arg tensor: An existing tensor object into which the form should be
        assembled. If this is not supplied, a new tensor will be created for
        the purpose.
    :arg form_compiler_parameters: (optional) dict of parameters to pass to
        the form compiler.
    :arg inverse: (optional) if f is a 2-form, then assemble the inverse
         of the local matrices.
    :arg mat_type: (optional) type for assembled matrices, one of
        "nest", "aij", "baij", or "matfree".
    :arg sub_mat_type: (optional) type for assembled sub matrices
        inside a "nest" matrix.  One of "aij" or "baij".
    :arg appctx: Additional information to hang on the assembled
         matrix if an implicit matrix is requested (mat_type "matfree").
    :arg options_prefix: An options prefix for the PETSc matrix
        (ignored if not assembling a bilinear form).
    """
    if mat_type is None:
        mat_type = parameters.parameters["default_matrix_type"]
    if mat_type not in ["matfree", "aij", "baij", "nest"]:
        raise ValueError("Unrecognised matrix type, '%s'" % 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("Invalid submatrix type, '%s' (not 'aij' or 'baij')",
                         sub_mat_type)

    if form_compiler_parameters:
        form_compiler_parameters = form_compiler_parameters.copy()
    else:
        form_compiler_parameters = {}
    form_compiler_parameters["assemble_inverse"] = inverse

    topology = f.ufl_domains()[0].topology
    for m in f.ufl_domains():
        # Ensure mesh is "initialised" (could have got here without
        # building a functionspace (e.g. if integrating a constant)).
        m.init()
        if m.topology != topology:
            raise NotImplementedError(
                "All integration domains must share a mesh topology.")

    for o in chain(f.arguments(), f.coefficients()):
        domain = o.ufl_domain()
        if domain is not None and domain.topology != topology:
            raise NotImplementedError(
                "Assembly with multiple meshes not supported.")

    if isinstance(f, slate.TensorBase):
        kernels = slac.compile_expression(
            f, tsfc_parameters=form_compiler_parameters)
        integral_types = [kernel.kinfo.integral_type for kernel in kernels]
    else:
        kernels = tsfc_interface.compile_form(
            f, "form", parameters=form_compiler_parameters, inverse=inverse)
        integral_types = [
            integral.integral_type() for integral in f.integrals()
        ]

    rank = len(f.arguments())

    is_mat = rank == 2
    is_vec = rank == 1

    if any((coeff.function_space()
            and coeff.function_space().component is not None)
           for coeff in f.coefficients()):
        raise NotImplementedError(
            "Integration of subscripted VFS not yet implemented")

    if inverse and rank != 2:
        raise ValueError("Can only assemble the inverse of a 2-form")

    zero_tensor = lambda: None

    if is_mat:
        matfree = mat_type == "matfree"
        nest = mat_type == "nest"
        if nest:
            baij = sub_mat_type == "baij"
        else:
            baij = mat_type == "baij"
        if matfree:  # intercept matrix-free matrices here
            if inverse:
                raise NotImplementedError(
                    "Inverse not implemented with matfree")
            if collect_loops:
                raise NotImplementedError("Can't collect loops with matfree")
            if tensor is None:
                return matrix.ImplicitMatrix(
                    f,
                    bcs,
                    fc_params=form_compiler_parameters,
                    appctx=appctx,
                    options_prefix=options_prefix)
            if not isinstance(tensor, matrix.ImplicitMatrix):
                raise ValueError("Expecting implicit matrix with matfree")
            tensor.assemble()
            return tensor
        test, trial = f.arguments()

        map_pairs = []
        cell_domains = []
        exterior_facet_domains = []
        interior_facet_domains = []
        if tensor is None:
            # For horizontal facets of extruded meshes, the corresponding domain
            # in the base mesh is the cell domain. Hence all the maps used for top
            # bottom and interior horizontal facets will use the cell to dofs map
            # coming from the base mesh as a starting point for the actual dynamic map
            # computation.
            for integral_type in integral_types:
                if integral_type == "cell":
                    cell_domains.append(op2.ALL)
                elif integral_type == "exterior_facet":
                    exterior_facet_domains.append(op2.ALL)
                elif integral_type == "interior_facet":
                    interior_facet_domains.append(op2.ALL)
                elif integral_type == "exterior_facet_bottom":
                    cell_domains.append(op2.ON_BOTTOM)
                elif integral_type == "exterior_facet_top":
                    cell_domains.append(op2.ON_TOP)
                elif integral_type == "exterior_facet_vert":
                    exterior_facet_domains.append(op2.ALL)
                elif integral_type == "interior_facet_horiz":
                    cell_domains.append(op2.ON_INTERIOR_FACETS)
                elif integral_type == "interior_facet_vert":
                    interior_facet_domains.append(op2.ALL)
                else:
                    raise ValueError('Unknown integral type "%s"' %
                                     integral_type)

            # To avoid an extra check for extruded domains, the maps that are being passed in
            # are DecoratedMaps. For the non-extruded case the DecoratedMaps don't restrict the
            # space over which we iterate as the domains are dropped at Sparsity construction
            # time. In the extruded case the cell domains are used to identify the regions of the
            # mesh which require allocation in the sparsity.
            if cell_domains:
                map_pairs.append(
                    (op2.DecoratedMap(test.cell_node_map(), cell_domains),
                     op2.DecoratedMap(trial.cell_node_map(), cell_domains)))
            if exterior_facet_domains:
                map_pairs.append(
                    (op2.DecoratedMap(test.exterior_facet_node_map(),
                                      exterior_facet_domains),
                     op2.DecoratedMap(trial.exterior_facet_node_map(),
                                      exterior_facet_domains)))
            if interior_facet_domains:
                map_pairs.append(
                    (op2.DecoratedMap(test.interior_facet_node_map(),
                                      interior_facet_domains),
                     op2.DecoratedMap(trial.interior_facet_node_map(),
                                      interior_facet_domains)))

            map_pairs = tuple(map_pairs)
            # Construct OP2 Mat to assemble into
            fs_names = (test.function_space().name,
                        trial.function_space().name)

            try:
                sparsity = op2.Sparsity((test.function_space().dof_dset,
                                         trial.function_space().dof_dset),
                                        map_pairs,
                                        "%s_%s_sparsity" % fs_names,
                                        nest=nest,
                                        block_sparse=baij)
            except SparsityFormatError:
                raise ValueError(
                    "Monolithic matrix assembly is not supported for systems with R-space blocks."
                )

            result_matrix = matrix.Matrix(f,
                                          bcs,
                                          mat_type,
                                          sparsity,
                                          numpy.float64,
                                          "%s_%s_matrix" % fs_names,
                                          options_prefix=options_prefix)
            tensor = result_matrix._M
        else:
            if isinstance(tensor, matrix.ImplicitMatrix):
                raise ValueError("Expecting matfree with implicit matrix")

            result_matrix = tensor
            # Replace any bcs on the tensor we passed in
            result_matrix.bcs = bcs
            tensor = tensor._M
            zero_tensor = tensor.zero

        if result_matrix.block_shape != (1, 1) and mat_type == "baij":
            raise ValueError(
                "BAIJ matrix type makes no sense for mixed spaces, use 'aij'")

        def mat(testmap, trialmap, i, j):
            m = testmap(test.function_space()[i])
            n = trialmap(trial.function_space()[j])
            maps = (m[op2.i[0]] if m else None,
                    n[op2.i[1 if m else 0]] if n else None)
            return tensor[i, j](op2.INC, maps)

        result = lambda: result_matrix
        if allocate_only:
            result_matrix._assembly_callback = None
            return result_matrix
    elif is_vec:
        test = f.arguments()[0]
        if tensor is None:
            result_function = function.Function(test.function_space())
            tensor = result_function.dat
        else:
            result_function = tensor
            tensor = result_function.dat
            zero_tensor = tensor.zero

        def vec(testmap, i):
            _testmap = testmap(test.function_space()[i])
            return tensor[i](op2.INC, _testmap[op2.i[0]] if _testmap else None)

        result = lambda: result_function
    else:
        # 0-forms are always scalar
        if tensor is None:
            tensor = op2.Global(1, [0.0])
        else:
            raise ValueError("Can't assemble 0-form into existing tensor")
        result = lambda: tensor.data[0]

    coefficients = f.coefficients()
    domains = f.ufl_domains()

    # These will be used to correctly interpret the "otherwise"
    # subdomain
    all_integer_subdomain_ids = defaultdict(list)
    for k in kernels:
        if k.kinfo.subdomain_id != "otherwise":
            all_integer_subdomain_ids[k.kinfo.integral_type].append(
                k.kinfo.subdomain_id)
    for k, v in all_integer_subdomain_ids.items():
        all_integer_subdomain_ids[k] = tuple(sorted(v))

    # Since applying boundary conditions to a matrix changes the
    # initial assembly, to support:
    #     A = assemble(a)
    #     bc.apply(A)
    #     solve(A, ...)
    # we need to defer actually assembling the matrix until just
    # before we need it (when we know if there are any bcs to be
    # applied).  To do so, we build a closure that carries out the
    # assembly and stash that on the Matrix object.  When we hit a
    # solve, we funcall the closure with any bcs the Matrix now has to
    # assemble it.

    # In collecting loops mode, we collect the loops, and assume the
    # boundary conditions provided are the ones we want.  It therefore
    # is only used inside residual and jacobian assembly.
    loops = []

    def thunk(bcs):
        if collect_loops:
            loops.append(zero_tensor)
        else:
            zero_tensor()
        for indices, kinfo in kernels:
            kernel = kinfo.kernel
            integral_type = kinfo.integral_type
            domain_number = kinfo.domain_number
            subdomain_id = kinfo.subdomain_id
            coeff_map = kinfo.coefficient_map
            pass_layer_arg = kinfo.pass_layer_arg
            needs_orientations = kinfo.oriented
            needs_cell_facets = kinfo.needs_cell_facets
            needs_cell_sizes = kinfo.needs_cell_sizes

            m = domains[domain_number]
            subdomain_data = f.subdomain_data()[m]
            # Find argument space indices
            if is_mat:
                i, j = indices
            elif is_vec:
                i, = indices
            else:
                assert len(indices) == 0

            sdata = subdomain_data.get(integral_type, None)
            if integral_type != 'cell' and sdata is not None:
                raise NotImplementedError(
                    "subdomain_data only supported with cell integrals.")

            # Extract block from tensor and test/trial spaces
            # FIXME Ugly variable renaming required because functions are not
            # lexical closures in Python and we're writing to these variables
            if is_mat and result_matrix.block_shape > (1, 1):
                tsbc = []
                trbc = []
                # Unwind ComponentFunctionSpace to check for matching BCs
                for bc in bcs:
                    fs = bc.function_space()
                    if fs.component is not None:
                        fs = fs.parent
                    if fs.index == i:
                        tsbc.append(bc)
                    if fs.index == j:
                        trbc.append(bc)
            elif is_mat:
                tsbc, trbc = bcs, bcs

            # Now build arguments for the par_loop
            kwargs = {}
            # Some integrals require non-coefficient arguments at the
            # end (facet number information).
            extra_args = []
            # Decoration for applying to matrix maps in extruded case
            decoration = None
            itspace = m.measure_set(integral_type, subdomain_id,
                                    all_integer_subdomain_ids)
            if integral_type == "cell":
                itspace = sdata or itspace

                if subdomain_id not in ["otherwise", "everywhere"] and \
                   sdata is not None:
                    raise ValueError(
                        "Cannot use subdomain data and subdomain_id")

                def get_map(x, bcs=None, decoration=None):
                    return x.cell_node_map(bcs)

            elif integral_type in ("exterior_facet", "exterior_facet_vert"):
                extra_args.append(m.exterior_facets.local_facet_dat(op2.READ))

                def get_map(x, bcs=None, decoration=None):
                    return x.exterior_facet_node_map(bcs)

            elif integral_type in ("exterior_facet_top",
                                   "exterior_facet_bottom"):
                # In the case of extruded meshes with horizontal facet integrals, two
                # parallel loops will (potentially) get created and called based on the
                # domain id: interior horizontal, bottom or top.
                decoration = {
                    "exterior_facet_top": op2.ON_TOP,
                    "exterior_facet_bottom": op2.ON_BOTTOM
                }[integral_type]
                kwargs["iterate"] = decoration

                def get_map(x, bcs=None, decoration=None):
                    map_ = x.cell_node_map(bcs)
                    if decoration is not None:
                        return op2.DecoratedMap(map_, decoration)
                    return map_

            elif integral_type in ("interior_facet", "interior_facet_vert"):
                extra_args.append(m.interior_facets.local_facet_dat(op2.READ))

                def get_map(x, bcs=None, decoration=None):
                    return x.interior_facet_node_map(bcs)

            elif integral_type == "interior_facet_horiz":
                decoration = op2.ON_INTERIOR_FACETS
                kwargs["iterate"] = decoration

                def get_map(x, bcs=None, decoration=None):
                    map_ = x.cell_node_map(bcs)
                    if decoration is not None:
                        return op2.DecoratedMap(map_, decoration)
                    return map_

            else:
                raise ValueError("Unknown integral type '%s'" % integral_type)

            # Output argument
            if is_mat:
                tensor_arg = mat(lambda s: get_map(s, tsbc, decoration),
                                 lambda s: get_map(s, trbc, decoration), i, j)
            elif is_vec:
                tensor_arg = vec(lambda s: get_map(s), i)
            else:
                tensor_arg = tensor(op2.INC)

            coords = m.coordinates
            args = [
                kernel, itspace, tensor_arg,
                coords.dat(op2.READ,
                           get_map(coords)[op2.i[0]])
            ]
            if needs_orientations:
                o = m.cell_orientations()
                args.append(o.dat(op2.READ, get_map(o)[op2.i[0]]))
            if needs_cell_sizes:
                o = m.cell_sizes
                args.append(o.dat(op2.READ, get_map(o)[op2.i[0]]))
            for n in coeff_map:
                c = coefficients[n]
                for c_ in c.split():
                    m_ = get_map(c_)
                    args.append(c_.dat(op2.READ, m_ and m_[op2.i[0]]))
            if needs_cell_facets:
                assert integral_type == "cell"
                extra_args.append(m.cell_to_facets(op2.READ))

            args.extend(extra_args)
            kwargs["pass_layer_arg"] = pass_layer_arg

            try:
                with collecting_loops(collect_loops):
                    loops.append(op2.par_loop(*args, **kwargs))
            except MapValueError:
                raise RuntimeError(
                    "Integral measure does not match measure of all coefficients/arguments"
                )

        # Must apply bcs outside loop over kernels because we may wish
        # to apply bcs to a block which is otherwise zero, and
        # therefore does not have an associated kernel.
        if bcs is not None and is_mat:
            for bc in bcs:
                fs = bc.function_space()
                # Evaluate this outwith a "collecting_loops" block,
                # since creation of the bc nodes actually can create a
                # par_loop.
                nodes = bc.nodes
                if len(fs) > 1:
                    raise RuntimeError(
                        """Cannot apply boundary conditions to full mixed space. Did you forget to index it?"""
                    )
                shape = result_matrix.block_shape
                with collecting_loops(collect_loops):
                    for i in range(shape[0]):
                        for j in range(shape[1]):
                            # 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 i != j:
                                continue
                            if fs.component is None and fs.index is not None:
                                # Mixed, index (no ComponentFunctionSpace)
                                if fs.index == i:
                                    loops.append(tensor[
                                        i,
                                        j].set_local_diagonal_entries(nodes))
                            elif fs.component is not None:
                                # ComponentFunctionSpace, check parent index
                                if fs.parent.index is not None:
                                    # Mixed, index doesn't match
                                    if fs.parent.index != i:
                                        continue
                                # Index matches
                                loops.append(
                                    tensor[i, j].set_local_diagonal_entries(
                                        nodes, idx=fs.component))
                            elif fs.index is None:
                                loops.append(tensor[
                                    i, j].set_local_diagonal_entries(nodes))
                            else:
                                raise RuntimeError("Unhandled BC case")
        if bcs is not None and is_vec:
            if len(bcs) > 0 and collect_loops:
                raise NotImplementedError(
                    "Loop collection not handled in this case")
            for bc in bcs:
                bc.apply(result_function)
        if is_mat:
            # Queue up matrix assembly (after we've done all the other operations)
            loops.append(tensor.assemble())
        return result()

    if collect_loops:
        thunk(bcs)
        return loops
    if is_mat:
        result_matrix._assembly_callback = thunk
        return result()
    else:
        return thunk(bcs)
Exemple #9
0
def _assemble(f, tensor=None, bcs=None, form_compiler_parameters=None,
              inverse=False, nest=None):
    """Assemble the form f and return a Firedrake object representing the
    result. This will be a :class:`float` for 0-forms, a
    :class:`.Function` for 1-forms and a :class:`.Matrix` for 2-forms.

    :arg bcs: A tuple of :class`.DirichletBC`\s to be applied.
    :arg tensor: An existing tensor object into which the form should be
        assembled. If this is not supplied, a new tensor will be created for
        the purpose.
    :arg form_compiler_parameters: (optional) dict of parameters to pass to
        the form compiler.
    :arg inverse: (optional) if f is a 2-form, then assemble the inverse
         of the local matrices.
    :arg nest: (optional) flag indicating if matrices on mixed spaces
         should be built in blocks or monolithically.

    """

    if form_compiler_parameters:
        form_compiler_parameters = form_compiler_parameters.copy()
    else:
        form_compiler_parameters = {}
    form_compiler_parameters["assemble_inverse"] = inverse

    kernels = tsfc_interface.compile_form(f, "form", parameters=form_compiler_parameters,
                                          inverse=inverse)
    rank = len(f.arguments())

    is_mat = rank == 2
    is_vec = rank == 1

    if any((coeff.function_space() and coeff.function_space().component is not None)
           for coeff in f.coefficients()):
        raise NotImplementedError("Integration of subscripted VFS not yet implemented")

    if inverse and rank != 2:
        raise ValueError("Can only assemble the inverse of a 2-form")

    integrals = f.integrals()

    if nest is None:
        nest = parameters.parameters["matnest"]
    # Pass this through for assembly caching purposes
    form_compiler_parameters["matnest"] = nest

    zero_tensor = lambda: None

    if is_mat:
        test, trial = f.arguments()

        map_pairs = []
        cell_domains = []
        exterior_facet_domains = []
        interior_facet_domains = []
        if tensor is None:
            # For horizontal facets of extrded meshes, the corresponding domain
            # in the base mesh is the cell domain. Hence all the maps used for top
            # bottom and interior horizontal facets will use the cell to dofs map
            # coming from the base mesh as a starting point for the actual dynamic map
            # computation.
            for integral in integrals:
                integral_type = integral.integral_type()
                if integral_type == "cell":
                    cell_domains.append(op2.ALL)
                elif integral_type == "exterior_facet":
                    exterior_facet_domains.append(op2.ALL)
                elif integral_type == "interior_facet":
                    interior_facet_domains.append(op2.ALL)
                elif integral_type == "exterior_facet_bottom":
                    cell_domains.append(op2.ON_BOTTOM)
                elif integral_type == "exterior_facet_top":
                    cell_domains.append(op2.ON_TOP)
                elif integral_type == "exterior_facet_vert":
                    exterior_facet_domains.append(op2.ALL)
                elif integral_type == "interior_facet_horiz":
                    cell_domains.append(op2.ON_INTERIOR_FACETS)
                elif integral_type == "interior_facet_vert":
                    interior_facet_domains.append(op2.ALL)
                else:
                    raise ValueError('Unknown integral type "%s"' % integral_type)

            # To avoid an extra check for extruded domains, the maps that are being passed in
            # are DecoratedMaps. For the non-extruded case the DecoratedMaps don't restrict the
            # space over which we iterate as the domains are dropped at Sparsity construction
            # time. In the extruded case the cell domains are used to identify the regions of the
            # mesh which require allocation in the sparsity.
            if cell_domains:
                map_pairs.append((op2.DecoratedMap(test.cell_node_map(), cell_domains),
                                  op2.DecoratedMap(trial.cell_node_map(), cell_domains)))
            if exterior_facet_domains:
                map_pairs.append((op2.DecoratedMap(test.exterior_facet_node_map(), exterior_facet_domains),
                                  op2.DecoratedMap(trial.exterior_facet_node_map(), exterior_facet_domains)))
            if interior_facet_domains:
                map_pairs.append((op2.DecoratedMap(test.interior_facet_node_map(), interior_facet_domains),
                                  op2.DecoratedMap(trial.interior_facet_node_map(), interior_facet_domains)))

            map_pairs = tuple(map_pairs)
            # Construct OP2 Mat to assemble into
            fs_names = (test.function_space().name, trial.function_space().name)
            sparsity = op2.Sparsity((test.function_space().dof_dset,
                                     trial.function_space().dof_dset),
                                    map_pairs,
                                    "%s_%s_sparsity" % fs_names,
                                    nest=nest)
            result_matrix = matrix.Matrix(f, bcs, sparsity, numpy.float64,
                                          "%s_%s_matrix" % fs_names)
            tensor = result_matrix._M
        else:
            result_matrix = tensor
            # Replace any bcs on the tensor we passed in
            result_matrix.bcs = bcs
            tensor = tensor._M
            zero_tensor = lambda: tensor.zero()

        def mat(testmap, trialmap, i, j):
            return tensor[i, j](op2.INC,
                                (testmap(test.function_space()[i])[op2.i[0]],
                                 trialmap(trial.function_space()[j])[op2.i[1]]),
                                flatten=True)
        result = lambda: result_matrix
    elif is_vec:
        test = f.arguments()[0]
        if tensor is None:
            result_function = function.Function(test.function_space())
            tensor = result_function.dat
        else:
            result_function = tensor
            tensor = result_function.dat
            zero_tensor = lambda: tensor.zero()

        def vec(testmap, i):
            return tensor[i](op2.INC,
                             testmap(test.function_space()[i])[op2.i[0]],
                             flatten=True)
        result = lambda: result_function
    else:
        # 0-forms are always scalar
        if tensor is None:
            tensor = op2.Global(1, [0.0])
        result = lambda: tensor.data[0]

    subdomain_data = f.subdomain_data()
    coefficients = f.coefficients()
    domains = f.ufl_domains()
    if len(domains) != 1:
        raise NotImplementedError("Assembly of forms with more than one domain not supported")
    m = domains[0]
    # Ensure mesh is "initialised" (could have got here without
    # building a functionspace (e.g. if integrating a constant)).
    m.init()
    subdomain_data = subdomain_data[m]

    # Since applying boundary conditions to a matrix changes the
    # initial assembly, to support:
    #     A = assemble(a)
    #     bc.apply(A)
    #     solve(A, ...)
    # we need to defer actually assembling the matrix until just
    # before we need it (when we know if there are any bcs to be
    # applied).  To do so, we build a closure that carries out the
    # assembly and stash that on the Matrix object.  When we hit a
    # solve, we funcall the closure with any bcs the Matrix now has to
    # assemble it.
    def thunk(bcs):
        zero_tensor()
        for indices, (kernel, integral_type, needs_orientations, subdomain_id, coeff_map) in kernels:
            # Find argument space indices
            if is_mat:
                i, j = indices
            elif is_vec:
                i, = indices
            else:
                assert len(indices) == 0

            sdata = subdomain_data.get(integral_type, None)
            if integral_type != 'cell' and sdata is not None:
                raise NotImplementedError("subdomain_data only supported with cell integrals.")

            # Extract block from tensor and test/trial spaces
            # FIXME Ugly variable renaming required because functions are not
            # lexical closures in Python and we're writing to these variables
            if is_mat and tensor.sparsity.shape > (1, 1):
                tsbc = []
                trbc = []
                # Unwind ComponentFunctionSpace to check for matching BCs
                for bc in bcs:
                    fs = bc.function_space()
                    if fs.component is not None:
                        fs = fs.parent
                    if fs.index == i:
                        tsbc.append(bc)
                    if fs.index == j:
                        trbc.append(bc)
            elif is_mat:
                tsbc, trbc = bcs, bcs

            # Now build arguments for the par_loop
            kwargs = {}
            # Some integrals require non-coefficient arguments at the
            # end (facet number information).
            extra_args = []
            # Decoration for applying to matrix maps in extruded case
            decoration = None
            if integral_type == "cell":
                itspace = sdata or m.cell_set

                def get_map(x, bcs=None, decoration=None):
                    return x.cell_node_map(bcs)

            elif integral_type in ("exterior_facet", "exterior_facet_vert"):
                itspace = m.exterior_facets.measure_set(integral_type, subdomain_id)
                extra_args.append(m.exterior_facets.local_facet_dat(op2.READ))

                def get_map(x, bcs=None, decoration=None):
                    return x.exterior_facet_node_map(bcs)

            elif integral_type in ("exterior_facet_top", "exterior_facet_bottom"):
                # In the case of extruded meshes with horizontal facet integrals, two
                # parallel loops will (potentially) get created and called based on the
                # domain id: interior horizontal, bottom or top.
                index, itspace = m.exterior_facets.measure_set(integral_type, subdomain_id)
                decoration = index
                kwargs["iterate"] = index

                def get_map(x, bcs=None, decoration=None):
                    map_ = x.cell_node_map(bcs)
                    if decoration is not None:
                        return op2.DecoratedMap(map_, decoration)
                    return map_

            elif integral_type in ("interior_facet", "interior_facet_vert"):
                itspace = m.interior_facets.set
                extra_args.append(m.interior_facets.local_facet_dat(op2.READ))

                def get_map(x, bcs=None, decoration=None):
                    return x.interior_facet_node_map(bcs)

            elif integral_type == "interior_facet_horiz":
                itspace = m.interior_facets.measure_set(integral_type, subdomain_id)
                decoration = op2.ON_INTERIOR_FACETS
                kwargs["iterate"] = op2.ON_INTERIOR_FACETS

                def get_map(x, bcs=None, decoration=None):
                    map_ = x.cell_node_map(bcs)
                    if decoration is not None:
                        return op2.DecoratedMap(map_, decoration)
                    return map_

            else:
                raise ValueError("Unknown integral type '%s'" % integral_type)

            # Output argument
            if is_mat:
                tensor_arg = mat(lambda s: get_map(s, tsbc, decoration),
                                 lambda s: get_map(s, trbc, decoration),
                                 i, j)
            elif is_vec:
                tensor_arg = vec(lambda s: get_map(s), i)
            else:
                tensor_arg = tensor(op2.INC)

            coords = m.coordinates
            args = [kernel, itspace, tensor_arg,
                    coords.dat(op2.READ, get_map(coords), flatten=True)]
            if needs_orientations:
                o = m.cell_orientations()
                args.append(o.dat(op2.READ, get_map(o), flatten=True))
            for n in coeff_map:
                c = coefficients[n]
                args.append(c.dat(op2.READ, get_map(c), flatten=True))

            args.extend(extra_args)
            try:
                op2.par_loop(*args, **kwargs)
            except MapValueError:
                raise RuntimeError("Integral measure does not match measure of all coefficients/arguments")

        # Must apply bcs outside loop over kernels because we may wish
        # to apply bcs to a block which is otherwise zero, and
        # therefore does not have an associated kernel.
        if bcs is not None and is_mat:
            for bc in bcs:
                fs = bc.function_space()
                if len(fs) > 1:
                    raise RuntimeError("""Cannot apply boundary conditions to full mixed space. Did you forget to index it?""")
                shape = tensor.sparsity.shape
                for i in range(shape[0]):
                    for j in range(shape[1]):
                        # 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 i != j:
                            continue
                        if fs.component is None and fs.index is not None:
                            # Mixed, index (no ComponentFunctionSpace)
                            if fs.index == i:
                                tensor[i, j].set_local_diagonal_entries(bc.nodes)
                        elif fs.component is not None:
                            # ComponentFunctionSpace, check parent index
                            if fs.parent.index is not None:
                                # Mixed, index doesn't match
                                if fs.parent.index != i:
                                    continue
                            # Index matches
                            tensor[i, j].set_local_diagonal_entries(bc.nodes, idx=fs.component)
                        elif fs.index is None:
                            tensor[i, j].set_local_diagonal_entries(bc.nodes)
                        else:
                            raise RuntimeError("Unhandled BC case")
        if bcs is not None and is_vec:
            for bc in bcs:
                bc.apply(result_function)
        if is_mat:
            # Queue up matrix assembly (after we've done all the other operations)
            tensor.assemble()
        return result()

    thunk = assembly_cache._cache_thunk(thunk, f, result(), form_compiler_parameters)

    if is_mat:
        result_matrix._assembly_callback = thunk
        return result()
    else:
        return thunk(bcs)
Exemple #10
0
    def _map_cache(self,
                   cache,
                   entity_set,
                   entity_node_list,
                   map_arity,
                   bcs,
                   name,
                   offset=None,
                   parent=None):
        if bcs is not None:
            # Separate explicit bcs (we just place negative entries in
            # the appropriate map values) from implicit ones (extruded
            # top and bottom) that require PyOP2 code gen.
            explicit_bcs = [
                bc for bc in bcs if bc.sub_domain not in ['top', 'bottom']
            ]
            implicit_bcs = [
                bc.sub_domain for bc in bcs
                if bc.sub_domain in ['top', 'bottom']
            ]
            if len(explicit_bcs) == 0:
                # Implicit bcs are not part of the cache key for the
                # map (they only change the generated PyOP2 code),
                # hence rewrite bcs here.
                bcs = None
            if len(implicit_bcs) == 0:
                implicit_bcs = None
        else:
            implicit_bcs = None
        if bcs is None:
            # Empty tuple if no bcs found.  This is so that matrix
            # assembly, which uses a set to keep track of the bcs
            # applied to matrix hits the cache when that set is
            # empty.  tuple(set([])) == tuple().
            lbcs = tuple()
        else:
            for bc in bcs:
                fs = bc.function_space()
                if isinstance(fs, IndexedVFS):
                    fs = fs._parent
                if fs != self:
                    raise RuntimeError(
                        "DirichletBC defined on a different FunctionSpace!")
            # Ensure bcs is a tuple in a canonical order for the hash key.
            lbcs = tuple(sorted(bcs, key=lambda bc: bc.__hash__()))
        try:
            # Cache hit
            val = cache[lbcs]
            # In the implicit bc case, we decorate the cached map with
            # the list of implicit boundary conditions so PyOP2 knows
            # what to do.
            if implicit_bcs:
                val = op2.DecoratedMap(val, implicit_bcs=implicit_bcs)
            return val
        except KeyError:
            # Cache miss.

            # Any top and bottom bcs (for the extruded case) are handled elsewhere.
            nodes = [
                bc.nodes for bc in lbcs
                if bc.sub_domain not in ['top', 'bottom']
            ]
            decorate = False
            if nodes:
                bcids = reduce(np.union1d, nodes)
                negids = np.copy(bcids)
                for bc in lbcs:
                    if bc.sub_domain in ["top", "bottom"]:
                        continue
                    if isinstance(bc.function_space(), IndexedVFS):
                        # For indexed VFS bcs, we encode the component
                        # in the high bits of the map value.
                        # That value is then negated to indicate to
                        # the generated code to discard the values
                        #
                        # So here we do:
                        #
                        # node = -(node + 2**(30-cmpt) + 1)
                        #
                        # And in the generated code we can then
                        # extract the information to discard the
                        # correct entries.
                        val = 2**(30 - bc.function_space().index)
                        # bcids is sorted, so use searchsorted to find indices
                        idx = np.searchsorted(bcids, bc.nodes)
                        negids[idx] += val
                        decorate = True
                node_list_bc = np.arange(self.node_count, dtype=np.int32)
                # Fix up for extruded, doesn't commute with indexedvfs for now
                if isinstance(self.mesh(), mesh_t.ExtrudedMesh):
                    node_list_bc[bcids] = -10000000
                else:
                    node_list_bc[bcids] = -(negids + 1)
                new_entity_node_list = node_list_bc.take(entity_node_list)
            else:
                new_entity_node_list = entity_node_list

            val = op2.Map(entity_set, self.node_set, map_arity,
                          new_entity_node_list, ("%s_" + name) % (self.name),
                          offset, parent, self.bt_masks)

            if decorate:
                val = op2.DecoratedMap(val, vector_index=True)
            cache[lbcs] = val
            if implicit_bcs:
                return op2.DecoratedMap(val, implicit_bcs=implicit_bcs)
            return val
Exemple #11
0
    def thunk(bcs):
        zero_tensor()
        for (
                i, j
        ), integral_type, subdomain_id, coords, coefficients, needs_orientations, kernel in kernels:
            m = coords.function_space().mesh()
            if needs_orientations:
                cell_orientations = m.cell_orientations()
            # Extract block from tensor and test/trial spaces
            # FIXME Ugly variable renaming required because functions are not
            # lexical closures in Python and we're writing to these variables
            if is_mat and tensor.sparsity.shape > (1, 1):
                tsbc = []
                trbc = []
                # Unwind IndexedVFS to check for matching BCs
                for bc in bcs:
                    fs = bc.function_space()
                    if isinstance(fs, functionspace.IndexedVFS):
                        fs = fs._parent
                    if fs.index == i:
                        tsbc.append(bc)
                    if fs.index == j:
                        trbc.append(bc)
            elif is_mat:
                tsbc, trbc = bcs, bcs
            if integral_type == 'cell':
                with timed_region("Assemble cells"):
                    if is_mat:
                        tensor_arg = mat(lambda s: s.cell_node_map(tsbc),
                                         lambda s: s.cell_node_map(trbc), i, j)
                    elif is_vec:
                        tensor_arg = vec(lambda s: s.cell_node_map(), i)
                    else:
                        tensor_arg = tensor(op2.INC)

                    itspace = m.cell_set
                    args = [
                        kernel, itspace, tensor_arg,
                        coords.dat(op2.READ,
                                   coords.cell_node_map(),
                                   flatten=True)
                    ]

                    if needs_orientations:
                        args.append(
                            cell_orientations.dat(
                                op2.READ,
                                cell_orientations.cell_node_map(),
                                flatten=True))
                    for c in coefficients:
                        args.append(
                            c.dat(op2.READ, c.cell_node_map(), flatten=True))

                    try:
                        op2.par_loop(*args)
                    except MapValueError:
                        raise RuntimeError(
                            "Integral measure does not match measure of all coefficients/arguments"
                        )

            elif integral_type in ['exterior_facet', 'exterior_facet_vert']:
                with timed_region("Assemble exterior facets"):
                    if is_mat:
                        tensor_arg = mat(
                            lambda s: s.exterior_facet_node_map(tsbc),
                            lambda s: s.exterior_facet_node_map(trbc), i, j)
                    elif is_vec:
                        tensor_arg = vec(lambda s: s.exterior_facet_node_map(),
                                         i)
                    else:
                        tensor_arg = tensor(op2.INC)
                    args = [
                        kernel,
                        m.exterior_facets.measure_set(integral_type,
                                                      subdomain_id),
                        tensor_arg,
                        coords.dat(op2.READ,
                                   coords.exterior_facet_node_map(),
                                   flatten=True)
                    ]
                    if needs_orientations:
                        args.append(
                            cell_orientations.dat(
                                op2.READ,
                                cell_orientations.exterior_facet_node_map(),
                                flatten=True))
                    for c in coefficients:
                        args.append(
                            c.dat(op2.READ,
                                  c.exterior_facet_node_map(),
                                  flatten=True))
                    args.append(m.exterior_facets.local_facet_dat(op2.READ))
                    try:
                        op2.par_loop(*args)
                    except MapValueError:
                        raise RuntimeError(
                            "Integral measure does not match measure of all coefficients/arguments"
                        )

            elif integral_type in [
                    'exterior_facet_top', 'exterior_facet_bottom'
            ]:
                with timed_region("Assemble exterior facets"):
                    # In the case of extruded meshes with horizontal facet integrals, two
                    # parallel loops will (potentially) get created and called based on the
                    # domain id: interior horizontal, bottom or top.

                    # Get the list of sets and globals required for parallel loop construction.
                    set_global_list = m.exterior_facets.measure_set(
                        integral_type, subdomain_id)

                    # Iterate over the list and assemble all the args of the parallel loop
                    for (index, set) in set_global_list:
                        if is_mat:
                            tensor_arg = mat(
                                lambda s: op2.DecoratedMap(
                                    s.cell_node_map(tsbc), index),
                                lambda s: op2.DecoratedMap(
                                    s.cell_node_map(trbc), index), i, j)
                        elif is_vec:
                            tensor_arg = vec(lambda s: s.cell_node_map(), i)
                        else:
                            tensor_arg = tensor(op2.INC)

                        # Add the kernel, iteration set and coordinate fields to the loop args
                        args = [
                            kernel, set, tensor_arg,
                            coords.dat(op2.READ,
                                       coords.cell_node_map(),
                                       flatten=True)
                        ]
                        if needs_orientations:
                            args.append(
                                cell_orientations.dat(
                                    op2.READ,
                                    cell_orientations.cell_node_map(),
                                    flatten=True))
                        for c in coefficients:
                            args.append(
                                c.dat(op2.READ,
                                      c.cell_node_map(),
                                      flatten=True))
                        try:
                            op2.par_loop(*args, iterate=index)
                        except MapValueError:
                            raise RuntimeError(
                                "Integral measure does not match measure of all coefficients/arguments"
                            )

            elif integral_type in ['interior_facet', 'interior_facet_vert']:
                with timed_region("Assemble interior facets"):
                    if is_mat:
                        tensor_arg = mat(
                            lambda s: s.interior_facet_node_map(tsbc),
                            lambda s: s.interior_facet_node_map(trbc), i, j)
                    elif is_vec:
                        tensor_arg = vec(lambda s: s.interior_facet_node_map(),
                                         i)
                    else:
                        tensor_arg = tensor(op2.INC)
                    args = [
                        kernel, m.interior_facets.set, tensor_arg,
                        coords.dat(op2.READ,
                                   coords.interior_facet_node_map(),
                                   flatten=True)
                    ]
                    if needs_orientations:
                        args.append(
                            cell_orientations.dat(
                                op2.READ,
                                cell_orientations.interior_facet_node_map(),
                                flatten=True))
                    for c in coefficients:
                        args.append(
                            c.dat(op2.READ,
                                  c.interior_facet_node_map(),
                                  flatten=True))
                    args.append(m.interior_facets.local_facet_dat(op2.READ))
                    try:
                        op2.par_loop(*args)
                    except MapValueError:
                        raise RuntimeError(
                            "Integral measure does not match measure of all coefficients/arguments"
                        )

            elif integral_type == 'interior_facet_horiz':
                with timed_region("Assemble interior facets"):
                    if is_mat:
                        tensor_arg = mat(
                            lambda s: op2.DecoratedMap(s.cell_node_map(tsbc),
                                                       op2.ON_INTERIOR_FACETS),
                            lambda s: op2.DecoratedMap(s.cell_node_map(
                                trbc), op2.ON_INTERIOR_FACETS), i, j)
                    elif is_vec:
                        tensor_arg = vec(lambda s: s.cell_node_map(), i)
                    else:
                        tensor_arg = tensor(op2.INC)

                    args = [
                        kernel,
                        m.interior_facets.measure_set(integral_type,
                                                      subdomain_id),
                        tensor_arg,
                        coords.dat(op2.READ,
                                   coords.cell_node_map(),
                                   flatten=True)
                    ]
                    if needs_orientations:
                        args.append(
                            cell_orientations.dat(
                                op2.READ,
                                cell_orientations.cell_node_map(),
                                flatten=True))
                    for c in coefficients:
                        args.append(
                            c.dat(op2.READ, c.cell_node_map(), flatten=True))
                    try:
                        op2.par_loop(*args, iterate=op2.ON_INTERIOR_FACETS)
                    except MapValueError:
                        raise RuntimeError(
                            "Integral measure does not match measure of all coefficients/arguments"
                        )

            else:
                raise RuntimeError('Unknown integral type "%s"' %
                                   integral_type)

        # Must apply bcs outside loop over kernels because we may wish
        # to apply bcs to a block which is otherwise zero, and
        # therefore does not have an associated kernel.
        if bcs is not None and is_mat:
            with timed_region('DirichletBC apply'):
                for bc in bcs:
                    fs = bc.function_space()
                    if isinstance(fs, functionspace.MixedFunctionSpace):
                        raise RuntimeError(
                            """Cannot apply boundary conditions to full mixed space. Did you forget to index it?"""
                        )
                    shape = tensor.sparsity.shape
                    for i in range(shape[0]):
                        for j in range(shape[1]):
                            # 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 i != j:
                                continue
                            if isinstance(fs,
                                          functionspace.IndexedFunctionSpace):
                                # Mixed, index
                                if fs.index == i:
                                    tensor[i, j].set_local_diagonal_entries(
                                        bc.nodes)
                            elif isinstance(fs, functionspace.IndexedVFS):
                                if isinstance(
                                        fs._parent,
                                        functionspace.IndexedFunctionSpace):
                                    if fs._parent.index != i:
                                        continue
                                tensor[i, j].set_local_diagonal_entries(
                                    bc.nodes, idx=fs.index)
                            elif fs.index is None:
                                tensor[i,
                                       j].set_local_diagonal_entries(bc.nodes)
                            else:
                                raise RuntimeError("Unhandled BC case")
        if bcs is not None and is_vec:
            for bc in bcs:
                bc.apply(result_function)
        if is_mat:
            # Queue up matrix assembly (after we've done all the other operations)
            tensor.assemble()
        return result()