AMR & ALE
Two optional mechanisms move the mesh during a run: adaptive mesh refinement (src/solvers/AMR.jl) and the arbitrary-Lagrangian–Eulerian free surface (src/physics/ALEFreeSurface.jl). Both produce a mesh that the rest of the code treats as adapted (MeshBundle.adapted = true), which disables every Cartesian fast path — read the limitations below before enabling either.
Adaptive mesh refinement
Strategy: standard fixed-fraction refinement. Every amr_every steps (when do_amr = true):
Compute a per-cell scalar indicator
( amr_indicator).Mark the top
amr_refine_fractionof cells by( fixed_fraction_marks— threshold at the-th largest indicator). Refine marked cells via
Gridap.Adaptivity.refine(each split into 4 in 2D / 8 in 3D); unmarked cells are untouched.
Indicators (amr_criterion)
| Value | Indicator | Captures |
|---|---|---|
"T_gradient" (default) | thermal interfaces — plume head, slab top | |
"strain_rate" | faults, shear bands | |
"viscosity" | rheology jumps | |
"composition" | do_composition_fe) | compositional interfaces |
"user" | closure (x, y, z, state, cfg) -> Float64 passed as amr_user_indicator to run_case | anything |
With sticky air (air_thick > 0) the indicator is masked to zero for all cells above air_thick + 2·Δy: the air/lithosphere velocity discontinuity creates spurious high strain-rate /
State transfer
apply_amr! rebuilds the MeshBundle around the RefinedDiscreteModel (rebuild_mesh_bundle, which preserves the inherited wall tags), rebuilds the FE spaces, and transfers the FE solutions (uh, ph, Th, Th_old, Ch, Ch_old, extra composition fields) by interpolation via Gridap's Interpolable. The transfer is NaN-safe (_safe_fe_transfer): any non-finite DOF — possible when the KDTree search misses a boundary point by floating-point ε — is replaced by a conservative fallback value, and a failed transfer falls back to a constant field.
Markers are unchanged (they hold global coordinates); only their host-cell association is recomputed at the next projection.
Per-cell state reset caveat
The per-cell arrays cannot be interpolated — the cell set itself changed — so apply_amr! reallocates and resets them to defaults for the new cell count: cfg.rocks (conventionally the asthenosphere background); eii_seed (or apply_amr! and additionally skips one heat solve (skip_heat_next) — on rare refinement patterns the first post-AMR heat solve produced NaN (a Gridap quadrature edge case with specific hanging-node configurations).
Config keys
[amr]
do_amr = false
amr_every = 10
amr_refine_fraction = 0.3
amr_criterion = "T_gradient" # T_gradient | composition | strain_rate | viscosity | user
# amr_coarsen_fraction = 0.0 # bottom fraction coarsened (refine-only by default)
# amr_max_level = 2 # cap on refinement levelsamr_coarsen_fraction and amr_max_level are parsed (src/io/TOMLLoader.jl) but coarsening is not yet wired into apply_amr! — the implementation is currently refine-only.
ALE free surface
do_ale_free_surface = true (2D only) replaces the sticky-air approximation with a mesh that follows the rock–air interface, using vertical stretching after Kaus, Mühlhaus & May (2010). Per step (after AMR, at the end of the Driver loop):
Sample
at every top-row -grid line at the current surface height ( _sample_vy_at_surface).Advance the 1-D height field
— positive = surface risen above its initial level, since points down — with the Kaus implicit stabilization against the drunken-sailor instability:
with fs_stabilisation (0 = none, 0.5–1 = strong; default 0.5), rho_ref_heat, eta_max (update_surface_height).
Optional Crameri-style horizontal smoothing, weight
surface_smooth(default 0):. Rebuild the
CartesianDiscreteModelwith the vertical-stretch map (build_mapped_model)
so the bottom stays fixed and the top sits at
- Re-wrap into a
MeshBundle(rebuild_mb_with_ale) and transferuh,ph,Th,Th_oldwith the same NaN-safeInterpolablemachinery as AMR, then re-project markers.
[free_surface]
do_ale_free_surface = false
fs_stabilisation = 0.5
surface_smooth = 0.0Limitations
Fast paths disabled on adapted meshes
Every mesh produced by AMR or ALE (and the chunk geometry) is flagged adapted = true, and all of the following silently switch from the
marker → cell projection (
_project_markers_general!, host-only stencil — the bilinear stencil needs the uniform neighbour layout);RK4 advection (generic
eval_velocityinstead of the direct Q2 snapshot evaluators — roughly the ~300× evaluator speedup is lost);marker temperature/strain/grain-size updates;
subgrid temperature diffusion (
apply_subgrid_diffusion!is a no-op on adapted meshes).
The GMG hierarchy also cannot be built on an adapted or mapped mesh — build_gmg_hierarchy returns nothing and solver_type = "gmg"/"auto" falls back to the cached MUMPS direct solve (one-time warning). Expect AMR/ALE runs to be markedly slower per marker and per solve than uniform-mesh runs of comparable size; the solver caches themselves (keyed on model identity and DOF counts) rebuild automatically after every mesh swap.
Other caveats
AMR refinement is one-way in practice (no coarsening yet); recovering a coarse mesh means rebuilding the model.
ALE is 2D-only and assumes the
boxgeometry (the rebuild re-tags walls with the fixed Cartesian entity IDs ofCartesianDiscreteModel).AMR and the heat solve interact through the one-step
skip_heat_nextguard; diagnostics for that step reflect the pre-AMR temperature.Combining AMR with two-phase flow or volatile transport is untested.