dolfinx icon indicating copy to clipboard operation
dolfinx copied to clipboard

Fix consistent restriction of one-sided interior facet integrals

Open jorgensd opened this issue 3 years ago • 1 comments

Currently on main, there is no way of doing consistent one-sided integration on interior facets. In old FEniCS, the workaround for this was to supply a cell-marker with values such that "+" would always be the side with the largest marker value. This PR reintroduces this fix, as it is crucial for post-processing in many applications, such as torque calculations in electromagnetics.

MWE:

from petsc4py import PETSc
import ufl
import dolfinx
from mpi4py import MPI
import numpy as np

mesh = dolfinx.mesh.create_unit_cube(MPI.COMM_WORLD, 10, 10, 10)
tdim = mesh.topology.dim
fdim = tdim - 1

plus_restriction = 5
minus_restriction = 3
facet_marker = 1
minus_cells = dolfinx.mesh.locate_entities(mesh, tdim, lambda x: x[1] <= 0.5 + 1e-16)
cell_map = mesh.topology.index_map(tdim)
all_cells = np.arange(cell_map.size_local + cell_map.num_ghosts, dtype=np.int32)
# All cells with x[1]>0.5 is tagged with plus restriction
cell_marker = np.full(len(all_cells), plus_restriction, dtype=np.int32)
# All cells with x[1]<=0.5 tagged with minus restriction
cell_marker[minus_cells] = 1
ct = dolfinx.mesh.meshtags(mesh, tdim, all_cells, cell_marker)

# Tag all facets on surface between tags with facet marker
midline_facets = dolfinx.mesh.locate_entities(mesh, fdim, lambda x: np.isclose(x[1], 0.5))
argsort_f = np.argsort(midline_facets)
ft = dolfinx.mesh.meshtags(mesh, fdim, midline_facets[argsort_f],
                           np.full(len(midline_facets), facet_marker, dtype=np.int32))


# Interpolate non-zero vlaue on minus restriction
V = dolfinx.fem.FunctionSpace(mesh, ("DG", 0))
u = dolfinx.fem.Function(V)
u.x.array[:] = 0
u.interpolate(lambda x: np.ones(x.shape[1]), minus_cells)
u.x.scatter_forward()

with dolfinx.io.XDMFFile(mesh.comm, "func.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_function(u, 0)

dS = ufl.Measure("dS", domain=mesh, subdomain_data=ft)
dx = ufl.Measure("dx", domain=mesh, subdomain_data=ct)
# Arbitrary orientation of +
form_0 = u("+") * dS(1)
# Arbitrary orientation of -
form_1 = u("-") * dS(1)
zero = dolfinx.fem.Constant(mesh, PETSc.ScalarType(0))
# + should be on side with larger value, i.e. exact value should be 0
form_2 = u("+") * dS(1) + zero * dx(88)
# - should point out of domain with smaller value, exact value is 1
form_3 = u("-") * dS(1) + zero * dx(88)
forms = [form_0, form_1, form_2, form_3]

for form in forms:
    scalar_form = dolfinx.fem.form(form)
    local_val = dolfinx.fem.assemble_scalar(scalar_form)
    global_val = mesh.comm.allreduce(local_val, op=MPI.SUM)
    if mesh.comm.rank == 0:
        print(str(form), global_val, flush=True)

On main:

mpirun -n 3 python3 inner_facets.py 
{ (f)('+') } * dS(<Mesh #0>[1], {}) 0.6250000000000003
{ (f)('-') } * dS(<Mesh #0>[1], {}) 0.3750000000000003
{ c_0 } * dx(<Mesh #0>[88], {})
  +  { (f)('+') } * dS(<Mesh #0>[1], {}) 0.6250000000000003
{ c_0 } * dx(<Mesh #0>[88], {})
  +  { (f)('-') } * dS(<Mesh #0>[1], {}) 0.3750000000000003

on this branch

mpirun -n 3 python3 inner_facets.py 
{ (f)('+') } * dS(<Mesh #0>[1], {}) 0.6250000000000003
{ (f)('-') } * dS(<Mesh #0>[1], {}) 0.3750000000000003
{ c_0 } * dx(<Mesh #0>[88], {})
  +  { (f)('+') } * dS(<Mesh #0>[1], {}) 0.0
{ c_0 } * dx(<Mesh #0>[88], {})
  +  { (f)('-') } * dS(<Mesh #0>[1], {}) 1.0000000000000004

jorgensd avatar Apr 13 '22 13:04 jorgensd

This feels too implicit to me. We already have 'one-sided' integration using ds. Would a generalisation of ds cover the use cases that this PR aims to address?

garth-wells avatar Apr 19 '22 08:04 garth-wells

Replaced by: https://github.com/FEniCS/dolfinx/pull/2269

jorgensd avatar Sep 06 '22 07:09 jorgensd