Source code for irksome.tools
from .backend import get_backend
import numpy
import ufl
from ufl.algorithms.analysis import extract_type
from ufl.classes import Variable
from ufl import as_tensor, replace as ufl_replace
import FIAT
from .ufl.deriv import TimeDerivative
from .ufl.lag import lag_label
[docs]
def dot(A, B):
return numpy.tensordot(A, B, (-1, 0))
[docs]
def reshape(expr, shape):
return numpy.reshape([expr[i] for i in numpy.ndindex(expr.ufl_shape)], shape)
[docs]
def flatten_dats(dats):
from pyop2.types import MixedDat
flat_dat = []
for dat in dats:
if isinstance(dat, (tuple, list, MixedDat)):
flat_dat.extend(dat)
else:
flat_dat.append(dat)
return MixedDat(flat_dat)
[docs]
def get_stage_space(V, num_stages, backend="firedrake"):
backend_cls = get_backend(backend)
return backend_cls.get_stage_space(V, num_stages)
[docs]
def split_stages(V, stages):
"""Reconstruct the stages as a list of Function(V)"""
num_fields = len(V)
if num_fields == 1:
return stages.subfunctions
stages_np = reshape(stages, (-1, *V.value_shape))
ks = [as_tensor(stages_np[i]) for i in range(stages_np.shape[0])]
return ks
[docs]
def fields_to_components(V, fields):
"""
Returns the scalar component indices corresponding to the possibly
tensor-valued subspaces of a mixed function space.
:arg V: a :class:`FunctionSpace`.
:arg fields: a list of integers defining subspaces of V.
:returns: a list of integers with the scalar components corresponding to
the subfields.
"""
cur = 0
components = []
if len(fields) == 0:
return components
for i, Vi in enumerate(V):
if i in fields:
components.extend(range(cur, cur+Vi.value_size))
cur += Vi.value_size
return components
[docs]
def replace(e, mapping):
"""A wrapper for ufl.replace that allows numpy arrays and skips
substitution into sub-expressions wrapped by :func:`~irksome.lag`."""
cmapping = {k: as_tensor(v) for k, v in mapping.items()}
for var in extract_type(e, Variable):
if var.ufl_operands[1] is lag_label:
cmapping.setdefault(var, var)
return ufl_replace(e, cmapping)
# Utility functions that help us refactor
[docs]
def AI(A):
return (A, numpy.eye(*A.shape, dtype=A.dtype))
[docs]
def IA(A):
return (numpy.eye(*A.shape, dtype=A.dtype), A)
[docs]
def is_ode(f, u):
"""Given a form defined over a function `u`, checks if
(each bit of) u appears under a time derivative."""
derivs = extract_type(f, TimeDerivative)
Dtbits = []
for k in derivs:
op, = k.ufl_operands
Dtbits.extend(op[i] for i in numpy.ndindex(op.ufl_shape))
ubits = [u[i] for i in numpy.ndindex(u.ufl_shape)]
return set(ubits) <= set(Dtbits)
[docs]
def get_lagrange_permutation(L):
"""Given a univariate Lagrange element, return the
points ordered from left to right and the permutation of the
dofs required to obtain this re-ordering."""
assert L.ref_el.get_spatial_dimension() == 1
points = []
for ell in L.dual.nodes:
if not isinstance(ell, FIAT.functional.PointEvaluation):
raise TypeError("Expecting a Lagrange element")
pt, = ell.get_point_dict().keys()
points.append(pt[0])
c = numpy.asarray(points)
perm = numpy.argsort(c)
return c[perm], perm
[docs]
def get_sub(u: ufl.FunctionSpace | ufl.Coefficient, indices: tuple[int, ...]) -> ufl.FunctionSpace | ufl.Coefficient:
"""Recursively access the subfunction of a mixed function space or the space itself, given the indices of the subspace.
Args:
u: A coefficient in a mixed function space
indices: A tuple of integers giving the indices of the subspace to access. For example
if u is in a mixed space (V1, V2, V3), then get_sub(u, (0, 2)) will return the third subfunction of u in V1,
i.e. `u.sub(0).sub(2)`.
"""
for i in indices:
if i is not None:
u = u.sub(i)
return u