Hello,
I’m working on the post-processing of a 3D electrial field (Nedelec elements) problem. I use perfectly matched layers on the surrounding, hence I want to evaluate the electrical field on an interior surface. The mesh is generated with gmsh and physical are defined within. Therefore, the selection of the corresponding surface for evaluation is possible with the defined facet tag.
With the surface evaluation of a simple problem provided by Dokken in a similar issue and the interior surface handling I generated the following code:
from mpi4py import MPI
import dolfinx
import ufl
import basix
import numpy as np
import numpy.typing as npt
import gmsh
import sys
def evaluate_expression_at_facet_midpoints(
expr: ufl.core.expr.Expr, mesh: dolfinx.mesh.Mesh, facets: npt.NDArray[np.int32]
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.inexact]]:
# Get coordinate element for facets of cell
facet_types = basix.cell.subentity_types(mesh.basix_cell())[fdim]
unique_facet_types = np.unique(facet_types)
assert len(unique_facet_types) == 1, (
"All facets must have the same type for interpolation."
)
facet_type = facet_types[0]
facet_geom = basix.cell.geometry(facet_type)
facet_midpoint = np.sum(facet_geom, axis=0) / facet_geom.shape[0]
# Compile code for evaluation
bndry_expr = dolfinx.fem.Expression(expr, facet_midpoint)
# Map facet indices to (cell, local_facet) pairs
try:
boundary_entities = dolfinx.fem.compute_integration_domains(
dolfinx.fem.IntegralType.exterior_facet, mesh.topology, facets
)
except TypeError:
boundary_entities = dolfinx.fem.compute_integration_domains(
dolfinx.fem.IntegralType.exterior_facet,
mesh.topology,
facets,
mesh.topology.dim - 1,
)
x = ufl.SpatialCoordinate(mesh)
coord_expr = dolfinx.fem.Expression(x, facet_midpoint)
return coord_expr.eval(mesh, boundary_entities), bndry_expr.eval(
mesh, boundary_entities
)
gmsh.initialize(sys.argv)
dim=3
box = gmsh.model.occ.addBox(0.0, 0.0, 0.0, 1.0, 1.0 , 1.0)
box2 = gmsh.model.occ.addBox(1.0, 0.0, 0.0, 1.0, 1.0 , 1.0)
gmsh.model.occ.synchronize()
gmsh.model.addPhysicalGroup(dim, [box], tag=801, name='box')
gmsh.model.addPhysicalGroup(dim, [box2], tag=802, name='box2')
surfaces = gmsh.model.getEntities(dim=dim-1)
comlist = []
othersurf = []
for surf in surfaces:
com = gmsh.model.occ.getCenterOfMass(surf[0], surf[1])
if com in comlist:# to avoid double tags of overlapping bodies
continue
comlist.append(com)
if np.allclose(com, [1.0, 0.5, 0.5]):
gmsh.model.addPhysicalGroup(surf[0], [surf[1]], tag=901, name='evalsurf')
else:
othersurf.append(surf[1])
dummy = gmsh.model.addPhysicalGroup(dim-1, othersurf, tag=902, name='other')
msize = 0.1
points = gmsh.model.getEntities(dim=dim-3)
for p in points:
coords = gmsh.model.getValue(p[0], p[1], [])
if np.allclose(coords, [0.0,0.0,0.0]):
gmsh.model.mesh.setSize([(p[0], p[1])], size=msize)
elif np.allclose(coords, [1.0,0.0,0.0]):
gmsh.model.mesh.setSize([(p[0], p[1])], size=msize)
else:
gmsh.model.mesh.setSize([(p[0], p[1])], size=msize)
gmsh.model.getPhysicalGroups()
gmsh.model.mesh.generate(dim-1)
gmsh.model.mesh.generate(dim)
gmsh.model.mesh.refine()
gmsh.write('doublebox.msh')
# read mesh
mesh, cell_tags, facet_tags = dolfinx.io.gmshio.read_from_msh("doublebox.msh", MPI.COMM_WORLD, 0, gdim=3)
# fe problem
V = dolfinx.fem.functionspace(mesh, ("N1curl", 3))
u = dolfinx.fem.Function(V)
u.interpolate(
lambda x: (x[1] * x[0] ** 2 + x[1] ** 2, np.zeros(x[0].shape), np.zeros(x[0].shape))
)
n = ufl.FacetNormal(mesh)
fdim = mesh.topology.dim - 1
facet_for_evaluation = facet_tags.find(901)
# rename for later use in function
facets = facet_for_evaluation
meshdom = mesh
facet_types = basix.cell.subentity_types(mesh.basix_cell())[fdim]
unique_facet_types = np.unique(facet_types)
assert len(unique_facet_types) == 1, (
"All facets must have the same type for interpolation."
)
facet_type = facet_types[0]
facet_geom = basix.cell.geometry(facet_type)
facet_midpoint = np.sum(facet_geom, axis=0) / facet_geom.shape[0]
mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim - 1)
interior_entities = dolfinx.fem.compute_integration_domains(
dolfinx.fem.IntegralType.interior_facet, mesh.topology, facets,
mesh.topology.dim - 1)
connected_cells = interior_entities.reshape(-1, 4)[:, [0, 2]]
switch = cell_tags.values[connected_cells[:,0]] < cell_tags.values[connected_cells[:,1]]
if len(switch)> 0 and any(switch):
tmp_entities = integration_entities.reshape(-1, 4)
tmp_entities[switch] = tmp_entities[switch][:, [2, 3, 0, 1]]
interior_entities = tmp_entities.flatten()
dS_custom = ufl.Measure("dS", domain=mesh, subdomain_data=[(1111, np.array(interior_entities, dtype=np.int32))])
vect = ufl.dot(ufl.grad(u("+")),n("+"))
# dolfinx.fem.form(vect*dS_custom(1111)) not working due to required scalar in integration
v0 = dolfinx.fem.form(vect[0]*dS_custom(1111))
v1 = dolfinx.fem.form(vect[1]*dS_custom(1111))
v2 = dolfinx.fem.form(vect[2]*dS_custom(1111))
# Compile code for evaluation
bndry_expr0 = dolfinx.fem.Expression(v0, facet_midpoint, comm=MPI.COMM_WORLD)
bndry_expr1 = dolfinx.fem.Expression(v1, facet_midpoint, comm=MPI.COMM_WORLD)
bndry_expr2 = dolfinx.fem.Expression(v2, facet_midpoint, comm=MPI.COMM_WORLD)
# Map facet indices to (cell, local_facet) pairs
try:
boundary_entities = dolfinx.fem.compute_integration_domains(
dolfinx.fem.IntegralType.interior_facet, mesh.topology, facet_for_evaluation
)
except TypeError:
boundary_entities = dolfinx.fem.compute_integration_domains(
dolfinx.fem.IntegralType.interior_facet,
mesh.topology,
facet_for_evaluation,
mesh.topology.dim - 1,
)
x = ufl.SpatialCoordinate(mesh)
coord_expr = dolfinx.fem.Expression(x, facet_midpoint)
coordinates2 = coord_expr.eval(mesh, boundary_entities)
values20 = bndry_expr0.eval(mesh, boundary_entities)
values21 = bndry_expr1.eval(mesh, boundary_entities)
values22 = bndry_expr2.eval(mesh, boundary_entities)
print(coordinates2)
print(values20)
print(values21)
print(values22)
There are a couple of issues with that. First of all, the interior_entities in line 120 remain an empty array. So further processing is not useful. However, the defined measure dS_custom is then defined on that region and planned to be multiplied to the gradient projection onto the normal vector, resulting in a moment. The integration requires scalar expressions, hence the moment is split in its components. But when the dolfinx.fem.form is then used in the dolfinx.fem.Expression in line 140 I get a TypeError: <class ‘tuple’>. If the mpi-communication option is not given, it yields the error AttributeError: ‘Form’ object has no attribute '_ufl_is_terminal_' .
Why is the interior_entities variable empty in the first place and how do I handle the custom measure properly?
Without the side-handling of the surface:
vect = ufl.dot(ufl.grad(u),n)
bndry_expr = dolfinx.fem.Expression(vect, facet_midpoint, comm=MPI.COMM_WORLD)
the program is running through, though the output is empty due to the empty array interior_entities.