|
15 | 15 | from dolfinx import default_scalar_type, fem, la
|
16 | 16 | from dolfinx.fem import compute_integration_domains
|
17 | 17 | from dolfinx.mesh import (
|
| 18 | + CellType, |
18 | 19 | GhostMode,
|
19 | 20 | compute_incident_entities,
|
20 | 21 | create_box,
|
@@ -609,3 +610,95 @@ def test_mixed_measures():
|
609 | 610 |
|
610 | 611 | # Check the results are the same
|
611 | 612 | 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