Source code for firedrake.mg.mesh

import numpy as np
from fractions import Fraction
from collections import defaultdict

from pyop2.datatypes import IntType

import firedrake
from firedrake.utils import cached_property
from firedrake.cython import mgimpl as impl
from .utils import set_level

__all__ = ("HierarchyBase", "MeshHierarchy", "ExtrudedMeshHierarchy", "NonNestedHierarchy",
           "SemiCoarsenedExtrudedHierarchy")


[docs] class HierarchyBase(object): """Create an encapsulation of an hierarchy of meshes. :arg meshes: list of meshes (coarse to fine) :arg coarse_to_fine_cells: list of numpy arrays for each level pair, mapping each coarse cell into fine cells it intersects. :arg fine_to_coarse_cells: list of numpy arrays for each level pair, mapping each fine cell into coarse cells it intersects. :arg refinements_per_level: number of mesh refinements each multigrid level should "see". :arg nested: Is this mesh hierarchy nested? .. note:: Most of the time, you do not need to create this object yourself, instead using :func:`MeshHierarchy`, :func:`ExtrudedMeshHierarchy`, or :func:`NonNestedHierarchy`. """ def __init__(self, meshes, coarse_to_fine_cells, fine_to_coarse_cells, refinements_per_level=1, nested=False): from firedrake_citations import Citations Citations().register("Mitchell2016") self._meshes = tuple(meshes) self.meshes = tuple(meshes[::refinements_per_level]) self.coarse_to_fine_cells = coarse_to_fine_cells self.fine_to_coarse_cells = fine_to_coarse_cells self.refinements_per_level = refinements_per_level self.nested = nested for level, m in enumerate(meshes): set_level(m, self, Fraction(level, refinements_per_level)) for level, m in enumerate(self): set_level(m, self, level) self._shared_data_cache = defaultdict(dict) @cached_property def comm(self): comm = self[0].comm if not all(m.comm == comm for m in self): raise NotImplementedError("All meshes in hierarchy must be on same communicator") return comm @cached_property def _comm(self): _comm = self[0]._comm if not all(m._comm == _comm for m in self): raise NotImplementedError("All meshes in hierarchy must be on same communicator") return _comm def __iter__(self): """Iterate over the hierarchy of meshes from coarsest to finest""" for m in self.meshes: yield m def __len__(self): """Return the size of hierarchy""" return len(self.meshes) def __getitem__(self, idx): """Return a mesh in the hierarchy :arg idx: The :func:`~.Mesh` to return""" return self.meshes[idx]
[docs] def MeshHierarchy(mesh, refinement_levels, refinements_per_level=1, netgen_flags=False, reorder=None, distribution_parameters=None, callbacks=None, mesh_builder=firedrake.Mesh): """Build a hierarchy of meshes by uniformly refining a coarse mesh. Parameters ---------- mesh : MeshGeometry the coarse mesh to refine refinement_levels : int the number of levels of refinement refinements_per_level : int the number of refinements for each level in the hierarchy. netgen_flags : bool, dict either a bool or a dictionary containing options for Netgen. If not False the hierachy is constructed using ngsPETSc, if None hierarchy constructed in a standard manner. distribution_parameters : dict options controlling mesh distribution, see :py:func:`.Mesh` for details. If ``None``, use the same distribution parameters as were used to distribute the coarse mesh, otherwise, these options override the default. reorder : bool optional flag indicating whether to reorder the refined meshes. callbacks : tuple A 2-tuple of callbacks to call before and after refinement of the DM. The before callback receives the DM to be refined (and the current level), the after callback receives the refined DM (and the current level). mesh_builder Function to turn a DM into a ``Mesh``. Used by pyadjoint. Returns ------- A :py:class:`HierarchyBase` object representing the mesh hierarchy. """ if (isinstance(netgen_flags, bool) and netgen_flags) or isinstance(netgen_flags, dict): try: from ngsPETSc import NetgenHierarchy except ImportError: raise ImportError("Unable to import netgen and ngsPETSc. Please ensure that netgen and ngsPETSc\ are installed and available to Firedrake. You can do this via \ firedrake-update --netgen.") if hasattr(mesh, "netgen_mesh"): return NetgenHierarchy(mesh, refinement_levels, flags=netgen_flags) else: raise RuntimeError("Cannot create a NetgenHierarchy from a mesh that has not been generated by\ Netgen.") cdm = mesh.topology_dm cdm.setRefinementUniform(True) dms = [] if mesh.comm.size > 1 and mesh._grown_halos: raise RuntimeError("Cannot refine parallel overlapped meshes " "(make sure the MeshHierarchy is built immediately after the Mesh)") parameters = {} if distribution_parameters is not None: parameters.update(distribution_parameters) else: parameters.update(mesh._distribution_parameters) parameters["partition"] = False distribution_parameters = parameters if callbacks is not None: before, after = callbacks else: before = after = lambda dm, i: None for i in range(refinement_levels*refinements_per_level): if i % refinements_per_level == 0: before(cdm, i) rdm = cdm.refine() if i % refinements_per_level == 0: after(rdm, i) rdm.removeLabel("pyop2_core") rdm.removeLabel("pyop2_owned") rdm.removeLabel("pyop2_ghost") dms.append(rdm) cdm = rdm # Fix up coords if refining embedded circle or sphere if hasattr(mesh, '_radius'): # FIXME, really we need some CAD-like representation # of the boundary we're trying to conform to. This # doesn't DTRT really for cubed sphere meshes (the # refined meshes are no longer gnonomic). coords = cdm.getCoordinatesLocal().array.reshape(-1, mesh.geometric_dimension()) scale = mesh._radius / np.linalg.norm(coords, axis=1).reshape(-1, 1) coords *= scale meshes = [mesh] + [mesh_builder(dm, dim=mesh.geometric_dimension(), distribution_parameters=distribution_parameters, reorder=reorder, comm=mesh.comm) for dm in dms] lgmaps = [] for i, m in enumerate(meshes): no = impl.create_lgmap(m.topology_dm) m.init() o = impl.create_lgmap(m.topology_dm) m.topology_dm.setRefineLevel(i) lgmaps.append((no, o)) coarse_to_fine_cells = [] fine_to_coarse_cells = [None] for (coarse, fine), (clgmaps, flgmaps) in zip(zip(meshes[:-1], meshes[1:]), zip(lgmaps[:-1], lgmaps[1:])): c2f, f2c = impl.coarse_to_fine_cells(coarse, fine, clgmaps, flgmaps) coarse_to_fine_cells.append(c2f) fine_to_coarse_cells.append(f2c) coarse_to_fine_cells = dict((Fraction(i, refinements_per_level), c2f) for i, c2f in enumerate(coarse_to_fine_cells)) fine_to_coarse_cells = dict((Fraction(i, refinements_per_level), f2c) for i, f2c in enumerate(fine_to_coarse_cells)) return HierarchyBase(meshes, coarse_to_fine_cells, fine_to_coarse_cells, refinements_per_level, nested=True)
[docs] def ExtrudedMeshHierarchy(base_hierarchy, height, base_layer=-1, refinement_ratio=2, layers=None, kernel=None, extrusion_type='uniform', gdim=None, mesh_builder=firedrake.ExtrudedMesh): """Build a hierarchy of extruded meshes by extruding a hierarchy of meshes. :arg base_hierarchy: the unextruded base mesh hierarchy to extrude. :arg height: the height of the domain to extrude to. This is in contrast to the extrusion routines, which take in layer_height, the height of an individual layer. This is because when refining in the extruded dimension, the height of an individual layer will vary. :arg base_layer: the number of layers to use the extrusion of the coarsest grid. :arg refinement_ratio: the ratio by which base_layer should be increased on every refinement. refinement_ratio = 2 means standard uniform refinement. refinement_ratio = 1 means to not refine in the extruded dimension, i.e. the multigrid hierarchy will use semicoarsening. :arg layers: as an alternative to specifying base_layer and refinement_ratio, one may specify directly the number of layers to be used by each level in the extruded hierarchy. This option cannot be combined with base_layer and refinement_ratio. Note that the ratio of successive entries in this iterable must be an integer for the multigrid transfer operators to work. :arg mesh_builder: function used to turn a ``Mesh`` into an extruded mesh. Used by pyadjoint. See :func:`~.ExtrudedMesh` for the meaning of the remaining parameters. """ if not isinstance(base_hierarchy, HierarchyBase): raise ValueError("Expecting a HierarchyBase, not a %r" % type(base_hierarchy)) if any(m.cell_set._extruded for m in base_hierarchy): raise ValueError("Meshes in base hierarchy must not be extruded") if layers is None: if base_layer == -1: raise ValueError("Must specify number of layers for coarsest grid with base_layer=N") layers = [base_layer * refinement_ratio**idx for idx in range(len(base_hierarchy._meshes))] else: if base_layer != -1: raise ValueError("Can't specify both layers and base_layer") meshes = [mesh_builder(m, layer, kernel=kernel, layer_height=height/layer, extrusion_type=extrusion_type, gdim=gdim) for (m, layer) in zip(base_hierarchy._meshes, layers)] return HierarchyBase(meshes, base_hierarchy.coarse_to_fine_cells, base_hierarchy.fine_to_coarse_cells, refinements_per_level=base_hierarchy.refinements_per_level, nested=base_hierarchy.nested)
[docs] def SemiCoarsenedExtrudedHierarchy(base_mesh, height, nref=1, base_layer=-1, refinement_ratio=2, layers=None, kernel=None, extrusion_type='uniform', gdim=None, mesh_builder=firedrake.ExtrudedMesh): """Build a hierarchy of extruded meshes with refinement only in the extruded dimension. :arg base_mesh: the unextruded base mesh to extrude. :arg nref: Number of refinements. :arg height: the height of the domain to extrude to. This is in contrast to the extrusion routines, which take in layer_height, the height of an individual layer. This is because when refining in the extruded dimension, the height of an individual layer will vary. :arg base_layer: the number of layers to use the extrusion of the coarsest grid. :arg refinement_ratio: the ratio by which base_layer should be increased on every refinement. refinement_ratio = 2 means standard uniform refinement. refinement_ratio = 1 means to not refine in the extruded dimension, i.e. the multigrid hierarchy will use semicoarsening. :arg layers: as an alternative to specifying base_layer and refinement_ratio, one may specify directly the number of layers to be used by each level in the extruded hierarchy. This option cannot be combined with base_layer and refinement_ratio. Note that the ratio of successive entries in this iterable must be an integer for the multigrid transfer operators to work. :arg mesh_builder: function used to turn a ``Mesh`` into an extruded mesh. Used by pyadjoint. See :func:`~.ExtrudedMesh` for the meaning of the remaining parameters. See also :func:`~.ExtrudedMeshHierarchy` if you want to extruded a hierarchy of unstructured meshes. """ if not isinstance(base_mesh, firedrake.mesh.MeshGeometry): raise ValueError(f"Can only extruded a mesh, not a {type(base_mesh)}") base_mesh.init() if base_mesh.cell_set._extruded: raise ValueError("Base mesh must not be extruded") if layers is None: if base_layer == -1: raise ValueError("Must specify number of layers for coarsest grid with base_layer=N") layers = [base_layer * refinement_ratio**idx for idx in range(nref+1)] else: if base_layer != -1: raise ValueError("Can't specify both layers and base_layer") if len(layers) == nref+1: raise ValueError("Need to provide a number of layers for every refined mesh. " f"Got {len(layers)}, needed {nref+1}") meshes = [mesh_builder(base_mesh, layer, kernel=kernel, layer_height=height/layer, extrusion_type=extrusion_type, gdim=gdim) for layer in layers] refinements_per_level = 1 identity = np.arange(base_mesh.cell_set.size, dtype=IntType).reshape(-1, 1) coarse_to_fine_cells = dict((Fraction(i, refinements_per_level), identity) for i in range(nref)) fine_to_coarse_cells = dict((Fraction(i+1, refinements_per_level), identity) for i in range(nref)) return HierarchyBase(meshes, coarse_to_fine_cells, fine_to_coarse_cells, refinements_per_level=refinements_per_level, nested=True)
[docs] def NonNestedHierarchy(*meshes): return HierarchyBase(meshes, [None for _ in meshes], [None for _ in meshes], nested=False)