Skip to content

Coupling to PROTEUS

This page is the practical recipe for using CALLIOPE inside a PROTEUS coupled run: which TOML blocks matter, which knobs are documented where, and which combinations are known to be safe.

For the underlying control flow (per-iteration sequence diagram, what the wrapper passes to equilibrium_atmosphere(), how warm-starts are constructed, what gets written back into hf_row), see the theory page.

Minimal [outgas] block

[outgas]
module       = "calliope"     # or "atmodeller" or "dummy"
fO2_shift_IW = 0.0            # log10 shift relative to IW buffer
mass_thresh  = 1e16           # kg; doubles as the absolute mass-balance
                              # tolerance and the per-element zero threshold
T_floor      = 700.0          # K, magma temperatures below this are clipped
solver_rtol  = 1e-4           # relative tolerance on mass balance
solver_atol  = 1e-6           # fsolve step tolerance (mapped to xtol, not atol)
h2_binodal   = false          # apply Rogers+2025 H2-MgSiO3 binodal override

  [outgas.calliope]
  include_H2O = true
  include_CO2 = true
  include_N2  = true
  include_S2  = true
  include_SO2 = true
  include_H2S = true
  include_NH3 = true
  include_H2  = true
  include_CH4 = true
  include_CO  = true
  solubility  = true          # if false, force Phi_global = 0 (no melt dissolution)

The four primary species (H2O, CO2, N2, S2) must be included; the wrapper will refuse to run with any of them set to false. The seven secondary species can be disabled individually if you want a constrained sub-system (e.g. include_NH3 = false to suppress ammonia chemistry).

Choosing the elemental-budget mode

CALLIOPE needs total H, C, N, S inventories in kilograms. PROTEUS gives you two ways to specify them via [planet]:

[planet]
volatile_mode       = "elements"        # or "gas_prs"
volatile_reservoir  = "mantle"          # or "mantle+core"; sets the reservoir for ppmw

volatile_mode = "elements" (most common)

[planet.elements]
use_metallicity = false      # if true, scales C/N/S to H from solar abundances

H_mode   = "oceans"          # "oceans" | "ppmw" | "kg"
H_budget = 1.0               # interpreted per H_mode

C_mode   = "C/H"             # "C/H" | "ppmw" | "kg"
C_budget = 0.1

N_mode   = "ppmw"
N_budget = 2.0

S_mode   = "ppmw"
S_budget = 200.0

The wrapper translates these into the four budget fields CALLIOPE expects:

  • H_mode = "oceans" β†’ hydrogen_earth_oceans = H_budget
  • H_mode = "ppmw" β†’ H_kg = H_budget * 1e-6 * M_reservoir
  • H_mode = "kg" β†’ H_kg = H_budget

C, N, S follow the same pattern; C/H is interpreted as a mass ratio relative to H.

volatile_mode = "gas_prs" (initial-pressure mode)

[planet]
volatile_mode = "gas_prs"

[planet.gas_prs]
H2O = 220.0     # bar
CO2 = 100.0
N2  = 1.0
S2  = 0.01
# secondary species default to 0.0 unless explicitly set

When volatile_mode = "gas_prs", the wrapper calls get_target_from_pressures(ddict) to back out the elemental inventory implied by the prescribed initial atmosphere; on every subsequent iteration the same elemental inventory is preserved.

Selecting the fO2 dispatch

The PROTEUS schema field [planet].fO2_source selects which CALLIOPE entry point the wrapper calls. The two paths share the same physics; they differ only in which quantity is supplied as input and which is solved for.

fO2_source Input Solved for Entry point
user_constant \(\Delta\mathrm{IW}\) atmospheric + dissolved O equilibrium_atmosphere
from_O_budget total O mass \(\Delta\mathrm{IW}\) equilibrium_atmosphere_authoritative_O

Under user_constant (the default) CALLIOPE buffers the redox state to the configured outgas.fO2_shift_IW and solves the four-equation H/C/N/S mass balance. The resulting O mass is whatever the equilibrium chemistry requires at that buffer, and is written into hf_row['O_kg_total'] so the rest of PROTEUS can read it. Under from_O_budget the wrapper passes the running whole-planet O total (maintained by the PROTEUS element-budget bookkeeping) as a fifth elemental target, uses outgas.fO2_shift_IW only as an initial-guess hint, and solves a five-equation system that returns the derived \(\Delta\mathrm{IW}\) in hf_row['fO2_shift_IW_derived'].

See Coupling to PROTEUS (theory) for the per-iteration control flow and Authoritative-oxygen mode for the augmented five-residual mass balance.

Worked example: buffered fO2 mode (user_constant)

In this mode the user fixes \(\Delta\mathrm{IW}\) and the chemistry returns the O budget. Set O_mode = "ic_chemistry" so the wrapper does not pre-populate an O target: the first outgas call writes one in, and PROTEUS carries it from there.

config_version = "3.0"

[orbit]
    semimajoraxis = 1.0                  # [AU]

[planet]
    mass_tot      = 1.0                  # [M_earth]
    volatile_mode = "elements"
    fO2_source    = "user_constant"      # buffered mode (the default)

    [planet.elements]
        H_mode   = "oceans"
        H_budget = 1.0                   # [Earth oceans]
        C_mode   = "C/H"
        C_budget = 0.1                   # [C/H mass ratio]
        N_mode   = "ppmw"
        N_budget = 2.0
        S_mode   = "ppmw"
        S_budget = 200.0
        O_mode   = "ic_chemistry"        # O is derived from the chemistry
        O_budget = 0.0                   # ignored under ic_chemistry

[outgas]
    module       = "calliope"
    fO2_shift_IW = 4.0                   # [log10] redox buffer offset (input)

    [outgas.calliope]
        include_H2O = true
        include_CO2 = true
        include_N2  = true
        include_S2  = true
        include_H2  = true
        include_CH4 = true
        include_CO  = true
        include_SO2 = true
        include_H2S = true
        include_NH3 = true
        solubility  = true

What the wrapper does on the first call: builds the four-element target \((m_\mathrm{H}, m_\mathrm{C}, m_\mathrm{N}, m_\mathrm{S})\) from H_budget etc.; calls equilibrium_atmosphere with fO2_shift_IW = 4.0; reads back the four primary partial pressures, the seven secondary species, the derived O_kg_total, and writes them all into hf_row. Subsequent iterations warm-start from the previous-iteration <s>_bar values and follow the same path.

Worked example: authoritative-O mode (from_O_budget)

In this mode the user fixes the total O mass and the chemistry returns the derived \(\Delta\mathrm{IW}\). The O_mode is one of "ppmw", "kg", or "FeO_mantle_wt_pct" (never "ic_chemistry", which the config-level validator rejects when fO2_source = "from_O_budget" because the chemistry needs a target to invert against). The outgas.fO2_shift_IW value becomes the initial-guess hint for the solver, not the buffered redox state; pick a value near the expected derived \(\Delta\mathrm{IW}\) for fast convergence, but the solver tolerates a poor hint at the cost of more Monte-Carlo restarts.

config_version = "3.0"

[orbit]
    semimajoraxis = 1.0                  # [AU]

[planet]
    mass_tot      = 1.0                  # [M_earth]
    volatile_mode = "elements"
    fO2_source    = "from_O_budget"      # authoritative-O mode

    [planet.elements]
        H_mode   = "oceans"
        H_budget = 1.0                   # [Earth oceans]
        C_mode   = "C/H"
        C_budget = 0.1                   # [C/H mass ratio]
        N_mode   = "ppmw"
        N_budget = 2.0
        S_mode   = "ppmw"
        S_budget = 200.0
        O_mode   = "FeO_mantle_wt_pct"   # interpret O_budget as mantle FeO wt%
        O_budget = 8.0                   # 8.0 wt% FeO ~ Earth's modern mantle

[outgas]
    module       = "calliope"
    fO2_shift_IW = 4.0                   # [log10] initial-guess HINT, not buffer

    [outgas.calliope]
        include_H2O = true
        include_CO2 = true
        include_N2  = true
        include_S2  = true
        include_H2  = true
        include_CH4 = true
        include_CO  = true
        include_SO2 = true
        include_H2S = true
        include_NH3 = true
        solubility  = true

What the wrapper does on the first call: builds the five-element target \((m_\mathrm{H}, m_\mathrm{C}, m_\mathrm{N}, m_\mathrm{S}, m_\mathrm{O})\), with \(m_\mathrm{O}\) derived from O_mode = "FeO_mantle_wt_pct" and O_budget = 8.0 via \(m_\mathrm{O} = M_\mathrm{O}/M_\mathrm{FeO} \times \mathrm{wt\%}/100 \times M_\mathrm{mantle} \approx 0.2227 \times 0.08 \times M_\mathrm{mantle}\); calls equilibrium_atmosphere_authoritative_O with fO2_hint = 4.0; reads back the four primary partial pressures, the seven secondary species, AND fO2_shift_derived (the redox state implied by the supplied O budget), writes them all into hf_row. The derived \(\Delta\mathrm{IW}\) appears in hf_row['fO2_shift_IW_derived']. Subsequent iterations carry the same authoritative O total (modulated by PROTEUS escape bookkeeping) and the redox state can drift across the trajectory.

Choosing between the two modes

Use user_constant when Use from_O_budget when
You want a fixed redox state for a parameter sweep (Nicholls et al. 2024 3 explored seven \(\Delta\mathrm{IW}\) values this way) You want whole-planet O accounting where escape, ingassing, and the mantle FeO inventory all debit the same O reservoir
You don't have an independent constraint on the planet's O budget You have an O constraint from an FeO-content estimate, a chondritic O/Si ratio, or an observational retrieval
Buffered chemistry is good enough for your scientific question The mantle redox state is itself the unknown you're trying to infer

The two modes give bit-identical results in the cases where they should: for any \(\Delta\mathrm{IW}\) accepted by user_constant, the chemistry returns an O budget; feeding that O budget back through from_O_budget recovers the same \(\Delta\mathrm{IW}\) to within ~0.05 dex (this is the round-trip property pinned by tests/test_authoritative_O.py::TestRoundTrip and documented on the authoritative-oxygen page).

Redox state

fO2_shift_IW is in \(\log_{10}\) units relative to the O'Neill & Eggins (2002) 4 IW buffer. Under fO2_source = "user_constant" this value is the buffer offset; under fO2_source = "from_O_budget" it is only the initial-guess seed. Common reference values:

\(\Delta\mathrm{IW}\) Description
\(-5\) Highly reduced (Mercury-like; sulphur-derived estimate IW-5.4, Cartier & Wood 2019 1)
\(-3\) Reduced (e.g. enstatite-chondrite-like; Mercury Fe-based estimate IW-2.8 to IW-4.5, Cartier & Wood 2019 1)
\(-1\) Moderately reduced; near the Mars-mantle source range (Wadhwa 2001 6 places the shergottite-source mantle at \(\approx\) IW)
\(0\) At iron-wΓΌstite buffer (core formation equilibrium at depth)
\(+3.5\) Sossi et al. (2020) 5 preferred Earth's mantle \(f_{\mathrm{O}_2}\)
\(+4\) CALLIOPE PROTEUS-side default; near-modern Earth upper mantle (within FMQ\(\,\pm\,2\) per Frost & McCammon 2008 2)

Sossi et al. (2020) 5 place Earth's modern upper mantle at \(\Delta\mathrm{IW} \approx +3.5\); Frost & McCammon (2008) 2 report a broader FMQ\(\,\pm\,2\) range across mantle settings (approximately IW+1.5 to IW+5.5). CALLIOPE defaults sit at \(\Delta\mathrm{IW} = 4.0\), consistent with a modern terrestrial composition.

Solver tolerances

The PROTEUS wrapper hard-codes a few solver knobs that override the CALLIOPE library defaults:

Wrapper override Value Reason
nguess \(10^3\) The PROTEUS warm-start almost always lands on the right basin in the first attempt; \(10^3\) Monte-Carlo restarts is plenty of margin without spending wall-time on degenerate cases.
nsolve \(3 \times 10^3\) Per-restart fsolve iteration cap.
opt_solver False Skip the alternating-solver fallback; if fsolve cannot converge from a warm start, it almost always means an upstream bug rather than a basin-of-attraction problem.
print_result False Suppress the per-call INFO log; the PROTEUS main loop already prints the partial pressures.

The TOML field names map to equilibrium_atmosphere keyword arguments as follows:

TOML field Solver argument Role
solver_rtol rtol Relative tolerance on the elemental mass-balance residual.
solver_atol xtol Step tolerance on the inner fsolve Powell-hybrid iteration. The name is historical; it does not set the absolute mass-balance tolerance.
mass_thresh atol Absolute tolerance on mass balance, in kg. Also the per-element threshold below which a species' inventory is treated as zero.

Warm-start behaviour

For Time > 1 yr, the wrapper builds p_guess from the previous-iteration H2O_bar, CO2_bar, N2_bar, S2_bar rows. For Time = 0 (first call) the wrapper passes p_guess = None and lets CALLIOPE's Monte-Carlo initial-guess generator find the basin. If an element's target inventory drops below mass_thresh, the corresponding p_guess is forced to zero so that the solver does not waste iterations chasing a vanishing species.

Common pitfalls

  • Missing required volatile at startup: one of H2O / CO2 / N2 / S2 is set to include = false. Fix: re-enable it (it can still be set to a tiny initial pressure if you want to suppress its mass).
  • Could not find solution for volatile abundances mid-run: CALLIOPE exhausted its nguess Monte-Carlo restarts. Almost always caused by an upstream NaN or unphysical state in hf_row (e.g. T_magma < T_floor after AGNI failed, M_mantle = 0 after a structure-solve failure). Inspect proteus_00.log for the iteration immediately before the CALLIOPE failure; do not bump nguess blindly.
  • Atmosphere mass collapses to zero without escape accounting it: the check_desiccation gate in proteus.outgas.wrapper will refuse to mark the planet desiccated if the loss is unexplained by cumulative escape. This is by design: a real desiccation event must be supported by tracked atmospheric escape, so the gate firing means an upstream module silently zeroed the atmosphere and the failure is upstream of CALLIOPE.
  • Hydrogen inventory must be > 0: H_budget = 0 or H_mode mistyped. CALLIOPE refuses to solve a zero-H system because the speciation is degenerate.
  • Solubility off but Phi_global > 0: not an error, but the wrapper will silently force Phi_global = 0 when [outgas.calliope].solubility = false, so dissolved masses will all be zero regardless of the live melt fraction.

Next step

For what the wrapper actually does on each iteration (sequence diagram, mapping table, hf_row keys), read Coupling to PROTEUS (theory).


  1. C. Cartier, B. J. Wood, The role of reducing conditions in building Mercury, Elements, 15(1), 39–45, 2019. SciX

  2. D. J. Frost, C. A. McCammon, The redox state of Earth's mantle, Annual Review of Earth and Planetary Sciences, 36, 389–420, 2008. SciX

  3. H. Nicholls, T. Lichtenberg, D. J. Bower, R. Pierrehumbert, Magma ocean evolution at arbitrary redox state, Journal of Geophysical Research: Planets, 129, e2024JE008576, 2024. SciX

  4. H. St. C. O'Neill, S. M. Eggins, The effect of melt composition on trace element partitioning: an experimental investigation of the activity coefficients of FeO, NiO, CoO, MoO\(_2\) and MoO\(_3\) in silicate melts, Chemical Geology, 186, 151–181, 2002. SciX

  5. P. A. Sossi, A. D. Burnham, J. Badro, A. Lanzirotti, M. Newville, H. St. C. O'Neill, Redox state of Earth's magma ocean and its Venus-like early atmosphere, Science Advances, 6, eabd1387, 2020. SciX

  6. M. Wadhwa, Redox state of Mars' upper mantle and crust from Eu anomalies in shergottite pyroxenes, Science, 291, 1527–1530, 2001. SciX