Repository navigation
Give the padded surface node its EOS density and Gauss gravity - #115
Merged
Merged
Conversation
When the structure integration stops inside the last shell, the nodes past the stop are the surface. The returned density there is now rho_EOS at the target surface pressure and the node's temperature, from the layer of the stop mass, instead of zero, so a consumer that integrates the density over the nodes no longer loses half of the outermost shell. The Picard iteration and its convergence test are unchanged; only the returned profile differs at the padded nodes.
A padded node keeps zero pressure and the mass at the stop, carries the gravity of that mass at its own radius and the EOS density at the target surface pressure; positive and density_eos read it, the latter with the lookup at the target pressure. The test sphere and the padding helper build their surface node that way.
The padded nodes past a surface stop are the surface: positive and density_eos now read them, density_eos with the lookup at the target surface pressure, so a zero or non-EOS surface density fails the run. padding keeps zero pressure and the stop mass but no longer requires the gravity at the stop: no mass lies above the stop, so a padded node carries the gravity of the stop mass at its own radius.
…dius No mass lies above a surface stop, so each padded node has g r^2 = G m_stop. The stop-pad tests, a Hypothesis test over the stop state and the surface pressure limit, the invariant test of a padded node with the gravity at the stop and the real padded solve now check that.
No mass lies above a surface stop, so pad_after_stop now fills the padded nodes with g = G m_stop / r^2 at each node instead of the gravity at the stop, which was too large by about 2 (R - r_stop) / R at the outer radius. The gravity invariant reads every node but the centre, the padded nodes included. The JAX padding test now expects the same fill. The docs state the residual: a consumer that integrates the padded surface density over the nodes counts the shell between the stop and the outer radius, which the enclosed mass does not include.
…node A CMB-anchored adiabat sets the surface temperature, so a padded node takes the adiabat at the target pressure, not the configured surface_temperature. The surface lookup reads the target pressure, the node temperatures and the layer of the stop mass, with the volatile profile only in the mantle, and stops the solve on a density that is not finite. padding holds the padded gravity to G m_stop / r^2 also at the Picard tolerance. A surface stop at the centre keeps the stop gravity there.
…its lookup With the adiabat integrated from the core-mantle boundary, the configured surface_temperature is not the temperature at the surface, so a padded node now takes the adiabat at the target surface pressure, which keeps its temperature and EOS density continuous with the node below; surface-anchored modes keep surface_temperature. The surface lookup is one helper that stops the solve on a density that is not finite, as the Picard update does. padding now holds the padded gravity to G m_stop / r^2 itself, so the gravity at the stop is refused also at the Picard tolerance; two invariant tests set the padded gravity to match their mass or expect padding. The docs name the midpoint mantle integrals among the sums that count the shell above the stop.
…olerance A padded node of a CMB-anchored adiabat takes the adiabat continued from the node below to the target pressure, checked against an independent integration in ln P; the continuation is exact for nabla_ad linear in ln P and holds T without a gradient or a pressure drop. A non-finite surface density is returned for the invariants rather than raised, so a Newton trial does not stop the solve. padding refuses a padded gravity 1e-9 off G m_stop / r^2.
The interpolated adiabat only held the temperature of the last node with positive pressure, which on a 50-layer grid sits about 4 GPa below the surface. At result assembly a padded node of a CMB-anchored adiabat now takes the adiabat integrated in ln P from that node down to the target surface pressure with the EOS nabla_ad; surface-anchored modes, an external temperature function and temperature arrays keep their own temperature. The temperature closures are back to their form on main, so the iteration is unchanged. The surface lookup returns a non-finite density as it is: in an accepted result it breaks positive, and a Newton trial no longer stops the solve. The docs describe the continuation and how the midpoint mantle integrals take the shell that crosses the stop.
A CMB run accepted before the adiabat blend reaches 1 keeps the linear profile at R for its padded node. The continuation reaches the exact solution of a T-dependent nabla_ad only with the predicted midpoint temperature, holds T when the midpoint gradient is missing and returns a non-finite start temperature as it is. Both padded lookups receive the arguments of calculate_mixed_density_batch after the mixture, in that order.
…p arguments The continuation to the surface now runs only once the adiabat blend has reached 1; a CMB run accepted earlier in the ramp keeps the linear profile at R, as its other nodes are not on the adiabat either. Both padded lookups take one argument tuple in the order of calculate_mixed_density_batch, and the continuation reorders it for get_mixed_nabla_ad; it returns a non-finite start temperature as it is.
… and the hold On a PALEOS 2-phase mantle with its melting curves and condensed_rho_scale apart from binodal_T_scale, both padded lookups get every argument of the solve in place. A CMB run accepted at blend 0.25, 0.5 or 0.75 keeps the linear profile at R. The continuation keeps the temperature of its completed steps when nabla_ad goes missing, logs one warning naming the pressure, and holds T when the midpoint gradient is negative.
…ntion The continuation logs one warning naming the temperature and the pressure from which it holds T. The docs state that a result accepted before the adiabat blend reaches 1 keeps the linear profile at the outer radius, so its padded temperature can lie above the point below it, with the jumps of a 50-point 1 Earth mass run.
…ng message A padded node of the surface-anchored adiabat of input/default.toml keeps surface_temperature. The continuation stops at 100 K under a gradient that would take T lower. The padding error names the expected mass and the gravity G m / r^2 of the node it reports.
The node-temperature tests map every batch EOS call to a solved pressure profile; a solve that pads makes one more lookup at the target surface pressure, which matches no profile and is not a density update, so the spy now passes it through.
…ound-off Whether a converged solve stops inside the last shell depends on round-off: the analytic solve padded on macOS and not on the Linux runner. The real-solve tests now lower each root of the central-pressure search by 1e-5, so the integration stops tens of metres below R within the accepted pressure residual and the outer node is padded on every platform. The mid-ramp case at blend 0.75 is dropped: its max_iter accept returns a best pass that is not padded.
A strict accept allows a surface pressure residual up to pressure_tolerance (1e9 Pa), so lowering the central-pressure root by 1e-5 leaves some converged solves unpadded on Linux (P[-1] 11 MPa at 1 atm target). The surface residual is not monotone in the central pressure at that scale (a root that pads can stop padding 1e-5 lower), so the wrapper now takes the root itself when it pads, or the root lowered by the smallest of 1e-5 to 1e-2 whose residual is that of a padded solve, and records any root it cannot pad; every test asserts exactly one padded node and no miss. The 300 s wall clock is lifted in these solves, since its accept returns a best pass that need not be padded.
Padded nodes take the gravity G m_stop / r^2 at their own radius, so the test asserts that at every padded node and replaces the stop-radius check, which returned the outer node by construction, with a bound from the mass gained past the last live node: at most at the density of that node, it places the smallest stop radius inside the shell.
6 of 8 tasks
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #115 +/- ##
==========================================
+ Coverage 96.22% 96.24% +0.02%
==========================================
Files 39 39
Lines 5692 5729 +37
Branches 952 958 +6
==========================================
+ Hits 5477 5514 +37
Misses 105 105
Partials 110 110
Flags with carried forward coverage won't be shown. Click here to find out more.
|
There was a problem hiding this comment.
🟡 Changes recommended
Positive padded densities reused as seeds can prevent inner density convergence and trigger unnecessary pressure solves.
1 open issue
What changed in this PR
This PR addresses #96 by giving padded surface nodes physically meaningful density and gravity for downstream consumers such as PROTEUS and Aragog.
Changes:
- Returns EOS density at the target surface pressure and gravity at each padded radius.
- Continues fully blended CMB-anchored adiabats to the padded surface.
- Extends invariant checks, regression tests, and documentation for the updated padding behavior.
| File | Description |
|---|---|
tests/test_surface_stop_pad.py |
Checks radius-dependent padded gravity and stop-mass preservation. |
tests/test_surface_node.py |
Adds padded-solve density, gravity, and temperature tests. |
tests/test_surface_density.py |
Tests surface EOS lookup and adiabat continuation helpers. |
tests/test_node_temperature_arrays.py |
Distinguishes padded lookups from interior density updates. |
tests/test_jax_wrapper_branches.py |
Updates JAX padding expectations. |
tests/test_jax_event_termination.py |
Checks padded gravity and bounds the event location. |
tests/test_invariants.py |
Adds padded density and gravity invariant tests. |
src/zalmoxis/structure_model.py |
Computes padded gravity from stop mass and node radius. |
src/zalmoxis/solver.py |
Adds surface density lookup and CMB-adiabat continuation. |
src/zalmoxis/jax_eos/solver.py |
Updates the padding contract in documentation only. |
src/zalmoxis/invariants.py |
Includes padded nodes in density and gravity checks. |
docs/How-to/usage.md |
Clarifies padded output values. |
docs/Explanations/process_flow.md |
Explains padding physics and downstream integral effects. |
Review effort: Balanced
timlichtenberg
added a commit
to FormingWorlds/PROTEUS
that referenced
this pull request
Oct 23, 2026
The JAX-path rebuild in zalmoxis_solver looks a node with P = 0 past a surface stop up at target_surface_pressure, as Zalmoxis does since FormingWorlds/Zalmoxis#115, instead of at 0 Pa. The docstring of _resolve_zalmoxis_anchor_temperatures states the padded node's temperature.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
Closes #96.
PROTEUS reads the padded node in FormingWorlds/PROTEUS#1045.
The outward integration stops when the pressure crosses zero inside the last shell, and the nodes past the stop are padded with P = 0 and the stop mass. Those nodes had density 0 and the gravity of the stop node. A padded node now gets:
target_surface_pressure, at its temperature, in the layer of the stop mass (the volatile profile only in the mantle);temperature_mode = 'adiabatic_from_cmb'at adiabat blend 1, the adiabat continued in ln P from the last node with P > 0 to the target surface pressure. Surface-anchored modes keepsurface_temperature, and a result accepted mid-ramp keeps the linear profile at R.The invariants now read every node:
positiveanddensity_eosinclude the padded nodes (looked up at the target surface pressure), andpaddingrequires P = 0, the stop mass and g r² = G m_stop. Three places outside the profile now see the padded density: the midpoint shell integrals ofsolve_miscible_interiorandsolve_strong_partition, and the density seed of a retry, which now starts from the EOS density at the padded node;process_flow.mddescribes the integrals. Oninput/default.tomlthe live nodes and every scalar are unchanged (table below). A strong-partition or miscible solve with melting curves can move its converged partition; no comparison here covers that. PROTEUS reaches neither: it refusesglobal_miscibilityat config load, and onlyzalmoxis/output.pycallssolve_strong_partition.Physics impact on
input/default.toml(my run,wall_timeout1e6, base 51e1705 vs this branch; both accept strict with 1 padded node, 149 of 150). Every other result entry is equal.Validation of changes
macOS, Python 3.12.
tests/test_invariants.pyfailed in my checkout only because it lacks the Chabrier H, PALEOS H2O and Seager iron tables. The nightly runs them in itsaccept_solves(1) andaccept_solves_long(3) jobs.pytest tests/test_jax_event_termination.pyat the head: 8 passed. Itstest_pad_holds_the_event_stateis markedslowonly, and no job ofnightly.ymlselects such a test, so it does not run in CI; I ran it locally.tests/test_surface_node.pyforce a padded solve and assert exactly one padded node; they run in the nightlymainjob.Checklist