Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
1dbba19
Refactor solve.py to plumb fO2_shift explicitly through private helpers
timlichtenberg May 14, 2026
fa11002
Add equilibrium_atmosphere_authoritative_O entry point (Path C)
timlichtenberg May 14, 2026
2e78c78
Harden authoritative-O solver against bad inputs and unstable trial p…
timlichtenberg May 14, 2026
8b6fc5c
Add input-validation tests for authoritative-O solver entry point
timlichtenberg May 14, 2026
b0ef062
Add round-trip and reproducibility tests for authoritative-O solver
timlichtenberg May 14, 2026
b233815
Add monotonicity property tests for O_kg(fO2_shift)
timlichtenberg May 14, 2026
91b7b35
Document the authoritative-oxygen solver mode
timlichtenberg May 14, 2026
5ab0b2f
Tighten the authoritative-oxygen docs against accuracy and voice find…
timlichtenberg May 14, 2026
083dd57
Surface the dual solver mode from the broader Explanation pages
timlichtenberg May 14, 2026
1a58d46
Replace the fabricated T_magma warning with the actual calibration ra…
timlichtenberg May 14, 2026
a5b34ff
Verify CALLIOPE outgassing docs against primary literature
timlichtenberg May 15, 2026
f499c2b
Refresh the test-marker counts in the testing-suite docs
timlichtenberg May 15, 2026
a1c08e3
Add physics and chemistry invariant tests
timlichtenberg May 15, 2026
ec5cdca
Add hypothesis-based exploration tests for the solubility laws
timlichtenberg May 15, 2026
3ebb88e
Rewrite TestHappyPath into an anti-happy-path contract
timlichtenberg May 15, 2026
c28fea1
Move CALLIOPE title above badges and force-green the test + coverage …
timlichtenberg May 15, 2026
37aed1a
Fix the broken \texttt{O\_kg\_total} render in the authoritative-O page
timlichtenberg May 15, 2026
1c5e3bf
Add full PROTEUS config examples for both fO2 dispatch modes
timlichtenberg May 15, 2026
9d5dece
Drop the matrix template from the CI job display name
timlichtenberg May 15, 2026
0d29a6c
Add backend-comparison docs page with CALLIOPE vs atmodeller figures
timlichtenberg May 15, 2026
790dcd5
Switch CALLIOPE docs citations to the aragog footnote scheme
timlichtenberg May 15, 2026
3f09a37
Fix fabricated paper titles and metadata in CALLIOPE docs citations
timlichtenberg May 15, 2026
024c485
Remove "Path C" AI-roadmap label from CALLIOPE docs and source
timlichtenberg May 15, 2026
defd873
Redesign Fig 2 so the four temperature markers are individually visible
timlichtenberg May 15, 2026
22bf126
Move fig3 legends out of the data area
timlichtenberg May 15, 2026
4e4ff7a
Match figure font to docs and clean up backend-comparison page
timlichtenberg May 15, 2026
074417f
Lift fig3 panel labels and shift fig4 tolerance text right
timlichtenberg May 15, 2026
5846faf
Center fig4 tolerance annotation over the middle bar
timlichtenberg May 15, 2026
4f20345
Introduce the Earth fiducial section on the backend-comparison page
timlichtenberg May 15, 2026
b285093
Add reference figure to the first-run tutorial
timlichtenberg May 15, 2026
2ccf02b
Add Two-mode round-trip tutorial
timlichtenberg May 15, 2026
2b4dc7c
Add Speciation phase diagram tutorial
timlichtenberg May 15, 2026
c6112ab
Add Coupled-loop driver tutorial
timlichtenberg May 15, 2026
9358844
Add Reproducing the Earth fiducial tutorial
timlichtenberg May 15, 2026
476d064
Add Mars-like atmosphere tutorial
timlichtenberg May 15, 2026
31caa1e
Tighten tutorial accuracy: warm-start chain, citations, physics
timlichtenberg May 15, 2026
e006a26
Format tutorial figures consistently and extend two of them
timlichtenberg May 15, 2026
3d67bff
Add dark borders around each phase-diagram simulation cell
timlichtenberg May 15, 2026
f2c53f5
Move Mars summary box to the bottom-right
timlichtenberg May 15, 2026
9bbc076
Double the phase-diagram simulation-cell border thickness
timlichtenberg May 15, 2026
c3edf4f
Mars tutorial: note the more-reducing Mars-mantle redox
timlichtenberg May 15, 2026
f0c6c01
Switch default IW buffer to Fischer 2011
timlichtenberg May 15, 2026
df15b62
Tighten cross-backend docs and figures after the buffer-default change
timlichtenberg May 15, 2026
1742985
Add AI agent guidelines and test-quality rules
timlichtenberg May 15, 2026
bac95fb
Add test-quality lint, ratchet script, and fast coverage gate
timlichtenberg May 15, 2026
afc595e
Mirror tests for structure.py and pin against Zeng 2016
timlichtenberg May 15, 2026
cd90c52
Mirror tests for oxygen_fugacity.py and pin against Fischer 2011
timlichtenberg May 15, 2026
35197cc
Mirror tests for solubility.py and pin against Sossi 2023 and Gaillar…
timlichtenberg May 15, 2026
879fc9f
Mirror tests for chemistry.py and pin against JANAF janaf_H2 fit
timlichtenberg May 15, 2026
29959fd
Mirror tests for solve.py and pin the forward-vs-inverse round trip
timlichtenberg May 15, 2026
d1309f6
Document the test-quality infrastructure in the testing and build-tes…
timlichtenberg May 16, 2026
2d71e96
Count match= in pytest.raises and pytest.warns as a second implicit a…
timlichtenberg May 16, 2026
3783ab3
Add wrong-formula discrimination guards across test_stoichiometry.py
timlichtenberg May 16, 2026
b20e7c9
Add wrong-formula discrimination guards across test_invariants.py
timlichtenberg May 16, 2026
6594519
Add wrong-formula discrimination guards across test_solubility.py
timlichtenberg May 16, 2026
05336c2
Add wrong-formula discrimination guards across test_equilibrium_paths.py
timlichtenberg May 16, 2026
7ddc232
Add wrong-formula discrimination guards across test_authoritative_O.py
timlichtenberg May 16, 2026
86d5e7e
Add wrong-formula discrimination guards across test_partial_species.py
timlichtenberg May 16, 2026
909cf42
Add wrong-formula discrimination guards across the last five test files
timlichtenberg May 16, 2026
01e3e9b
Gate the hypothesis import in test_invariants_hypothesis.py with impo…
timlichtenberg May 16, 2026
f4cd2c9
Fix the Gaillard 2022 S2 re-derivation in docs/Validation/solubility.md
timlichtenberg May 16, 2026
1bfea62
Split mixed-tier test files so module-level pytestmark is well-defined
timlichtenberg May 16, 2026
59039c2
Clear the last 43 baseline violations: docstrings on every test, no
timlichtenberg May 16, 2026
ff8a72c
Remove repo-internal infrastructure references from the public docs
timlichtenberg May 16, 2026
fce51d4
Strip drift-prone test counts from the public docs
timlichtenberg May 16, 2026
13226f5
Fix four defects in the AST test-quality linter
timlichtenberg May 16, 2026
0b748e6
Re-attribute the 0.325 Earth core mass fraction to Wang 2017
timlichtenberg May 16, 2026
840158a
Fix Fischer DOI and the schaefer arithmetic slip in chemistry.md
timlichtenberg May 16, 2026
7cb01ab
Voice rule cleanup: prose, public docs, and CLI surface
timlichtenberg May 16, 2026
d201dbf
Tidy CI install steps and bump checkout to v4 on code-style
timlichtenberg May 16, 2026
0ab363f
Add Validation anchor pages to the docs nav
timlichtenberg May 16, 2026
48e8c64
Wire up citations in anchor tables; switch ADS links to SciX; fix Wan…
timlichtenberg May 16, 2026
749057e
Give draft PRs a lightweight CI signal
timlichtenberg May 16, 2026
102fb64
Apply ruff lint + format to scripts/
timlichtenberg May 16, 2026
a8ec1cd
Extend the documented ruff scope to include tools/ and scripts/
timlichtenberg May 16, 2026
298431f
Untrack .vscode/settings.json
timlichtenberg May 16, 2026
09c79c9
Refresh README for the dual-mode chemistry and the new docs
timlichtenberg May 16, 2026
df0ec52
Add Nicholls et al. 2026 (Nature Astronomy) to README citations
timlichtenberg May 16, 2026
ef7a931
Point the README License line at the PROTEUS framework site
timlichtenberg May 16, 2026
fc5d330
Align methods citations across README, front page, and Publications
timlichtenberg May 16, 2026
592452a
Add the Nicholls 2026 arXiv link (2507.02656) to all citation surfaces
timlichtenberg May 16, 2026
e4b3ff1
Bound derived fO2 and tighten the authoritative-O residual gate
timlichtenberg May 30, 2026
60875e9
Cover the authoritative-O solver-loop branches with unit tests
timlichtenberg May 30, 2026
6ff50de
Pin Codecov gates to the 90% coverage target
timlichtenberg May 30, 2026
38cac41
Reject authoritative-O roots above the pressure ceiling
timlichtenberg May 31, 2026
56dbcbc
Bump CI actions to current Node-24 versions
timlichtenberg May 31, 2026
00519ed
Show O2 as a major species in the speciation phase diagram
timlichtenberg May 31, 2026
2a6e54b
Consolidate solver bounds and share the default fO2 buffer
timlichtenberg May 31, 2026
3523722
Fix package author metadata and add a second author
timlichtenberg May 31, 2026
08b1553
Sync CITATION.cff version to the latest release tag
timlichtenberg May 31, 2026
94ed43a
Drop the manually-maintained version from CITATION.cff
timlichtenberg May 31, 2026
36a8fd9
Remove placeholder DOI from CITATION.cff
timlichtenberg May 31, 2026
9c59070
Let callers widen the CALLIOPE cold-start pressure draw
timlichtenberg May 31, 2026
b710e87
Validate and clarify the CALLIOPE cold-start pressure ceiling
timlichtenberg May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ coverage:
status:
project:
default:
target: auto
target: 90%
threshold: 1%
patch:
default:
target: auto
target: 90%
threshold: 1%
comment:
layout: "reach, diff, flags, files"
Expand Down
126 changes: 126 additions & 0 deletions .github/.claude/rules/calliope-code-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
description: CALLIOPE-specific code review criteria for the generator-evaluator pattern. Applies domain expertise (chemistry, fO2 buffers, solubility, PROTEUS coupling) to all code review in this repo.
---

# CALLIOPE Code Review Criteria

When reviewing CALLIOPE code (either your own or via code-reviewer agents), apply these domain-specific checks in addition to standard code quality review.

> **Discovery note.** CALLIOPE keeps its Claude-Code rule files under `.github/.claude/rules/` (not the conventional repo-root `.claude/`) so they can be tracked in git and shared across collaborators. Claude does NOT auto-discover them at this path; the repo-root `CLAUDE.md` (symlinked to `.github/copilot-instructions.md`) names this file and `calliope-tests.md` explicitly. **Before opening any review pass, read both this file and `calliope-tests.md`.**

## Physics plausibility

- Temperature must be positive everywhere (Kelvin). Flag any code path where T could reach zero or go negative.
- Total pressure must be positive; partial pressures must be non-negative and sum to total pressure within solver tolerance.
- Mole fractions must sum to 1.0. Flag any composition-returning function that doesn't enforce or verify normalization.
- Henry's-law solubility outputs must be non-negative. Flag any solubility law that could return a negative or `nan` partial pressure for a physically valid input.
- `log10(fO2)` outputs must be finite (no `nan`, `inf`, or `complex`). Flag any buffer evaluation that does not clip / sanitize before return.
- Equilibrium constants `Keq` must be positive. The modified-Keq formulation in `chemistry.py` returns `log10(Keq)`; verify the exponentiation step has not been accidentally inverted.
- Mantle mass closure: `M_mantle = M_planet - M_core`; flag any path that lets `M_mantle <= 0` reach return.

## Unit convention boundaries

CALLIOPE has a mixed unit convention:

- **Solver inputs**: T in K, P in bar, mass in kg, mole fractions dimensionless.
- **Henry's-law constants**: literature values in `(mol / kg) / bar^n` (variable `n` per fit); the conversion to internal units lives in `solubility.py`.
- **Oxygen fugacity**: `log10(fO2)` (dimensionless) and `fO2_shift_IW` (dex relative to the IW buffer).
- **PROTEUS interop**: the returned dictionary uses kg for species masses, bar for partial pressures, K for temperatures.

When reviewing code that crosses these boundaries (e.g. a new solubility law, a new buffer fit, a new PROTEUS-side caller), verify the unit is correct at each conversion site. The `bar` vs `Pa` boundary is the recurring trap: literature fits are nearly always in bar, internal SI bookkeeping is in Pa.

## Buffer-default flip safety

When the default value of a dispatched-by-name physics path (IW buffer, modified-Keq formulation, solubility law) is changed, the change has fan-out across:

1. Tests pinning a buffer-specific reference value (e.g. `EARTH_VOLATILE_O_REF_KG` was tied to the O'Neill 2002 default and had to update when the default flipped to Fischer 2011).
2. Documentation pages citing the buffer-specific number (the cross-backend comparison, the oxygen-fugacity reference page, the tutorials).
3. PROTEUS-side tests pinning against CALLIOPE outputs.
4. Frozen reference fixtures used by the cross-backend harness.

Required workflow for any buffer / law default flip:

1. `git grep` the old buffer / law name; update every reference value tied to it.
2. Update test discrimination guards (Section 2 rule 4 of `calliope-tests.md`) so the test would fail loudly under the wrong-default regression.
3. Update `docs/Explanations/cross_backend_comparison.md` and any tutorial that quotes a buffer-specific number.
4. Note the flip explicitly in the release notes.

A PR that changes a default value but does not touch the test reference values is a red flag during review.

## Solver intermediate-state types

`solve.py`'s root-finder and equilibrium solvers operate on intermediate vectors that can drift through `complex` / `nan` / `inf` during iteration. The current source has `clip` hardening against these; flag any new solver code path that:

- Does not check `np.isreal` and `np.isfinite` on intermediate state before passing to a downstream step.
- Catches `RuntimeWarning` from numpy without preserving the warning category in a log line, or silences it altogether.
- Coerces a complex intermediate to a real float without first asserting the imaginary part is negligible.

The unit tests of `solve.py` verify the intermediate-state defenses fire (see `calliope-tests.md` Section 16); the code review's job is to make sure the defenses are present before the test asks them to fire.

## PROTEUS coupling patterns

CALLIOPE is called by PROTEUS through the chemistry / outgassing step. Four coupling patterns need explicit care during review.

### 1. Authoritative-O IC reconciliation

Under Path C (`fO2_source = "from_O_budget"`), PROTEUS supplies an authoritative oxygen mass `O_kg_total` and CALLIOPE inverts the IW shift that recovers it. PROTEUS stashes the user-supplied `O_kg_total` as `O_kg_user_ic` in `hf_row` before the first CALLIOPE call; the runtime helper `check_ic_oxygen_budget` compares CALLIOPE's solver-derived `O_kg_total` against the sentinel and hard-fails on >50% divergence.

Any change to `solve.py`'s authoritative-O path (`equilibrium_atmosphere_authoritative_O`) must preserve this contract:

- The return dict MUST contain `O_kg_total` (the solver-recovered value, used by PROTEUS to verify the reconciliation).
- The return dict MUST NOT silently substitute a clipped or fallback value for `O_kg_total` without surfacing the substitution in the return dict (e.g. via a `O_kg_total_clipped` flag).

A regression that breaks reconciliation will surface as a >50% divergence hard-fail in PROTEUS; the test on the CALLIOPE side must catch it before it ships.

### 2. fO2_shift_IW echo-back pattern

PROTEUS sometimes overrides `hf_row['fO2_shift_IW']` (e.g. when iterating Path C) and must restore the original value after the CALLIOPE call. CALLIOPE returns `fO2_shift_IW_derived` as a separate key so the user-supplied and solver-derived values are distinguishable in the helpfile CSV.

Required for any change that touches the chemistry-step return contract:

- The return dict MUST keep `fO2_shift_IW_derived` distinct from `fO2_shift_IW`.
- A new field that overloads the meaning of `fO2_shift_IW` (e.g. silently treating it as a target rather than a user input) is a red flag.

The save/restore pattern lives on the PROTEUS side; CALLIOPE's responsibility is to not silently mutate the input.

### 3. Per-species mass closure across modules

PROTEUS's mass-conservation invariant `M_atm <= M_planet` depends on CALLIOPE summing per-species masses (`H2O_kg_total`, `CO2_kg_total`, etc.) consistently. A regression in CALLIOPE's `_kg_total` aggregation (e.g. forgetting the oxygen contribution from H2O when O is treated as a separate element) would silently violate the PROTEUS-side invariant. The asymmetry between "elements as buffered reservoirs" (chemistry view) and "elements as tracked masses" (PROTEUS view) is the lesson from PROTEUS issue #677.

Flag any chemistry-step code that:

- Adds a new species without updating the corresponding `_kg_total` aggregation.
- Treats oxygen as a separate accounting entity from H2O / CO2 / SO2 in a way that changes the per-species totals.
- Returns a per-species total that is not the sum of `kg_atm + kg_liquid + kg_solid` for that species.

### 4. Buffer-default flip impact on downstream PROTEUS

A buffer-default flip in CALLIOPE (the 2026-05 Fischer-vs-O'Neill flip is the canonical example) changes the IW value silently for any PROTEUS run that uses the CALLIOPE default. PROTEUS-side tests that pin a specific IW value are sensitive to this. The rule:

- Announce buffer-default flips in CALLIOPE's release notes.
- Bump the CALLIOPE version pin in PROTEUS's `pyproject.toml` to the post-flip release.
- Run PROTEUS's unit + smoke suite against the new CALLIOPE before merging the version bump.
- Update any PROTEUS-side hardcoded IW value tied to the old default.

A CALLIOPE PR that flips a default but does not anticipate the PROTEUS-side fallout is a red flag during review.

## Config mutability

`Config` (or any dataclass / attrs object) used to carry user input must not be mutated at runtime after IC. Flag any code that sets `config.X.Y = value` outside of config initialization. Use local variables instead.

## Cross-module constant duplication

Physical constants (`R_gas`, `M_earth`, `R_earth`, `N_avogadro`, `M_O`, `M_H`) are defined in `src/calliope/constants.py`. When reviewing code that uses a physical constant, check that the import is from `calliope.constants` and not re-derived. A new constant introduced as a literal in a body (e.g. `5.4e-26 * T` for a Boltzmann-related coefficient) is a red flag.

## Test marker discipline

Every test file must begin with a module-level `pytestmark = [pytest.mark.<tier>, pytest.mark.timeout(<budget>)]` (unit/30 s, smoke/60 s, integration/300 s, slow/3600 s). Per-function markers are additive but do not replace the module-level marker; CI runs `pytest -m "(unit or smoke) and not skip"` and any file missing the tier marker ships untested.

## Test quality (cross-reference)

Test-content rules (anti-happy-path, discriminating-value guards, physics-invariant tiering, `physics_invariant` / `reference_pinned` certification markers, adversarial-review trigger, mocking discipline, `importorskip` + module-constant-monkeypatch traps, buffer-flip propagation, hypothesis seed stability, solver intermediate-state assertions) live in [`calliope-tests.md`](calliope-tests.md). When reviewing tests, apply both files: this one for marker discipline and review-pass gate, the deep-dive for the content contract.

## Sister rules (cross-link)

- [`.github/copilot-instructions.md`](../../copilot-instructions.md) "Testing Standards" -- high-level rules visible to all readers. Repo-root `CLAUDE.md` is a symlink to this file.
- [`calliope-tests.md`](calliope-tests.md) -- test quality deep-dive; the canonical source for anti-happy-path patterns and the validation certification markers.
Loading
Loading