Here is an example where one can visualize the FacetNormal properly:
from mpi4py import MPI
import dolfinx
import basix.ufl
import scifem
import ufl
def move_to_facet_quadrature(ufl_expr, mesh, sub_facets, scheme="default", degree=6):
fdim = mesh.topology.dim - 1
# Create submesh
bndry_mesh, entity_map, _, _ = dolfinx.mesh.create_submesh(mesh, fdim, sub_facets)
# Create quadrature space on submesh
q_el = basix.ufl.quadrature_element(
bndry_mesh.basix_cell(), ufl_expr.ufl_shape, scheme, degree
)
Q = dolfinx.fem.functionspace(bndry_mesh, q_el)
# Compute where to evaluate expression per submesh cell
integration_entities = dolfinx.fem.compute_integration_domains(
dolfinx.fem.IntegralType.exterior_facet, mesh.topology, entity_map, fdim
)
compiled_expr = dolfinx.fem.Expression(ufl_expr, Q.element.interpolation_points())
# Evaluate expression
q = dolfinx.fem.Function(Q)
q.x.array[:] = compiled_expr.eval(mesh, integration_entities).reshape(-1)
return q
# Facet expression evaluations
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)
n = ufl.FacetNormal(mesh)
mesh.topology.create_connectivity(1, 2)
exterior_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
n_h = move_to_facet_quadrature(n, mesh, exterior_facets, degree=2)
with dolfinx.io.XDMFFile(mesh.comm, "mesh.xdmf", "w") as xdmf:
xdmf.write_mesh(mesh)
with scifem.xdmf.XDMFFile("approximation.xdmf", [n_h]) as xdmf:
xdmf.write(0.0)
Taking the tangential projection one gets
(i.e. with code)
from mpi4py import MPI
import dolfinx
import basix.ufl
import scifem
import ufl
def move_to_facet_quadrature(ufl_expr, mesh, sub_facets, scheme="default", degree=6):
fdim = mesh.topology.dim - 1
# Create submesh
bndry_mesh, entity_map, _, _ = dolfinx.mesh.create_submesh(mesh, fdim, sub_facets)
# Create quadrature space on submesh
q_el = basix.ufl.quadrature_element(
bndry_mesh.basix_cell(), ufl_expr.ufl_shape, scheme, degree
)
Q = dolfinx.fem.functionspace(bndry_mesh, q_el)
# Compute where to evaluate expression per submesh cell
integration_entities = dolfinx.fem.compute_integration_domains(
dolfinx.fem.IntegralType.exterior_facet, mesh.topology, entity_map, fdim
)
compiled_expr = dolfinx.fem.Expression(ufl_expr, Q.element.interpolation_points())
# Evaluate expression
q = dolfinx.fem.Function(Q)
q.x.array[:] = compiled_expr.eval(mesh, integration_entities).reshape(-1)
return q
# Facet expression evaluations
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)
V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1, (2,)))
u = dolfinx.fem.Function(V)
u.interpolate(lambda x: (x[0], x[1]))
n = ufl.FacetNormal(mesh)
def tangential_projection(u: ufl.Coefficient, n: ufl.FacetNormal) -> ufl.Coefficient:
return (ufl.Identity(u.ufl_shape[0]) - ufl.outer(n, n)) * u
u_t = tangential_projection(u, n)
mesh.topology.create_connectivity(1, 2)
exterior_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
n_h = move_to_facet_quadrature(u_t, mesh, exterior_facets, degree=2)
with dolfinx.io.XDMFFile(mesh.comm, "mesh.xdmf", "w") as xdmf:
xdmf.write_mesh(mesh)
with scifem.xdmf.XDMFFile("approximation.xdmf", [n_h]) as xdmf:
xdmf.write(0.0)

