Repository navigation
Trap volatiles in the solid mantle behind the freezing front - #928
Conversation
Computes, from a completed run's helpfile, the volatile mass a crystallising mantle sequesters into the solid at an effective partition coefficient set by the trapped-melt fraction, under none, constant and dynamic modes. Post-processing only: no value it produces reaches PROTEUS state.
…apped melt fraction Adds solid-phase volatile trapping to the coupled loop (outgas.trap_mode, default 'none', which leaves every existing run bit-identical). Each step moves [(1 - F_tl) D_Z + F_tl] C_Z dM_RM of every dissolved species from the melt into the solid mantle (Sim, Hirschmann and Hier-Majumder 2024, Eq. 6), with C_Z the concentration of the previous chemistry solve and dM_RM the mass crystallised from the global melt fraction. F_tl comes from one of three sources: a constant, the published linear law (trap_tau_source = 'fixed'), or the drainage of a parcel crossing the freezing front resolved by the interior solver (trap_tau_source = 'aragog'): dphi/dt = -phi / max(L / w_D(phi), tau_s), F_vol = phi(L / v_f) with the solver's three-regime permeability, per-node gravity averaged over the front, and the front speed from the growth of the solid mantle. The disaggregation melt fraction is interior_energetics.rfront_loc throughout. Steps whose front cannot be integrated take the no-drainage bound, capped at rfront_loc, and log the cause. Trapped mass lives in the *_kg_solid reservoirs. The chemistry solve, escape and the desiccation gate see only the reachable inventory, and the per-element closure atm + liquid + solid = total is checked every step, to the chemistry solver's own tolerance.
…iagnostics The initialisation iterations advance the clock but not the mantle, and the time is reset to zero after each of them, so the melt-fraction change across one is not a crystallisation step. run_trapping takes a keyword-only init_stage and returns before burying anything while it is set, and the main loop passes Proteus.init_stage. Three helpfile columns record the drainage diagnostics of a step: trap_n_exited (mush nodes that reached the porosity floor), trap_frac_bound (the share of their mass trapped at the no-drainage bound) and trap_n_substeps (drainage sub-steps). The front scheme leaves them at 0, NaN and 0. The golden trajectory gains the three constant-zero columns and is otherwise unchanged.
…d reservoir
Trapped mass shared the *_kg_solid columns with the chemistry backend, which
writes its own condensate there on every solve: zero for CALLIOPE and the
dummy backend, graphite for atmodeller. Three faults followed.
- atmodeller's graphite was taken for trapped mass: it was withheld from the
carbon budget of the next solve and restored as the larger of the old and
new solid, so the carbon reservoirs fell short of the total and the
per-element closure check aborted the run, with trapping on or off.
- Species totals were put back to their pre-solve values after every solve,
which froze them from the first trapped step onward.
- Desiccation zeroed the solid reservoirs but not the escape-owned totals, so
a desiccated planet with trapped mass, or with any noble gas, failed the
closure check.
Trapped mass now lives in {sp}_kg_trapped and {e}_kg_trapped, which escape
and the desiccation gate read as the locked mass. It is still counted in
*_kg_solid, so atm + liquid + solid = total holds, and the rest of *_kg_solid
belongs to the backend. trapped_mass_withheld takes the trapped mass out of
the element totals and the solid columns for the duration of a solve and puts
it back afterwards, also when the solve raises. Species totals are rebuilt as
atm + liquid + solid instead of being restored. Desiccation keeps the trapped
mass and empties the totals of the volatile and noble elements left with
nothing.
Zalmoxis takes the dissolved H2 target as the species total less the
atmospheric and solid reservoirs, since the total now includes trapped H2.
The output-reference tools learn registry loops over vol_list and
vol_element_list, and the golden trajectory gains sixteen constant-zero
*_kg_trapped columns and is otherwise unchanged.
porosity_from_densities floored the phase-boundary contrast rho_s - rho_l at +1e-6, which flips its sign wherever the tabulated melt is the denser phase. The interior solver builds the mush density as the two-phase mixture of the same end members, so a mush there read as porosity 0 at any melt fraction. Below about 5 GPa the phase-boundary tables make the melt the denser phase, so the top of the front stopped at the last node with lighter melt, the entry porosity came from that node, and neither the dense-melt guard nor the front-at-surface guard could fire. Inside the mush the lever rule now keeps the sign of the contrast, which gives the melt volume fraction exactly whichever phase is denser. On the snapshots of a 1 M_E, 1.5 AU Aragog run it matches phi_s * rho / rho_l to 1e-14 at all 19073 mush node samples, 817 of them with dense melt. Fully solid and fully molten nodes take their melt fraction, 0 or 1: the solver evaluates their density on the single-phase table at their own entropy, where the lever rule does not hold, and in the same run it would read some fully molten nodes as porosity 0. Where the two densities are equal the melt fraction is used as well, since mass and volume fractions coincide there. A mush that reaches the dense shallow mantle now takes the guard branch at the entry porosity of its top node instead of integrating a front cut short. In that run this applies to 349 of the 409 dynamic steps, which carried 65% of the buried mass.
# Conflicts: # tests/integration/golden_run.tsv
outgas.trap_mode now takes 'none' or 'front'. The front scheme takes F_tl from the drainage of pore melt across the freezing front the interior solver resolves; the constant fraction and the fraction from the secular cooling rate (Sim et al. 2024 Eq. 7) are removed, together with the fields they needed (trap_F_tl, trap_tau, trap_delta_T, trap_tau_source), the derivation of DeltaT from the melting curves, the dT/dt path, and the trap_dT_dt and trap_delta_T helpfile columns. The Eq. 6 bracket, the partition coefficients and the reservoir bookkeeping are unchanged. A step on which the interior profiles are unavailable used to fall back to the published law. It now buries at the crystal partition coefficients alone (F_tl = 0) on branch 6 and logs why. The offline calculator for the published law leaves the branch with it. The golden trajectory loses the two constant-zero columns and is otherwise unchanged.
The front scheme locates the freezing front on the melt-fraction, density and pressure profiles only the Aragog interior exports. With any other interior it could never find a front, so a config that sets outgas.trap_mode = "front" with another interior_energetics.module is now rejected at load, with a message naming both settings.
…with A step on which the global melt fraction rises remelts part of the solid mantle. It used to be clamped to zero, which left the trapped reservoir untouched however much of the mantle remelted. The flux is now computed as for a crystallising step, [(1 - F_tl) D_Z + F_tl] C_Z |dM_RM| at the same C_Z and F_tl, and applied in reverse, from the trapped reservoir back into the melt, capped at what trapping holds of each species. A freeze followed by the matching remelt returns every reservoir to its start. The front speed now comes from the same crystallised mass dM_RM the flux uses, instead of the change in M_mantle_solid. The two could disagree (after a structure re-solve, or a released temperature clamp), and a step on which the solid mass shrank while the melt fraction fell was buried at the no-drainage bound. trapped_mass is the one implementation of the capped bracket, for both directions, and reports whether the cap bound; run_trapping calls it instead of repeating the product inline. trap_dM_RM and trap_kg_step are negative on remelting steps.
Under planet.fO2_source = "user_constant" the chemistry writes O_kg_total itself, as its atmosphere plus melt, and trapping left that total alone. The trapped oxygen then sat outside the total while escape and the desiccation gate still subtracted it as locked mass: the reachable oxygen was undercounted by the trapped share, the escape floor could raise O_kg_total to the trapped mass, and _kg_solid meant part of the total for H and C but extra to it for O. Oxygen is now withheld and restored like every other element. The solve sees the same reachable oxygen as before and writes its own total, and the trapped oxygen is added back to it, so O_kg_total is the whole planet's oxygen under either fO2 source, and desiccation keeps it the same way. The per-element closure check still skips oxygen under the buffer.
The permeability cross-check re-typed the interior solver's law and compared the module against that copy, so a change to the solver would have passed. It now calls aragog's EntropyPhaseEvaluator.relative_velocity on a stand-in mush and backs the mobility out of it, across all three regimes and both blends, with the tolerance set by the solver's soft porosity clip. It also pins the drainage velocity as the solver's velocity times (1 - phi). The Table 2 test compared the trapped fractions to 0.015, more than the smallest entries themselves, and discarded the timescale columns. With the (1 - phi) factor removed, as in the note, the integral now reproduces every column to the table's printed precision, and the departure the factor makes is pinned separately: a percolation time longer by exactly 1 / (1 - phi_c) and a retained fraction never lower. The matrix-limited case is pinned to its closed form phi_c exp(-t_res / tau_s). The Eq. 6 bracket test carries the reference marker now that the DeltaT test is gone, with the bracket's analytic limits added, and trapping.py and compaction.py get validation pages.
input/example.grid.toml had been switched to use_slurm = true and max_jobs = 100 for local runs on a cluster. The documented example runs without SLURM, so it is back to main's use_slurm = false and max_jobs = 10.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #928 +/- ##
==========================================
+ Coverage 94.06% 94.52% +0.45%
==========================================
Files 132 135 +3
Lines 21681 22284 +603
Branches 3644 3745 +101
==========================================
+ Hits 20395 21063 +668
+ Misses 888 875 -13
+ Partials 398 346 -52
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
timlichtenberg
left a comment
There was a problem hiding this comment.
Thanks, this is a substantial piece of work, and the bookkeeping closes in a real coupled run with the front engaged. The global front scheme is fine for this PR; per node can follow later. Five things need fixing before this can go in.
Make remelting the reverse of burial. Release is sized from the current melt concentration C_Z, not from what was buried (trapping.py:785-799). Freeze from Phi 1 to 0.5, degas the melt by 100x, remelt to Phi 1: 98 % of the buried water stays in H2O_kg_trapped with no solid mantle left; the kg_liquid <= 0 and melt_mass <= 0 exits release nothing. Size the release from the trapped reservoir (or a burial ledger) and add a test where Phi returns to 1 after C_Z has changed.
trap_mode = 'none' must not change behaviour. run_desiccated now calls keep_only_trapped_mass unconditionally, so on desiccation every H, C, N, S and noble _kg_total becomes 0 where main keeps them. Reproduced on both trees with the same row.
Remove the dt dependence of F_tl. The Courant guard switches to the no-drainage bound when v_f dt / L > 1, and F_tl then rises by about 50 % for a longer step with the same front. t_res = L / v_f does not depend on dt, so the drainage integral stays valid there.
Keep existing runs resumable. A helpfile written by main raises HelpfileSchemaDriftError on this branch, also with trap_mode = 'none'. Give the 32 new columns a default of 0 on read.
Drop the references to the compaction note. Nobody reading this PR can open it. The description, docs/Validation/outgas/compaction.md and test_compaction.py:261-290 should state the equations and expected numbers themselves, and the test should pin the model as shipped, not a variant with the (1 - phi) factor patched out.
Also: noble gases dissolved in the melt are never buried (run_trapping loops over vol_list only), and 21 inline comment blocks are over the 3-line cap.
A remelting step now returns the share of every trapped species and element that sits in the remelted solid, (Phi(t) - Phi(t-1)) / (1 - Phi(t-1)), taken from the trapped reservoir rather than sized from the melt concentration. A mantle that freezes, degasses and then remelts completely gets back all it buried, also when no solid or melt is left to size a flux from. Remelting steps report branch 7. The drained fraction no longer switches to the no-drainage bound when the front advances more than its own thickness in one step: t_res = L / v_f is set by the front speed, not the step length. The advance is still reported in trap_front_courant. Noble gases dissolved in the melt are buried with the interstitial melt at F_tl C_Z dM_RM, withheld from and restored to the chemistry like the other elements, and recorded in new He/Ne/Ar/Kr/Xe_kg_trapped columns (zero in the golden run, which does not trap).
With trapping off, desiccation keeps every element total again instead of emptying the ones with nothing trapped, and the per-element closure check is not asked for: assert_mass_conservation gains check_element_closure, off by default, which the main loop sets only when trapping is on. A helpfile written before the trapping columns existed resumes. The 37 trap_* and *_kg_trapped columns are left out of the core keys and filled with zeros on read, the value they hold in a run that never trapped, with a line in the log; any other missing column is still refused.
The five-case martian front test now checks the model with its (1 - phi) matrix fraction instead of a variant with that factor removed, and states its setup and expected values itself: a 60 km front entered at phi_c = 0.3, drho = 330 kg/m3, g = 3.711 m/s2, crossed at 70 and 5 cm/yr. The timescales are checked against their closed forms. The retained fractions are checked against t_res = int max(tau_D, tau_s) dphi / phi, evaluated by quadrature and independent of the ODE solver, to 1e-6, and against the series solution in the Rumpf-Gupte regime to 1e-5, and pinned at 1e-4 (0.27165 and 0.18134 down to 0.0094342 and 0.0025379). Dropping (1 - phi) from the Darcy speed moves the percolation-limited fractions by more than 0.007.
Comment-only change. The longer rationale these blocks carried is either in the docstrings they sit under or no longer needed after the changes above.
|
Thanks Tim, all seven points are addressed in the four commits just pushed. The PR description is updated to match.
The Earth pair was not re-run: none of the changed paths occurs in it. |
With trapping on, desiccation emptied the melt and set each total to its trapped mass. A desiccated planet that remelted in steps each releasing less than mass_thresh never cleared the flag, so every step's release was deleted without escape booking it: 1000 steps of dPhi = 2e-4 from Phi = 0.30 to 0.50 lost 1.03e19 kg of water that 4 steps of 0.05 kept. Desiccation now changes no total, as without trapping. It empties the atmosphere; each species and element keeps its trapped mass in the solid and the rest of its total in the melt (hold_in_mantle). Released mass stays in the melt and the totals until check_desiccation finds it above mass_thresh, whatever the step length, and the remainder below the threshold at the onset of desiccation is kept too instead of deleted.
The escape step emptied an element to its locked mass whenever the reachable remainder fell below mass_thresh. After a desiccated planet's flag cleared, the hydrogen a slow remelt released each step sat below the threshold, so every step deleted it again, even at an escape rate of zero. The floor now applies, as on main, when the whole-planet total falls below mass_thresh, and still never takes the total below the locked mass; the desiccation gate already reads the reachable inventory. A new main-loop test runs the dummy configuration through a desiccated planet remelting from Phi = 0.30 to 0.50 in 8 steps that each release less than mass_thresh. The totals hold on every row and the run ends with the trapped 5/7 that 4 steps of 0.05 reach. It fails with the old desiccation rule, with the old escape floor, and without the desiccation re-check in the loop.
The closure skipped any element whose total and reservoir sum were both below mass_thresh, a user setting, so a break below it passed unseen. It now refuses a mismatch once it exceeds both 1 kg and the relative tolerance times the total: the trace melt mass the chemistry leaves of an element the run does not carry, a few hundredths of a kilogram, passes, and a 2 kg break on a dust total or a 1e15 kg break on a 5e14 kg one does not.
|
Thanks Tim! The slow remelt is fixed and tested through the main loop, and I took your suggestion for the closure tolerance. Main is merged up to #947, and the PR description is updated. Slow remelt after desiccation (162350c). With trapping on, desiccation now changes no total, the same as with trapping off. It empties the atmosphere, keeps each species' and element's trapped mass in the solid, and holds the rest of the total in the melt.
A second deletion of the same kind (d82096a). Writing the main-loop test turned up another one, in the escape step. Its desiccation floor emptied an element to its locked mass whenever the reachable remainder fell below Main-loop test (d82096a).
Closure tolerance (f5e736b). The check no longer uses |
There was a problem hiding this comment.
Thanks Imre, both points are fixed: the slow remelt no longer depends on the step length, and the closure check no longer depends on mass_thresh.
Edit, 4 Oct: #952 and #953 are on main now and take work off this PR, so I rewrote the rest of this comment. Please merge main first; it conflicts in escape/wrapper.py, outgas/wrapper.py, output.md, golden_run.tsv and test_proteus.py.
What main does for you now
- The closure stop on a crystallized planet (3e16 kg of N,
freeze_volatiles, trapping on) came from main: the floor zeroed a total whose atmosphere column still held mass. #953 keeps the floor off on a frozen mantle, so there is nothing to fix here anymore. Please run that case again after the merge. - Desiccation keeps the
*_kg_solidcolumns, sets each total to its solid share and books the removed mass in a new columnM_desiccated.check_desiccationleaves that column out of the loss, so a resume gives the same verdict. Trapped mass survives desiccation without extra handling in the trapping code. - Aragog 26.10.03 (#952) has
mobility_functioninaragog.eosand writesporosity_b,rho_solid_b,rho_melt_bandg_bat the basic nodes. The transcribed permeability law incompaction.pyand the read through_lookup_at_phase_boundaryintrapping.pycan go.
Still open: the escape floor depends on the trapped mass. On a molten mantle the floor still acts. Two runs that differ only in D_const_N2: with 5e14 kg of N trapped, the floor deletes 1.2e16 kg of reachable N in one step, not booked as escape; with 1.1e16 kg trapped, the total never falls below mass_thresh and the remainder is kept. check_desiccation tests the reachable mass, so the two disagree on what depleted means. Use the reachable mass in both.
The (1 - phi) in darcy_velocity stays, but the reason in the compaction.py docstring is not right. Both forms are the melt velocity relative to the matrix. McKenzie (1984, Eq. 3.7) takes the buoyancy against the mixture, (1 - phi) (rho_s - rho_f) g, and Abe (1995, Eq. 40) takes it against the solid. Please reword that bullet and state the range: McKenzie's solution assumes touching grains, so melt fractions below about 0.2, and the front enters at rfront_loc = 0.4 by default. FormingWorlds/aragog#67 tracks the same factor for Aragog.
Two comments still state the opposite of the code (escape/wrapper.py 607, outgas/wrapper.py 335).
I can send the configs of the runs.
Brings in the frozen-mantle escape floor and the booked desiccation (#953), fwl-aragog 26.10.03 (#952), the impact and escape bookkeeping fixes (#950, #951), the shared install-script library (#864) and the full-precision helpfile. Desiccation now follows #953: the atmosphere is emptied and booked in M_desiccated, and each total becomes the share its mantle holds. With trapping on, the melt is kept as well as the solid, so that mass a remelt returns stays in the planet however many steps the remelt takes; that replaces the branch's own hold_in_mantle. The escape floor keeps main's frozen-mantle switch and floors at the locked mass. The chemistry stage passes the new initial flag to the dummy backend, the escape module imports the trapping helpers where it uses them to avoid an import cycle, and the Aragog cross-check builds its stand-in on the evaluator so the new phase-boundary lookup runs. The helpfile registry and golden trajectory keep both sides' columns, and the output reference is regenerated.
…supplies On a molten mantle the escape floor emptied an element once its whole total fell below mass_thresh, while the desiccation gate tests the reachable mass, the total less what trapping locks in the solid. Two runs differing only in D_const_N2 then disagreed: with 5e14 kg of N trapped the floor deleted the reachable N, with 1.1e16 kg trapped the total stayed above the threshold and the remainder was kept. The floor now reads the reachable mass, as the gate does, and empties it down to the locked mass. On a step on which a remelt returned buried mass to the melt, the element is being resupplied, not depleted, and flooring the small release every step would make a slow remelt keep less than a fast one. run_escape turns the floor off on such a step, as it does on a frozen mantle (remelt_returned_mass reads trap_branch and trap_kg_step).
…1 - phi) Aragog 26.10.03 exposes its three-regime permeability as aragog.eos.mobility_function, so compaction.mobility_function now calls it instead of a transcription, keeping only the grain-size check, and imports it where it is used because the solver package takes seconds to load. The docstring gave the wrong reason for the (1 - phi) in darcy_velocity. Both forms are the melt velocity relative to the matrix: McKenzie (1984, Eq. 3.7) takes the buoyancy against the mixture, (1 - phi) (rho_s - rho_f) g, and Abe (1995, Eq. 40), which the solver follows, against the solid. McKenzie's solution assumes touching grains, so melt fractions below about 0.2, while the front is entered at rfront_loc, 0.4 by default; the docstring now says so and points to FormingWorlds/aragog#67, which tracks the same factor.
Aragog 26.10.03 writes the phase-boundary densities and the gravity at the basic nodes. The aragog interior now stores them with the basic-node melt fraction and mixture density, and the trapping step locates and drains the front on that mesh. That retires the reads of solver internals: the private _lookup_at_phase_boundary, phase_staggered._g and the structure profile the solver was built from. The lowest basic node is the core-mantle boundary, so a front still porous there needs no separate floor. The porosity stays the lever rule on the solver's densities with the sign of the contrast kept. Aragog's porosity_b is the same rule with a soft clip where the melt is the lighter phase, which a new cross-check pins, but reads about 0 where the melt is denser, as its docstring warns, and is not the melt fraction at a single-phase node; the Earth-analogue front held dense melt on 257 steps. An interior without usable basic-node profiles buries at the crystal partition coefficients alone, and one without usable gravity drains under the surface value, each with a warning.
…alidation page The page now lists the escape floor test on two runs that differ only in the trapped N, and states what desiccation keeps with trapping on.
|
Thanks Tim. Main is merged (up to #954), and all your points are in. The PR description is updated. Merge with #952 and #953 (18fb224).
Crystallised N case, after the merge. I reproduced the path with the dummy modules, a trapping run and a
On 37 frozen steps the N total is below Escape floor on the reachable mass (3179257). The floor now tests the total less what trapping locks, which is what
Aragog 26.10.03 (727b4d6, 4592d2f).
(1 - phi) (727b4d6). The docstring bullet now says what you wrote: both forms are the velocity relative to the matrix, with McKenzie (1984, Eq. 3.7) taking the buoyancy against the mixture and Abe (1995, Eq. 40) against the solid. It states that McKenzie's solution holds below about 0.2 while the front enters at Comments. Both are rewritten. |
timlichtenberg
left a comment
There was a problem hiding this comment.
Thanks Imre, this round closes most of it: the slow remelt after desiccation, the floor on the reachable mass and the drainage on Aragog's exports all hold, also in runs. I hope the point below is the last change before this can go in.
A frozen-mantle trapping run with escape still stops, now on the closure check. With the R3 setup from my last review the run gets past the floor, but raises in the per-element closure check at about 2.7e4 yr.
The mismatch behind it is tiny and already on main: escape debits each element total by its share of the escaping mass, while run_crystallized scales the atmosphere by (M_atm - esc)/M_atm, and M_atm is 1.0000177 times the sum of the element masses in the atmosphere. That leaves about 1.8e-5 of each step's N loss unaccounted, which is harmless in itself, but as escape draws the N total down the relative gap keeps growing and crosses the 1e-4 limit, so any long enough run of this kind ends in the RuntimeError. With trapping off the same drift runs on, because the check is skipped.
Please make the two debits remove the same mass, for example by debiting each element's atmosphere by what escape took from its total, or let the check allow for that drift, and add a frozen-mantle escape case over enough steps to the main-loop test.
For the next rounds, and future PRs that touch the volatile bookkeeping: please run a few coupled test cases to the end before pushing, including a frozen mantle with escape over a long stretch. Most of the points in the last rounds only showed up after many steps, and a full run catches them much earlier than the unit tests can.
On the reply: the main-loop test passes with the whole-total floor in my run; only the unit test fails without the switch.
Brings in the prescribed orbital migration (#937), the CALLIOPE cold-start seed (#958), the fwl-io dataset fetch and stellar spectra changes (#960, #964, #967, #968), the PETSc download (#965), Zalmoxis Anderson acceleration with fwl-aragog 26.10.08 (#971) and the CI changes (#955, #970). The schema invariant tests keep both the trapping and the migration validators, and the golden trajectory is main's re-recorded one with the trapping columns added.
On a frozen mantle, escape debits each element total by its share of the escaping mass, its atmospheric mass over the sum of the element masses in the atmosphere, while run_crystallized scaled the atmosphere by (M_atm - esc) / M_atm. M_atm is the sum of the species masses, which with CALLIOPE's molar masses is 1.0000177 times the element sum, so each step left 1.8e-5 of every element's loss in its atmosphere. As escape drew a total down towards what the solid holds, that gap grew past the 1e-4 closure limit, and a long frozen-mantle trapping run with escape stopped on the closure check; with trapping off the same drift ran on unchecked. run_crystallized now takes the retained fraction over the element masses in the atmosphere, so each element's atmosphere loses exactly what escape took from its total, and scales the species columns and pressures by the same fraction, which keeps the composition and the ratio of M_atm to the element sum. A row that records no element atmosphere keeps the species sum. A unit test runs 60 frozen escape steps at the CALLIOPE ratio, and a new main-loop test freezes the dummy mantle and escapes from it for 20 steps with trapping on; under the old scaling the loop stops on the closure check for H at a relative difference of 1.07e-4.
…e-total one The test never put an element below mass_thresh while its total stayed above it, the one case where the two floors differ, so it passed with the escape floor on the whole total. The desiccated planet now also holds 1.5e18 kg of trapped N2. The remelt returns 2/7 of it, 4.3e17 kg, below the 7e17 kg threshold: the release is kept while the remelt lasts, and once it stops the floor reads that reachable N as depleted, as the desiccation gate does, and empties it to the locked 5/7, which a whole-total floor would not.
Brings in fwl-aragog 26.10.10 (#978), the restart of a failed Aragog call from its start state (#979) and the Aragog core evolution model (#980). test_aragog.py keeps the basic-node profile test next to main's core-module tests, and the golden trajectory is main's with the 37 constant-zero trapping columns added. The trapping step still reads the basic-node fields from _store_profiles, which #979 now calls only after a call that returned.
|
Thanks Tim. The frozen-mantle drift is fixed. Two coupled pairs ran to the end before the first push; a third was still running then. I'll do the same before every push that touches the volatile bookkeeping. The PR description is updated. Frozen-mantle escape (42be63c). I took your first option: the two debits now remove the same mass.
Tests:
Correction to my last reply. You're right: the main-loop test passed with the whole-total floor, and my claim that it failed was wrong. My mutation harness copied only
Coupled runs. All at 63a808e on the Earth analogue from the description, each with a
Environment for these runs. The cluster moved to AlmaLinux 9.8 this week.
Happy to run your R3 config directly if you send it. |
…993) * Bump the AGNI pin to restore the data fetch in CI (#982) Move the AGNI pin from 2180f489 to 21691545 and regenerate docs/Reference/module_versions.md. The new commit downloads each Zenodo record file by file in src/get_data.sh, because Zenodo no longer serves a whole record as one zip; it differs from the previous pin in that one file only. The CI set-up step runs get_data.sh from the pinned checkout, so with the old pin no job that installs AGNI reached its tests. * Stop the helpfile warning about the M_int_target key (#981) With the Zalmoxis structure, ExtendHelpfile warned on every row whose loop had a structure solve that the key M_int_target is not declared in GetHelpfileKeys(). The key is the mass target of that solve, and the mass-anchor check reads it directly after. It is not kept from one row to the next, because the main loop builds each row from the helpfile table, which has the schema columns only, and each solve writes the key again. So the warning pointed at nothing that is lost. M_int_target is now on the allowlist of non-schema keys, which moves to module level as _ALLOWED_NON_SCHEMA_KEYS in utils/coupler.py. No helpfile column is added and no numeric path changes. The test of the warning has all three allowlisted keys in its row, plus a key that extends an allowlisted name, and compares the listed keys exactly. A new static test checks that every literal hf_row[...] key written under interior_struct/ is a helpfile column, an allowlisted key or private, that each allowlisted key has a writer, and that none of them is a column. * Add the Roche et al. (2026) impact-loss law as the default (#975) The accretion module can take the atmospheric loss of a giant impact from the Roche et al. (2026) law as well as from Kegerreis et al. (2020), both evaluated in ZEPHYRUS 26.10.10. The new setting accretion.atmloss_law selects the law when atmloss_module = "zephyrus". Its default is "roche2026", so a config with atmloss_module = "zephyrus" and no atmloss_law switches from the Kegerreis to the Roche law; atmloss_law = "kegerreis2020" keeps the old behaviour, which is identical to before. The Roche law takes the target mass without its atmosphere, with the atmosphere mass fraction from the PROTEUS planet; masses, radii, impact parameter and contact speed come from the impact event. The atmosphere fraction counts the rock-vapour elements, but only the volatile elements are stripped. Each impact logs the law, the atmosphere fraction, the speed ratio, gamma and the loss fraction, and one warning per impact names each parameter that is outside the fitted range of the law or at a clamp. The loss helpers move from accretion/wrapper.py to accretion/atmloss.py. The floor of fwl-zephyrus goes to 26.10.10. The config reference, the validation page of the accretion wrapper and the coupling-loop page describe both laws and the caveat that both are fitted on H2-He atmospheres. Closes #407. * Set the default oxygen fugacity to IW+2 (#984) The default of outgas.fO2_shift_IW goes from 4.0 to 2.0, two log units above the iron-wüstite buffer. input/all_options.toml, input/minimal.toml and the Earth and Venus tutorial configs take the same value, so the default, the reference config and the tutorials agree. A config that does not set the key now runs at IW+2; a config that sets the key runs as before. The first atmosphere of a run at IW+2 is much thinner than at IW+4, because the O2 partial pressure follows the buffer. The CHILI intercomparison keeps its protocol value IW+4. tools/chili_generate.py sets the redox state of all 5 cases from one constant, PROTOCOL_FO2_SHIFT_IW, and refuses a case that lists a value of its own; the 7 files that it writes under input/chili/intercomp/ are unchanged. The 9 grid configs under input/tutorials/chili_grid/ and input/planets/toi561b.toml keep the value 4. The generator renders all 7 files before it writes any of them, so a refused case leaves the committed files as they are. The CHILI tutorial page runs input/chili/intercomp/earth.toml and venus.toml in place of the tutorial configs, and tools/plot_chili_comparison.py names output/chili_earth/ and output/chili_venus/ in its help text. The Earth analogue tutorial page still describes the IW+4 run. * Draw AGNI aerosols in the colour and name of their gas (#985) In a run with AGNI, the log had "Using fallback colour" for fe, feo, mgo, nh3, sio and sio2 each time the atmosphere chemistry plot was made, and the lower panel of that plot drew these 6 aerosols in magenta with lower-case names. AGNI writes the aerosol names of an atmosphere file in lower case, and the colour presets are keyed by the gas name. The plot now matches each aerosol to the gas of the same file whose name is equal apart from case, takes the colour of that gas, and writes the gas formula in the legend. An aerosol with no such gas, or with 2 gases that differ only in case, keeps its own name. The colour lookup and the file reader are unchanged, and no model result changes. * Set the core mass once from the kept volatiles and keep it (#986) With the Zalmoxis structure, the core mass was core_frac of the structure target at every structure solve. The structure target leaves out every volatile inventory, and with planet.fO2_source = "user_constant" that includes the oxygen inventory that the outgassing derives from the fO2 buffer at each call. This inventory is large over a hot magma ocean and falls as the surface cools, so the core mass of the structure followed it, although nothing moves mass through the core-mantle boundary. The core mass target is now core_frac of the planet mass without the kept volatiles: H, C, N, S and the noble gases, and oxygen only when it is an input of the outgassing (planet.fO2_source = "from_O_budget", or outgas.module = "dummy"). Oxygen that the buffer derives is mantle mass, like rock vapour. With zalmoxis.dry_mantle = false only the atmosphere part of the kept inventories is taken off. Each structure solve at Time = 0 computes the target from its row; the target is set when the init stage ends, stored in the new helpfile column M_core_target, and read by every later solve. A giant impact adds core_frac of the impactor rock to it. Zalmoxis gets the fraction M_core_target / structure target, and the run stops with an error when the target leaves no mantle, or no ice layer in a structure that has one. A run that resumes from a helpfile without the column continues on core_frac of the structure target of its row, logs one warning, and keeps that core mass. A stored target that is NaN, infinite or negative stops the run. For 1 Earth mass, core_frac = 0.325 and a dry mantle, the core mass target rises by 3.7 % at fO2_shift_IW = 4 and by 0.10 % at fO2_shift_IW = 2. The helpfile column M_core is the mass inside the first grid node at or above the boundary, so it stays above the target by a part of one mantle shell. The interior config reference describes the rule and the output reference lists the new column. * Make a resumed run equal the uninterrupted run (#987) A Zalmoxis + Aragog run solves the structure after the interior step of an iteration, and Aragog takes the new mesh at the start of the next iteration, where it records the compression work of the update and carries the entropy to the new mesh by mass parcel. A run that stopped between the two left an interior snapshot on the earlier structure and a helpfile row on the new one. The resumed process built Aragog on the structure of the row and never made that update, so the resumed run differed from the uninterrupted run from its first row. On resume, aragog_resume.pending_structure_update now finds the earlier row and a mesh file of the earlier structure: the copy saved with that row, zalmoxis_output.dat.prev, or the copy saved with an older row on the same structure. Aragog is built on the earlier structure, the snapshot is restored, and the update is replayed through the helper that the running loop uses. The replay runs for core_bc = "energy_balance". With another core boundary condition, with a giant impact between the snapshot and the row, or with no mesh file of the earlier structure left, the run continues on the structure of its row as before, and one warning says so. A snapshot whose entropy does not fit the solver stops the resume with an error. The radiogenic heat F_radio of a row is now sampled at the end of the step, the time of the other quantities of the row. It was sampled at the start of the step plus the length of the previous step. This is the only change for an uninterrupted run: the decay constant times the shift of the sample time, 5e-8 at most with K, U and Th in a run of 11 rows, and larger for a short-lived isotope such as Al-26. The troubleshooting page has a section on when a resumed run equals the uninterrupted one, and the error index lists the two warnings. * Take the neutral plot colours from the active matplotlib style (#988) The docs show the Earth and the orbit tutorial figures in a light and a dark variant. With a dark matplotlib style, plot_global, plot_fluxes_global and the orbit plots gave a figure that could not be read, because they fixed some neutral colours: a black total pressure line, a dark grey for the surface temperature, net flux and melt fraction curves, and white boxes behind the panel labels. These artists now take their colour from the style when the plot is drawn. The new helper style_neutral(fade) in utils/plot.py mixes the text colour of the style towards its axes face colour; a transparent axes face stands for the figure face colour. The 3 plot files use it for the lines, labels, label boxes, legend frame and grid that were fixed before. With the default style every colour is the same as before. Species and module colours are not touched, and no model result changes. * Install module clones at their release tags and keep existing ones (#989) New clones of the Python modules (MORS, JANUS, CALLIOPE, ZEPHYRUS, Aragog, Zalmoxis, VULCAN, Morrigan) start at the release tag of their minimum version in pyproject.toml, so an install holds the module code that PROTEUS names. tools/get_python_module.sh clones the four modules that had no setup script. Aragog and Zalmoxis still take the branch named like the PROTEUS feature branch when the module has one. The setup scripts leave a module folder that is already there as it is: they print its state, install it editable and, for a clone behind its tag, print the commands that move it by hand. Only --force replaces a folder, through a temporary clone, and a failed step or a signal leaves the old folder in place. proteus doctor runs nothing in these clones. A module below its minimum version gets advice, and a folder that is not a git clone of its own gets no git command. The essential data set of install.sh includes the PHOENIX grid of the default star. tools/check_floor_tags.sh checks that each minimum version has exactly one tag on its remote. tools/_get_common.sh unsets the GIT_DIR family of variables for every script that sources it. * Keep the mole columns in step when trapping moves mass A burial or a release changes the mass of a species in the melt and in the solid. Its mole columns now follow in the same step. Before, only the next chemistry solve wrote them, so in a frozen or desiccated mantle, where no solve follows, the mole columns of the solid and the melt kept the values of the last solve while the masses moved. * Remove three trapping columns that no code computes and correct four texts trap_n_exited, trap_frac_bound and trap_n_substeps held a constant in every row: nothing in the trapping step computes them. They leave the helpfile keys, the output reference and the recorded trajectory, and the unused branch code 4 leaves compaction.py. The trajectory is recorded again; its other 926 columns are unchanged. The description of trap_branch says that a step with code 0 can still bury mass by crystal partitioning. The mass_thresh text says that escape empties an element by its reachable mass, down to the trapped mass. The validation page states what the 16-step remelt run through the main loop ends with. The trapping step takes the length of the year from utils.constants. * Register the mole columns that the trapping move writes The output reference lists the trapping step as a writer of the mole columns of the melt and the solid. The test of the initial stage asserts the trapped melt fraction in place of the removed columns. * Shorten four spots in the outgassing code ledger_total is one sum, the frozen-mantle rescale takes its fallback with or, the trapping names come in one import in wrapper.py, and atmodeller.py imports the stoichiometry table under its module name. No behaviour changes. * Assert the mole columns through the main loop The 16-step remelt run through the main loop asserts that the moles of H2O and N2 in the melt and in the solid equal mass over molar mass on every row after the row that the test writes. The docstring of the move states that it covers the melt and the solid columns and leaves the mole total as it is. The compaction tests take the length of the year from utils.constants.
timlichtenberg
left a comment
There was a problem hiding this comment.
Thanks Imre. The frozen-mantle drift is closed, the mole columns follow the masses, the three unused columns are gone and main is merged. Approved; please merge only once all checks on this head are green.
Main's #928 added a trapping stage to [IT_TIMING], which the known-name check rejected.
Description
Adds solid-phase volatile trapping during mantle crystallisation. The trapped
melt fraction comes from the drainage of pore melt across the freezing front
that the Aragog interior resolves. Relates to #519.
outgas.trap_modetakes'none'(default) or'front'. With'none'thetrapping step does not run, desiccation and the escape floor act as on main,
and the per-element closure check is not applied. One fix applies with
trapping off as well: a frozen mantle's atmosphere is now debited by what
escape took from each element total (see Bookkeeping), where main left
1.8e-5 of every step's loss in the atmosphere with CALLIOPE. The golden
trajectory differs from main only by new constant-zero columns, and a
helpfile written by main resumes on this branch.
Physics
Mass balance. Each crystallisation step moves
[(1 - F_tl) D_Z + F_tl] C_Z dM_RMof every dissolved species from the meltinto the solid mantle (Sim, Hirschmann & Hier-Majumder 2024, Eq. 6). Here:
C_Zis the concentration from the previous chemistry solve, taken on themelt it dissolved into;
dM_RM = M_mantle [Phi(t-1) - Phi(t)];D_Zcomes fromoutgas.D_const_<species>. Water is 0.0017 and everyother species is 0, so the others are buried through the interstitial melt
alone. That includes the noble gases dissolved in the melt, at
F_tl C_Z dM_RM.The step is capped at what the melt holds.
Trapped melt fraction. The front runs from the node where the solver's
melt fraction crosses
interior_energetics.rfront_locdown to the porosityfloor
trap_phi_min. A parcel enters it at the top at porosityphi_top.Over the residence time
t_res = L / v_fit followsdphi/dt = -phi / max(L / w_D(phi), tau_s), and it keepsphi(t_res),where:
w_D = (1 - phi) |drho| g F(phi) / eta_meltis the Darcy speed.FisAragog's own three-regime permeability over porosity,
aragog.eos.mobility_function(Bower et al. 2018), and the drag is themelt viscosity;
tau_s = mu_s / (|drho| g L)is the matrix deformation time, with the mushviscosity
trap_mush_log10visc(default 1e20 Pa s);v_f = |dM_RM| / (4 pi r_base^2 rho_s dt)is the front speed from thecrystallised mass.
t_resdepends on the front speed, not on the step length. The retainedvolume fraction is converted to a mass fraction.
Guards. A front the mesh cannot resolve takes the no-drainage bound,
which is the entry porosity capped at
rfront_loc, and logs the cause. Thecauses are:
If the interior profiles are unavailable, the step buries at
D_Zalone(
F_tl = 0), on a branch of its own.Remelting. The solid that remelts first is the solid that froze last.
A burial ledger (
outgas/burial_ledger.py) records each step's burialagainst the interval of melt fraction the mantle froze through, and a step
on which
Phi_globalrises toPhireturns what is recorded belowPhi:the melt changed in between.
Phi_globaland the_kg_trappedcolumns ofthe completed rows. It keeps no state of its own, survives a resume, and
the rebuild takes about 35 ms per remelt step on a 5000-row history.
helpfile written before trapping, returns in proportion to the solid that
remelted,
(Phi(t) - Phi(t-1)) / (1 - Phi(t-1)).Remelting steps report branch 7.
Front profiles. The front is located and drained on Aragog's basic
nodes, from the core-mantle boundary up, with the melt fraction, mixture
density, phase-boundary densities and gravity that Aragog 26.10.03 writes
there (
phi_basic,rho_basic,rho_solid_b,rho_melt_b,g_b). Nosolver internals are read.
Porosity. Inside the mush, porosity comes from the density lever rule
on those densities with the sign of the contrast kept. That gives the exact
volume fraction for Aragog's two-phase mixture, whichever phase is denser.
Aragog's
porosity_bis the same rule with a soft clip where the melt islighter, but reads about 0 where the melt is denser, as its docstring warns,
so it is not used for the drainage.
Single-phase nodes take their melt fraction.
Bookkeeping
{species}_kg_trappedand{element}_kg_trappedcolumns and counted in_kg_solid. There is onecolumn for each volatile species, volatile element and noble gas. The rest
of
_kg_solidstays with the chemistry backend, for example atmodeller'sgraphite.
totals and the solid columns, then put back after the solve. A try/finally
context manager does the swap, and the species totals are rebuilt from the
new partition.
whole-planet total, so dust-level melt mass of an element the run does not
carry stays where the chemistry put it.
O_kg_totalincludes the trapped oxygen under both fO2 sources.removed mass in
M_desiccated, and keeps the solid share, trapped massincluded. With trapping on it keeps the melt as well, so mass a remelt
returns to a desiccated planet stays in the planet however many steps the
remelt takes.
and the flag clears once the mass held in the melt reaches
mass_thresh.locks in the solid, as the desiccation gate does, and empties it down to
the locked mass. It is off on a frozen mantle, as on main, and on a step on
which a remelt returned buried mass, so that a slow remelt keeps what a
fast one keeps.
escaping mass, its atmospheric mass over the sum of the element masses in
the atmosphere.
run_crystallizednow scales the atmosphere by thefraction of that same sum that remains, so each element's atmosphere loses
exactly what escape took from its total. It used
(M_atm - esc) / M_atm,and
M_atm, the sum of the species masses, is 1.0000177 times the elementsum with CALLIOPE's molar masses: 1.8e-5 of each step's loss stayed in the
atmosphere, and as escape drew a total down towards what the solid holds
that gap crossed the closure limit. Species columns and pressures scale by
the same fraction, so the composition and the ratio of
M_atmto theelement sum are kept.
assert_mass_conservation(check_element_closure=True)checks
total = atm + liquid + solidfor every element at every step. Amismatch is refused once it exceeds both 1 kg and the chemistry solver's
relative tolerance times the total.
trap_*and*_kg_trappedcolumns are read as zero through the same path as main'sRESUMABLE_ZERO_FILL_KEYS: zero is their value in a run that nevertrapped. Any other missing column is still refused.
Other changes
trap_mode = "front"with any interior otherthan Aragog.
trap_*diagnostics: branch, fraction, timescales, front geometry andspeed, and buried mass per step and cumulative.
trap_front_courantreports the front's advance over one step, in units of its own thickness;
_kg_trappedcolumns.solid.
vol_list,vol_element_listandnoble_gases.input/all_options.toml, and there are validation pages fortrapping.pyand
compaction.py.Modelling choices, for review
w_Dcarries(1 - phi), which Aragog'srelative_velocityomits. Bothgive the melt velocity relative to the matrix: McKenzie (1984, Eq. 3.7)
takes the buoyancy against the mixture,
(1 - phi) (rho_s - rho_f) g, andAbe (1995, Eq. 40) against the solid. The factor lengthens the percolation
time by
1 / (1 - phi), 1.43 atphi = 0.3, and in the five test casesbelow raises the retained fraction by at most 0.0095. McKenzie's solution
assumes touching grains, so melt fractions below about 0.2, while the front
is entered at
rfront_loc, 0.4 by default; Measure the (1 - phi) Darcy factor in the gravitational separation velocity aragog#67 tracksthe same factor for Aragog.
(
trap_max_front_fraction = 1). Where percolation controls, the retainedfraction depends on
w_D / v_fand not onL. A value below 1 sendsthicker fronts to the no-drainage bound.
dM_RM, the same incrementthe flux uses, rather than from
M_mantle_solid. The two therefore cannotdisagree about whether a step froze.
separation_viscosity.burial ledger is kept in melt-fraction coordinates, not radius: it orders
the solid by when it froze, which is what decides what remelts first in a
global model.
trace melt mass the chemistry leaves of an element the run does not carry
(6.7e-2 kg in the Earth run with He and Ar) and is independent of
mass_thresh, so a break below that setting is still caught.the result of a remelt then does not depend on how many steps it takes.
the element is being resupplied, and flooring the small release each step
would delete it whenever it is below
mass_thresh.freeze_volatiles, burial goes on between the freeze andPhi_global = 0. The last melt crystallises and buries atF_tlwhile theatmosphere stays out of the exchange. What the burial leaves in the melt
reservoir stays there, as the whole reservoir does on main.
Known limitations
mantle,
v_fis only an effective speed, and with the dense-melt andsurface-node guards now able to fire, such steps take the no-drainage bound.
about 5 GPa. That looks like a table artifact, but trapping takes it as
given (the dense-melt guard).
Validation of changes
Unit tests (
tests/outgas/test_trapping.py,test_compaction.py,test_wrapper.py,tests/utils/test_coupler.py,tests/config,tests/interior_struct/test_zalmoxis.py):Eq. 6 is pinned against hand-computed values and its analytic limits, and
the supply cap is tested through
run_trapping.Remelting, tested on a mantle frozen from
Phi = 1to 0.5, which buries4.333e19 kg of water, then degassed a hundredfold:
at zero;
left 98% trapped;
Phi = 0, no melt) still releasesits share.
With that one-row history the ledger has nothing to replay, so these
releases test the fallback share; helium runs through the same partial and
complete remelt, coming back once, with its moles following the masses
after the withhold and restore.
A closed melt frozen from
Phi = 0.6to 0.4 and cycled a hundred timesbetween 0.41 and 0.40 keeps its trapped water to 1e-12. Releasing the mean
of the solid instead raises it by 4.4% a cycle and triples it in a hundred.
The ledger itself is checked against an independent fine-grid ledger (2e5
melt-fraction bins) on a seeded random history of 400 steps.
Dissolved helium is buried at
F_tl C_Z dM_RM, withheld from the chemistryand restored with its moles.
With a zero N budget, 6.7e-2 kg of N2 and 1e-2 kg of NH3 in the melt are
not buried while water still is. The closure check passes the dust-level N
row and refuses it with no absolute tolerance, and refuses a 2 kg break on
a dust total, a 1e15 kg break on a 5e14 kg total (below a
mass_threshof1e16 kg) and a 10% break on a planetary one.
A desiccated planet with H, O and Ar trapped that remelts half its solid
is no longer desiccated, with every element total kept, and kept also
while it stays flagged.
A desiccated planet with 4e18 kg of H and 3.2e19 kg of O trapped, remelting
from
Phi = 0.30to 0.50 in 1000 steps of 2e-4 that each release less thanmass_thresh, keeps every total and ends with 5/7 of its water trapped and1.03e19 kg in the melt, as 4 steps of 0.05 do.
The same slow remelt runs through
Proteus.starton the dummyconfiguration: 16 steps of 0.0125, each releasing less than
mass_thresh,with the flag set over the first remelt step and the released hydrogen
below the threshold for the rest of the run. The totals hold on every row
and the run ends with the trapped 5/7 and the outgassed 2/7 that 4 steps of
0.05 reach. The planet also holds 1.5e18 kg of trapped N2, whose released
2/7 stays below
mass_thresh: it is kept while the remelt lasts and, onceit stops, floored to the locked 5/7, which a whole-total floor would not do.
A frozen H2O-N2 atmosphere whose species masses are 1.0000177 times its
element masses, as with CALLIOPE, goes through 60 alternating escape and
frozen steps of a quarter of the atmosphere each: every element closes to
1e-10 while the N total falls to what the solid holds. Through
Proteus.start, the dummy mantle is frozen withfreeze_volatilesandtrapping on and escapes for 20 steps at that ratio, with every element
closing to 1e-10 on every row; with the old scaling the loop stops on the
closure check for H at 1.07e-4.
Desiccation with trapping on keeps the solid and the melt and books the
atmosphere in
M_desiccated; with trapping off it empties the melt as onmain.
Two molten-mantle runs that differ only in the trapped N, 5e14 or 1.1e16
kg, both empty a reachable 9e15 kg below
mass_threshto the locked mass,where a whole-total floor would empty one and keep the other. On a remelt
step the floor is off; a step that released nothing keeps it.
The trapping step's lever-rule porosity on Aragog's densities equals
Aragog's own
porosity_bformula to2e-5where the melt is lighter, andkeeps the melt volume where Aragog's reads 0.
The same front crossed at the same speed drains the same
F_tlover a stepfive times longer, with the front advancing 1.99 of its own thickness.
A helpfile written without the 37 trapping columns loads with them zeroed.
A missing core column is still refused.
Per-element closure holds after burial and after release.
run_outgassingis driven with a stand-in backend. It hands the chemistrythe reachable inventory, keeps atmodeller-style graphite separate from
trapped mass, rebuilds species totals, and restores the trapped mass when
the solve raises.
The permeability is checked against Aragog's own
EntropyPhaseEvaluator.relative_velocityin all three regimes. Thetolerance is rtol 2e-4 above a porosity of 0.05; below that it is set by
Aragog's soft porosity clip.
The drainage integral is checked on a 60 km martian front entered at
phi_c = 0.3(drho = 330kg/m3,g = 3.711m/s2), crossed at 70 and5 cm/yr (
t_res= 85.7 kyr and 1.2 Myr), for five parameter sets:The checks are:
tau_D = L eta_melt / ((1 - phi) drho g (5/7) a^2 phi^4.5)andtau_s = mu_s / (drho g L).F_volsolvest_res = int_F^phi_c max(tau_D, tau_s) dphi / phi. The module matchesthis quadrature, which is independent of the ODE solver, to 1e-6 in all
ten fractions.
matches the series solution to 1e-5.
phi_c exp(-t_res / tau_s)to 1e-6.A dense-melt profile run through the whole front path takes the guard.
The config validator is tested on test doubles and on a real loaded
config.
Mutation checks. The new tests fail when any of the following is
introduced:
skipping the element reservoirs on release;
popping the oldest layer first, or dropping the fallback for mass the
ledger does not hold;
updates on restore or on desiccation;
absolute tolerance to 1e16 kg;
main loop;
there, or dropping the mole columns it writes;
mass, or keeping it on during a remelt, both also in the main loop, or
ignoring the sign of the release;
M_atminstead of the element sum, in theunit test and in the main loop;
g_b, a wrong melt density,a mobility scaled by 1.1, or not storing the basic-node fields;
the closure by default;
them;
(1 - phi)fromw_D, taking the faster of the two processes, orchanging the Rumpf-Gupte exponent from 4.5 to 4.4;
porosity rule.
Suite results:
and the migration test keep the trapping columns next to main's accretion
and core-module columns. The trapping sites are declared in the scanner's
per-function override table, and the output and config references are
regenerated.
5329 passed, none failed on a clean checkout. The golden run passes.
The coverage figure below was measured at the merge with Turn on Zalmoxis Anderson acceleration by default and require aragog 26.10.08 #971 (5105
passed).
executable lines).
ruff check,ruff format --check,tools/validate_test_structure.sh, thefour reference generators with
--check, and the AGENTS.md checks pass.tools/check_test_quality.py --checkgives the same per-rule counts asorigin/main.
tests/integration/test_integration_golden_run.py)reproduces every existing column of main's recorded trajectory.
Earth-analogue run (1 M_E, 1.5 AU, Aragog with 80 levels, PALEOS). It
equals the exact volume fraction to 1e-14 at all 19073 mush node samples.
Coupled validation. Earth-analogue runs (1 M_E at 1.5 AU, Aragog with 80
levels, PALEOS, AGNI, CALLIOPE,
rfront_loc = 0.4), each a front run and atrap_mode = "none"control identical otherwise, at 63a808e.freeze_volatilesis on and the solidification stop is off, so each run goeson as a frozen mantle under escape until another criterion stops it.
With ZEPHYRUS escape:
Phi_global = 0.05(H, C and O).
M_atmis 1.000017 to 1.000031 times the element sum in theatmosphere.
Phi_globalnever rises, so no step remelts.Phi_globalfell to 0.46. The crystals still take up
D_Zon them: 0.3% of the burial.F_tl0.13 to 0.16), between 2.9e5 and 5.5e5 yr.Phi_global = 0.30) until the mantle is fully solid, everystep took the no-drainage bound. 278 of these 279 steps have melt denser
than the solid inside the front, 115 of them with the front also reaching
the surface node, and one has the mush split into three layers.
low-pressure PALEOS behaviour noted under Known limitations, so the trapped
inventory in this case depends mostly on how dense melt is treated. This is
the main point for review.
crystallises, until
Phi_globalreaches 0 at 2.4 Myr. It moves dissolvedmass from the melt reservoir into the solid and leaves the frozen atmosphere
alone. It raises the trapped H from 34.6% to 40.6% of the total. In the
control the melt reservoir keeps its 2.1e20 kg of H from the freeze on, as
on main.
is two steps whose front is a single node of zero thickness, where
trap_front_courantreads inf and the step takes the guard.Frozen mantle under strong escape. The same pair with 3e16 kg of N and
the dummy escape at 1e7 kg/s.
Phi_global = 0.05atmosphere drops below
mass_thresh. Below that escape stops, as on main,just above
p_stop = 1e-3bar, and both runs went on to the 5 Gyr limit.mass_threshfrom 4.4 Myr in the front run and from6.1 Myr in the control. Escape draws it down to what the melt and the solid
hold.
what trapping buried never reached the atmosphere, so escape could not take
it.
63a808e and once at 2e04fb2, the commit before the fix:
stopped on the next on
Per-element reservoir closure failed for N ... (relative difference 0.0127%), with N at 5% of its value at the freezeand C at 7.3e-5;
every element within 3.7e-10, and ended where the original run did
(6.25e13 kg of N, 1.1e-3 bar);
limit. Its N gap passed 1e-4 at 10.2 Myr, and it ended with 2.1% of its N
total and 0.3% of its C unaccounted for. At 63a808e the same control
closes every element to 4e-10.
Test configuration: Habrok cluster (AlmaLinux 9.8, x86_64), Python 3.12
conda environment, with fwl-aragog 26.10.8, fwl-io 26.10.7.1, MORS 26.10.6,
ZEPHYRUS 26.7.24, Zalmoxis 26.9.21 and AGNI at its pin. CALLIOPE, the
outgassing backend, is at 1e3ad73, older than its 26.10.05 pin, so the coupled
runs carry no noble gases. atmodeller is not installed there, so the graphite
path is tested through a stand-in with atmodeller's write convention.
Checklist