from __future__ import annotations
import numpy
from fractions import Fraction
from mpi4py import MPI
from pyop2 import op2
import pyop2
from firedrake.utils import IntType
from firedrake.functionspacedata import entity_dofs_key
import finat.ufl
import firedrake
from firedrake.cython import mgimpl as impl
[docs]
def identity_node_map(V):
cache = V.mesh()._shared_data_cache["hierarchy_identity_node_map"]
key = (V.ufl_element(), V.boundary_set)
try:
return cache[key]
except KeyError:
values = numpy.arange(V.node_set.total_size, dtype=IntType).reshape(-1, 1)
return cache.setdefault(key, op2.Map(V.node_set, V.node_set, 1, values=values))
[docs]
def fine_node_to_coarse_node_map(Vf, Vc):
if len(Vf) > 1:
assert len(Vf) == len(Vc)
return op2.MixedMap(map(fine_node_to_coarse_node_map, Vf, Vc))
mesh = Vf.mesh()
assert hasattr(mesh, "_shared_data_cache")
hierarchyf, levelf = get_level(Vf.mesh())
hierarchyc, levelc = get_level(Vc.mesh())
if hierarchyc != hierarchyf:
raise ValueError("Can't map across hierarchies")
hierarchy = hierarchyf
increment = Fraction(1, hierarchyf.refinements_per_level)
if levelc + increment != levelf:
raise ValueError("Can't map between level %s and level %s" % (levelc, levelf))
key = _cache_key(Vc, Vf)
cache = mesh._shared_data_cache["hierarchy_fine_node_to_coarse_node_map"]
try:
return cache[key]
except KeyError:
assert Vc.extruded == Vf.extruded
if Vc.mesh().variable_layers or Vf.mesh().variable_layers:
raise NotImplementedError("Not implemented for variable layers, sorry")
if Vc.extruded and not ((Vf.mesh().layers - 1)/(Vc.mesh().layers - 1)).is_integer():
raise ValueError("Coarse and fine meshes must have an integer ratio of layers")
fine_to_coarse = hierarchy.fine_to_coarse_cells[levelf]
fine_to_coarse_nodes = impl.fine_to_coarse_nodes(Vf, Vc, fine_to_coarse)
# Detect fine nodes that only touch orphaned fine cells with no coarse parent
orphaned = (fine_to_coarse_nodes[:Vf.node_set.size] < 0).all(axis=1).any()
with pyop2.mpi.temp_internal_comm(Vf.comm) as icomm:
orphaned = icomm.allreduce(bool(orphaned), op=MPI.LOR)
if orphaned:
raise NotImplementedError("Transfer is not implemented for fine nodes that only touch "
"orphaned fine cells with no coarse parent, as in a "
"SubmeshHierarchy of interior facets")
return cache.setdefault(key, op2.Map(Vf.node_set, Vc.node_set,
fine_to_coarse_nodes.shape[1],
values=fine_to_coarse_nodes))
[docs]
def coarse_node_to_fine_node_map(Vc, Vf):
if len(Vf) > 1:
assert len(Vf) == len(Vc)
return op2.MixedMap(map(coarse_node_to_fine_node_map, Vf, Vc))
mesh = Vc.mesh()
assert hasattr(mesh, "_shared_data_cache")
hierarchyf, levelf = get_level(Vf.mesh())
hierarchyc, levelc = get_level(Vc.mesh())
if hierarchyc != hierarchyf:
raise ValueError("Can't map across hierarchies")
hierarchy = hierarchyf
increment = Fraction(1, hierarchyf.refinements_per_level)
if levelc + increment != levelf:
raise ValueError("Can't map between level %s and level %s" % (levelc, levelf))
key = _cache_key(Vc, Vf)
cache = mesh._shared_data_cache["hierarchy_coarse_node_to_fine_node_map"]
try:
return cache[key]
except KeyError:
assert Vc.extruded == Vf.extruded
if Vc.mesh().variable_layers or Vf.mesh().variable_layers:
raise NotImplementedError("Not implemented for variable layers, sorry")
if Vc.extruded and not ((Vf.mesh().layers - 1)/(Vc.mesh().layers - 1)).is_integer():
raise ValueError("Coarse and fine meshes must have an integer ratio of layers")
coarse_to_fine = hierarchy.coarse_to_fine_cells[levelc]
coarse_to_fine_nodes = impl.coarse_to_fine_nodes(Vc, Vf, coarse_to_fine)
# op2.Map cannot hold the -1 that pads a short row, so fill each
# padded slot with a real entry from its own row. The injection
# kernel picks the candidate that matches the coarse node's physical
# location, so a repeated entry changes nothing.
valid = coarse_to_fine_nodes >= 0
nonempty = valid.any(axis=1)
if not Vc.comm.allreduce(bool(nonempty[:Vc.node_set.size].all()), op=MPI.LAND):
raise RuntimeError("Adaptive coarse-to-fine map has empty node candidates")
replacement = numpy.zeros(coarse_to_fine_nodes.shape[0],
dtype=coarse_to_fine_nodes.dtype)
rows = numpy.nonzero(nonempty)[0]
replacement[rows] = coarse_to_fine_nodes[rows, valid[rows].argmax(axis=1)]
coarse_to_fine_nodes = numpy.where(valid, coarse_to_fine_nodes,
replacement[:, None])
return cache.setdefault(key, op2.Map(Vc.node_set, Vf.node_set,
coarse_to_fine_nodes.shape[1],
values=coarse_to_fine_nodes))
[docs]
def coarse_cell_to_fine_node_map(Vc, Vf):
if len(Vf) > 1:
assert len(Vf) == len(Vc)
return op2.MixedMap(coarse_cell_to_fine_node_map(f, c) for f, c in zip(Vf, Vc))
mesh = Vc.mesh()
assert hasattr(mesh, "_shared_data_cache")
hierarchyf, levelf = get_level(Vf.mesh())
hierarchyc, levelc = get_level(Vc.mesh())
if hierarchyc != hierarchyf:
raise ValueError("Can't map across hierarchies")
hierarchy = hierarchyf
increment = Fraction(1, hierarchyf.refinements_per_level)
if levelc + increment != levelf:
raise ValueError("Can't map between level %s and level %s" % (levelc, levelf))
key = _cache_key(Vc, Vf, needs_coarse_entity_dofs=False)
cache = mesh._shared_data_cache["hierarchy_coarse_cell_to_fine_node_map"]
try:
return cache[key]
except KeyError:
assert Vc.extruded == Vf.extruded
if Vc.mesh().variable_layers or Vf.mesh().variable_layers:
raise NotImplementedError("Not implemented for variable layers, sorry")
if Vc.extruded:
level_ratio = (Vf.mesh().layers - 1) // (Vc.mesh().layers - 1)
else:
level_ratio = 1
coarse_to_fine = hierarchy.coarse_to_fine_cells[levelc]
_, ncell = coarse_to_fine.shape
iterset = Vc.mesh().cell_set
fine_per_cell = Vf.finat_element.space_dimension()
arity = fine_per_cell * ncell
coarse_to_fine_nodes = numpy.full((iterset.total_size, arity*level_ratio), -1, dtype=IntType)
# The DG injection kernel skips the padded slots of a row, but PyOP2
# still reads through them. Fill each one with the row's first child.
children = coarse_to_fine[:iterset.size, :]
children = numpy.where(children >= 0, children, children[:, :1])
values = Vf.cell_node_map().values[children]
if Vc.extruded:
# Keep the layers of each child together, so that the children of
# a coarse cell come before its padded slots.
values = values[:, :, None, :] + numpy.arange(level_ratio)[:, None] * Vf.offset
coarse_to_fine_nodes[:iterset.size, :] = values.reshape(iterset.size, -1)
offset = Vf.offset
if offset is not None:
offset = numpy.tile(offset*level_ratio, ncell*level_ratio)
return cache.setdefault(key, op2.Map(iterset, Vf.node_set,
arity=arity*level_ratio, values=coarse_to_fine_nodes,
offset=offset))
[docs]
def coarse_cell_child_count(
Vc: firedrake.functionspaceimpl.WithGeometry,
Vf: firedrake.functionspaceimpl.WithGeometry,
) -> op2.Dat:
"""Count the fine cells that each coarse cell was refined into.
A row of `HierarchyBase.coarse_to_fine_cells` is as wide as the busiest
coarse cell's count, so its width overstates how many children most cells
have. The DG injection kernel reads this count to stop at a coarse cell's
own children, and so leaves the padding alone.
Parameters
----------
Vc : firedrake.functionspaceimpl.WithGeometry
The coarse function space.
Vf : firedrake.functionspaceimpl.WithGeometry
The fine function space, on the next level of the same hierarchy.
Returns
-------
pyop2.types.dat.Dat
One count per cell of ``Vc``'s mesh, over that mesh's cell set. Halo
cells are left at zero: a par_loop visits the core and owned parts
only, so the kernel never reads them.
"""
mesh = Vc.mesh()
assert hasattr(mesh, "_shared_data_cache")
hierarchyf, levelf = get_level(Vf.mesh())
hierarchyc, levelc = get_level(Vc.mesh())
if hierarchyc != hierarchyf:
raise ValueError("Can't map across hierarchies")
hierarchy = hierarchyf
increment = Fraction(1, hierarchyf.refinements_per_level)
if levelc + increment != levelf:
raise ValueError(f"Can't map between level {levelc} and level {levelf}")
key = (levelc, Vc.extruded and (Vf.mesh().layers, Vc.mesh().layers))
cache = mesh._shared_data_cache["hierarchy_coarse_cell_child_count"]
try:
return cache[key]
except KeyError:
if Vc.extruded:
level_ratio = (Vf.mesh().layers - 1) // (Vc.mesh().layers - 1)
else:
level_ratio = 1
coarse_to_fine = hierarchy.coarse_to_fine_cells[levelc]
iterset = mesh.cell_set
counts = numpy.zeros(iterset.total_size, dtype=IntType)
# Each child of a coarse cell becomes level_ratio cells once extruded.
counts[:iterset.size] = (coarse_to_fine[:iterset.size] >= 0).sum(axis=1) * level_ratio
# A count belongs to a base cell, and every layer of that cell shares
# it. An ExtrudedSet holds no data of its own, so hang the counts off
# the base set that it was built on.
dset = op2.DataSet(iterset.parent if Vc.extruded else iterset, 1)
return cache.setdefault(key, op2.Dat(dset, counts, dtype=IntType))
[docs]
def physical_node_locations(V):
element = V.ufl_element()
if V.value_shape:
assert isinstance(element, (finat.ufl.VectorElement, finat.ufl.TensorElement))
element = element.sub_elements[0]
mesh = V.mesh()
# This is a defaultdict, so the first time we access the key we
# get a fresh dict for the cache.
cache = mesh.geometric_shared_data_cache["hierarchy_physical_node_locations"]
key = (element, V.boundary_set)
try:
return cache[key]
except KeyError:
Vc = V.collapse().reconstruct(element=finat.ufl.VectorElement(element, dim=mesh.geometric_dimension))
# FIXME: This is unsafe for DG coordinates and CG target spaces.
locations = firedrake.assemble(firedrake.interpolate(firedrake.SpatialCoordinate(mesh), Vc))
return cache.setdefault(key, locations)
[docs]
def set_level(obj, hierarchy, level):
"""Attach hierarchy and level info to an object."""
setattr(obj.topological, "__level_info__", (hierarchy, level))
return obj
[docs]
def get_level(obj):
"""Try and obtain hierarchy and level info from an object.
If no level info is available, return ``None, None``."""
try:
return getattr(obj.topological, "__level_info__")
except AttributeError:
return None, None
[docs]
def has_level(obj):
"""Does the provided object have level info?"""
return hasattr(obj.topological, "__level_info__")
def _cache_key(Vc, Vf, needs_coarse_entity_dofs=True):
"""Construct a cache key for node maps"""
_, levelf = get_level(Vf.mesh())
_, levelc = get_level(Vc.mesh())
if needs_coarse_entity_dofs:
key = entity_dofs_key(Vc.finat_element.entity_dofs())
else:
key = ()
key += entity_dofs_key(Vf.finat_element.entity_dofs())
key += (levelc, levelf)
key += (Vc.boundary_set, Vf.boundary_set)
return key