Source code for firedrake.adapt

"""Adaptive mesh refinement helpers."""
import numpy as np
import petsctools

from firedrake.cython import dmcommon
from firedrake.cython import mgimpl as impl
from firedrake.utils import IntType
from firedrake.function import Function
from firedrake.functionspace import FunctionSpace
from firedrake.mesh import Mesh, DISTRIBUTION_PARAMETERS_NOOP
from firedrake.netgen import _transfer_high_order_coordinates
from firedrake.petsc import PETSc


# PETSc's DMAdaptFlag value requesting refinement, for the adapt label.
DM_ADAPT_REFINE = 1

# Label holding, on every cell, the number of the cell of the original mesh it
# descends from. The refinement transform propagates labels from a cell to its
# children, so this stays relative to the original mesh however many times we
# refine.
PARENT_LABEL = "_adaptive_dmplex_parent"

ADAPT_LABEL = "_adaptive_dmplex_adapt"


def _adapt_marked_cells(mesh, cell_marker):
    """Refine the cells of ``mesh`` marked by ``cell_marker`` and return the refined DMPlex."""
    dm = mesh.topology_dm
    ncoarse = mesh.cell_set.size

    with PETSc.Log.Event("AdaptiveRefine: mark cells"):
        dm.createLabel(ADAPT_LABEL)
        adapt_label = dm.getLabel(ADAPT_LABEL)
        adapt_indicator = np.zeros(cell_marker.dat.data_ro_with_halos.shape, dtype=IntType)
        adapt_indicator[:ncoarse] = cell_marker.dat.data_ro.real > 0
        dmcommon.mark_points_with_function_array(
            dm, cell_marker.function_space().dm.getSection(), 0,
            adapt_indicator, adapt_label, DM_ADAPT_REFINE,
        )

    parameters = {"dm_plex_transform_type": "refine_sbr"}
    try:
        # options_prefix="" is essential
        with petsctools.inserted_options(parameters=parameters, options_prefix=""):
            with PETSc.Log.Event("AdaptiveRefine: adaptLabel"):
                new_dm = dm.adaptLabel(ADAPT_LABEL)
    finally:
        # Ensure the temporary label is removed even if adaptation fails
        dm.removeLabel(ADAPT_LABEL)

    # The transform propagates every label, including the temporary adapt
    # label and the coarse mesh's stale pyop2_core/owned/ghost point
    # classification. Mesh() skips recomputing that classification if it's
    # already present, so it must be dropped here to force a fresh one for
    # the new mesh's own point count and distribution.
    for label in ("pyop2_core", "pyop2_owned", "pyop2_ghost", ADAPT_LABEL):
        if new_dm.hasLabel(label):
            new_dm.removeLabel(label)

    return new_dm


def _copy_adaptive_refinement_metadata(source_mesh, target_mesh):
    """Copy mesh-construction metadata from a mesh onto its adaptively-derived successor."""
    target_mesh._distribution_parameters = dict(source_mesh._distribution_parameters)
    target_mesh._did_reordering = source_mesh._did_reordering
    target_mesh._tolerance = source_mesh.tolerance
    if hasattr(source_mesh, "netgen_mesh") and not hasattr(target_mesh, "netgen_mesh"):
        target_mesh.netgen_mesh = source_mesh.netgen_mesh
    if hasattr(source_mesh, "netgen_flags") and not hasattr(target_mesh, "netgen_flags"):
        target_mesh.netgen_flags = source_mesh.netgen_flags


[docs] def refine_marked_elements(mesh, cell_marker): """Adaptively refine a mesh using a DG0 marking function. Positive integer marker values request repeated refinement of the corresponding cells. Curved Netgen meshes are re-curved to the original coordinate degree after refinement. Parameters ---------- mesh The mesh to refine. cell_marker A DG0 `~firedrake.function.Function` on ``mesh``: cells with a positive value ``n`` are refined ``n`` times. Returns ------- MeshGeometry The adaptively refined mesh, with ``adaptive_parent`` set to ``mesh`` and ``adaptive_cell_maps`` set to the ``(coarse_to_fine, fine_to_coarse)`` cell maps relative to it. """ with cell_marker.dat.vec_ro as v: _, num_refinements = v.max() # Always run at least one adaptation pass, even when no cell is marked, # so that a fresh mesh (with its own cell maps) is produced uniformly. num_refinements = max(int(np.rint(num_refinements)), 1) coarse_dm = mesh.topology_dm with PETSc.Log.Event("AdaptiveRefine: set_adaptive_parent_label"): impl.set_adaptive_parent_label(coarse_dm, mesh._cell_numbering, PARENT_LABEL) current_mesh = mesh current_mark = cell_marker try: for ref in range(num_refinements): new_dm = _adapt_marked_cells(current_mesh, current_mark) with PETSc.Log.Event("AdaptiveRefine: Mesh()"): current_mesh = Mesh( new_dm, dim=mesh.geometric_dimension, reorder=False, distribution_parameters=DISTRIBUTION_PARAMETERS_NOOP, comm=mesh.comm, tolerance=mesh.tolerance, ) with PETSc.Log.Event("AdaptiveRefine: adaptive_parent_child_cell_maps"): coarse_to_fine, fine_to_coarse = impl.adaptive_parent_child_cell_maps( coarse_dm, new_dm, current_mesh._cell_numbering, PARENT_LABEL ) if ref < num_refinements - 1: with PETSc.Log.Event("AdaptiveRefine: re-mark"): # A cell asking for n refinements stays marked until n rounds # have happened, so its descendants inherit n minus the number # of rounds so far. ancestor = fine_to_coarse[:, 0] refined = ancestor >= 0 current_mark = Function(FunctionSpace(current_mesh, "DG", 0)) current_mark.dat.data_wo[refined] = \ cell_marker.dat.data_ro[ancestor[refined]] - (ref + 1) finally: # Ensure the temporary label is removed even if adaptation fails coarse_dm.removeLabel(PARENT_LABEL) final_mesh = current_mesh if hasattr(mesh, "netgen_mesh"): order = mesh.coordinates.function_space().ufl_element().degree() if order > 1: with PETSc.Log.Event("AdaptiveRefine: recurve netgen coords"): final_mesh = _transfer_high_order_coordinates(mesh, final_mesh, order) final_mesh.topology_dm.removeLabel(PARENT_LABEL) final_mesh.adaptive_parent = mesh final_mesh.adaptive_cell_maps = (coarse_to_fine, fine_to_coarse) _copy_adaptive_refinement_metadata(mesh, final_mesh) return final_mesh