Example of custom kernel for facet assembly

I am looking for an example of a custom kernel that assembles a facet contribution (interior or exterior), either in c++ or Python. I found such examples for cells in the demos / tests but not for facets. In particular I am curious about what is done with the local_facet and permutation arguments.

For a custom kernel, you might not need them ( that depends on the kernel).
What local_facet is is the local index of a facet (relative to the input cell). This is used to accessed the correct coordinates of the mesh nodes for this facet, and appropriate basis values.

The permutation integer is used to ensure that for dS integrals for two adjacent cells, that the facet quadrature rule is aligned.

Here is an example of manual usage of the ufcx kernel for exterior facets:

from mpi4py import MPI
import numpy as np
import dolfinx
import cffi

mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 1, 2, dolfinx.cpp.mesh.CellType.triangle)

V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1, (2, )))
u = dolfinx.fem.Function(V)
u.interpolate(lambda x: (np.sin(2.3*x[0]), x[1]))

def right_facets(x):
    return np.isclose(x[0], 1)


facets = dolfinx.mesh.locate_entities_boundary(mesh, mesh.topology.dim-1, right_facets)

integration_entities = dolfinx.fem.compute_integration_domains(dolfinx.fem.IntegralType.exterior_facet,mesh.topology, facets, mesh.topology.dim-1).reshape(-1, 2)

import ufl

ds = ufl.Measure("ds", domain=mesh, subdomain_data=([(2, integration_entities.flatten())]), subdomain_id=2)
v = ufl.TestFunction(V)
L = dolfinx.fem.form(ufl.inner(u, v) * ds)



x = mesh.geometry.x
v = mesh.geometry.dofmap

dtype= np.float64
entity_local_index = np.array([0], dtype=np.intc)
perm = np.array([0], dtype=np.uint8)
geometry = np.zeros((3, 3), dtype=x.dtype)
constants = np.zeros(1, dtype=dtype)

b_local = np.zeros(V.dofmap.dof_layout.num_dofs*V.dofmap.bs, dtype=dtype)

mesh.topology.create_entity_permutations()
perms = mesh.topology.get_facet_permutations()

# Assume all cells has the same number of facets

num_facets_per_cell = dolfinx.cpp.mesh.cell_num_entities(mesh.topology.cell_type, mesh.topology.dim-1)

kernel = getattr(L.ufcx_form.form_integrals[0], f"tabulate_tensor_{np.dtype(dtype).name}")
ffi = cffi.FFI()
b_func = dolfinx.fem.Function(V)
all_coeffs = dolfinx.fem.assemble.pack_coefficients(L._cpp_object)[(dolfinx.fem.IntegralType.exterior_facet, 2)].reshape(integration_entities.shape[0], -1)
coeffs = np.zeros(all_coeffs.shape[1], dtype=dtype)

for i, (cell, local_facet_index) in enumerate(integration_entities):
    for j in range(v.shape[1]):
        geometry[j] = x[v[cell, j], :]
    entity_local_index[0] = local_facet_index
    perm[0] = perms[cell*num_facets_per_cell + local_facet_index]
    coeffs[:] = all_coeffs[i]
    b_local.fill(0.0)
    kernel(
        ffi.from_buffer(b_local),
        ffi.from_buffer(coeffs),
        ffi.from_buffer(constants),
        ffi.from_buffer(geometry),
        ffi.from_buffer(entity_local_index),
        ffi.from_buffer(perm),
    )
    local_dofs = V.dofmap.cell_dofs(cell)
    for j in range(3):
        for k in range(V.dofmap.bs):
            b_func.x.array[local_dofs[j]*V.dofmap.bs+k] += b_local[j*V.dofmap.bs+k]


b_ref = dolfinx.fem.assemble_vector(L)
np.testing.assert_allclose(b_func.x.array, b_ref.array)
2 Likes