Repository navigation
Subduction phase-change rule engine (phase_change_option 3) on a VirtualTopology marker grid - #110
Conversation
phase_changes() implements only options 0, 1 and 101. Any other value passed input, wrote frame 0 and died at the first phase-change step. --help still listed option 2 and named classes that do not exist. validate_parameters now rejects any other value with exit 11. The check in phase_changes() cannot fire from a cfg any more, so it exits 61, an internal error. The help names the functions behind options 1 and 101, marks four keys of the removed mid-ocean-ridge rule as unused, and names the real use of mattype_oceanic_crust in bc.cxx. Option 0 is bit-identical (test-rect-tiny). test-3d-equ-big.cfg sets option 2 and now exits 11 at input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simple_subduction() read *var.hydrous_elemmarkers on every call, but that marker set exists only with control.has_hydration_processes, so option 1 without it dereferenced a null pointer at its first phase-change step and crashed (exit 139). Only the mantle -> serpentinized mantle rule reads it; it now reads it only with hydration on. With hydration on, option 1 is bit-identical; with it off, a run that crashed now runs, and no marker serpentinizes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With mesh.quality_check_step_interval = 1 and the default averaged output, the averaged buffers stayed empty: every frame after the first carried zero-length rate and averaged arrays, dt_sec held the elapsed model time, and a restart from such a frame died (exit 21). Averaging is now off for that interval, so its frames carry the instantaneous fields and the step's dt. Other intervals are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An averaging window holds N steps (N = mesh.quality_check_step_interval). The output divided its N stress samples by N + 1, counted the snapshot step's plastic increment in a rate over the N - 1 steps after it, and divided that span by N for dt_sec. Each now uses its own count. Averaged output is on by default, so every run with N > 1 sees stress averaged grow by (N+1)/N, and dt_sec, the .info dt column and the screen dt by N/(N-1); plastic strain-rate loses the snapshot step's increment (each about 1% at the default N = 100). The physics does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a slow-update step, phase_changes() converts markers after the lumped masses in use were built. The run marches on the old masses, but a restart from that step rebuilds them from the new composition. So a checkpoint on a step with conversions does not reproduce the run. compute_mass() now runs after phase_changes() when a phase-change option is on. Option 0 stays bit-identical. With phase_change_option 3, which this series adds later, a restart from a step with conversions diverged by 5e-12 relative in temperature; now it is bit-exact in 2D and 3D. On a moving mesh, option 1 changes from its first conversion. On a static mesh without rsf_dtheta_max > 0, the time loop rebuilt no masses, so options 1 and 101 change from the first slow-update step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
read_data() took marker column 1 as Z. In 3D that column is y, so a 3D compare never compared the markers' depth. It now takes the last column, as the node fields do. In 2D it is the same column, so 2D compares are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mattype_crust and mattype_sed are ambiguous beside mattype_oceanic_crust, and phase_change_option 3, later in this series, adds mattype_sediment_rock. They become mattype_continental_crust and mattype_sediment: the Mat fields, the options, validate_parameters' range-check table, the help texts and every access, and every example, benchmark and test cfg follows. Results are bit-identical. BREAKING: a cfg using mattype_crust or mattype_sed is rejected; rename the keys to mattype_continental_crust and mattype_sediment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SPR remap centers its reference pressure on ref_pressure(zeff), with zeff from SurfaceTopo's depth-attenuated table. A caller that needs only the depth below the local surface does not need that table, and should not pay for it on every build. build() now takes elev_only (default false) to skip it. build(param, var) still builds the full table, as on master, so the two SPR call sites in remeshing.cxx do not change. No caller passes elev_only yet, so results are bit-exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Serpentinization must find the mantle markers within a set thickness above oceanic crust. The marker set has no spatial index for that. VirtualTopology bins marker ids into a background Cartesian grid. It keeps a build-time cell label per marker, the crust-dominant cells and their optional crust extents. remove_markers() flags each main-set id it back-fills or retires, so a reused id never reads a dead marker's entry. The checkpoint carries the index, so a restart keeps the lagged binning. A corrupt index dies at restart (exit 22). The next commit adds the caller and the option-3 keys, and gates the restore on uses_serpentinization_grid(). Numerical effect: none, as nothing builds the grid yet. With the next commit, a restart across a remesh is bit-identical to a continuous run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Option 1, the only subduction rule set, hard-codes materials 0-7 and serpentinizes mantle only in elements that hold hydrous markers. Option 3 ports geoflac's change_phase as a table of P-T rules. A rule acts only when its mat.has_* switch is on and its target mat.mattype_* names a distinct material. The rules read the mean stress of the marker's element as pressure, so the model's tectonic over- and underpressure counts (geoflac reads a fixed-density lithostat), and depth from the current surface. Mantle inside the antigorite stability wedge serpentinizes when the VirtualTopology grid holds a crust cell below it within mat.serpentinization_thickness: one where more than half the markers are oceanic crust or sediment. GeoFLAC master looks for oceanic crust or sedimentary rock in 80% of an element; on these half-element cells 80% drops the cell at the slab top. Both serpentinite rules read that one wedge, so no mantle serpentinizes where serpentinite would dehydrate; geoflac's mantle rule uses a wider window, whose low-P point (670 C, 0.6 GPa) reaches into that region. A cfg on which a rule would misfire or never fire dies at input (exit 11). The time summary and the manifest time the phase changes apart from compute. Options 0, 1 and 101 are bit-identical; option 3 is new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No example exercises mat.phase_change_option 3. This commit adds subduction.cfg and poly-subduction.poly. The oceanic slab starts in the mantle, so the serpentinization and eclogitization rules convert markers early. A small domain and inertial_scaling 1e4 keep the run short. It stops at 5 Myr, before the slab tip reaches the model bottom (about 7.8 Myr). It changes no code, so all results stay bit-exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every call computed the coordinate of all markers and transformed it to the grid's (x, zeff) space in a separate parallel pass, though only the markers that reach the grid read it, and get_ZPT(topo) computes the same coordinate and zeff for each of them anyway (same node order, so the same bits). The overload now returns it, and the all-marker pass, its array and its acc wait go. A probe of the subduction example put that pass at 39 of the call's 133 us. Output is bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass 1 interpolated the position and temperature and read the pressure of every non-mantle marker before it looked up its material's rule. A material with no active rule (in the subduction example: sediment and eclogite) cannot convert, so pass 1 now finds the rule first and skips those markers. A probe put 1387 of the 5463 pass-1 markers per call in that group. Output is bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass 2 evaluated temperature and pressure for every mantle marker before it looked for crust below, which most mantle markers lack. A labelled marker with the crust margin off now checks the grid first, so those markers skip the P-T evaluation; the live-cell fallback and the margin walk keep the old order. Output is bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Walking the grid before get_ZPT made every mantle marker pay the whole walk down serpentinization_thickness. At 30 km (10 cells) this cost more than it saved: PhaseChg took 0.227 ms per call, against 0.210 ms with get_ZPT first. Between the passes, one thread now flags every cell that has a crust-dominant cell within the thickness below it. This work scales with the dominant cells, not the mantle markers. Pass 2 reads the flag at a labelled marker's cell before get_ZPT. With the crust margin off, the flag is the whole answer; with it on, it prefilters the margin walk. Pass 1 continues past a marker its rule rejects, so the -1 sentinel in `to` and a to != mt test that could never fail go. Output is bit-identical. At 30 km, PhaseChg falls to 0.054 ms per call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mutex and its 8 lock sites served one concurrent caller: invalidate_dominant_cell() from pass 1 of subduction_phase_changes(). Every other mutator, and serialize(), runs in serial code. Pass 1 now records each cell where a crust marker turns non-crust (one push_back per such marker, in a critical section). The omp single between the passes drops those cells from dominant_cells_ just before it builds the crust-below map. It built the map after pass 1's barrier anyway, so pass 2 sees the same set, and the class takes no lock. Output is bit-identical at one thread, and in every frame at 6 threads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a GPU build, subduction_phase_changes() ran both marker passes on the host's OpenMP threads, over managed memory: 13% of the first 100 kyr of examples/subduction.cfg on an A5000. Each pass is now one element loop with the paired omp/acc pragmas. Rules dispatch on a TransitionKind, dominance and crust extents are flat per-cell arrays, pass 1 flags the cells whose crust it converts, and elev() no longer calls std::upper_bound. The checkpoint layout is unchanged; a restart now refuses a non-finite crust extent. The first 100 kyr now take 3:01 instead of 3:43 (PhaseChg 3.36 to 1.29 ms per call). Bit-identical in 2D on CPU and GPU, fresh and restarted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5c51fa7 to
e0cbefd
Compare
The Unreleased section gains this branch's major user-visible changes: phase_change_option 3 under Added, and the renamed material keys and the changed averaged output and phase-change masses as Breaking entries, each pointing to PR #110, with its link definition. The smaller fixes stay in the pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Unreleased section gains this branch's major user-visible changes: the VirtualTopology grid and phase_change_option 3 under Added, the renamed material keys as a Breaking entry, and the averaged-output and phase-change mass fixes under Fixed, each pointing to PR #110, with its link definition. The smaller fixes stay in the pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ee1e844 to
0df59fc
Compare
The 100-yr decay of plastic strain in top elements skipped those where mattype_oceanic_crust is the most common marker material. The skip came with the mid-ocean-ridge eruption, which put new oceanic crust on the surface, and outlived it: since the eruption was removed, nothing deposits oceanic crust at the top, and the skip only exempted whatever material index mattype_oceanic_crust held -- 0 by default, often the only material. It also made the default of mattype_oceanic_crust change results in every 2D run. Raised in the PR #110 review. Behavior change, 2D only (3D does not call the decay): plastic strain in top elements whose most common material is mattype_oceanic_crust now decays like the rest. test-rect-tiny: the 2 top elements with plastic strain fall by (1 - ln2 dt / 100 yr)^2, nothing else moves; test-core drifts from there; test-tiny, test-topo and test-vt-tiny stay bit-exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
surfinfo.dh_oc was still allocated at init and at every remesh, and the terrigenous report still declared max_dh_oc, although nothing has filled or read either since the eruption that wrote them was removed. No behavior change (vt-t0, test-tiny, test-topo, test-core bit-exact). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asked in the PR #110 review (section 5). mattype_continental_crust and mattype_oceanic_crust default to -1 (unset) instead of 0, which named the same material as the other base defaults. Each now has one reader: the continental geotherm (ic.temperature_option 1), which dies at input when the index is not set, and phase_change_option 3. Both leave the base range check. Under phase_change_option 3, a has_* switch that is on used to drop its rule silently when its source or target was unset, out of range, or the same material -- with has_eclogitization on by default, an unset mattype_eclogite or mattype_oceanic_crust meant no eclogite and no message. check_phase_transition_rules() now dies on each of these. It also dies when mattype_sediment equals mattype_oceanic_crust while eclogitization is on, since deposited sediment would turn into eclogite. phase_change_keys() lists mattype_mantle once. examples/subduction.cfg drops its mattype_continental_crust placeholder and names the machine behind its run time. Breaking: an option-3 cfg with has_eclogitization on (the default) must set mattype_oceanic_crust and mattype_eclogite to two distinct materials, and mattype_sediment to another one; a cfg with ic.temperature_option 1 must set mattype_continental_crust. No tracked cfg is affected. Results unchanged (vt-t0, test-tiny, test-topo, test-core bit-exact). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
restart() restored the checkpoint's grid with its cell size and kept it until the next remesh, although the input check validates serpentinization_thickness against the cfg's mesh.resolution * mesh.vt_size. A restart that changed either could stop serpentinization with no message (a thickness under the old cell size reaches no cell) or be refused although the grid it would really use was valid. Raised in the PR #110 review (section 3). When the sizes differ, restart() now warns and drops the restored grid; the next phase change or remesh builds one at the cfg's size, as for a checkpoint without a grid. Such a run does not continue the checkpointed one bit-exactly; a restart with an unchanged cfg still does (vt-t0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid was rebuilt only at init and at each remesh, so in a run that seldom remeshes it kept the slab geometry of its last build, and serpentinization stopped following the slab. The main loop now rebuilds it on each quality-check step, after the surface processes. Markers of the indexed set are added or removed only there and in a remesh, which rebuilds at its end, so the grid always indexes every marker. This makes the removed-id set (mark_removed and the live-position fallback of an unlabelled marker) dead, and it is deleted. A host assert replaces it: subduction_phase_changes dies when the marker count or the set's last id differs from the build's. Results of option-3 runs with serpentinization change: serpentinite now grows between remeshes. Breaking: the checkpoint's grid layout changes (no removed ids, the build's last id added); a checkpoint written before this cannot restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid kept one std::vector per cell, so its memory, its rebuild cost and its checkpoint grew with the cell count: a 3D 1000 x 1000 x 300 km domain at 5 km resolution has about 19M cells, most of them empty vector headers. The buckets are now cell offsets plus one id array, filled from the per-marker labels by a counting sort, which keeps each cell's ids ascending as before. The checkpoint stores only the labels and derives the buckets from them, so its size follows the marker count. Results are unchanged. Breaking: the checkpoint's grid layout changes again; a checkpoint written before this cannot restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rpentinize The grid reached the deepest marker, so in a deep domain most of its cells lay below anything that can serpentinize: in a 660 km domain about 75% of them. Its bottom is now the 150 km depth cap plus the look-down distance (serpentinization_thickness) plus one cell, and build_index() leaves the markers below it out with label -1 rather than clamping them into the bottom row. A converted wet-crust marker below the grid drops no cell, and pass 2 skips a mantle marker below it. The cap is now one named function, serpentinization_max_depth(). A domain shallower than the bottom keeps its grid, and its results are unchanged. In a deeper one the rows now sit at fixed depths below the surface instead of moving with the deepest marker, so results change: in examples/subduction.cfg (160 km) serpentinite is 7% lower at 5 Myr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fill_buckets() kept a second offset array, one int per cell, during each rebuild, which doubled the per-cell cost the flat layout set out to cut. It now places the ids from the last down, before each cell's running start, so the offsets themselves end at the cell starts and each cell's ids stay ascending. It also sizes the offsets itself, so the restart reader no longer has to. The comments now say what the reader does not bound (the cell count the dims ask for), what the grid costs in all, and that a restart which raises serpentinization_thickness keeps the old grid bottom until the next rebuild. Results are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he masses after A slow step converted markers after update_mesh() had rebuilt the lumped masses, so a second compute_mass() followed to keep a restart from that step bit-exact. update_mesh() already rebuilds the masses at its end; converting just before it lets that one rebuild see the new composition. On a moving mesh this saves one compute_mass() every slow step. A fixed mesh, which has no update_mesh(), still rebuilds them after the conversions. The rules now read each marker's position from the start of the step, before the coordinate update; the pressure, a stress trace, does not change under rotate_stress(). On a quality-check step they read the grid of the previous quality check, which still indexes every marker, and sediment deposited on that step converts one slow step later. Results change for every phase_change_option but 0; option 0 is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0df59fc to
4a3dda2
Compare
The Unreleased section gains this branch's major user-visible changes: the VirtualTopology grid and phase_change_option 3 under Added; the renamed material keys and the unset default of the continental and oceanic crust indices as Breaking entries, and the 2D surface plastic strain that now decays in oceanic crust too, under Changed; and the averaged-output fix, the phase-change mass fix and the temporary repair of the bottom-snap fold under Fixed, each pointing to PR #110, with its link definition. The smaller fixes stay in the pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lded mesh A temporary repair of the bottom-snap fold. flatten_bottom() puts a bottom node that sank below the bottom plane back onto it; when the node sank far, a triangle above it turns inside out. MMG reorients that triangle silently and returns a folded mesh, and the remesh stops with exit 42. examples/subduction.cfg did so at 3.7 Myr on Vista (gcc 14). When MMG's output is folded, optimize_mesh_2d() now lifts the interior nodes of the input's inverted triangles, each by the least amount that unfolds its triangle, so they move up with the bottom node, and runs MMG once more. The fold test is check_mesh_tangle()'s own, now shared, so only a remesh that would have stopped is repaired; every other run is unchanged. In examples/subduction.cfg the repair lifted one node by 1.2 km, and the run reached 5 Myr; a restart from before the fold reproduces it exactly. The full fix, the bc.has_winkler_bottom_restore option, comes in a later release; this repair then goes. Not handled: 3D; an inverted triangle that only a boundary node can unfold; a fold from a self-intersecting boundary; with remeshing option 13, a lifted node on a required interface edge bends that interface. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Unreleased section gains this branch's major user-visible changes: the VirtualTopology grid and phase_change_option 3 under Added; the renamed material keys and the unset default of the continental and oceanic crust indices as Breaking entries, and the 2D surface plastic strain that now decays in oceanic crust too, under Changed; and the averaged-output fix, the phase-change mass fix and the temporary repair of the bottom-snap fold under Fixed, each pointing to PR #110, with its link definition. The smaller fixes stay in the pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4a3dda2 to
bc20494
Compare
|
Hi @tan2, Thank you for the careful review. Here is what changed: Run time. The Following are the revision regarding each comment:1. Naming. "Crust" is now "wet crust" everywhere, and the parameter is now 2. Stale grid. The grid is now rebuilt at every quality check. Markers change only at those steps, so the grid is never out of date for long. This let me delete the removed-id set and the unlabelled-marker fallback. Restarts are still bit-exact. 3. Restart. If 4. Memory and speed.
5. Input checks. Not in your review. The 5 Myr runs hit a known crash, where the bottom of the mesh folds. I added a temporary repair. It acts only when the mesh folds, so models that run today give the same results. The full fix, Not changed: the
In a small 3D test model (about 0.39 M markers), both are about 0.1 %. The grid is rebuilt only once per quality-check interval. Its build time and the step time both grow with the number of markers, so the share should stay this small for large models. Each change would save less than 0.2 % of the run, and a cache could also change results. If a large 3D model shows otherwise, I will speed up the slowest part of the build. The PR description has the full checks. AI disclosure: I wrote this reply and the revision with help from Claude Code (Anthropic's Claude Opus 5.5). I reviewed and tested all the changes before I submitted them. |
Answers to your open questions
|
The rules read the element's mean stress, so tectonic over- and underpressure move the reaction boundaries. The phase diagrams they come from are drawn for a lithostat, and geoflac reads one; the PR reviewer asked for both, selectable. mat.phase_change_pressure_option: 0 (the default) keeps the mean stress; 1 reads ref_pressure() (control.ref_pressure_option) at the depth below the local surface that the rules already use. get_ZPT()'s surface-relative overload takes Param to choose. Any other value stops at input with exit 11. Options 1 and 101 already read ref_pressure() and ignore the key. The default runs are bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two untuned constants of the option-3 serpentinization were hard-coded: no mantle below 150 km serpentinizes, and a grid cell is wet-crust-dominant above half its markers. The PR reviewer asked for keys, since the result is sensitive to both. mat.serpentinization_max_depth (default 150e3, positive) is the cap and also ends the grid; mat.wet_crust_fraction_threshold (default 0.5, in [0, 1): the dominance test is a strict '>') sets dominance. Their rationale moves from the code to --help. A restart that changes either keeps the checkpoint's grid until the next rebuild, at most one quality check later. The --help of option 3 also says "wet crust" where it still said "crust". The default runs are bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Unreleased section gains this branch's major user-visible changes: the VirtualTopology grid and phase_change_option 3 under Added; the renamed material keys and the unset default of the continental and oceanic crust indices as Breaking entries, and the 2D surface plastic strain that now decays in oceanic crust too, under Changed; and the averaged-output fix, the phase-change mass fix and the temporary repair of the bottom-snap fold under Fixed, each pointing to PR #110, with its link definition. The smaller fixes stay in the pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bc20494 to
84bd8ae
Compare
|
Hi @tan2, Thank you. I made both changes and added those three avariables:
The static-mesh mass rebuild stays as it is. With the defaults, results are bit-identical. The PR description has the checks. |
|
Hi @tan2, Because of your previous requested changes, this PR might need your approval to be merged. |
Why
DES's subduction rules,
phase_change_option = 1, hard-code materials 0–7 and serpentinize onlyelements with hydrous markers. geoflac's rules (eclogite, mantle
⇄ serpentinite, chlorite-out, the sediment chain) had no DES port. They serpentinize mantle above
subducted wet crust (oceanic crust, sediment or sedimentary rock), which needs a marker-cell index
that follows the slab and that each checkpoint carries. This PR adds the index, VirtualTopology,
and the rule engine on it,
phase_change_option = 3, with depth measured below the current surface.What
31 commits, each message with its why and what; (10) is the 10th, in the order of the Commits tab.
C++: +1886 −101, mostly
phasechanges.cxxandmesh.cxx/mesh.hpp. A regression gate for option 3follows as its own PR.
compare.pymarker z in 3D (6)examples/subduction.cfg(11)update_mesh()(27); a temporary repair of the bottom-snap fold (28)Breaking:
mat.mattype_crust→mat.mattype_continental_crust,mat.mattype_sed→mat.mattype_sediment;an old name stops at input with exit 11 (7).
mat.mattype_continental_crustandmat.mattype_oceanic_crustdefault to −1 (unset):ic.temperature_option = 1, or an active option-3 rule that reads one, stops at input withexit 11 until it is set (21).
the mid-ocean-ridge eruption that Enable GPU acceleration and parallelization across Dynearthsol #25 removed. 2D runs with oceanic crust at the surface change (19).
mesh.quality_check_step_interval; with an interval of 1 it is instantaneous (3, 4). Physicsunchanged.
phase_change_option≠ 0: conversions read the positions at the start of the step,and the lumped masses are rebuilt from the converted composition (5, 27). Options 1 and 101
change from their first conversion; on a static mesh (
control.has_moving_mesh = no,control.rsf_dtheta_max= 0), from step 10.For the reviewer
rule table. The three open questions are answered:
mat.phase_change_pressure_option(29). 0 (default) = the element meanstress, 1 =
ref_pressure()at the depth below the local surface.mat.serpentinization_max_depth(150e3; it also ends the grid) andmat.wet_crust_fraction_threshold(0.5) (30).phasechanges.cxx,subduction_phase_changes()andbuild_phase_transition_rules();then the
VirtualTopologyoverview inmesh.hpp.antigorite_stable()bounds both serpentinite rules; its low-pressure point (650 °C, 0.2 GPa)replaces geoflac's (670 °C, 0.6 GPa) in the mantle rule. With geoflac's two fields, mantle at
642–730 °C between the low-pressure lines serpentinizes and dehydrates back at every call; one
field leaves no pressure and temperature where both rules apply.
update_mesh(): markers are added orremoved only there and in a remesh, which rebuilds it too, so the grid indexes every marker.
An assert stops the run (exit 60) if a marker count or id changes between rebuilds. A remesh
step builds the grid twice.
bc.has_winkler_bottom_restore, comes in a later release.It acts only when MMG returns a mesh the existing tangle check would stop on.
mem:managed, which everyopenacc=1build sets.Try it
phase_change_option = 0. With 3, a rule runs when itsmat.has_*switch is on; itssource and product must then name distinct, set materials, or the run stops at input (exit 11).
Serpentinization (on) needs
mat.mattype_serpentiniteandmat.mattype_sediment;eclogitization (on) needs
mat.mattype_oceanic_crustandmat.mattype_eclogite.mat.serpentinization_thickness(10 km): raise it for a thicker serpentinite band.mesh.vt_size(0.5 ×mesh.resolution): lower it for a sharper slab outline, while cells stillhold markers.
mat.serpentinization_wet_crust_margin(−1, off): set it ≥ 0 to drop false positives under aslab bend.
mat.phase_change_pressure_option(0, the element mean stress): set it to 1 for thereference lithostat that the phase diagrams use.
mat.serpentinization_max_depth(150 km) andmat.wet_crust_fraction_threshold(0.5):untuned.
examples/subduction.cfg(2D,usemmg=1; 5 Myr in about 22 min on 8 threads of aTACC Vista node); it lists every option-3 key, most with a comment.
Numerical effect
(16)); part 5 changes them as the review asked; part 6 keeps them bit-exact at the defaults of
its new keys. Runs without option 3 change only as Breaking lists; option 0 physics is bit-exact but for the 2D surface plastic strain of (19).
make ndims=2 usemmg=1, then inexamples/OMP_NUM_THREADS=8 ../dynearthsol2d subduction.cfg: frame 100 (5 Myr) holds 3292 serpentinite (material 4) and 392 eclogite(material 5) markers (g++ 14.2.0 on Vista). Another compiler gives other counts.
examples/subduction.cfgon 8 threadsof a TACC Vista node; (27) cut the time per step by 3 %. On a busy Apple M3 Max with 6 threads,
the same run takes 28 min 56 s, and phase changes take 4.43 %.
Cost per part, and the host
From the end-of-run time summary (also in the
.manifest): 5.78 M steps, 220 remeshes, 1294 s inall. Option 3 runs every 10 steps.
Host, from the run's
.manifest: a TACC Vista CPU node (NVIDIA Grace, Arm Neoverse-V2; Rocky Linux9.7; g++ 14.2.0), 8 OpenMP threads pinned to one 8-core block (
OMP_PLACES=cores,OMP_PROC_BIND=close); other runs used other cores of the node. (27): 214 → 207 µs per step, tworepeats each, on the same node.
The same run on an Apple M3 Max (12 performance + 4 efficiency cores; macOS 26.6; Apple clang 21),
6 OpenMP threads, not pinned: 28 min 56 s, 5.89 M steps, 279 µs per step. Phase changes took 4.43 %
(0.131 ms per call), remeshing 0.47 % (200 remeshes) and output 0.38 %. Other jobs shared the Mac
during the run: the 1-minute load average was 14–20 on its 16 cores (mean 16), so an idle M3 Max
runs faster. The step and remesh counts differ from Vista's because another compiler gives other
round-off.
Checks of the second review round (29)–(30)
ndims2/3,usemmg=1 hdf5=1, Apple clang; the tip also withusemmg=0examples/subduction.cfgto 1 Myr, pressure option 0 against 1 (Apple M3 Max, 6 threads)Not run for (29)–(30): g++, nvc++
openacc=1, and the 3D gate.Checks of the review revisions (18)–(28)
ndims2/3,usemmg=1 hdf5=1, Apple clang ((25) in 3D together with (26); (28) in 3D with g++ 14.2.0 only)test-tiny,test-topo,test-core,test-rect-tiny) bit-exact but at (19), which changes 2D surface plastic strain as intended (test-rect-tiny: two top elements;test-core); option-3 results re-pinned only where the commit intends a changeexamples/subduction.cfgto 5 Myr on Vista, against the commit beforeChecks of the first round (1)–(17)
ndims2/3,usemmg=1 hdf5=1, Apple clang and gcc 8.5openacc=1,ndims2/3test-rect-tinytest-rect-tinywithout option 3; a small subduction model with its grid checked against the markers in every checkpoint, restarts after a remesh, with the crust margin on and from a conversion step, rule checks, a hot case at the edge of the antigorite field, and gravity off to check the pressure source. 3D: grid checks and a restart across a remeshexamples/subduction.cfgto 100 and 500 kyrDemo: examples/subduction.cfg to 5 Myr (g++ 14.2.0 on Vista, 8 threads, 22 min)
The slab at 0-4 Myr: the mesh with each element filled by its main material, isotherms, the 3 km
VirtualTopology grid, its wet-crust cells (orange: more than half oceanic crust, sediment or
sedimentary rock) and the cells the 5 km search reaches. As the slab sinks to about 110 km, mantle
serpentinizes just above its wet-crust cells, along the slab top and in the wedge corner, and
eclogite forms at its tip.
At 5 Myr. Top left: the same map. Right: a zoom on the slab top with the markers, and its plastic
strain with the same isotherms. Bottom, through the run: the element count with each remesh's
change (the jump at 3.7 Myr is the remesh (28) repairs), the 220 remeshes by trigger (bottom drift
early, then low quality and tiny elements), and the serpentinite and eclogite markers.
Demo before the review revisions: examples/subduction.cfg to 5 Myr (Apple clang, 6 threads, 20 min)
The slab at 0-4 Myr: the mesh with each element filled by its main material, isotherms, the 3 km
VirtualTopology grid, its crust cells (orange: more than half oceanic crust or sediment) and the
cells the 5 km search reaches. As the slab sinks to about 110 km, mantle serpentinizes just above
its crust cells, along the slab top and in the wedge corner, and eclogite forms at its tip.
At 5 Myr. Top left: the same map. Right: a zoom on the slab top with the markers, and its plastic
strain with the same isotherms. Bottom, through the run: the element count with each remesh's
change, the 206 remeshes by trigger (bottom drift early, then low quality and tiny elements), and
the serpentinite and eclogite markers.
New or retired names
stops a restart with exit 22; a checkpoint without one still restarts, and a restart with another
grid cell size warns and rebuilds the grid); a
PhaseChg:line in the time summary andphase_change_*fields in the manifest (option 3). No new exit codes.--helpdrops option 2, the mid-ocean-ridgerule that Enable GPU acceleration and parallelization across Dynearthsol #25 removed, and marks its four keys unused; they are still accepted (1).
The 16 keys of option 3 with defaults, and the four unused keys
mat.mattype_serpentinite,mat.mattype_eclogite,mat.mattype_hydrous_mantle,mat.mattype_sediment_rock,mat.mattype_metasediment,mat.mattype_schist.mat.has_serpentinization(yes),mat.has_eclogitization(yes),mat.has_hydrous_mantle_dehydration(no),mat.has_sediment_metamorphism(no).mat.phase_change_pressure_option(0, the element mean stress; 1,ref_pressure()).mat.serpentinization_thickness(10e3 m),mat.serpentinization_wet_crust_margin(−1),mat.serpentinization_max_depth(150e3 m),mat.wet_crust_fraction_threshold(0.5),mesh.vt_size(0.5). It countsmat.mattype_oceanic_crust,mat.mattype_sedimentandmat.mattype_sediment_rock(when set) as wet crust.mat.mattype_depleted_mantle,mat.mattype_partial_melting_mantle,mat.mattype_mor_extrusion,mat.convert_rate_oceanic_crust.Checklist
ndims=2andndims=3:yes, every commit.
## [Unreleased]in CHANGELOG.md: yes (31).input.cxxand documented inexamples/defaults.cfg: yes.tests/functional/orbenchmarks-cores/: in the follow-up PR.AI disclosure
Claude Opus 5.5 co-authored every commit, ran the checks and subagent reviews, restructured the
history and drafted this text.
🤖 Generated with Claude Code