Skip to content

Commit e6143ae

Browse files
authored
Fix codim-1 assembly on interior facets (#3452)
* Add failing test * Add fix for assemble_scalar * Switch to ffcx branch with fix * Modify oneapi branch * updates to test * ruff format * Revert branches --------- Co-authored-by: nate-sime <>
1 parent 99e29fb commit e6143ae

File tree

2 files changed

+121
-4
lines changed

2 files changed

+121
-4
lines changed

cpp/dolfinx/fem/Form.h

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,35 @@ class Form
428428
}
429429
case IntegralType::interior_facet:
430430
{
431-
for (std::size_t i = 0; i < entities.size(); i += 2)
431+
// Get the codimension of the mesh
432+
const int tdim = _mesh->topology()->dim();
433+
const int codim = tdim - mesh.topology()->dim();
434+
assert(codim >= 0);
435+
if (codim == 0)
436+
{
437+
for (std::size_t i = 0; i < entities.size(); i += 2)
438+
{
439+
// Add cell and the local facet index
440+
mapped_entities.insert(mapped_entities.end(),
441+
{entity_map[entities[i]], entities[i + 1]});
442+
}
443+
}
444+
else if (codim == 1)
432445
{
433-
// Add cell and the local facet index
434-
mapped_entities.insert(mapped_entities.end(),
435-
{entity_map[entities[i]], entities[i + 1]});
446+
// In this case, the entity maps take facets in (`_mesh`) to cells in
447+
// `mesh`, so we need to get the facet number from the (cell,
448+
// local_facet pair) first.
449+
auto c_to_f = _mesh->topology()->connectivity(tdim, tdim - 1);
450+
assert(c_to_f);
451+
for (std::size_t i = 0; i < entities.size(); i += 2)
452+
{
453+
// Get the facet index
454+
const std::int32_t facet
455+
= c_to_f->links(entities[i])[entities[i + 1]];
456+
// Add cell and the local facet index
457+
mapped_entities.insert(mapped_entities.end(),
458+
{entity_map[facet], entities[i + 1]});
459+
}
436460
}
437461
break;
438462
}

python/test/unit/fem/test_assemble_submesh.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from dolfinx import default_scalar_type, fem, la
1616
from dolfinx.fem import compute_integration_domains
1717
from dolfinx.mesh import (
18+
CellType,
1819
GhostMode,
1920
compute_incident_entities,
2021
create_box,
@@ -609,3 +610,95 @@ def test_mixed_measures():
609610

610611
# Check the results are the same
611612
assert np.allclose(b0.norm(), b1.norm())
613+
614+
615+
@pytest.mark.parametrize(
616+
"msh",
617+
[
618+
pytest.param(
619+
create_unit_interval(MPI.COMM_WORLD, 10),
620+
marks=pytest.mark.xfail(
621+
reason="Interior facet submesh of dimension 0 not supported in submesh creation",
622+
strict=True,
623+
),
624+
),
625+
create_unit_square(
626+
MPI.COMM_WORLD, 10, 10, cell_type=CellType.triangle, ghost_mode=GhostMode.shared_facet
627+
),
628+
create_unit_cube(
629+
MPI.COMM_WORLD,
630+
3,
631+
3,
632+
3,
633+
cell_type=CellType.tetrahedron,
634+
ghost_mode=GhostMode.shared_facet,
635+
),
636+
],
637+
)
638+
def test_interior_facet_codim_1(msh):
639+
"""
640+
Check that assembly on an interior facet with coefficients defined on a co-dim 1
641+
mesh gives the correct result.
642+
"""
643+
# Collect mesh properties
644+
tdim = msh.topology.dim
645+
fdim = tdim - 1
646+
msh.topology.create_connectivity(fdim, tdim)
647+
facet_imap = msh.topology.index_map(fdim)
648+
num_facets = facet_imap.size_local + facet_imap.num_ghosts
649+
650+
# Mark all local and owned interior facets and "unmark" exterior facets
651+
facet_vector = la.vector(facet_imap, 1, dtype=np.int32)
652+
facet_vector.array[: facet_imap.size_local] = 1
653+
facet_vector.array[facet_imap.size_local :] = 0
654+
facet_vector.array[exterior_facet_indices(msh.topology)] = 0
655+
facet_vector.scatter_forward()
656+
interior_facets = np.flatnonzero(facet_vector.array)
657+
658+
# Create submesh with all owned and ghosted interior facets
659+
submesh, sub_to_parent, _, _ = create_submesh(msh, fdim, interior_facets)
660+
661+
# Create inverse map
662+
mesh_to_submesh = np.full(num_facets, -1)
663+
mesh_to_submesh[sub_to_parent] = np.arange(len(sub_to_parent))
664+
entity_maps = {submesh: mesh_to_submesh}
665+
666+
def assemble_interior_facet_formulation(formulation, entity_maps):
667+
F = fem.form(formulation, entity_maps=entity_maps)
668+
if F.rank == 0:
669+
return msh.comm.allreduce(fem.assemble_scalar(F), op=MPI.SUM)
670+
elif F.rank == 1:
671+
b = fem.assemble_vector(F)
672+
b.scatter_reverse(la.InsertMode.add)
673+
b.scatter_forward()
674+
return b
675+
raise NotImplementedError(f"Unexpected formulation of rank {F.rank}")
676+
677+
def f(x):
678+
return 2 + x[0] + 3 * x[1]
679+
680+
# Compare evaluation of finite element formulations on the submesh and the parent mesh
681+
metadata = {"quadrature_degree": 4}
682+
v = ufl.TestFunction(fem.functionspace(msh, ("DG", 2)))
683+
684+
# Assemble forms using function interpolated on the submesh
685+
dS_submesh = ufl.Measure("dS", domain=msh, metadata=metadata)
686+
j = fem.Function(fem.functionspace(submesh, ("Lagrange", 1)))
687+
j.interpolate(f)
688+
j.x.scatter_forward()
689+
J_submesh = assemble_interior_facet_formulation(ufl.avg(j) * dS_submesh, entity_maps)
690+
b_submesh = assemble_interior_facet_formulation(
691+
ufl.inner(ufl.jump(v), j) * dS_submesh, entity_maps
692+
)
693+
694+
# Assemble reference value forms on the parent mesh using function defined with UFL
695+
x = ufl.SpatialCoordinate(msh)
696+
J_ref = assemble_interior_facet_formulation(ufl.avg(f(x)) * ufl.dS(metadata=metadata), None)
697+
b_ref = assemble_interior_facet_formulation(
698+
ufl.inner(ufl.jump(v), f(x)) * ufl.dS(metadata=metadata), None
699+
)
700+
701+
# Ensure both are equivalent
702+
tol = 100 * np.finfo(default_scalar_type()).eps
703+
assert np.isclose(J_submesh, J_ref, atol=tol)
704+
np.testing.assert_allclose(b_submesh.array, b_ref.array, atol=tol)

0 commit comments

Comments
 (0)