Source code for scifem.bcs
import basix
import dolfinx
import ufl
import numpy.typing as npt
import numpy as np
__all__ = ["interpolate_function_onto_facet_dofs"]
[docs]
def interpolate_function_onto_facet_dofs(
Q: dolfinx.fem.FunctionSpace,
expr: ufl.core.expr.Expr,
facets: npt.NDArray[np.int32],
) -> dolfinx.fem.Function:
"""
Create a function :math:`u_h\\in Q` such that :math:`u_h=\\text{expr}` for all dofs belonging
to a subset of ``facets``. All other dofs are set to zero.
Note:
The resulting function is only correct in the "normal" direction,
i.e. :math:`u_{bc}\\cdot n = expr`, while the tangential component is uncontrolled.
This makes it hard to visualize the function when outputting it to file, either
through interpolation to an appropriate DG space, or to a point-cloud.
Args:
Q: The function space to create the function $u_h$ in.
expr: The expression to evaluate.
facets: The facets on which to evaluate the expression.
"""
domain = Q.mesh
Q_el = Q.element
fdim = domain.topology.dim - 1
domain.topology.create_connectivity(fdim, domain.topology.dim)
interpolation_points = Q_el.basix_element.x
c_el = domain.ufl_domain().ufl_coordinate_element()
ref_top = c_el.reference_topology
ref_geom = c_el.reference_geometry
facet_types = set(basix.cell.subentity_types(domain.basix_cell())[fdim])
assert len(facet_types) == 1, "All facets must have the same topology"
# Pull back interpolation points from reference coordinate element to facet reference element
facet_cmap = basix.ufl.element(
"Lagrange",
facet_types.pop(),
c_el.degree,
shape=(domain.geometry.dim,),
dtype=np.float64,
)
facet_cel = dolfinx.fem.CoordinateElement(
dolfinx.cpp.fem.CoordinateElement_float64(facet_cmap.basix_element._e)
)
reference_facet_points = None
for i, points in enumerate(interpolation_points[fdim]):
geom = ref_geom[ref_top[fdim][i]]
ref_points = facet_cel.pull_back(points, geom)
# Assert that interpolation points are all equal on all facets
if reference_facet_points is None:
reference_facet_points = ref_points
else:
assert np.allclose(reference_facet_points, ref_points)
assert reference_facet_points is not None
# Create expression for BC
normal_expr = dolfinx.fem.Expression(expr, reference_facet_points)
points_per_entity = [sum(ip.shape[0] for ip in ips) for ips in interpolation_points]
offsets = np.zeros(domain.topology.dim + 2, dtype=np.int32)
offsets[1:] = np.cumsum(points_per_entity[: domain.topology.dim + 1])
values_per_entity = np.zeros(
(offsets[-1], domain.geometry.dim), dtype=dolfinx.default_scalar_type
)
# Compute integration entities (cell, local_facet index) for all facets
all_connected_cells = dolfinx.mesh.compute_incident_entities(
domain.topology, facets, domain.topology.dim - 1, domain.topology.dim
)
values = np.zeros(len(all_connected_cells) * offsets[-1] * domain.geometry.dim)
domain.topology.create_connectivity(domain.topology.dim, fdim)
c_to_f = domain.topology.connectivity(domain.topology.dim, fdim)
num_facets_on_process = (
domain.topology.index_map(fdim).size_local + domain.topology.index_map(fdim).num_ghosts
)
is_marked = np.zeros(num_facets_on_process, dtype=np.int8)
is_marked[facets] = 1
for i, cell in enumerate(all_connected_cells):
values_per_entity[:] = 0.0
local_facets = c_to_f.links(cell)
for j, lf in enumerate(local_facets):
if not is_marked[lf]:
continue
insert_pos = offsets[fdim] + reference_facet_points.shape[0] * j
# Backwards compatibility
entity = np.array([[cell, j]], dtype=np.int32)
try:
normal_on_facet = normal_expr.eval(domain, entity)
except (AttributeError, AssertionError):
normal_on_facet = normal_expr.eval(domain, entity.flatten())
# NOTE: evaluate within loop to avoid large memory requirements
values_per_entity[insert_pos : insert_pos + reference_facet_points.shape[0]] = (
normal_on_facet.reshape(-1, domain.geometry.dim)
)
values[
i * offsets[-1] * domain.geometry.dim : (i + 1) * offsets[-1] * domain.geometry.dim
] = values_per_entity.reshape(-1)
qh = dolfinx.fem.Function(Q)
if hasattr(qh._cpp_object, "interpolate_f"):
interpolate_func = qh._cpp_object.interpolate_f
else:
interpolate_func = qh._cpp_object.interpolate
interpolate_func(values.reshape(-1, domain.geometry.dim).T.copy(), all_connected_cells)
qh.x.scatter_forward()
return qh