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 _snap_to_netgen, _curve_netgen_mesh
from firedrake.petsc import PETSc


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

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

    # Save the transform, so that the refined DMPlex can tell which of its
    # points came from which point of ``dm``.
    dm.setSaveTransform()

    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.getLocalSection(), 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


[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. The vertices of a Netgen mesh are snapped onto its geometry after each round, and the coordinates are curved to their original degree at the end. 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_fine_to_coarse_points`` set to the DMPlex point of ``mesh`` that each of its DMPlex points was refined from. """ 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) current_mesh = mesh current_mark = cell_marker fine_to_coarse_points = np.arange(*mesh.topology_dm.getChart(), dtype=IntType) is_netgen = hasattr(mesh, "netgen_mesh") for ref in range(num_refinements): new_dm = _adapt_marked_cells(current_mesh, current_mark) if is_netgen: ngmesh = _snap_to_netgen(new_dm, mesh.netgen_mesh) fine_to_coarse_points = impl.compose_points( fine_to_coarse_points, impl.transform_source_points(new_dm)) 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, ) if is_netgen: current_mesh.netgen_mesh = ngmesh current_mesh.netgen_flags = mesh.netgen_flags 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. _, fine_to_coarse = impl.coarse_to_fine_cells(mesh, current_mesh, fine_to_coarse_points) 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) final_mesh = current_mesh if is_netgen: coordinates = mesh.coordinates.function_space() with PETSc.Log.Event("AdaptiveRefine: recurve netgen coords"): final_mesh = _curve_netgen_mesh(final_mesh, coordinates.ufl_element().degree(), cg_field=not coordinates.finat_element.is_dg()) final_mesh._adaptive_parent = mesh final_mesh._adaptive_fine_to_coarse_points = fine_to_coarse_points _copy_adaptive_refinement_metadata(mesh, final_mesh) return final_mesh