Source code for pulse.utils
import logging
import dolfinx
import numpy as np
import numpy.typing as npt
import scifem
import ufl
from packaging.version import Version
from .kinematics import DeformationGradient
logger = logging.getLogger(__name__)
_dolfinx_version = Version(dolfinx.__version__)
[docs]
def map_vector_field(
f: dolfinx.fem.Function,
u: dolfinx.fem.Function | None = None,
normalize: bool = True,
name: str = "mapped_f0",
) -> dolfinx.fem.Function:
"""
Map a vector field defined on a mesh to a new mesh.
Parameters
----------
f: dolfinx.fem.Function
The vector field to map.
new_mesh: dolfinx.mesh.Mesh
The new mesh to map the vector field to.
u: dolfinx.fem.Function | None, optional
The displacement field used to map the points, by default None.
normalize: bool, optional
Whether to normalize the vector field after mapping, by default True.
Returns
-------
dolfinx.fem.Function
The mapped vector field on the new mesh.
"""
f_new = dolfinx.fem.Function(f.function_space, name=name)
if _dolfinx_version >= Version("0.10"):
points = f.function_space.element.interpolation_points
else:
points = f.function_space.element.interpolation_points()
if u is None:
f_new.interpolate(f)
else:
F = DeformationGradient(u)
f_map = F * f
if normalize:
f_norm = ufl.sqrt(ufl.dot(f_map, f_map))
f_normalized = f_map / f_norm
f_expr = dolfinx.fem.Expression(
f_normalized,
points,
)
else:
f_expr = dolfinx.fem.Expression(f_map, points)
f_new.interpolate(f_expr)
return f_new
[docs]
def evaluate_at_vertex_tag(
u: dolfinx.fem.Function,
vt: dolfinx.mesh.MeshTags,
tag: int,
) -> npt.NDArray[np.int32]:
"""
Given a function `u` and a vertex tag `vt` return the values of `u` at the vertices
tagged with `tag`
Parameters
----------
u: dolfinx.fem.Function
The function to evaluate
vt: dolfinx.mesh.MeshTags
The vertex tags
tag: int
The tag to evaluate at
Returns
-------
npt.NDArray[np.int32]
The values of `u` at the vertices tagged with `tag`
"""
v2d = scifem.vertex_to_dofmap(u.function_space)
block_index = v2d[vt.find(tag)]
dofs = scifem.utils.unroll_dofmap(block_index.reshape(-1, 1), u.function_space.dofmap.bs)
return u.x.array[dofs]
[docs]
def gather_broadcast_array(comm, local_array):
"""
Collects local arrays from all processes on the root process
and distributes the global array to all processes.
Assumes that the local arrays are either the same of empty / None on all processes.
Parameters
----------
comm: MPI.Comm
The MPI communicator
local_array: np.ndarray
The local array
Returns
-------
np.ndarray
The global array on the root process
"""
all_arrays = comm.gather(local_array, root=0)
if comm.rank == 0:
nonempty_arrays = [array for array in all_arrays if array is not None and len(array) > 0]
# Check that all nonempty arrays are the same
if len(nonempty_arrays) == 0:
global_array = np.array([])
elif len(nonempty_arrays) == 1:
global_array = nonempty_arrays[0]
else:
all_same = True
for array in nonempty_arrays[1:]:
all_same = all_same and np.allclose(array, nonempty_arrays[0])
if not all_same:
raise ValueError("Local arrays are not the same", nonempty_arrays)
global_array = nonempty_arrays[0]
else:
global_array = np.array([])
return comm.bcast(global_array, root=0)
def matrix_is_zero(A: ufl.core.expr.Expr) -> bool:
n = ufl.domain.find_geometric_dimension(A)
for i in range(n):
for j in range(n):
value = dolfinx.fem.assemble_scalar(dolfinx.fem.form(A[i, j] * ufl.dx))
print(i, j, value)
is_zero = np.isclose(value, 0)
if not is_zero:
return False
return True
def float2object(
f: float,
obj_str: str,
mesh: dolfinx.mesh.Mesh,
V: dolfinx.fem.FunctionSpace,
):
if obj_str == "float":
return f
if obj_str == "Constant":
return dolfinx.fem.Constant(mesh, f)
if obj_str == "Function":
v = dolfinx.fem.Function(V)
v.x.array[:] = f
return v
raise ValueError(f"Invalid object string {obj_str!r}")