from enum import Enum
from typing import Iterable, Sequence
from mpi4py import MPI
import basix
import dolfinx
import numpy as np
QUADRATURE_FAMILY = 100
[docs]
def default_markers() -> dict[str, list[int]]:
"""
Default markers for the mesh boundaries
"""
return dict(base=[10], rv=[20], lv=[30], epi=[40])
[docs]
def parse_element(space_string: str, mesh: dolfinx.mesh.Mesh, dim: int) -> basix.ufl._ElementBase:
"""
Parse a string representation of a basix element family
"""
family_str, degree_str = space_string.split("_")
kwargs = {"degree": int(degree_str), "cell": mesh.basix_cell()}
if dim > 1:
if family_str in ["Quadrature", "Q", "Quad"]:
kwargs["value_shape"] = (dim,)
else:
kwargs["shape"] = (dim,)
if family_str in ["Lagrange", "P", "CG"]:
el = basix.ufl.element(family=basix.ElementFamily.P, discontinuous=False, **kwargs)
elif family_str in ["Discontinuous Lagrange", "DG", "dP"]:
el = basix.ufl.element(family=basix.ElementFamily.P, discontinuous=True, **kwargs)
elif family_str in ["Quadrature", "Q", "Quad"]:
el = basix.ufl.quadrature_element(scheme="default", **kwargs)
else:
families = list(basix.ElementFamily.__members__.keys())
msg = f"Unknown element family: {family_str}, available families: {families}"
raise ValueError(msg)
return el
[docs]
def space_from_string(
space_string: str, mesh: dolfinx.mesh.Mesh, dim: int
) -> dolfinx.fem.FunctionSpace:
"""
Constructed a finite elements space from a string
representation of the space
Arguments
---------
space_string : str
A string on the form {family}_{degree} which
determines the space. Example 'Lagrange_1'.
mesh : df.Mesh
The mesh
dim : int
1 for scalar space, 3 for vector space.
"""
el = parse_element(space_string, mesh, dim)
return dolfinx.fem.functionspace(mesh, el)
[docs]
def geometry_degree(mesh: dolfinx.mesh.Mesh) -> int:
"""The degree of the coordinate element of the mesh"""
domain = mesh.ufl_domain()
assert domain is not None
return int(domain.ufl_coordinate_element().degree)
[docs]
def laplace_space(mesh: dolfinx.mesh.Mesh) -> dolfinx.fem.FunctionSpace:
"""The space in which the Laplace problems are solved: Lagrange elements of the
same degree as the geometry, so that the solutions are as accurate as the geometry.
"""
return dolfinx.fem.functionspace(mesh, ("Lagrange", geometry_degree(mesh)))
[docs]
def closest_dofs(V: dolfinx.fem.FunctionSpace, point: Sequence[float] | np.ndarray) -> np.ndarray:
"""The local (owned and ghost) dofs of the node closest to `point`.
The node is chosen among the owned nodes of all processes and identified by its global
index, so every process that has it, as owned or ghost, returns it. Matching by coordinates
would miss ghost copies, whose tabulated coordinates can differ in the last bits.
"""
comm = V.mesh.comm
index_map = V.dofmap.index_map
coords = V.tabulate_dof_coordinates()
distance = np.linalg.norm(coords[: index_map.size_local] - np.asarray(point), axis=1)
local = (float(distance.min()), comm.rank) if len(distance) > 0 else (np.inf, comm.rank)
_, owner = comm.allreduce(local, op=MPI.MINLOC)
closest = None
if comm.rank == owner:
closest = int(index_map.local_to_global(np.array([distance.argmin()], dtype=np.int32))[0])
closest = comm.bcast(closest, root=owner)
num_local = index_map.size_local + index_map.num_ghosts
local_to_global = index_map.local_to_global(np.arange(num_local, dtype=np.int32))
return np.flatnonzero(local_to_global == closest).astype(np.int32)
def element2array(el: basix.ufl._BlockedElement) -> np.ndarray:
try:
el = el.basix_element
family = int(el.family)
cell_type = int(el.cell_type)
degree = int(el.degree)
discontinuous = int(el.discontinuous)
except NotImplementedError:
assert el.family_name == "quadrature"
family = QUADRATURE_FAMILY
cell_type = int(el.cell_type)
degree = int(el.degree)
discontinuous = int(el.discontinuous)
return np.array(
[family, cell_type, degree, discontinuous],
dtype=np.uint8,
)
def number2Enum(num: int, enum: Iterable) -> Enum:
for e in enum:
if int(e) == num:
return e
raise ValueError(f"Invalid value {num} for enum {enum}")
def array2element(arr: np.ndarray) -> basix.finite_element.FiniteElement:
cell_type = number2Enum(arr[1], basix.CellType)
degree = int(arr[2])
discontinuous = bool(arr[3])
if arr[0] == QUADRATURE_FAMILY:
return basix.ufl.quadrature_element(
scheme="default",
cell=cell_type,
degree=degree,
value_shape=(3,),
)
else:
family = number2Enum(arr[0], basix.ElementFamily)
# TODO: Shape is hardcoded to (3,) for now, but this should also be stored
return basix.ufl.element(
family=family,
cell=cell_type,
degree=degree,
discontinuous=discontinuous,
shape=(3,),
)