Adaptive Mesh Refinement¶
Adaptive mesh refinement (AMR) utilities.
This module provides mesh adaptation capabilities for Underworld3, enabling dynamic refinement and coarsening based on solution features or error estimates. AMR is particularly useful for problems with localized features such as boundary layers, shear zones, or phase boundaries.
Key Functions¶
- metric_from_gradientfunction
Create adaptation metric from gradient of a scalar field. Refines where gradients are steep, coarsens where field is smooth.
- metric_from_fieldfunction
Create adaptation metric from any scalar indicator field. General-purpose utility for creating metrics from error estimates, phase indicators, etc.
- create_metricfunction
Create adaptation metric directly from target edge lengths (h-field). Low-level utility used by other metric functions.
- mesh_adapt_meshVarfunction
Adapt mesh based on a metric MeshVariable (internal utility).
- mesh2mesh_meshVariablefunction
Transfer a MeshVariable from one mesh to another using swarm intermediary. Useful for checkpoint/restart workflows.
Notes
Metric Tensor Mathematics
For isotropic mesh adaptation, MMG/PETSc uses a metric tensor:
where \(h\) is the target edge length and \(I\) is the identity matrix. This relationship is dimension-independent - the same formula applies in 2D and 3D because the metric defines edge lengths, not areas or volumes.
The adaptation algorithm seeks to make all edges have unit length in the metric space (i.e., \(\mathbf{e}^T M \mathbf{e} = 1\) for edge vector \(\mathbf{e}\)). Higher metric values produce smaller elements.
Boundary Label Handling
The boundary label stacking utilities handle the constraint that PETSc’s adaptive meshing interpolates only one boundary label at a time. The stacking approach combines multiple gmsh-generated boundary labels into a single composite label for adaptation, then unstacks them afterward.
This module requires PETSc to be compiled with adaptive mesh support (pragmatic, mmg, or parmmg).
See also
underworld3.discretisationMesh classes that can be adapted.
underworld3.meshingMesh generation utilities.
underworld3.meshing.Surface.refinement_metricSurface-based adaptation.
Mesh Adaptation¶
Tools for adaptive mesh refinement (AMR) in Underworld3.
Note: AMR features require the amr environment with custom PETSc build.
mesh_adapt_meshVar¶
Perform mesh adaptation based on a mesh variable field.
mesh2mesh_meshVariable¶
Transfer mesh variable data between meshes.
mesh2mesh_swarm¶
Transfer swarm data between meshes.
- underworld3.adaptivity.mesh2mesh_swarm(mesh0, mesh1, swarm0, swarmVarList, proxy=True, verbose=False)[source]¶
Warning [NSFW] - this uses EXPLICIT message passing calls to handle the situation where a swarm cell_dm cannot find particles after mesh redistribution. This occurs when particles are moved accross non-neighbouring processes or if the mesh neighbours are redistricted. This should be fixed at the DMSwarm / DMPlex level so this code is just a placeholder. Or maybe it’s just user error !
Notes 1: This copies a swarm from one mesh to another allowing for completely incommensurate partitionings. Warning: this is not always a 1->1 mapping. There may be some duplication and particles may go missing along curved boundaries where the meshes do not necessarily overlap. The same is true in the shadow spaces.
Note 2: The swarm is “adapted” to the original mesh and will need to be repopulated on the new one, or data can be mapped to a purpose-built swarm.
Note 3: We pass the data around as floats for the time being. Be careful when converting back.
Note 4: set proxy=True to automatically generate proxy variables on mesh1 but consider skipping if the returned swarm is ephemeral
Metric Functions¶
Functions for computing adaptation metrics that guide mesh refinement.
create_metric¶
Create a metric field for mesh adaptation.
- underworld3.adaptivity.create_metric(mesh, h_values, name=None)[source]¶
Create adaptation metric from target edge lengths.
This is the core utility that converts target edge lengths (h-field) to the metric tensor format required by MMG/PETSc mesh adaptation.
- Parameters:
- Returns:
Scalar MeshVariable containing metric values ready for mesh.adapt().
- Return type:
MeshVariable
Notes
Metric Tensor Mathematics
For isotropic mesh adaptation, MMG/PETSc uses a metric tensor:
\[M = h^{-2} \cdot I\]where \(h\) is the target edge length and \(I\) is the identity matrix. This relationship is dimension-independent - the same formula applies in 2D and 3D.
Higher metric values produce smaller elements. The adaptation algorithm seeks to make \(\mathbf{e}^T M \mathbf{e} = 1\) for all edges.
Examples
>>> # Create metric from h-field computed elsewhere >>> h_field = compute_error_based_h(solution) # User function >>> metric = uw.adaptivity.create_metric(mesh, h_field) >>> mesh.adapt(metric)
See also
metric_from_gradientCreate metric from scalar field gradient.
metric_from_fieldCreate metric from indicator field.
metric_from_field¶
Compute an adaptation metric from a scalar field.
- underworld3.adaptivity.metric_from_field(indicator, h_min, h_max, indicator_min=None, indicator_max=None, invert=False, profile='linear', name=None)[source]¶
Create adaptation metric from an indicator field.
Maps a scalar indicator field (e.g., error estimate, phase field, distance) to target edge lengths. This is more general than gradient-based adaptation - you provide any field indicating where refinement is needed.
- Parameters:
indicator (MeshVariable) – Scalar field indicating where refinement is needed. Higher values (by default) produce finer mesh.
h_min (float) – Target edge length where indicator is highest (finest mesh).
h_max (float) – Target edge length where indicator is lowest (coarsest mesh).
indicator_min (float, optional) – Indicator values below this use h_max. If None, uses field minimum.
indicator_max (float, optional) – Indicator values above this use h_min. If None, uses field maximum.
invert (bool, optional) – If True, high indicator values give coarse mesh (swap h_min/h_max roles). Useful when indicator represents “smoothness” rather than “need for refinement”. Default: False.
profile (str, optional) – Interpolation profile: “linear” or “smoothstep”. Default: “linear”.
name (str, optional) – Name for the metric MeshVariable. Defaults to “{indicator.name}_metric”.
- Returns:
Scalar MeshVariable containing metric values ready for mesh.adapt().
- Return type:
MeshVariable
Notes
Use Cases
Error estimates: Pass a computed error field; high error gives fine mesh
Phase fields: Refine at interfaces (field near transition value)
Distance fields: Refine near surfaces (use with Surface.distance)
Material boundaries: Refine near composition gradients
Relationship to Surface.refinement_metric()
This function is a general-purpose version. Surface.refinement_metric() is a specialized wrapper that computes the indicator from surface distance.
Examples
>>> # Refine based on error estimate >>> error = compute_error_estimate(solution) # User function >>> metric = uw.adaptivity.metric_from_field(error, h_min=0.005, h_max=0.05) >>> mesh.adapt(metric)
>>> # Refine at phase boundaries (phi transitions from 0 to 1) >>> # Want fine mesh where phi is near 0.5 >>> phi_interface = 1 - 4 * (phi - 0.5)**2 # Peak at phi=0.5 >>> metric = uw.adaptivity.metric_from_field(phi_interface, h_min=0.01, h_max=0.1)
See also
create_metricCreate metric from h-field directly.
metric_from_gradientCreate metric from field gradient.
metric_from_gradient¶
Compute an adaptation metric from field gradients.
- underworld3.adaptivity.metric_from_gradient(field, h_min, h_max, gradient_min=None, gradient_max=None, profile='linear', name=None)[source]¶
Create adaptation metric from gradient of a scalar field.
Produces a metric that refines where gradients are steep (high \(\lvert\nabla\phi\rvert\)) and coarsens where the field is smooth (low \(\lvert\nabla\phi\rvert\)). This is the standard approach for error-driven or feature-based mesh adaptation.
- Parameters:
field (MeshVariable) – Scalar MeshVariable whose gradient drives refinement. Must have num_components=1.
h_min (float) – Target edge length where gradient is highest (finest mesh).
h_max (float) – Target edge length where gradient is lowest (coarsest mesh).
gradient_min (float, optional) – Gradient magnitude below this uses h_max. If None, uses 5th percentile of gradient magnitude values.
gradient_max (float, optional) – Gradient magnitude above this uses h_min. If None, uses 95th percentile of gradient magnitude values.
profile (str, optional) –
Interpolation profile: “linear”, “smoothstep”, or “power” (default: “linear”).
”linear”: h varies linearly with gradient magnitude
”smoothstep”: smooth S-curve transition (\(C^1\) continuous)
”power”: \(h \propto \lvert\nabla\phi\rvert^{-1/2}\), natural for error equidistribution
name (str, optional) – Name for the metric MeshVariable. Defaults to “{field.name}_gradient_metric”.
- Returns:
Scalar MeshVariable containing metric values ready for mesh.adapt().
- Return type:
MeshVariable
Notes
Gradient-Based Refinement Strategy
The idea is that steep gradients indicate regions where the solution is changing rapidly - these need finer resolution to capture accurately. Smooth regions can use coarser mesh without losing accuracy.
The mapping is:
High \(\lvert\nabla\phi\rvert\) : small h : large metric : finer mesh
Low \(\lvert\nabla\phi\rvert\) : large h : small metric : coarser mesh
Choosing h_min and h_max
h_mincontrols finest resolution (where gradients are steepest)h_maxcontrols coarsest resolution (smooth regions)Ratio
h_max/h_mingives refinement factor (e.g., 10 = 10× finer at peaks)
Auto-detection of Gradient Range
If
gradient_minandgradient_maxare not specified, they are computed from the actual gradient field:gradient_min = 5th percentile of \(\lvert\nabla\phi\rvert\)
gradient_max = 95th percentile of \(\lvert\nabla\phi\rvert\)
This ensures robust behavior even when gradient magnitudes span many orders of magnitude.
Implementation Note
Gradients are computed using the Clement interpolant via
uw.function.evaluate(field.sym.diff(x), coords). This uses PETSc’sDMPlexComputeGradientClementInterpolantwhich averages cell-wise gradients at vertices. The result is O(h) accurate and fast (no linear solve required).Examples
>>> # Refine based on temperature gradient >>> metric = uw.adaptivity.metric_from_gradient( ... T, h_min=0.005, h_max=0.05, profile="smoothstep" ... ) >>> mesh.adapt(metric)
>>> # Refine based on strain rate >>> # First compute strain rate magnitude as scalar field >>> SR = uw.discretisation.MeshVariable("SR", mesh, 1) >>> # ... populate SR with strain rate second invariant ... >>> metric = uw.adaptivity.metric_from_gradient(SR, h_min=0.01, h_max=0.1) >>> mesh.adapt(metric)
See also
create_metricCreate metric from h-field directly.
metric_from_fieldCreate metric from indicator field (not gradient).