Assembly of combination of facet and cell functions

I’m assembling interface contributions to two (or more) submeshes, closely following the helpful example from Jorgen Dokken:

problem.py for working with submeshes

There, the forms containing the interface contributions were in terms of restrictions on the boundary of the “bulk” functions. Now I want to also have them depend on a function defined directly on that interface.

As an example, I created a unit square mesh, divided into two submeshes along x=0.5 and defined a parameter function along the interface, p = y. Now I want to assemble ∫ v_Γ p (1 - u) dS. Creating the functionspace for the interface and defining this functions worked fine, but its contribution are not correctly assembled. Here is my MWE:

#!/usr/bin/env python3

from mpi4py import MPI
import numpy as np

from dolfinx.mesh import (
    create_unit_square,
    locate_entities,
    locate_entities_boundary,
    create_submesh,
    compute_incident_entities,
    meshtags
)
from dolfinx.fem import (
    form,
    functionspace,
    Function,
)

from dolfinx.fem.petsc import assemble_vector

from ufl import (
    Measure,
    TestFunction,
)

# Create main mesh
mesh = create_unit_square(MPI.COMM_WORLD, 10, 10)

# cell and facet indices
tdim = mesh.topology.dim
fdim = mesh.topology.dim - 1
left_cells = locate_entities(mesh, tdim, lambda x: x[0] <= 0.5)
right_cells = locate_entities(mesh, tdim, lambda x: x[0] >= 0.5)
mesh.topology.create_connectivity(fdim, tdim)
left_facets = compute_incident_entities(mesh.topology, left_cells, tdim, fdim)
right_facets = compute_incident_entities(mesh.topology, right_cells, tdim, fdim)
interface = np.intersect1d(left_facets, right_facets)

# create submeshes
submesh_left, cell_map_left, _, _ = create_submesh(mesh, tdim, left_cells)

# facet tag = 3 at interface
num_facets_local = (
    mesh.topology.index_map(fdim).size_local + mesh.topology.index_map(fdim).num_ghosts
)
facet_indices = np.arange(num_facets_local, dtype=np.int32)
values = np.zeros(num_facets_local, dtype=np.int32)
values[interface] = 3
facet_tags = meshtags(mesh, fdim, facet_indices, values)

# create entity maps
num_cells_local = (
    mesh.topology.index_map(tdim).size_local + mesh.topology.index_map(tdim).num_ghosts
)
mesh_to_left = np.full(num_cells_local, -1, dtype=np.int32)
mesh_to_left[cell_map_left] = np.arange(len(cell_map_left), dtype=np.int32)

f_to_c = mesh.topology.connectivity(fdim, tdim)
for facet in facet_tags.find(3):
    cells = f_to_c.links(facet)
    assert len(cells) == 2
    a_map = mesh_to_left[cells]
    mesh_to_left[cells] = max(a_map)

interface_mesh, cell_map_interface, _, _ = create_submesh(mesh, fdim, interface)
inverse_facet_map = np.full(num_facets_local, -1, dtype=np.int32)
inverse_facet_map[cell_map_interface] = np.arange(len(cell_map_interface), dtype=np.int32)

entity_maps = {submesh_left: mesh_to_left, interface_mesh: inverse_facet_map}

# Create measures
cell_tags_left = meshtags(mesh, tdim, left_cells, 1)
dx_l = Measure("dx", domain=mesh, subdomain_data=cell_tags_left, subdomain_id=1)
dS = Measure("dS", domain=mesh, subdomain_data=facet_tags, subdomain_id=3)

# create functions
V_l = functionspace(submesh_left, ("Lagrange", 1))
V_i = functionspace(interface_mesh, ("Lagrange", 1))

p = Function(V_i)
p.interpolate(lambda x: x[1])

v_l = TestFunction(V_l)
u_l = Function(V_l)
u_l.vector.array.fill(0.0)

# forms
F_l = v_l("+") * (1.0 - u_l("+")) * dS
F_form = form(F_l, entity_maps=entity_maps)
F_vec = assemble_vector(F_form)

F_l2 = v_l("+") * p * (1.0 - u_l("+")) * dS
F_form2 = form(F_l2, entity_maps=entity_maps)
F_vec2 = assemble_vector(F_form2)

np.set_printoptions(precision=3)
print(f"Values of facet fct (as expected):\n{p.vector.array}")
print(f"Values of facet integral without p (as expected):\n{F_vec.array}")
print(f"Values of facet integral with p (unexpected):\n{F_vec2.array}")

Any hint on what I’m doing wrong is greatly appreciated.