Skip to content

Configuration file

PROTEUS uses TOML to structure its configuration files. This page lists all parameters with their types, defaults, and descriptions. For topic-specific parameter guides, see the configuration reference pages:

For worked examples, see the Tutorials.

Defaults and required parameters

Every parameter has a built-in default. The defaults are defined in the configuration schema in src/proteus/config/, so a configuration file only needs to set the parameters whose defaults you want to change. Empty sections can be omitted entirely. The minimal.toml example shows how little a working file needs: a few science-critical choices (planet mass, orbit, volatiles, redox), with everything else left at its default.

Where to find the default for each parameter. The configuration reference pages listed above give the default value of every parameter in their Default column, alongside its type and description. The auto-generated listing further down this page is built from the same schema and shows each parameter's source definition, including the coded default value.

To see exactly which defaults applied to a run, open the init_coupler.toml file in that run's output folder. It is a completed copy of the configuration with every parameter resolved, including all the defaults that were filled in for the options you did not set. This is the fully-expanded configuration the simulation actually used.

Some parameters are conditionally required. A parameter that is only meaningful for one module is required when that module is selected. For example, the mors stellar evolution module (star.module = 'mors') requires star.mors.age_now, whereas the dummy module does not. The configuration loader reports an error at startup if a required parameter is missing for the chosen modules.

Keys outside the schema are refused. Loading a configuration fails if the file contains a key that the schema does not define, and the error names every such key. Because each parameter has a default, a misspelled or outdated option name would otherwise take no effect at all: the run would proceed on the default while the file appears to say otherwise. The check covers nested keys, so planet.elements.H_budgets is caught as readily as a stray top-level section. It runs when a run configuration is loaded, which includes proteus start, the data-download commands, and the base configuration of a parameter grid. If a key is rejected that you expect to exist, check its spelling against the configuration reference pages above or against all_options.toml.

Sections must be written as single tables. Writing [[planet]] rather than [planet] declares an array of tables, which the parser cannot map onto the schema: the section is discarded whole and every parameter inside it reverts to its default. The name itself is spelled correctly in that case, so nothing about it looks wrong in the file. Loading refuses such a section and names it, and the fix is to remove the extra brackets.

See all_options.toml for a comprehensive example. Have a look at the other input configs for ideas of how to set up your config in practice.

Root parameters

Config

Root config parameters.

Attributes:

Name Type Description
config_version str

Version of the configuration file format.

params Params

Parameters for code execution, output files, time-stepping, convergence.

star Star

Stellar parameters, model selection.

orbit Orbit

Orbital and star-system parameters.

planet Planet

Bulk planet properties (mass, initial volatile inventory).

interior_struct Struct

Planetary structure calculation (radius, composition, Zalmoxis).

interior_energetics Interior

Magma ocean / mantle energetics model parameters, model selection.

outgas Outgas

Outgassing parameters (fO2, etc) and included volatiles.

atmos_clim AtmosClim

Planetary atmosphere climate parameters, model selection.

atmos_chem AtmosChem

Planetary atmosphere chemistry parameters, model selection.

escape Escape

Atmospheric escape parameters, model selection.

accretion Accretion

Late accretion / delivery model selection.

observe Observe

Synthetic observations.

write(out, overrides=None)

Write configuration to a new TOML file.

Parameters:

Name Type Description Default
out str

Output path for the TOML file.

required
overrides dict | None

Dotted-path values to substitute in the written file, e.g. {'interior_energetics.aragog.phi_step_cap': 0.1}. Lets a caller record a resolved value (one a module derives from a config default at runtime) without mutating this Config object.

None
Source code in src/proteus/config/_config.py
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
def write(self, out: str, overrides: dict | None = None):
    """
    Write configuration to a new TOML file.

    Parameters
    ----------
    out
        Output path for the TOML file.
    overrides
        Dotted-path values to substitute in the written file, e.g.
        ``{'interior_energetics.aragog.phi_step_cap': 0.1}``. Lets a
        caller record a resolved value (one a module derives from a
        config default at runtime) without mutating this Config object.
    """

    # Convert to dictionary
    cfg = dict(asdict(self))

    # Replace None with "none"
    cfg = dict_replace_none(cfg)

    # Apply any resolved-value overrides
    if overrides:
        # Imported here to avoid a module-level import cycle: orphans
        # imports Config from this module.
        from .orphans import UnknownConfigKeyError

        for dotted_key, value in overrides.items():
            segments = dotted_key.split('.')
            *parents, leaf = segments
            node = cfg
            for depth, key in enumerate(parents):
                if not isinstance(node, dict) or key not in node:
                    failing = '.'.join(segments[: depth + 1])
                    raise UnknownConfigKeyError(
                        f"Override key '{dotted_key}' does not match the schema: "
                        f"no config section '{failing}'."
                    )
                node = node[key]
            if not isinstance(node, dict) or leaf not in node:
                raise UnknownConfigKeyError(
                    f"Override key '{dotted_key}' does not match the schema: "
                    f"no config field '{dotted_key}'."
                )
            if isinstance(node[leaf], dict):
                raise UnknownConfigKeyError(
                    f"Override key '{dotted_key}' targets a config section, "
                    f'not a field; refusing to replace the whole section.'
                )
            # Route None through the same None -> "none" handling used above,
            # so a None override does not reach tomlkit as a bare None.
            node[leaf] = 'none' if value is None else value

    # An Aragog step cap still at its 0.0 schema default (no override
    # replaced it above) is ambiguous with an explicit, disabling 0.0
    # once written to TOML, so omit the key and let a reload fall back
    # to the same schema default it already carries here.
    interior_energetics = cfg.get('interior_energetics')
    if (
        isinstance(interior_energetics, dict)
        and interior_energetics.get('module') == 'aragog'
    ):
        aragog = interior_energetics.get('aragog')
        if isinstance(aragog, dict):
            for field_name in _STEP_CAP_FIELDS:
                if aragog.get(field_name) == 0.0:
                    del aragog[field_name]

    # Write to TOML file
    with open(out, 'w') as hdl:
        tomlkit.dump(cfg, hdl)

spada_zephyrus(instance, attribute, value)

ZEPHYRUS escape requires the MORS star module with the Spada evolution tracks.

Source code in src/proteus/config/_config.py
25
26
27
28
29
30
def spada_zephyrus(instance, attribute, value):
    """ZEPHYRUS escape requires the MORS star module with the Spada evolution tracks."""
    if (instance.escape.module == 'zephyrus') and not (
        (instance.star.module == 'mors') and (instance.star.mors.tracks == 'spada')
    ):
        raise ValueError('ZEPHYRUS must be used with MORS and the Spada evolution tracks')

instmethod_dummy(instance, attribute, value)

Instellation method 'inst' is only available with the dummy star module.

Source code in src/proteus/config/_config.py
33
34
35
36
def instmethod_dummy(instance, attribute, value):
    """Instellation method 'inst' is only available with the dummy star module."""
    if (instance.orbit.instellation_method == 'inst') and not (instance.star.module == 'dummy'):
        raise ValueError("Instellation method can only be 'inst' when star.module=dummy ")

instmethod_evolve(instance, attribute, value)

Orbital evolution cannot be combined with instellation method 'inst'.

'inst' derives the semi-major axis from the flux, while every star-planet model, a prescribed track included, starts from orbit.semimajoraxis.

Source code in src/proteus/config/_config.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def instmethod_evolve(instance, attribute, value):
    """Orbital evolution cannot be combined with instellation method 'inst'.

    'inst' derives the semi-major axis from the flux, while every star-planet
    model, a prescribed track included, starts from orbit.semimajoraxis.
    """
    if (instance.orbit.instellation_method == 'inst') and (
        instance.orbit.star_planet_model is not None
    ):
        raise ValueError(
            "Planet orbital evolution not supported for `instellation_method='inst'`: "
            f'orbit.star_planet_model = {instance.orbit.star_planet_model!r} starts from '
            "orbit.semimajoraxis, so set instellation_method = 'distance'"
        )

satellite_evolve(instance, attribute, value)

Star-planet orbital evolution and the planet-satellite model are mutually exclusive.

Source code in src/proteus/config/_config.py
55
56
57
58
59
60
61
62
63
def satellite_evolve(instance, attribute, value):
    """Star-planet orbital evolution and the planet-satellite model are mutually exclusive."""
    if (
        instance.orbit.star_planet_model is not None
        and instance.orbit.planet_satellite_model is not None
    ):
        raise ValueError(
            'Planet orbital evolution cannot be used simultaneously with a satellite'
        )

tides_enabled_orbit(instance, attribute, value)

Interior tidal heating requires an tides module to be enabled.

Source code in src/proteus/config/_config.py
66
67
68
69
def tides_enabled_orbit(instance, attribute, value):
    """Interior tidal heating requires an tides module to be enabled."""
    if (instance.interior_energetics.heat_tidal) and (instance.orbit.module is None):
        raise ValueError('Interior tidal heating requires a tides module to be enabled')

obliqua_requires_perturber(instance, attribute, value)

The Obliqua tidal-response module requires an explicit perturber.

Source code in src/proteus/config/_config.py
72
73
74
75
76
77
78
def obliqua_requires_perturber(instance, attribute, value):
    """The Obliqua tidal-response module requires an explicit perturber."""
    if instance.orbit.module == 'obliqua' and instance.orbit.perturber is None:
        raise ValueError(
            "orbit.module = 'obliqua' requires orbit.perturber to be explicitly set to "
            "'star' or 'satellite' (it has no default tidal-forcing body to fall back on)"
        )

sp0d_obliqua_degree_mismatch(instance, attribute, value)

sp0d's closed-form is by definition the n=2 Love number. Obliqua can compute arbitrary tidal degree(s), block the mismatch.

Source code in src/proteus/config/_config.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def sp0d_obliqua_degree_mismatch(instance, attribute, value):
    """sp0d's closed-form is by definition the n=2 Love number. Obliqua can compute
    arbitrary tidal degree(s), block the mismatch.
    """
    if instance.orbit.module == 'obliqua' and instance.orbit.star_planet_model == 'sp0d':
        if instance.orbit.obliqua.n != [2]:
            raise ValueError(
                "orbit.star_planet_model = 'sp0d' requires orbit.obliqua.n == [2]: "
                'set orbit.obliqua.n = [2] to use sp0d with Obliqua, or use'
                "orbit.star_planet_model = 'sp1d' instead."
            )
        log.warning(
            "orbit.star_planet_model = 'sp0d' with orbit.module = 'obliqua': Imk2 is "
            "the mean of Obliqua's per-mode Im(k2) spectrum collapsed to a single "
            'scalar, which discards the eccentricity-dependent mode weighting sp1d '
            'uses directly. This is an approximation, least accurate at high or '
            "rapidly-changing eccentricity. Prefer orbit.star_planet_model = 'sp1d'"
            ' when using Obliqua.'
        )

orbit_requires_tides(instance, attribute, value)

sp1d, ps1d, and ps1d_evec require at least Lovepy, but ideally the Obliqua tidal-response module: all three read the full per-mode spectrum in tides_o, which dummy never populates. sp0d/ps0d read the scalar Imk2 instead (which dummy does provide), so they are unrestricted here; see "Compatibility between orbit models and tidal modules" in docs/Explanations/orbit.md.

Source code in src/proteus/config/_config.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def orbit_requires_tides(instance, attribute, value):
    """sp1d, ps1d, and ps1d_evec require at least Lovepy, but ideally the Obliqua
    tidal-response module: all three read the full per-mode spectrum in
    ``tides_o``, which ``dummy`` never populates. ``sp0d``/``ps0d`` read the
    scalar ``Imk2`` instead (which ``dummy`` does provide), so they are
    unrestricted here; see "Compatibility between orbit models and tidal
    modules" in docs/Explanations/orbit.md.
    """
    needs_full_spectrum = instance.orbit.star_planet_model == 'sp1d' or (
        instance.orbit.planet_satellite_model in ('ps1d', 'ps1d_evec')
    )
    if needs_full_spectrum and instance.orbit.module not in ('obliqua', 'lovepy'):
        raise ValueError(
            "orbit.star_planet_model = 'sp1d' or orbit.planet_satellite_model = "
            "'ps1d'/'ps1d_evec' requires orbit.module = 'obliqua' or 'lovepy'"
        )

parameterized_excludes_tides(instance, attribute, value)

The parameterized model prescribes the orbit as a closed-form track and exchanges no energy with the interior, so no tides module can run alongside it; see "Star-planet models" in docs/Explanations/orbit.md.

Source code in src/proteus/config/_config.py
120
121
122
123
124
125
126
127
128
129
130
131
132
def parameterized_excludes_tides(instance, attribute, value):
    """The parameterized model prescribes the orbit as a closed-form track
    and exchanges no energy with the interior, so no tides module can run
    alongside it; see "Star-planet models" in docs/Explanations/orbit.md.
    """
    if (
        instance.orbit.star_planet_model == 'parameterized'
        and instance.orbit.module is not None
    ):
        raise ValueError(
            "orbit.star_planet_model = 'parameterized' requires orbit.module = 'none' "
            f'(got {instance.orbit.module!r}): a prescribed migration track computes no tides'
        )

spinless_orbit_keeps_spin_synchronous(instance, attribute, value)

sp0d and parameterized evolve no spin, so the planet stays synchronous and orbit.axial_period must be unset; see "Star-planet models" in docs/Explanations/orbit.md.

Source code in src/proteus/config/_config.py
135
136
137
138
139
140
141
142
143
144
145
146
def spinless_orbit_keeps_spin_synchronous(instance, attribute, value):
    """sp0d and parameterized evolve no spin, so the planet stays synchronous
    and orbit.axial_period must be unset; see "Star-planet models" in
    docs/Explanations/orbit.md.
    """
    model = instance.orbit.star_planet_model
    if model in ('sp0d', 'parameterized') and instance.orbit.axial_period is not None:
        raise ValueError(
            f'orbit.star_planet_model = {model!r} evolves no spin, so the planet is kept '
            "synchronous with its orbit and requires orbit.axial_period = 'none' "
            f'(got {instance.orbit.axial_period!r})'
        )

parameterized_high_ecc_inward(instance, attribute, value)

The high-eccentricity law circularises at fixed orbital angular momentum, which can only shrink the orbit, so its final semi-major axis must not exceed the initial one, orbit.semimajoraxis.

Source code in src/proteus/config/_config.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def parameterized_high_ecc_inward(instance, attribute, value):
    """The high-eccentricity law circularises at fixed orbital angular momentum,
    which can only shrink the orbit, so its final semi-major axis must not
    exceed the initial one, orbit.semimajoraxis.
    """
    params = instance.orbit.parameterized
    if (
        instance.orbit.star_planet_model == 'parameterized'
        and params.migration == 'high_ecc'
        and params.sma_final is not None
        and params.sma_final > instance.orbit.semimajoraxis
    ):
        raise ValueError(
            'High-eccentricity migration is inward only and requires '
            'orbit.parameterized.sma_final <= orbit.semimajoraxis, got '
            f'semimajoraxis={instance.orbit.semimajoraxis} and sma_final={params.sma_final}'
        )

parameterized_excludes_accretion(instance, attribute, value)

Reject giant impacts with the parameterized star-planet model.

After the migration epoch the track sets the semi-major axis every step, so an impact's new semi-major axis would be lost while its eccentricity change persisted; see "Star-planet models" in docs/Explanations/orbit.md.

Source code in src/proteus/config/_config.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def parameterized_excludes_accretion(instance, attribute, value):
    """Reject giant impacts with the parameterized star-planet model.

    After the migration epoch the track sets the semi-major axis every step,
    so an impact's new semi-major axis would be lost while its eccentricity
    change persisted; see "Star-planet models" in docs/Explanations/orbit.md.
    """
    if (
        instance.orbit.star_planet_model == 'parameterized'
        and instance.accretion.module is not None
    ):
        raise ValueError(
            "orbit.star_planet_model = 'parameterized' requires accretion.module = 'none' "
            f'(got {instance.accretion.module!r}): the prescribed track overwrites the '
            'semi-major axis an impact sets but keeps its eccentricity change'
        )

check_module_dependencies(instance, attribute, value)

Check that required external packages are importable for the selected modules.

Source code in src/proteus/config/_config.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def check_module_dependencies(instance, attribute, value):
    """Check that required external packages are importable for the selected modules."""
    import importlib

    checks = {
        'calliope': (
            instance.outgas.module == 'calliope',
            'calliope',
            'outgas.module = "calliope" requires the calliope package. '
            'Install with: git clone git@github.com:FormingWorlds/CALLIOPE && pip install -e CALLIOPE/.',
        ),
        'atmodeller': (
            instance.outgas.module == 'atmodeller',
            'atmodeller',
            'outgas.module = "atmodeller" requires the optional atmodeller package '
            '(GPL-3.0 licensed). The standard outgassing backend is calliope and '
            'needs no extra install. '
            'Install atmodeller with: pip install "fwl-proteus[atmodeller]" '
            '(or pip install "atmodeller>=1.0.0").',
        ),
        'vulcan': (
            instance.atmos_chem.module == 'vulcan',
            'vulcan',
            'atmos_chem.module = "vulcan" requires the optional VULCAN package '
            '(GPL-3.0 licensed). VULCAN is not needed for a standard PROTEUS run. '
            'Install it with: pip install "fwl-proteus[vulcan]" '
            '(or bash tools/get_vulcan.sh for an editable checkout).',
        ),
        'boreas': (
            instance.escape.module == 'boreas',
            'boreas',
            'escape.module = "boreas" requires the optional boreas package. '
            'Install it with: bash tools/get_boreas.sh',
        ),
        'morrigan': (
            instance.accretion.module == 'morrigan',
            'morrigan',
            'accretion.module = "morrigan" requires the optional morrigan package, '
            'which runs the giant-impact model. Morrigan is not needed for a '
            'standard PROTEUS run. Install it with: pip install '
            '"fwl-proteus[morrigan]" (or bash tools/get_morrigan.sh for an '
            'editable checkout).',
        ),
    }

    for name, (needed, pkg, msg) in checks.items():
        if needed:
            try:
                importlib.import_module(pkg)
            except ImportError as e:
                raise ImportError(f'{msg}\n  Original error: {e}') from e

check_accretion_interior_compatibility(instance, attribute, value)

Reject accretion runs on an interior that cannot apply an impact.

A giant impact re-melts the mantle and has to be applied at the state the timeline places it at. An interior that cannot do both is refused here at configuration load rather than at the first impact, which can be many hours into a run. SPIDER keeps its state in a restart file written by the external binary; the boundary interior never forwards its interior state object to the time-stepper, so the step cannot be capped to land on the impact and impacts would be applied late by an unbounded amount, or several would collapse onto the end of one long step. Furthermore, interior_struct.module == 'spider' is refused because SPIDER structure solves do not support non-thermal solves during giant impacts.

Source code in src/proteus/config/_config.py
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
def check_accretion_interior_compatibility(instance, attribute, value):
    """Reject accretion runs on an interior that cannot apply an impact.

    A giant impact re-melts the mantle and has to be applied at the state the
    timeline places it at. An interior that cannot do both is refused here at
    configuration load rather than at the first impact, which can be many hours
    into a run. SPIDER keeps its state in a restart file written by the external
    binary; the boundary interior never forwards its interior state object to the
    time-stepper, so the step cannot be capped to land on the impact and impacts
    would be applied late by an unbounded amount, or several would collapse onto
    the end of one long step. Furthermore, interior_struct.module == 'spider' is
    refused because SPIDER structure solves do not support non-thermal solves
    during giant impacts.
    """
    if instance.accretion.module is None:
        return

    interior = instance.interior_energetics.module
    reason = _ACCRETION_INCOMPATIBLE_INTERIORS.get(interior)
    if reason is not None:
        raise ValueError(
            "accretion.module = '"
            + str(instance.accretion.module)
            + "' cannot run with interior_energetics.module = '"
            + str(interior)
            + "': a giant impact re-melts the mantle and has to be applied where "
            'the timeline places it, but '
            + reason
            + ". Use interior_energetics.module = 'aragog' (or 'dummy' for a test)."
        )

    struct_module = getattr(getattr(instance, 'interior_struct', None), 'module', None)
    if struct_module == 'spider':
        raise ValueError(
            f"accretion.module = '{instance.accretion.module}' cannot run with "
            "interior_struct.module = 'spider': SPIDER structure solve does not "
            'support non-thermal solves during giant impacts.'
        )

check_accretion_vapourise_compatibility(instance, attribute, value)

Reject accretion runs that also vapourise rock into the atmosphere.

The two models keep incompatible books on the rock-forming elements. Rock vapourisation moves rock mass into the atmosphere without debiting it from the interior, which is why those elements are deliberately left out of M_ele and the whole-planet mass. An impact, in contrast, grows the planet's rock through the structure solve and conserves the per-element volatile budgets across that growth, and it sizes both the atmosphere it strips and the volatiles the impactor delivers from those same per-element columns. Run together, the vapour column would be stripped and delivered as though it were a tracked volatile budget while the mass it stands for is accounted nowhere, so the combination is refused at configuration load.

Source code in src/proteus/config/_config.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
def check_accretion_vapourise_compatibility(instance, attribute, value):
    """Reject accretion runs that also vapourise rock into the atmosphere.

    The two models keep incompatible books on the rock-forming elements. Rock
    vapourisation moves rock mass into the atmosphere without debiting it from
    the interior, which is why those elements are deliberately left out of
    ``M_ele`` and the whole-planet mass. An impact, in contrast, grows the
    planet's rock through the structure solve and conserves the per-element
    volatile budgets across that growth, and it sizes both the atmosphere it
    strips and the volatiles the impactor delivers from those same per-element
    columns. Run together, the vapour column would be stripped and delivered as
    though it were a tracked volatile budget while the mass it stands for is
    accounted nowhere, so the combination is refused at configuration load.
    """
    if instance.accretion.module is not None and instance.outgas.vapourise:
        raise ValueError(
            "accretion.module = '"
            + str(instance.accretion.module)
            + "' cannot run with outgas.vapourise = true: rock vapour adds "
            'rock-forming mass to the atmosphere that the whole-planet mass does '
            'not track, and an impact sizes its atmospheric stripping and volatile '
            'delivery from budgets that do. Disable one of the two.'
        )

check_accretion_core_stratification_compatibility(instance, attribute, value)

Reject accretion runs with a stratified Aragog core_module core.

An impact refits the core and books the heat of its temperature lift from the full-core heat content, while a stratified core evolves only its convecting volume, so the booked lift would not be the heat the solver integrates.

Source code in src/proteus/config/_config.py
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
def check_accretion_core_stratification_compatibility(instance, attribute, value):
    """Reject accretion runs with a stratified Aragog core_module core.

    An impact refits the core and books the heat of its temperature lift from the
    full-core heat content, while a stratified core evolves only its convecting
    volume, so the booked lift would not be the heat the solver integrates.
    """
    interior = instance.interior_energetics
    if (
        instance.accretion.module is not None
        and interior.module == 'aragog'
        and interior.aragog.core_bc == 'core_module'
        and interior.aragog.core_module.stratification
    ):
        raise ValueError(
            f"accretion.module = '{instance.accretion.module}' cannot run with "
            'interior_energetics.aragog.core_module.stratification = true: the core heat '
            'booked at an impact is the full-core content, while a stratified core evolves '
            'only its convecting volume. Disable one of the two.'
        )

check_core_module_requirements(instance, attribute, value)

Reject an Aragog core_module core that its structure or solver cannot serve.

The core profile is fitted to the core mass and the central pressure, which only the Zalmoxis structure provides, its CMB flux needs the entropy EOS tables, and Aragog refuses the resolved shell of a stratified core on Radau. A stratified core cannot be carried across a structure update, because Aragog gives no heat content for a stratified budget, so it needs a structure that is solved once: no dynamic update and no baseline re-solve after the start.

Source code in src/proteus/config/_config.py
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
def check_core_module_requirements(instance, attribute, value):
    """Reject an Aragog core_module core that its structure or solver cannot serve.

    The core profile is fitted to the core mass and the central pressure, which only the
    Zalmoxis structure provides, its CMB flux needs the entropy EOS tables, and Aragog
    refuses the resolved shell of a stratified core on Radau. A stratified core cannot be
    carried across a structure update, because Aragog gives no heat content for a
    stratified budget, so it needs a structure that is solved once: no dynamic update and
    no baseline re-solve after the start.
    """
    interior = instance.interior_energetics
    if interior.module != 'aragog' or interior.aragog.core_bc != 'core_module':
        return
    if instance.interior_struct.module != 'zalmoxis':
        raise ValueError(
            "interior_energetics.aragog.core_bc = 'core_module' needs "
            f"interior_struct.module = 'zalmoxis', not '{instance.interior_struct.module}': "
            'the core profile is fitted to the core mass and the central pressure of the '
            'Zalmoxis structure.'
        )
    if interior.const_properties:
        raise ValueError(
            "interior_energetics.aragog.core_bc = 'core_module' needs "
            'interior_energetics.const_properties = false: the CMB flux of the core model '
            'is computed with the entropy EOS tables, which constant properties do not load.'
        )
    if not interior.aragog.core_module.stratification:
        return
    if interior.aragog.solver_method == 'radau':
        raise ValueError(
            'interior_energetics.aragog.core_module.stratification = true needs '
            "interior_energetics.aragog.solver_method = 'cvode' or 'bdf', not 'radau'."
        )
    if (
        instance.interior_struct.zalmoxis.update_interval > 0
        or instance.planet.temperature_mode != 'liquidus_super'
    ):
        raise ValueError(
            'interior_energetics.aragog.core_module.stratification = true needs a structure '
            'that is solved once: interior_struct.zalmoxis.update_interval = 0 and '
            "planet.temperature_mode = 'liquidus_super'. The core is carried across a "
            'structure update with its heat content, which Aragog does not give for a '
            'stratified core.'
        )

boreas_requires_atmosphere(instance, attribute, value)

BOREAS escape requires a radiative atmosphere (not dummy).

Source code in src/proteus/config/_config.py
398
399
400
401
402
403
404
def boreas_requires_atmosphere(instance, attribute, value):
    """BOREAS escape requires a radiative atmosphere (not dummy)."""
    if (instance.escape.module == 'boreas') and (instance.atmos_clim.module == 'dummy'):
        raise ValueError(
            'escape.module = "boreas" requires a radiative atmosphere model (agni or janus), '
            'not atmos_clim.module = "dummy". BOREAS needs per-level T/P/composition profiles.'
        )

observe_resolved_atmosphere(instance, attribute, value)

Synthetic observations require a spatially resolved atmosphere (not dummy).

Source code in src/proteus/config/_config.py
407
408
409
410
def observe_resolved_atmosphere(instance, attribute, value):
    """Synthetic observations require a spatially resolved atmosphere (not dummy)."""
    if (instance.observe.module is not None) and (instance.atmos_clim.module == 'dummy'):
        raise ValueError('Observational synthesis requires that atmos_clim != dummy')

janus_escape_atmosphere(instance, attribute, value)

ZEPHYRUS escape with JANUS requires the escape stop criterion to be enabled.

Source code in src/proteus/config/_config.py
413
414
415
416
417
418
419
420
421
422
def janus_escape_atmosphere(instance, attribute, value):
    """ZEPHYRUS escape with JANUS requires the escape stop criterion to be enabled."""
    if (
        (instance.escape.module == 'zephyrus')
        and (instance.atmos_clim.module == 'janus')
        and (not instance.params.stop.escape.enabled)
    ):
        raise ValueError(
            'When using escape.zephyrus with JANUS, params.stop.escape must be True.'
        )

planet_mass_valid(instance, attribute, value)

Validate that mass_tot is within range.

Source code in src/proteus/config/_config.py
425
426
427
428
429
430
431
def planet_mass_valid(instance, attribute, value):
    """Validate that mass_tot is within range."""
    mass_tot = instance.planet.mass_tot
    if mass_tot <= 0:
        raise ValueError('The total planet mass must be > 0')
    if mass_tot > 20:
        raise ValueError('The total planet mass must be < 20 M_earth')

planet_oxygen_mode_explicit(instance, attribute, value)

Validate O_mode in planet.elements.

Whole-planet oxygen accounting requires every config to declare how the IC O budget is interpreted. Valid modes: 'ic_chemistry' (default, defer to CALLIOPE equilibrium), 'ppmw', 'kg', 'FeO_mantle_wt_pct'.

Source code in src/proteus/config/_config.py
434
435
436
437
438
439
440
441
442
443
444
def planet_oxygen_mode_explicit(instance, attribute, value):
    """Validate O_mode in planet.elements.

    Whole-planet oxygen accounting requires every config to declare how
    the IC O budget is interpreted. Valid modes: 'ic_chemistry' (default,
    defer to CALLIOPE equilibrium), 'ppmw', 'kg', 'FeO_mantle_wt_pct'.
    """
    # O_mode is now validated by the attrs in_() validator on the field
    # itself. This function remains as a hook for cross-field checks
    # (e.g. fO2_source compatibility) that reference O_mode.
    pass

planet_liquidus_super_needs_tables(instance, attribute, value)

Require a P-S table route for temperature_mode = "liquidus_super".

The spider and aragog interior modules build the liquidus_super initial condition on interior P-S tables, which the interior wrapper supplies for the "zalmoxis", "spider" and "dummy" structure modules. With no structure module, no table set is provided and the initial condition has nothing to solve on.

Source code in src/proteus/config/_config.py
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
def planet_liquidus_super_needs_tables(instance, attribute, value):
    """Require a P-S table route for ``temperature_mode = "liquidus_super"``.

    The spider and aragog interior modules build the ``liquidus_super``
    initial condition on interior P-S tables, which the interior wrapper
    supplies for the ``"zalmoxis"``, ``"spider"`` and ``"dummy"`` structure
    modules. With no structure module, no table set is provided
    and the initial condition has nothing to solve on.
    """
    if value.temperature_mode != 'liquidus_super':
        return
    if instance.interior_energetics.module not in ('spider', 'aragog'):
        return
    if instance.interior_struct.module is None:
        raise ValueError(
            "planet.temperature_mode = 'liquidus_super' has no valid route with "
            'interior_struct.module = None under interior_energetics.module = '
            f"'{instance.interior_energetics.module}': no P-S table set is provided. "
            "Set interior_struct.module to 'spider', 'dummy' or 'zalmoxis', or "
            'choose another planet.temperature_mode.'
        )

energetics_needs_a_thermal_mantle_eos(instance, attribute, value)

Require a PALEOS or temperature-dependent energetics key for SPIDER or Aragog.

Under the Zalmoxis structure the energetics read the tables and melting curves of the mantle's energetics key; a Seager2007, Chabrier or analytic key has none, so the run would read another set as a stand-in.

Source code in src/proteus/config/_config.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
def energetics_needs_a_thermal_mantle_eos(instance, attribute, value):
    """Require a PALEOS or temperature-dependent energetics key for SPIDER or Aragog.

    Under the Zalmoxis structure the energetics read the tables and melting
    curves of the mantle's energetics key; a Seager2007, Chabrier or analytic
    key has none, so the run would read another set as a stand-in.
    """
    from proteus.utils.constants import PALEOS_EOS_PREFIXES, TDEP_EOS_PREFIXES
    from proteus.utils.helper import energetics_eos_key

    struct = instance.interior_struct
    zc = struct.zalmoxis if struct.module == 'zalmoxis' else None
    if value.module not in ('spider', 'aragog') or zc is None:
        return
    key = energetics_eos_key(zc.mantle_eos)
    if not (key or '').startswith(PALEOS_EOS_PREFIXES + TDEP_EOS_PREFIXES):
        raise ValueError(
            f"interior_energetics.module = '{value.module}' with mantle_eos="
            f"'{zc.mantle_eos}' has no energetics tables (energetics key: {key}). "
            'Use a PALEOS, WolfBower2018 or RTPress100TPa mantle EOS, or an MgSiO3 component '
            'of one of them in a mixture.'
        )

aragog_needs_melting_curves(instance, attribute, value)

Require interior_struct.melting_dir for Aragog without a PALEOS table set.

Aragog reads a solidus and a liquidus in every mode, from the generated PALEOS set or from melting_dir. SPIDER is checked when the run starts instead: a resumed run continues on its kept P-S tables, and a const_properties run reads no curves.

Source code in src/proteus/config/_config.py
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
def aragog_needs_melting_curves(instance, attribute, value):
    """Require ``interior_struct.melting_dir`` for Aragog without a PALEOS table set.

    Aragog reads a solidus and a liquidus in every mode, from the generated
    PALEOS set or from ``melting_dir``. SPIDER is checked when the run starts
    instead: a resumed run continues on its kept P-S tables, and a
    ``const_properties`` run reads no curves.
    """
    from proteus.utils.helper import generates_paleos_tables

    if value.module != 'aragog' or instance.interior_struct.melting_dir is not None:
        return
    if generates_paleos_tables(instance.interior_struct):
        return
    raise ValueError(
        "interior_energetics.module = 'aragog' needs interior_struct.melting_dir unless Zalmoxis "
        'generates a PALEOS table set (a PALEOS mantle EOS under the Zalmoxis structure; a mixture '
        'follows its MgSiO3 component). The dummy structure requires it with any mantle EOS. '
        'Set melting_dir to a melting curve name (e.g. "Monteux-600").'
    )

dummy_struct_mantle_eos(instance, attribute, value)

Check the mantle EOS that the dummy structure hands to SPIDER or Aragog.

A mixture with two MgSiO3 sources has no energetics key and is rejected. With a PALEOS energetics key the solve uses the PALEOS P-S set and its curves, so a set melting_dir is passed to Aragog but does not change the solve; one warning says so.

Source code in src/proteus/config/_config.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
def dummy_struct_mantle_eos(instance, attribute, value):
    """Check the mantle EOS that the dummy structure hands to SPIDER or Aragog.

    A mixture with two MgSiO3 sources has no energetics key and is rejected. With a
    PALEOS energetics key the solve uses the PALEOS P-S set and its curves, so a set
    ``melting_dir`` is passed to Aragog but does not change the solve; one warning says so.
    """
    from proteus.utils.constants import PALEOS_REGISTRY_KEYS
    from proteus.utils.helper import energetics_eos_key

    struct = instance.interior_struct
    if struct.module != 'dummy' or struct.zalmoxis is None:
        return
    if value.module not in ('spider', 'aragog'):
        return
    key = energetics_eos_key(struct.zalmoxis.mantle_eos)
    if struct.melting_dir is not None and key in PALEOS_REGISTRY_KEYS:
        log.warning(
            'interior_struct.melting_dir=%r does not change the solve: with module = "dummy" '
            'and the PALEOS mantle EOS %s, %s uses the PALEOS P-S set and its curves.',
            struct.melting_dir,
            struct.zalmoxis.mantle_eos,
            value.module,
        )

planet_fO2_source_compat(instance, attribute, value)

Validate planet.fO2_source against O_mode, volatile_mode, and against availability.

Rejection rules:

  1. fO2_source = "from_mantle_redox" is a reserved enum value for the radial Fe3+/Fe2+ fO2 framework (issue #653, Schaefer et al. 2024). The runtime path for it does not exist yet; reject the config so users do not silently fall through to the default behaviour.

  2. fO2_source = "from_O_budget" requires the O budget to be authoritative. O_mode = "ic_chemistry" defers the O inventory to the chemistry solver, so there is nothing to invert against. Require a concrete O budget ("ppmw" / "kg" / "FeO_mantle_wt_pct") instead.

  3. fO2_source = "from_O_budget" requires the volatile budget to be set element-wise. volatile_mode = "gas_prs" supplies partial pressures directly and makes planet.elements.O_mode inoperative, so there is nothing to invert against. Switch volatile_mode to "elements" or pick a different fO2_source.

  4. fO2_source = "from_O_budget" requires an outgassing backend with an authoritative-O implementation: CALLIOPE (equilibrium_atmosphere_authoritative_O) and atmodeller (native mass-constraint API) both qualify; the dummy backend does not. The runtime dispatch echoes this rejection too, but failing at config-load saves the user from burning interior IC and structure setup before hitting the wall.

fO2_source = "user_constant" (default) accepts every O_mode and every volatile_mode.

Warning rule:

  • fO2_source = "from_O_budget" with a non-default outgas.fO2_shift_IW emits a UserWarning. With this source the buffer offset is derived, so a user-supplied value is silently ignored. The warning surfaces the misconfiguration without blocking the run.
Source code in src/proteus/config/_config.py
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
def planet_fO2_source_compat(instance, attribute, value):
    """Validate planet.fO2_source against O_mode, volatile_mode, and
    against availability.

    Rejection rules:

    1. ``fO2_source = "from_mantle_redox"`` is a reserved enum value for
       the radial Fe3+/Fe2+ fO2 framework (issue #653, Schaefer et al.
       2024). The runtime path for it does not exist yet; reject the
       config so users do not silently fall through to the default behaviour.

    2. ``fO2_source = "from_O_budget"`` requires the O budget to be
       authoritative. ``O_mode = "ic_chemistry"`` defers the O inventory
       to the chemistry solver, so there is nothing to invert against.
       Require a concrete O budget
       ("ppmw" / "kg" / "FeO_mantle_wt_pct") instead.

    3. ``fO2_source = "from_O_budget"`` requires the volatile budget to
       be set element-wise. ``volatile_mode = "gas_prs"`` supplies
       partial pressures directly and makes ``planet.elements.O_mode``
       inoperative, so there is nothing to invert against. Switch
       ``volatile_mode`` to ``"elements"`` or pick a different fO2_source.

    4. ``fO2_source = "from_O_budget"`` requires an outgassing backend
       with an authoritative-O implementation: CALLIOPE
       (``equilibrium_atmosphere_authoritative_O``) and atmodeller
       (native mass-constraint API) both qualify; the ``dummy`` backend
       does not. The runtime dispatch echoes this rejection too, but
       failing at config-load saves the user from burning interior IC
       and structure setup before hitting the wall.

    ``fO2_source = "user_constant"`` (default) accepts every O_mode and
    every volatile_mode.

    Warning rule:

    - ``fO2_source = "from_O_budget"`` with a non-default
      ``outgas.fO2_shift_IW`` emits a UserWarning. With this source the
      buffer offset is *derived*, so a user-supplied value is silently
      ignored. The warning surfaces the misconfiguration without
      blocking the run.
    """
    fO2_source = instance.planet.fO2_source
    O_mode = instance.planet.elements.O_mode
    volatile_mode = instance.planet.volatile_mode

    if fO2_source == 'from_mantle_redox':
        raise ValueError(
            'planet.fO2_source = "from_mantle_redox" is reserved for the '
            'radial Fe3+/Fe2+ tracking framework (issue #653) and is not '
            'yet wired into the runtime. Use "user_constant" (fO2 '
            'buffered by outgas.fO2_shift_IW) or "from_O_budget" '
            '(authoritative O budget, fO2 derived) instead.'
        )

    if fO2_source == 'from_O_budget' and O_mode == 'ic_chemistry':
        raise ValueError(
            'planet.fO2_source = "from_O_budget" requires an authoritative '
            'O budget but planet.elements.O_mode = "ic_chemistry" defers '
            'the O inventory to the chemistry solver. Pick an explicit O '
            'budget mode ("ppmw", "kg", or "FeO_mantle_wt_pct") or switch '
            'fO2_source back to "user_constant".'
        )

    if fO2_source == 'from_O_budget' and volatile_mode == 'gas_prs':
        raise ValueError(
            'planet.fO2_source = "from_O_budget" requires '
            'planet.volatile_mode = "elements" so the O inventory is '
            'derived from planet.elements.O_budget. Under '
            'volatile_mode = "gas_prs" the user supplies partial '
            'pressures directly and the element budgets are inert, so '
            'there is no O target to invert against. Either switch '
            'volatile_mode to "elements" or set fO2_source back to '
            '"user_constant".'
        )

    if fO2_source == 'from_O_budget':
        outgas_module = getattr(instance.outgas, 'module', None)
        if outgas_module == 'dummy':
            raise ValueError(
                'planet.fO2_source = "from_O_budget" requires an '
                'outgassing backend with an authoritative-O '
                'implementation. outgas.module = "dummy" has no '
                'chemistry to invert against. Switch outgas.module to '
                '"calliope" or '
                '"atmodeller", or set fO2_source back to '
                '"user_constant".'
            )

    if fO2_source == 'from_O_budget':
        import warnings

        fO2_shift_IW = getattr(instance.outgas, 'fO2_shift_IW', None)
        if fO2_shift_IW is not None and fO2_shift_IW != 0.0:
            warnings.warn(
                'outgas.fO2_shift_IW = %.3f is set but '
                'planet.fO2_source = "from_O_budget" derives the buffer '
                'offset from the O budget. The configured value is used '
                'only as the solver initial fO2 guess, not as the buffered '
                'offset.' % fO2_shift_IW,
                UserWarning,
                stacklevel=2,
            )

boundary_requires_fixed_surface_state(instance, attribute, value)

Boundary backend assumes a fixed surface state coupling.

Source code in src/proteus/config/_config.py
647
648
649
650
651
652
653
654
def boundary_requires_fixed_surface_state(instance, attribute, value):
    """Boundary backend assumes a fixed surface state coupling."""
    if (instance.interior_energetics.module == 'boundary') and (
        instance.atmos_clim.surf_state != 'fixed'
    ):
        raise ValueError(
            "Must set atmos_clim.surf_state='fixed' when interior_energetics.module='boundary'"
        )

General parameters

This module describes the parameters for the data location, data output, and logging. It also defines stopping criteria.

OutputParams

Parameters for output files and logging.

Note that write_mod and dt_write_rel are independent triggers for writing output files. The model will write output if either trigger is individually satisfied.

Attributes:

Name Type Description
path str

Output folder name inside PROTEUS/output/. Set to "auto" (default) for a unique timestamped name (run_YYYYMMDD_HHMMSS_xxxx), or any string for a fixed folder (e.g. "my_earth_run").

logging str

Log verbosity. Choices: 'INFO', 'DEBUG', 'ERROR', 'WARNING'.

plot_fmt str

Plotting output file format. Choices: "png", "pdf".

write_mod int

Write data iteration-interval trigger. 0: wait until completion. n: every n iterations.

dt_write_rel float

Write data time-interval trigger. Expressed as a fraction of the current simulation time. Set to 0 to disable.

plot_mod int | None

Plotting frequency. 0: wait until completion. n: every n iterations. None: never plot.

archive_mod int | None

Archive frequency. 0: wait until completion. n: every n iterations. None: never archive.

remove_sf bool

Remove SOCRATES spectral files after model terminates.

TimeStepParams

Parameters for time-stepping.

Attributes:

Name Type Description
minimum float

Minimum absolute time-step size [yr].

minimum_rel float

Minimum relative time-step size [dimensionless].

maximum float

Maximum time-step size [yr].

initial float

Initial time-step size [yr].

starspec float

Maximum interval at which to recalculate the stellar spectrum [yr].

starinst float

Maximum interval at which to recalculate instellation flux [yr].

method str

Time-stepping method. Choices: 'proportional', 'adaptive', 'maximum'.

propconst float

Proportionality constant (proportional method).

atol float

Absolute tolerance on time-step size (adaptive method) [yr].

rtol float

Relative tolerance on time-step size (adaptive method) [dimensionless].

scale_incr float

Scale factor to grow time-step on a successful adaptive step [dimensionless, must be >1].

scale_decr float

Scale factor to shrink time-step on a rejected adaptive step [dimensionless, in (0, 1)].

window int

Number of previous steps to consider for adaptive-method comparison [dimensionless].

max_growth_factor float

Cap on the dt growth ratio between consecutive steps [dimensionless]. Bounds dtswitch / dtprev, preventing large jumps that can wedge the interior solver; 0 (default) disables the cap. After a step that landed on a giant impact, dtprev is the larger of that step and min(dtfloor, last step that did not land on an impact), or dtfloor when there is no such step.

maximum_rel float

Time-fraction allowance added to dt.maximum on every step [dimensionless]. The effective per-step cap is the sum dt.maximum + maximum_rel * Time, so at the default 1.0 the allowance grows linearly with simulation Time, and the absolute dt.maximum acts as the early-time floor (cap doubles at Time = dt.maximum). Set maximum_rel = 0.0 to disable the time-proportional allowance and recover the strict dt = min(dt.maximum, ...) cap.

mushy_maximum float

Maximum time-step size [yr] during the mushy-zone transition (phi_crit < Phi_global < mushy_upper). Tighter than maximum because the interior solver hits stiffness cliffs in this regime (phase-boundary Jgrav + rheology contrast). Set to 0 (default) to disable the mushy-regime cap, in which case maximum applies throughout. A typical value for Aragog at 1 M_E is ~4e3 yr; see input/tutorials/tutorial_earth.toml.

mushy_upper float

Upper bound of the mushy regime [dimensionless melt fraction]. When Phi_global < mushy_upper AND Phi_global > stop.solid.phi_crit, mushy_maximum takes over from maximum. Default 0.99 so the cap kicks in as soon as the first cell crystallises.

evection_maximum float | str

Ceiling on the time-step size [yr] while the planet-satellite system is inside, or approaching, the evection resonance band. Must be > 0 when set. Default 'none' (disables the whole mechanism).

evection_target_rel_de float

Target maximum fractional change in eccentricity_sat per macro-step while inside/approaching the evection band. Obliqua's own adaptive mode-window selection is keyed on the eccentricity it is given at call time, and that window is then held fixed for the whole of the following macro-step, so a large fractional swing in e within one step risks needing modes outside that window. Default 0.05 (5%).

evection_de_floor float

Floor on the eccentricity value used in the evection_target_rel_de ratio's denominator, so a tiny eccentricity right at capture onset does not make the allowed step blow up. Default 0.02.

evection_rate_window int

Number of trailing accepted macro-steps used to estimate the SECULAR |de/dt| this cap bounds against (a least-squares linear fit for windows > 2, the plain two-point difference at the default of 2). Default 2, preserves the two-point behaviour.

evection_growth_factor float | str

Cap on the dt growth ratio between consecutive steps while the system is inside/approaching the evection band, or within evection_cooldown_iters steps of having left it. Separate from the global max_growth_factor (which most evection runs leave disabled, since it would also throttle ordinary bulk evolution for the rest of the run). Must be > 0 when set. Default 'none' (disabled).

evection_cooldown_iters int | str

Number of PROTEUS iterations, after the system is no longer judged in/near the evection band, during which evection_growth_factor remains active. Refreshed to this value on every iteration the zone is active, so a long stay in the band does not exhaust it before exit. Must be > 0 when set. Default 'none' (no cooldown tail; growth limiting turns off the instant the zone is left).

hysteresis_iters int

Number of PROTEUS iterations after an adaptive "slow down" decision during which the speed-up factor is suppressed. Prevents the controller from ramping dt straight back into the same stiffness cliff it just escaped from. Default 3; set to 0 to disable.

hysteresis_sfinc float

Replacement speed-up factor applied while the hysteresis counter is active. Must be >= 1.0 and <= SFINC (1.6). Default 1.1 (gentle ramp-up).

impact_maximum float

Ceiling [yr] for the step that reaches a scheduled giant impact. Such a step is otherwise clamped only to the time left before the impact, which after a long quiescent phase can itself be large. With the ceiling set, a reaching step longer than max(impact_maximum, dtfloor) (dtfloor = dt.minimum + dt.minimum_rel * Time) is cut to an equal fraction of the time left; later steps are evaluated again, and the step that lands is no longer than max(impact_maximum, dtfloor). Set to 0 (default) to disable. Independent of this setting, the step that lands on an impact can be shorter than dtfloor, a step ending at most 1e-3 yr short of an impact is extended onto it (unless the step already reaches stop.time.maximum), and an impact is applied at the end of the first step that reaches its time. Impacts each less than 1e-3 yr after the one before form a chain that the step aims at the last of, so an impact in a chain can be applied later than its time by up to the chain span; a chain spanning 1e-3 yr or more is logged as a warning.

StopIters

Parameters for iteration number criteria.

Attributes:

Name Type Description
enabled bool

Enable criteria if True

minimum int

Minimum number of iterations.

maximum int

Maximum number of iterations.

StopTime

Parameters for maximum time criteria.

Attributes:

Name Type Description
enabled bool

Enable criteria if True

minimum float

Model will absolutely not terminate until at least this time is reached [yr].

maximum float

Model will terminate when this time is reached [yr].

StopSolid

Parameters for solidification criteria.

Attributes:

Name Type Description
enabled bool

Enable termination at solidification if True.

phi_crit float

Model will terminate (if enabled) or freeze volatiles (if freeze_volatiles) when global melt fraction drops below this value.

freeze_volatiles bool

When True, outgassing stops at crystallization (Phi_global < phi_crit) but the simulation continues. Dissolved volatiles are trapped in the solid mantle and preserved in the helpfile. The atmosphere retains its current composition. When False, outgassing continues regardless of melt fraction.

StopRadeqm

Parameters for radiative equilibrium stopping criteria.

Attributes:

Name Type Description
enabled bool

Enable criteria if True

atol float

Absolute tolerance on energy balance [W m-2].

rtol float

Relative tolerance on energy balance.

StopEscape

Parameters for escape stopping criteria.

Attributes:

Name Type Description
enabled bool

Enable criteria if True

p_stop float

Model will terminate when surface pressure is less than this value [bar].

StopDisint

Parameters for planet disintegration stopping criteria.

Attributes:

Name Type Description
enabled bool

Enable all planet disintegration criteria if True

roche_enabled bool

Disable Roche limit criterion

offset_roche float

Absolute correction (+/-) to (increase/decrease) calculated Roche limit [m].

spin_enabled bool

Disable Breakup period criterion

offset_spin float

Absolute correction (+/-) to (increase/decrease) calculated Breakup period [s].

StopDisintSat

Parameters for satellite disintegration stopping criteria.

Attributes:

Name Type Description
enabled bool

Enable all planet disintegration criteria if True

roche_enabled bool

Disable Roche limit criterion

offset_roche float

Absolute correction (+/-) to (increase/decrease) calculated Roche limit [m].

spin_enabled bool

Disable Breakup period criterion

offset_spin float

Absolute correction (+/-) to (increase/decrease) calculated Breakup period [s].

StopSatellite

Parameters for satellite escape stopping criteria.

Attributes:

Name Type Description
enabled bool

Enable criteria if True

sma_max float

Maximum semi-major axis for the satellite [R_Earth].

StopClock

Parameters for maximum clock runtime stopping criteria.

Attributes:

Name Type Description
enabled bool

Enable criteria if True

maximum float

Model will terminate when runtime exceeds this value [s].

StopStall

Parameters for the unconverged-atmosphere stopping criteria.

Attributes:

Name Type Description
enabled bool

Enable criteria if True

maximum int

Model will terminate after this many consecutive iterations without a converged atmosphere solve, whatever the interior is doing. Sized on 27 stalled GJ 9827 d cases as they stood on 2026-08-08: the deepest streak a run recovered from is 126 and the deepest open one, which never converged, is 233. Raising it past the open streak defeats the criterion; lowering it below the recovered one ends runs that come back.

StopParams

Parameters for termination criteria.

Attributes:

Name Type Description
strict bool

Require termination criteria to be satisfied twice before the model exits.

iters StopIters

Parameters for iteration number criteria.

time StopTime

Parameters for maximum time criteria.

solid StopSolid

Parameters for solidification criteria.

radeqm StopRadeqm

Parameters for radiative equilibrium criteria.

escape StopEscape

Parameters for escape criteria.

disint StopDisint

Parameters for planet disintegration criteria.

disint_sat StopDisintSat

Parameters for satellite disintegration criteria.

clock StopClock

Parameters for maximum clock runtime criteria.

stall StopStall

Parameters for the unconverged-atmosphere criteria.

Params

Parameters for code execution, output files, time-stepping, convergence.

Attributes:

Name Type Description
out OutputParams

Parameters for data / logging output.

dt TimeStepParams

Parameters for time-stepping.

stop StopParams

Parameters for stopping criteria.

resume bool

Resume the simulation from the last archived state in the output folder, instead of starting from scratch.

offline bool

Run without network access; never download reference data, and fail if a required file is missing locally.

max_bigger_than_min(instance, attribute, value)

The maximum must exceed the minimum on the same section.

Source code in src/proteus/config/_params.py
17
18
19
20
def max_bigger_than_min(instance, attribute, value):
    """The maximum must exceed the minimum on the same section."""
    if value <= instance.minimum:
        raise ValueError("'maximum' has to be bigger than 'minimum'.")

Stellar evolution

Mors

Module parameters for MORS module.

Attributes:

Name Type Description
rot_pcntle float

Rotation, as percentile of stellar population.

rot_period float

Rotation rate [days].

tracks str

Stellar evolution track to be used. Choices: 'spada', 'baraffe'.

age_now float

Observed estimated age of the star [Gyr].

star_name str

Name of the star, to find appropriate stellar spectrum. See documentation.

star_path str

Path to custom stellar spectra. If 'none', star_name will be used to find spectra in default locations.

spectrum_source str

Source of stellar spectra. Choices: 'solar', 'muscles', 'phoenix', 'none'.

phoenix_FeH float

Stellar metallicity [Fe/H] to be used for PHOENIX synthetic spectra, if spectrum_source is 'phoenix'.

phoenix_alpha float

Alpha-element enhancement [alpha/Fe] to be used for PHOENIX synthetic spectra, if spectrum_source is 'phoenix'.

phoenix_radius float

Stellar radius [R_sun]. If 'none', radius will be calculated using mors' stellar tracks, if spectrum_source is 'phoenix'.

phoenix_log_g float

Surface gravity [cgs]. If 'none', log g will be calculated will be calculated using mors' stellar tracks, if spectrum_source is 'phoenix'.

phoenix_Teff float

Effective temperature [K]. If 'none', Teff will be calculated will be calculated using mors' stellar tracks, if spectrum_source is 'phoenix'.

StarDummy

Dummy star module.

Attributes:

Name Type Description
radius float

Observed radius [R_sun].

calculate_radius bool

Calculate the radius using empirical mass-luminosity and mass-radius relation

Teff float

Observed effective temperature [K].

Star

Stellar parameters, model selection.

You can find useful reference data in the documentation.

Attributes:

Name Type Description
bol_scale float

Scale factor applied to bolometrically modulate the stellar fluxes.

bol_scale_start float | None

Stellar age from which bol_scale is applied [Gyr]. 'None' to disable.

bol_scale_duration float

Duration for which bol_scale is applied [Gyr].

mass float

Stellar mass [M_sun]. Note that for Mors, it should be between 0.1 and 1.25 solar masses. Values outside of the valid range will be clipped.

age_ini float

Stellar age at model initialisation, relative to stellar birthline [Gyr].

module str | None

Select star module to use.

mors Mors

Parameters for MORS module.

dummy StarDummy

Parameters for the dummy star module

valid_mors(instance, attribute, value)

Validate MORS settings: positive age, spectrum-source requirements, and rotation set by exactly one of percentile or period.

Source code in src/proteus/config/_star.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def valid_mors(instance, attribute, value):
    """Validate MORS settings: positive age, spectrum-source requirements, and rotation
    set by exactly one of percentile or period."""
    if instance.module != 'mors':
        return

    if (instance.mors.age_now is None) or (instance.mors.age_now <= 0):
        raise ValueError('mors.age_now must be > 0')
    src = instance.mors.spectrum_source

    # star_name required for solar and muscles spectra (not for phoenix)
    if instance.mors.star_name is None and src in ('solar', 'muscles'):
        raise ValueError('Must provide mors.star_name for spectrum_source=%s' % src)
    if src == 'phoenix':
        if instance.mors.phoenix_alpha is None or instance.mors.phoenix_FeH is None:
            raise ValueError(
                'mors.phoenix_alpha and mors.phoenix_FeH must be set when using PHOENIX spectra'
            )

    set_pcntle = instance.mors.rot_pcntle is not None
    set_period = instance.mors.rot_period is not None

    if set_pcntle and set_period:
        raise ValueError('Stellar rotation must be set by percentile or period, not both')
    if (not set_pcntle) and (not set_period):
        raise ValueError('Stellar rotation must be set either by percentile or period')

    if set_pcntle and not (0 <= instance.mors.rot_pcntle <= 100):
        raise ValueError('Rotation percentile must be >=0 and <=100')

    if set_period and (instance.mors.rot_period <= 0):
        raise ValueError('Rotation period must be greater than zero')

valid_bol_scale_start(instance, attribute, value)

A bolometric scaling other than 1 requires bol_scale_start and a positive duration.

Source code in src/proteus/config/_star.py
114
115
116
117
118
119
120
121
def valid_bol_scale_start(instance, attribute, value):
    """A bolometric scaling other than 1 requires bol_scale_start and a positive duration."""
    if instance.bol_scale == 1.0:
        return
    if value is None:
        raise ValueError('star.bol_scale_start must be set when star.bol_scale != 1.0')
    if instance.bol_scale_duration <= 0.0:
        raise ValueError('star.bol_scale_duration must be > 0 when star.bol_scale != 1.0')

valid_stardummy(instance, attribute, value)

Dummy star requires a consistent radius specification and a valid Teff.

Source code in src/proteus/config/_star.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def valid_stardummy(instance, attribute, value):
    """Dummy star requires a consistent radius specification and a valid Teff."""
    if instance.module != 'dummy':
        return

    if instance.dummy.calculate_radius:
        if instance.dummy.radius is not None:
            raise ValueError("Star radius must be 'none' when calculate_radius is True")

    else:
        if (instance.dummy.radius is None) or (instance.dummy.radius <= 0):
            raise ValueError('Star radius MUST be set >0 when calculate_radius is False')

    if (instance.dummy.Teff is None) or (instance.dummy.Teff < 1):
        raise ValueError('star.dummy.Teff must be >0 K')

Orbital evolution and tides

Dummy

Dummy tidal heating module.

Uses a fixed tidal heating power density and love number.

Attributes:

Name Type Description
H_tide float

Fixed global heating rate from tides [W kg-1].

Phi_tide str

Inequality which, if locally true, determines in which regions tides are applied.

Imk2 float

Imaginary part of k2 Love number, which is usually negative.

Lovepy

Lovepy tides module.

Attributes:

Name Type Description
visc_thresh float

Minimum viscosity required for heating [Pa s].

ncalc int

Number of interpoltaed interior levels to use for solving tidal heating rates.

ObliquaSolid

Solid-tide configuration for Obliqua tides.

Attributes:

Name Type Description
ncalc int

Number of interpolated interior levels to use for solving tidal heating rates (shooting method).

dr_min float

Minimum radial grid spacing [m] (Henyey/relaxation method).

dr_max float

Maximum radial grid spacing [m] (Henyey/relaxation method).

core str

Core solution vector ("liquid", "solid", "inertial-liquid", or "inertial").

core_props str

Core properties to use ("core" or "mantle").

inertial_terms bool

Whether to include inertial terms in the solid-tide solution.

bulk_l float

Bulk modulus of the liquid phase [Pa].

porosity_thresh float

Porosity threshold, hard cutoff below which melt fraction is set to zero [dimensionless].

dbulk_power float

Drained bulk modulus powerlaw scaling exponent [dimensionless].

ObliquaMushy

Mushy-tide configuration for Obliqua tides.

Attributes:

Name Type Description
b_width float

Scale width of the bottom heating decay profile [dimensionless].

t_width float

Scale width of the top heating decay profile [dimensionless].

ObliquaFluid

Fluid-tide configuration for Obliqua tides.

Attributes:

Name Type Description
sigma_R float

Rayleigh drag in the fluid-mush/solid boundary layers [1/s].

sigma_R_factor float

Rayleigh drag in the pure fluid as a fraction of the interface [dimensionless].

sigma_R_prf str

Radial heating distribution profile [dimensionless].

H_R float

Scale height to be used by heating profile [m].

efficiency float

Rayleigh drag efficiency at core interface [dimensionless].

Obliqua

Obliqua tides module.

Attributes:

Name Type Description
store_3D bool

Whether to store 3D information for solid tides.

enforce_ec bool

Whether to enforce energy conservation between Lovenumbers and heating profile.

optimize_scales bool

Whether to optimize the non-dimensional scaling parameters for solid tides.

solid_shell bool

Whether to insert an infinitesimal solid shell around the core.

cap_LN bool

Whether to clamp each mode's Love number to a fixed multiple of the classical fluid limit for its degree.

min_frac float

Minimal segment radius fraction before smoothing.

visc_lus float

Liquidus viscosity [Pa s].

visc_sus float

Solidus viscosity [Pa s].

n int

Power of the radial factor (r/a)^n.

m int

Tidal harmonic (m=2 semidiurnal, m=1 diurnal).

k_min int

Minimum Fourier index in mean anomaly (adaptive spectrum).

k_max int

Maximum Fourier index in mean anomaly (adaptive spectrum).

evection_padding_factor float

Safety multiplier on the linear look-ahead eccentricity padding applied to Obliqua's own adaptive k-range selection.

material_mu str

Rheology model for complex shear modulus ("andrade" or "maxwell").

material_k str

Rheology model for complex bulk modulus ("andrade" or "maxwell").

alpha float

Andrade power-law exponent.

verbosity int

Logging verbosity level (0=silent, 1=info, 2=debug).

module_solid str

Solid-tide module to use ("none", "solid0d", "solid1d", "solid1d-relax", "solid1d-mush", "solid1d-mush-relax", "solid1d-equil-relax").

module_mushy str

Mushy-tide module to use ("none", "interp").

module_fluid str

Fluid-tide module to use ("none", "fluid0d", "fluid1d").

solid ObliquaSolid

Solid-tide configuration.

mushy ObliquaMushy

Mushy-tide configuration.

fluid ObliquaFluid

Fluid-tide configuration.

Parameterized

Parameterized orbital migration module.

The track starts from orbit.semimajoraxis and orbit.eccentricity, which seed the orbit at the initial condition as for every other model.

Attributes:

Name Type Description
migration str

Type of orbital migration to apply.

sma_final float | None

Final semi-major axis [AU].

time_migration float

Time at which migration begins [yr].

tau_migration float

Timescale of migration, used by the sigmoid and high_ecc laws [yr].

Satellite

Satellite orbit configuration for planet-satellite systems.

Attributes:

Name Type Description
include_satellite bool

Whether to model a satellite orbiting the planet.

mass_sat float

Satellite mass [M_earth].

radius_sat float

Satellite radius [R_earth].

axial_period_sat float | None

Satellite initial day length [hours], will use orbital period if value is None.

semimajoraxis_sat float

Satellite initial semi-major axis [R_earth].

eccentricity_sat float

Satellite initial orbital eccentricity [dimensionless].

evection_angle float

Satellite evection angle [deg].

c_factor_sat float

Satellite tidal dissipation factor (<= 0.4) [dimensionless].

love_number_sat str | None

Satellite love number spectrum, provide absolute path to netCDF file containing forcing frequencies and complex Lovenumbers, and corresponding tidal degree in nmk format.

OrbitSolver

Shared numerical-solver settings for the orbital-evolution ODE models.

Used by both the star-planet models (sp0d, sp1d) and the planet-satellite models (ps0d, ps1d, ps1d_evec): a single set of tolerances and adaptive-substep-controller knobs, since only one of the two model families is ever active in a given run (star-planet evolution and a satellite are mutually exclusive; see satellite_evolve).

Attributes:

Name Type Description
method str

scipy.integrate.solve_ivp integration method.

rtol float

Relative tolerance passed to solve_ivp.

atol float

Absolute tolerance passed to solve_ivp.

dt0_yr float

Initial adaptive-substep size [yr].

dt_max_yr float

Maximum adaptive-substep size [yr].

growth float

Substep growth factor applied after an accepted step.

shrink float

Substep shrink factor applied after a rejected step.

max_rel_da float

Maximum tolerated relative change in semi-major axis per substep.

max_rel_de float

Maximum tolerated relative change in eccentricity per substep.

max_rel_dOmega float

Maximum tolerated relative change in a spin rate per substep.

de_floor float

Floor on the eccentricity-change-ratio denominator, so a small starting eccentricity does not make the ratio spuriously huge.

max_substeps int

Maximum number of substeps attempted per call.

resonance_margin_enter float

Evection-band entry margin (ps1d_evec only).

resonance_margin_exit float

Evection-band exit margin (ps1d_evec only).

resonance_margin_approach float

Wider, purely-diagnostic margin used to set a pre-emptive signal that the system is closing in on the band before the tighter resonance_margin_enter would declare capture.

fine_csv_target_rel_dt float

Target storage-clock spacing for out-of-band fine samples, as a fraction of the requested call duration (ps1d_evec only).

Orbit

Planetary and satellite orbital parameters.

Includes initial conditions, and options for enabling dynamical evolution.

Attributes:

Name Type Description
semimajoraxis float

Initial semi-major axis of the planet's orbit [AU].

eccentricity float

Initial Eccentricity of the planet's orbit.

instellation_method str

Whether to use the semi-major axis ('distance') or instellation flux ('inst') to define the planet's initial orbit. With 'inst' the semi-major axis follows the flux at every step, so a giant impact's semi-major axis change is not applied; its eccentricity change is.

instellationflux float

Instellation flux initially received by the planet in Earth units.

zenith_angle float

Characteristic angle of incoming stellar radiation, relative to the zenith [deg].

s0_factor float

Scale factor applies to incoming stellar radiation to represent planetary rotation.

star_planet_model str | None

Select star-planet orbit module to use. Choices: 'none', 'sp0d', 'sp1d', 'parameterized'.

axial_period float | None

Planet initial day length [hours], will use orbital period if value is None. Must be None for sp0d and parameterized, which keep the spin locked to the orbit.

satellite Satellite

Satellite and orbit configuration for planet-satellite systems.

planet_satellite_model str | None

Select planet-satellite orbit module to use. Choices: 'none', 'ps0d', 'ps1d', 'ps1d_evec'.

solver OrbitSolver

Shared ODE-solver and adaptive-substep-controller settings for the star-planet and planet-satellite orbital-evolution models.

parameterized Parameterized

Prescribed orbital-migration track, used when star_planet_model = 'parameterized'.

perturber str | None

Select perturber to induce tides on the planet. Options: 'none', 'star', 'satellite'.

module str | None

Select tides module to use. Choices: 'none', 'dummy', 'lovepy', 'obliqua'.

dummy Dummy

Dummy tidal heating module configuration.

lovepy Lovepy

Lovepy tidal heating module configuration.

obliqua Obliqua

Obliqua tidal heating module configuration.

Interior structure

Zalmoxis

Parameters for Zalmoxis module.

Attributes:

Name Type Description
core_eos str

EOS for the core layer. Format: ":". Tabulated: "PALEOS:iron" (default), "Seager2007:iron". Analytic: "Analytic:iron", "Analytic:MgFeSiO3", etc.

mantle_eos str

EOS for the mantle layer. Format: ":". Tabulated: "PALEOS:MgSiO3" (default), "PALEOS-2phase:MgSiO3", "Seager2007:MgSiO3", "WolfBower2018:MgSiO3". Analytic: "Analytic:MgSiO3", "Analytic:MgFeSiO3", etc.

ice_layer_eos str or None

EOS for the ice/water layer (3-layer model). 'none' for 2-layer model (core + mantle only). Tabulated: "PALEOS:H2O", "Seager2007:H2O". Analytic: "Analytic:H2O".

mushy_zone_factor float

Cryoscopic depression factor controlling the width of the mushy zone (partially molten region) in the PALEOS EOS family. Defines the solidus as T_sol = T_liq * mushy_zone_factor. 1.0 = sharp phase boundary (no mushy zone). 0.8 = solidus at 80% of the liquidus temperature, the constant solidus-to-liquidus ratio of Stixrude+2014 for MgSiO3, applied here to the PALEOS liquidus. Must be in [0.7, 1.0]. Applies to the PALEOS EOS family (PALEOS, PALEOS-2phase, PALEOS-API, PALEOS-API-2phase); ignored for WolfBower2018 and RTPress100TPa (which use explicit melting curve files) and for Seager2007/Analytic (no derived solidus).

mantle_mass_fraction float

Fraction of the planet's interior mass corresponding to the mantle. Required for 3-layer models (with ice layer) and for T-dependent 2-layer models (WolfBower2018, RTPress100TPa) where it partitions mass between core and mantle layers.

num_levels int

Number of Zalmoxis radius layers.

solver_tol_outer float

Relative tolerance for mass convergence (outer loop).

solver_tol_inner float

Relative tolerance for density convergence (inner loop).

solver_max_iter_outer int

Max iterations for mass convergence (outer loop).

solver_max_iter_inner int

Max iterations for density convergence (inner loop).

lookup_nP int

Number of pressure points in SPIDER P-S tables generated from PALEOS.

lookup_nS int

Number of entropy points in SPIDER P-S tables generated from PALEOS.

outer_solver str

Outer mass-radius solver: 'newton' (recommended) or 'picard'.

use_jax bool

Use the JAX backend for the structure solver.

use_anderson bool

Anderson Type-II Picard acceleration on the density loop.

newton_max_iter int

Maximum Newton iterations (outer_solver = 'newton').

newton_tol float

Newton convergence tolerance (outer_solver = 'newton').

newton_relative_tolerance float

Integrator relative tolerance for the Newton path.

newton_absolute_tolerance float

Integrator absolute tolerance for the Newton path.

update_dphi_abs float

Re-solve the structure when the global melt fraction changes by this absolute amount since the last solve.

update_dtmagma_frac float

Re-solve the structure when T_magma changes by this fraction.

update_dw_comp_abs float

Re-solve when the relative dissolved-volatile (H2O or H2) mantle mass fraction changes by this amount.

update_interval float

Maximum time between structure updates [yr]; effectively disabled at the default.

update_min_interval float

Minimum time between structure updates [yr]; prevents thrashing.

update_stale_ceiling float

Time since the last successful re-solve after which a trigger refires [yr]; 0 disables.

mesh_max_shift float

Maximum fractional radius shift per structure update.

mesh_convergence_interval float

Convergence relaxation time after a mesh update [yr].

equilibrate_init bool

Equilibrate structure and composition before the main loop.

equilibrate_max_iter int

Maximum equilibration iterations.

equilibrate_tol float

Equilibration convergence tolerance.

dry_mantle bool

Structure EOS assumes a dry mantle. Set False for melt-fraction-aware dissolved-volatile mixing in the mantle density (per-shell volatile profile); the dissolved mass then stays inside the interior mass target and inside the mass that core_frac refers to.

global_miscibility bool

Enable the H2-silicate binodal-aware radial structure. True is rejected at config load: it requires the binodal handoff on the Zalmoxis side, which is not yet implemented.

miscibility_max_iter int

Maximum miscibility iterations.

miscibility_tol float

Miscibility convergence tolerance.

Struct

Planetary structure (mass, radius).

Attributes:

Name Type Description
core_frac float

Core fraction of the planet, by mass or by radius (see core_frac_mode). With module = 'zalmoxis' the core mass is this fraction of the planet mass without the volatile inventories that the outgassing takes as inputs (H, C, N, S, noble gases; O when it is an input), or without their atmosphere part with zalmoxis.dry_mantle = false. It is set when the init stage ends and then kept.

module str

Module for solving the planet's interior structure. Choices: 'dummy', 'spider', 'zalmoxis'.

zalmoxis Zalmoxis or None

Zalmoxis parameters if module is 'zalmoxis'.

core_frac_mode str

How core_frac is interpreted. 'radius': fraction of planet radius. 'mass': mass fraction, of planet.mass_tot with module = 'dummy' and of the mass that core_frac names with module = 'zalmoxis'. Only 'radius' is supported when module = 'spider'. The zalmoxis module always interprets core_frac as a mass fraction and ignores this flag (a warning is emitted at runtime if 'radius' is set with module = 'zalmoxis').

core_density float or str

Density of the planet's core [kg m-3]. Set to 'self' for self-consistent calculation by Zalmoxis (requires module = 'zalmoxis').

core_heatcap float or str

Specific heat capacity of the planet's core [J kg-1 K-1]. Set to 'self' for self-consistent calculation by Zalmoxis (requires module = 'zalmoxis').

melting_dir str

Melting curve name in FWL_DATA. Required for the SPIDER structure module and for any run without a PALEOS table set; not read with module = 'zalmoxis' and a single PALEOS mantle EOS. With module = 'dummy' and a PALEOS mantle EOS, Aragog requires it but the solve uses the PALEOS set.

eos_dir str

EOS folder name in FWL_DATA, for the SPIDER structure module.

valid_zalmoxis(instance, attribute, value)

Validate Zalmoxis EOS format strings and reject unsupported miscibility options.

Source code in src/proteus/config/_struct.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def valid_zalmoxis(instance, attribute, value):
    """Validate Zalmoxis EOS format strings and reject unsupported miscibility options."""
    # `global_miscibility` needs the H2-silicate binodal handoff (Zalmoxis tracker #64),
    # not yet implemented. Reject it for every structure module: only the zalmoxis
    # structure writes the solvus that the main loop and SPIDER read.
    if getattr(instance.zalmoxis, 'global_miscibility', False):
        raise ValueError(
            '`interior_struct.zalmoxis.global_miscibility = true` is not yet usable: '
            'it requires the H2-silicate binodal handoff on the Zalmoxis side '
            '(Zalmoxis tracker #64), which the pinned release does not implement. '
            'Use `dry_mantle = false` for phase-aware H2O mixing without miscibility.'
        )

    if instance.module == 'spider':
        return

    core_eos = instance.zalmoxis.core_eos
    mantle_eos = instance.zalmoxis.mantle_eos
    ice_layer_eos = instance.zalmoxis.ice_layer_eos
    mantle_mass_fraction = instance.zalmoxis.mantle_mass_fraction

    # EOS format validation: must be "<source>:<material>"
    for name, eos_val in [('core_eos', core_eos), ('mantle_eos', mantle_eos)]:
        if ':' not in eos_val:
            raise ValueError(
                f"`interior_struct.zalmoxis.{name}` must be in '<source>:<material>' format, "
                f"got '{eos_val}'"
            )
    if ice_layer_eos is not None and ':' not in ice_layer_eos:
        raise ValueError(
            f"`interior_struct.zalmoxis.ice_layer_eos` must be 'none' or '<source>:<material>' format, "
            f"got '{ice_layer_eos}'"
        )

    # A component is '<source>:<material>' with at most one positive fraction, as Zalmoxis
    # parses it; Zalmoxis divides a mixture's fractions by their sum.
    def _number(token):
        try:
            return float(token)
        except ValueError:
            return None

    layers = [
        ('core_eos', core_eos),
        ('mantle_eos', mantle_eos),
        ('ice_layer_eos', ice_layer_eos),
    ]
    for name, eos_val in (layer for layer in layers if layer[1]):
        for comp in (c.strip() for c in eos_val.split('+')):
            tokens = comp.split(':')
            fraction = _number(tokens[2]) if len(tokens) == 3 else 1.0
            if (
                len(tokens) not in (2, 3)
                or not all(tokens[:2])
                or any(_number(t) is not None for t in tokens[:2])
                or any(ch.isspace() for ch in comp)
                or fraction is None
                or not 0 < fraction < float('inf')
            ):
                raise ValueError(
                    f"`interior_struct.zalmoxis.{name}` component '{comp}' is not "
                    "'<source>:<material>[:<fraction>]' with a positive finite fraction "
                    'and no spaces'
                )

    # WolfBower2018 EOS is limited to 1 TPa. For planets > 2 M_earth,
    # CMB pressure exceeds this and Zalmoxis will fail to converge.
    import logging as _logging

    _log = _logging.getLogger('fwl.' + __name__)
    # mushy_zone_factor scales the derived solidus only for the PALEOS EOS family
    mzf = getattr(instance.zalmoxis, 'mushy_zone_factor', 0.8)
    layer_eos = [core_eos, mantle_eos, ice_layer_eos]
    if mzf < 1.0 and not any(e and e.startswith(PALEOS_EOS_PREFIXES) for e in layer_eos):
        _log.warning(
            'mushy_zone_factor=%.2f has no effect with core EOS %s, mantle EOS %s '
            'and ice layer EOS %s. The mushy zone factor applies only to the PALEOS '
            'EOS family. For WolfBower2018/RTPress100TPa, the mushy zone is defined '
            'by the solidus/liquidus melting curve files.',
            mzf,
            core_eos,
            mantle_eos,
            ice_layer_eos,
        )

    # 2-layer model (no ice layer, non-T-dep mantle): mantle_mass_fraction must be 0
    if ice_layer_eos is None and not mantle_eos.startswith(TDEP_EOS_PREFIXES):
        if mantle_mass_fraction != 0:
            raise ValueError(
                '`interior_struct.zalmoxis.mantle_mass_fraction` must be 0 for a 2-layer model '
                'without T-dependent mantle EOS (only core + mantle are modeled).'
            )

    # 3-layer model (with ice layer): mass fractions must not exceed 75%
    if ice_layer_eos is not None:
        cmf = instance.core_frac if instance.core_frac_mode == 'mass' else 0.325
        if cmf + mantle_mass_fraction > 0.75:
            raise ValueError(
                '`core_frac` and `zalmoxis.mantle_mass_fraction` '
                'must add up to <= 75% for a 3-layer model (Seager 2007).'
            )

Magma ocean and planetary interior

Spider

SPIDER-specific parameters.

solver_type is the SUNDIALS integrator choice. tolerance_rel is a deprecated alias for the top-level [interior_energetics].rtol; set rtol instead. matprop_smooth_width sets the smoothing width for material-property blending across the solidus/liquidus, read by both SPIDER and Aragog.

Attributes:

Name Type Description
solver_type str

SUNDIALS integrator choice. Choices: 'adams', 'bdf'.

tolerance_rel float

Deprecated alias for Interior.rtol. Set interior_energetics.rtol at the top level instead.

matprop_smooth_width float

Melt-fraction window width for smoothing material properties across the solidus/liquidus. Passed to SPIDER as -matprop_smooth_width and to Aragog via _PhaseMixedParameters.

tolerance_struct float

Absolute mass tolerance [kg] for the interior-radius secant solver.

log_output bool

Write SPIDER solver log output.

AragogCoreModule

Parameters of the core evolution module (core_bc = 'core_module').

The core carries its own state: an energy budget with inner-core growth and light-element gravitational energy, evolved as an extra ODE state inside Aragog. The CMB radius and pressure always come from the running mesh, so only material and model choices live here.

Attributes:

Name Type Description
alpha float

Core thermal expansion coefficient [K-1].

c_p float

Core specific heat capacity [J kg-1 K-1].

melting_curve str

'iron' (Anzellini et al. 2013 via PALEOS, with light-element depression) or 'quadratic' (Nimmo 2015 Eq. 6 parameterisation).

light_element_fraction float

Mole fraction of light elements depressing the iron melting curve.

depression float

Melting-point depression per unit mole fraction, as a fraction of the melting temperature [1]; its product with light_element_fraction must stay below 1.

t_m0 float

Quadratic-curve prefactor [K] (melting_curve = 'quadratic').

t_m1 float

Quadratic-curve linear coefficient [Pa-1].

t_m2 float

Quadratic-curve quadratic coefficient [Pa-2].

ds_fusion float

Entropy of fusion at the inner-core boundary [J kg-1 K-1].

alpha_c float

Compositional expansivity of the outer-core alloy.

c_light float

Light-element mass fraction of the outer core (complete rejection).

q_radio float

Core radiogenic power [W], constant over a run.

stratification bool

When true, the outer core above layer_base_fraction of the CMB radius is a resolved shell whose temperatures are part of the solver state: heat moves through it by conduction and, where its gradient is superadiabatic, by convective mixing, so a stable layer forms below the CMB when the CMB heat flow is subadiabatic and erodes when it is not. The CMB heat flow sees the temperature of the top of the shell. Experimental: erosion of a layer does not run on CVODE at the default tolerances. Not available with accretion.

k_core float

Core thermal conductivity [W m-1 K-1] for conduction in the stratified shell and the entropy and dynamo diagnostics; the default is the Nimmo (2015) Table 2 value.

layer_base_fraction float

Base of the stratified shell as a fraction of the CMB radius, in (0, 1); the inner core must stay below it. Used with the experimental stratification.

layer_k_mix float

Eddy diffusivity [m2 s-1] of convective mixing in the stratified shell at the reference superadiabatic gradient. Used with the experimental stratification.

layer_g_mix float

Reference superadiabatic gradient of the shell mixing, as a fraction of the adiabatic gradient at the CMB. Used with the experimental stratification.

f_ohm float

Ohmic fraction of the dissipation in the field-strength scaling, in (0, 1]; Christensen et al. (2009) adopt 1 for planets.

ra_crit_cmb float

Critical Rayleigh number of the mantle-side boundary layer that sets the CMB heat flux from the core-mantle temperature contrast; the default is the theoretical value of Thiriet et al. (2019, Table 2).

flux_geometry str

Which printed Earth-core efficiency factor converts the superadiabatic flux into the field-strength scaling's F: 'const_flux' or 'zero_outer'.

Aragog

Aragog-specific parameters.

Attributes:

Name Type Description
mass_coordinates bool

Whether to use mass coordinates in the model. Default is True. Uses uniform spacing in mass coordinate space, giving larger cells at the surface where density is lower, matching SPIDER's mesh.

backend str

ODE backend selector. Default 'jax'. - 'jax' : CVODE with JAX-derived RHS and JAX analytic Jacobian (jax.jacrev). Recommended for production. - 'numpy' : CVODE with numpy RHS and CVODE finite-difference Jacobian. Available for development and SPIDER-parity comparisons; less robust than 'jax' at production resolution because the FD-Jacobian noise can trip Aragog's T_core-jump retry guard at tight tolerances. The diffrax direct-JAX integration path is research-only and gated on a code-level flag in proteus.interior_energetics.aragog.

atol_temperature_equivalent float

Effective temperature-scale absolute tolerance [K] for Aragog's CVODE integrator. Aragog's state variable is entropy (J/kg/K), but users think in Kelvin, so this is exposed as a temperature equivalent that Aragog converts internally via Cp/T. Default is 1e-8 K, matching SPIDER's atol=rtol=1e-8 setting; this tight tolerance eliminates the CVODE marginal-stability bifurcation at the first dt jump after equilibration.

core_bc str

Core-mantle boundary condition mode. Default 'energy_balance'. Valid values: - 'quasi_steady': alpha-factor heat-flux partition; gives about -19% T_core offset vs SPIDER. - 'energy_balance': SPIDER bit-parity BC with dSdr_cmb as a new state variable (mirrors SPIDER bc.c:76-131). - 'gradient': gradient-based state with two boundary entropies as state variables. - 'bower2018': experimental, do not use for production. - 'core_module': full core evolution module with nucleation, crystallisation regimes, and dynamo energetics (aragog.core), coupled through a boundary-layer CMB flux in the core-mantle temperature contrast.

atol_temperature_equivalent = field(default=1e-08, validator=gt(0)) class-attribute instance-attribute

Effective temperature-scale absolute tolerance [K] for Aragog's ODE integrator. Default 1e-8 matches SPIDER's atol=rtol=1e-8 setting; this tight tolerance avoids a marginal-stability bifurcation at the first dt jump after equilibration.

phase_smoothing = field(default='tanh', validator=in_(('tanh', 'cubic_hermite'))) class-attribute instance-attribute

Phase-boundary smoothing for Jgrav and Jmix: 'tanh' (SPIDER parity) or 'cubic_hermite'.

separation_viscosity = field(default='mixture', validator=in_(('melt', 'mixture'))) class-attribute instance-attribute

Drag viscosity for the gravitational-separation velocity v_rel = |dRho| g F(phi) / eta. 'melt' (fixed single-phase liquid viscosity, SPIDER parity) keeps separation active below the rheological transition in coupled caps-off runs, which collapses the CMB temperature; 'mixture' (rheological-transition-blended bulk viscosity) ties the drag viscosity to the same solid-fraction rise that stiffens the bulk rheology, so separation locks up at the same melt fraction instead, which is why the default here is 'mixture' while Aragog's own default stays 'melt' for SPIDER parity. The regime boundaries are the porosities where adjacent permeability laws cross (Bower et al. 2018, section 2.1, Eqs. 13a to 13c).

solver_method = field(default='cvode', validator=in_(('cvode', 'radau', 'bdf'))) class-attribute instance-attribute

ODE solver: 'cvode' (SUNDIALS, SPIDER parity; needs scikits-odes-sundials and stops the run at setup without it), 'radau' (scipy), 'bdf' (scipy).

scalar_gravity_override = field(default=False) class-attribute instance-attribute

Scalar-gravity comparison knob. When True, the external mesh file that Zalmoxis writes has its gravity column overwritten with a uniform scalar (the surface value from hf_row['gravity']) before Aragog reads it, so Aragog's per-node gravity path interpolates to that scalar everywhere. False by default; set True only when running a paired scalar-gravity comparison.

phi_step_cap = field(default=0.0, validator=_step_cap_valid) class-attribute instance-attribute

Per-call melt-fraction step cap. When > 0 and any staggered cell is in or near the two-phase window at solve() entry, a CVODE root function (and the equivalent scipy event) returns control at the exact time the larger of the global mass-weighted |ΔΦ| and the maximum single-cell |Δφ| reaches this cap. Off by default: the schema default 0.0 resolves to no cap, because on a benign freezing-front crossing the root function slices the coupled step into many small ones and drives the reported CMB heat flux briefly negative where the uncapped run stays positive, so the cap is a debugging control, not a production setting. Set a positive value to enable it; -1.0 is the explicit off spelling. An explicit 0.0 is rejected at load, since it cannot be told apart from the unset default; any other negative, NaN, or infinity is rejected too.

core_module = field(factory=AragogCoreModule) class-attribute instance-attribute

Core evolution module parameters, active when core_bc = 'core_module': the core evolves its own energy budget (inner-core growth, latent and gravitational terms) as an extra ODE state, and the reported core temperature (T_cmb) is that state, not the temperature of the lowermost mantle node.

temperature_step_cap = field(default=0.0, validator=_step_cap_valid) class-attribute instance-attribute

Per-call per-cell temperature step cap [K]. Shares the same root function as phi_step_cap and fires on the maximum single-cell |ΔT| since solve() entry. When enabled it bounds the per-cell temperature change on the solid adiabat just below the solidus, where the melt-fraction cap cannot act because a fully solid cell's melt fraction no longer moves. Off by default (schema default 0.0 resolves to no cap); on a benign freezing-front crossing the caps slice the coupled step into many small ones and drive the reported CMB heat flux briefly negative, so they are a debugging control, not a production setting. Set a positive value to enable it; -1.0 is the explicit off spelling. An explicit 0.0 is rejected at load, since it cannot be told apart from the unset default; any other negative, NaN, or infinity is rejected too.

entropy_step_cap = field(default=0.0, validator=_step_cap_valid) class-attribute instance-attribute

Per-call per-cell entropy step cap [J/kg/K], in the native solver variable; same role as temperature_step_cap without an EOS lookup in the root function. Off by default (schema default 0.0 resolves to no cap); on a benign freezing-front crossing the caps slice the coupled step into many small ones and drive the reported CMB heat flux briefly negative, so they are a debugging control, not a production setting. Set a positive value to enable it; -1.0 is the explicit off spelling. An explicit 0.0 is rejected at load, since it cannot be told apart from the unset default; any other negative, NaN, or infinity is rejected too.

phase_boundary_entropy_margin = field(default=200.0, validator=gt(0)) class-attribute instance-attribute

Phase-boundary proximity band [J/kg/K] within which a staggered cell counts as near a solidus or liquidus crossing, tightening the integrator max_step so CVODE resolves the stiff two-phase RHS across the boundary. This is a solver-accuracy control, not a cosmetic step-size setting: at the default it reproduces the fixed band and the converged trajectory is unchanged, but lowering it below the default can under-resolve a real phase crossing and shift the converged state, because CVODE's local error control can accept an over-large step across the near-discontinuous RHS. Keeping the default reproduces current behaviour, and modestly widening the band does not move a converged result because tighter steps only refine an adaptive integrator; but a value orders of magnitude above the default makes every cell count as near a boundary at all times, so every call runs under the phase-boundary step control: with phase_boundary_cap = 'fixed' that clamps the integrator to 1 yr steps (versus 100 yr otherwise) and stalls the run, and with 'rate' every call runs as CVODE segments with max_step between 1 and 100 yr. Keep the band of order a few hundred J/kg/K. Default 200.0, matching Aragog's own default; a positive value is required (0 or negative is not a valid disabled state for a proximity band).

phase_boundary_cap = field(default='rate', validator=in_(('fixed', 'rate'))) class-attribute instance-attribute

Phase-boundary step-size policy: 'fixed' tightens max_step to 1 yr whenever any cell is near or inside the two-phase band; 'rate' (default) uses event-driven CVODE segments with max_step scaled by approach time to the nearest phase boundary, clipped to [1, 100] yr. 'rate' needs the CVODE solver and a core_bc other than 'gradient'; otherwise Aragog logs it and uses 1 yr. Its accuracy is verified at rtol 1e-8; Aragog warns once per solver at an rtol above 1e-7.

tolerance_struct = field(default=100.0, validator=gt(0)) class-attribute instance-attribute

Absolute mass tolerance [kg] for the secant solver in determine_interior_radius. Default 100 kg; pairs with Spider's matching field so both backends drive the same outer-loop convergence criterion.

InteriorBoundary

Parameters for Boundary interior module. Default values taken from Schaefer et al. 2016 (https://iopscience.iop.org/article/10.3847/0004-637X/829/2/63/pdf).

Attributes:

Name Type Description
T_solidus float

Mantle solidus temperature [K].

T_liquidus float

Mantle liquidus temperature [K].

critical_rayleigh_number float

Critical Rayleigh number for onset of convection [-].

nusselt_exponent float

Nusselt-Rayleigh scaling exponent [-].

silicate_heat_capacity float

Silicate heat capacity [J/kg/K].

atm_heat_capacity_const bool

Always use fallback atmosphere heat capacity?

atm_heat_capacity float

Used as fallback for atmosphere heat capacity when layer-specific value is not available [J/kg/K].

silicate_density float

Silicate density [kg/m^3]. Default taken from Fei et. al. 2021 (https://ui.adsabs.harvard.edu/abs/2021NatCo..12..876F).

core_density float

Core density [kg/m^3].

core_shear float

Core shear modulus [Pa].

core_bulk float

Core bulk modulus [Pa].

thermal_conductivity float

Thermal conductivity [W/m/K].

thermal_diffusivity float

Thermal diffusivity [m^2/s].

thermal_expansivity float

Thermal expansivity [1/K].

viscosity_model int

Viscosity parameterisation model. Choices: 1 (constant), 2 (aggregate smooth transition), 3 (Arrhenius temperature-dependent).

dynamic_viscosity float

Reference dynamic viscosity [Pa s] for Arrhenius solid mantle model.

activation_energy float

Activation energy [J/mol] for Arrhenius solid mantle model.

creep_parameter float

Creep parameter [-] for Arrhenius solid mantle model.

viscosity_prefactor float

Viscosity prefactor [Pa s] for Vogel-Fulcher-Tammann magma ocean model.

viscosity_activation_temp float

Activation temperature [K] for Vogel-Fulcher-Tammann magma ocean model.

logging bool

Whether to create diagnostic CSV data files from boundary interior module.

InteriorDummy

Parameters for Dummy interior module.

Attributes:

Name Type Description
mantle_rho float

Mantle density [kg m-3] for the dummy density profile and the fallback mantle-mass estimate. When the interior structure provides the masses, the mantle mass is taken as M_int - M_core instead.

mantle_cp float

Mantle specific heat capacity [J kg-1 K-1]

mantle_tliq float

Mantle liquidus temperature [K]

mantle_tsol float

Mantle solidus temperature [K]

heat_internal float

Fixed internal heating rate (e.g. radiogenic) [W kg-1]. Tidal heating is handled separately via heat_tidal and is added on top of this value.

Interior

Magma ocean model selection and parameters.

Attributes:

Name Type Description
grain_size float

Crystal settling grain size [m].

flux_guess float

Initial heat flux guess [W m-2]. When < 0 (default), computed automatically as sigma * T_magma^4. Set to a positive value to prescribe a specific initial flux. Set to 0 for zero initial flux.

radio_tref float

Reference age for setting radioactive decay [Gyr].

radio_K float

Concentration (ppmw) of potassium-40 at reference age t=radio_tref.

radio_U float

Concentration (ppmw) of uranium at reference age t=radio_tref.

radio_Th float

Concentration (ppmw) of thorium-232 at reference age t=radio_tref.

radio_Al float

Concentration (ppmw) of aluminium-26 at reference age t=radio_tref; 1.23 is the canonical early solar system value.

radio_Fe float

Concentration (ppmw) of iron-60 at reference age t=radio_tref.

heat_radiogenic bool

Include radiogenic heat production?

heat_tidal bool

Include tidal heating?

rfront_loc float

Centre of rheological transition in terms of melt fraction. SPIDER receives it as -phi_critical. Aragog centres its viscosity blend, its reported rheological front and the melt-fraction ramp of kappah_floor on it; the boundary module uses it as the transition point of its viscosity models. SPIDER and Aragog use melt mass fraction (0.4 by volume is about 0.375 by mass at a 10 percent melt-solid density contrast); the boundary module takes its melt fraction linearly in temperature between solidus and liquidus and its rheological transition width from phase_transition_width, not rfront_wid.

rfront_wid float

Width of rheological transition in terms of melt fraction

num_levels int

Number of radial grid levels for the energetics domain.

num_tolerance float

Deprecated alias for rtol; emits a DeprecationWarning when set.

trans_conduction bool

Include conductive heat transfer.

trans_convection bool

Include convective heat transfer (mixing length theory).

trans_grav_sep bool

Include gravitational separation (Stokes settling).

trans_mixing bool

Include the chemical mixing flux.

mixing_length str

Mixing-length scale: 'nearest' (distance to nearest boundary) or 'constant' (a quarter of the mantle depth).

kappah_floor float

Eddy diffusivity floor [m2 s-1]; prevents mixing-length transport from freezing out.

tmagma_atol float

Maximum absolute change in T_magma per PROTEUS step [K].

tmagma_rtol float

Maximum relative change in T_magma per PROTEUS step.

tmagma_tides_step float

Maximum change in T_magma allowed when tides are active [K].

param_utbl bool

Enable the ultra-thin boundary layer parameterisation.

param_utbl_const float

Ultra-thin boundary layer scaling constant [K-1].

surface_bc_mode str

Surface boundary condition for SPIDER/Aragog: 'flux' (prescribed F_atm from the atmosphere module) or 'grey_body' (native grey-body boundary condition computed inside the interior solver).

const_properties bool

Enable constant-properties mode: bypass the EOS tables and use the analytical T(S) = T_ref * exp((S - S_ref) / Cp) relationship, for controlled parity tests.

module str

Module for simulating the magma ocean. Choices: 'spider', 'aragog', 'dummy'.

spider Spider

Parameters for running the SPIDER module.

aragog Aragog

Parameters for running the aragog module.

dummy Dummy

Parameters for running the dummy module.

Notes

The melting_dir and eos_dir fields live on the parent [interior_struct] section (class Struct in config/_struct.py), not here. They are shared across SPIDER, Aragog, and Zalmoxis and so belong with the structure config.

rtol = field(default=_TOL_UNSET, validator=_gt0_or_unset) class-attribute instance-attribute

Relative numerical tolerance for the interior ODE solver. SPIDER: -ts_sundials_rtol (used internally via atol_sf scaling). Aragog: scipy solve_ivp rtol. The deprecated aliases num_tolerance and [interior_energetics.spider].tolerance_rel copy into this field. Resolves to 1e-8 for Aragog and 1e-10 for SPIDER when left unset. With interior_struct.module = 'spider' it also sets the relative tolerance of the secant solve for the interior radius.

atol = field(default=1e-10, validator=gt(0)) class-attribute instance-attribute

Absolute numerical tolerance for the interior ODE solver. SPIDER: -ts_sundials_atol (scaled by atol_sf at runtime). Aragog uses [interior_energetics.aragog].atol_temperature_equivalent instead because its state variable is entropy, not temperature, and a direct entropy-scale atol would be unintuitive to tune.

adams_williamson_rhos = field(default=4078.95095544, validator=gt(0)) class-attribute instance-attribute

Adams-Williamson surface density [kg/m^3]. Matches SPIDER -adams_williamson_rhos and Aragog _MeshParameters.surface_density.

adams_williamson_beta = field(default=1.1115348931000002e-07, validator=gt(0)) class-attribute instance-attribute

Adams-Williamson density gradient [1/m]. Matches SPIDER -adams_williamson_beta. Aragog derives its own via bulk modulus, so this value applies to SPIDER only.

adiabatic_bulk_modulus = field(default=260000000000.0, validator=gt(0)) class-attribute instance-attribute

Adiabatic bulk modulus [Pa] used by Aragog's Adams-Williamson EOS (_MeshParameters.adiabatic_bulk_modulus). SPIDER derives its own.

melt_log10visc = field(default=2.0) class-attribute instance-attribute

log10 viscosity of molten silicate [Pa s]. Matches SPIDER -melt_log10visc (2.0 = 1e2 Pa s).

solid_log10visc = field(default=22.0) class-attribute instance-attribute

log10 viscosity of solid silicate [Pa s]. Matches SPIDER -solid_log10visc (22.0 = 1e22 Pa s). Shared by SPIDER and Aragog so both apply the same solid-phase rheology; a mis-set value diverges both solvers' solid-phase rheology by the same factor.

melt_cond = field(default=4.0, validator=gt(0)) class-attribute instance-attribute

Thermal conductivity of molten silicate [W/m/K]. Matches SPIDER -melt_cond.

solid_cond = field(default=4.0, validator=gt(0)) class-attribute instance-attribute

Thermal conductivity of solid silicate [W/m/K]. Matches SPIDER -solid_cond.

eddy_diffusivity_thermal = field(default=1.0) class-attribute instance-attribute

Multiplier on the internally-computed thermal eddy diffusivity. SPIDER: -eddy_diffusivity_thermal (1.0 default).

eddy_diffusivity_chemical = field(default=1.0) class-attribute instance-attribute

Multiplier on the internally-computed chemical eddy diffusivity. SPIDER: -eddy_diffusivity_chemical (1.0 default).

const_rho = field(default=4000.0, validator=gt(0)) class-attribute instance-attribute

Constant density [kg/m3].

const_Cp = field(default=1000.0, validator=gt(0)) class-attribute instance-attribute

Constant heat capacity [J/kg/K].

const_alpha = field(default=1e-05, validator=gt(0)) class-attribute instance-attribute

Constant thermal expansivity [1/K].

const_cond = field(default=4.0, validator=gt(0)) class-attribute instance-attribute

Constant thermal conductivity [W/m/K].

const_log10visc = field(default=2.0) class-attribute instance-attribute

Constant log10 dynamic viscosity [Pa.s].

const_T_ref = field(default=3500.0, validator=gt(0)) class-attribute instance-attribute

Reference temperature for T(S) = T_ref * exp((S-S_ref)/Cp) [K].

const_S_ref = field(default=3000.0) class-attribute instance-attribute

Reference entropy for T(S) [J/kg/K]. No positivity constraint since entropy reference states can be zero or negative.

latent_heat_of_fusion = field(default=4000000.0, validator=gt(0)) class-attribute instance-attribute

Latent heat of fusion of silicate [J/kg]. Aragog uses this as a scalar in _PhaseMixedParameters. SPIDER derives it per-(P,S) from dS * T_fus via the EOS tables; the SPIDER derivation is more physically correct but this scalar is good to ~10% at Earth-mantle conditions. TODO: switch Aragog to SPIDER's derivation once the EntropyEOS exposes a dS_fus(P) method.

phase_transition_width = field(default=0.1, validator=(gt(0), lt(1))) class-attribute instance-attribute

Width [fraction] of the mushy-zone transition in Aragog's _PhaseMixedParameters. Sets the width of the phase boundary in Aragog's mixed-phase blending (viscosity, thermal conductivity etc.). Distinct from [interior_energetics.spider].matprop_smooth_width which is SPIDER's analogous knob for its own solver.

core_tfac_avg = field(default=1.147, validator=gt(0)) class-attribute instance-attribute

Core T_avg / T_cmb ratio from adiabatic gradient (Bower+2018 Table 2). Used by Aragog's _BoundaryConditionsParameters.tfac_core_avg. SPIDER derives its own internally.

write_flux_diagnostics = field(default=False) class-attribute instance-attribute

When True, Aragog's NetCDF output includes per-component flux decomposition (Jcond_b, Jconv_b, Jgrav_b, Jmix_b) and basic-node state variables (dSdr_b, eddy_diff_b, phi_basic_b, T/cp/rho_basic_b). Adds ~10 fields per snapshot; default False keeps output compact. Useful for diagnosing T_core and CMB-closure behaviour near phi=0. SPIDER path ignores this flag (uses SPIDER's own JSON output which already includes Jcond_b, Jconv_b, Jgrav_b, Jmix_b).

default_rtol(module)

Interior rtol when unset: 1e-8 for Aragog (the tolerance at which its rate phase-boundary cap is verified), 1e-10 otherwise.

Source code in src/proteus/config/_interior.py
12
13
14
15
def default_rtol(module: str) -> float:
    """Interior rtol when unset: 1e-8 for Aragog (the tolerance at which its rate
    phase-boundary cap is verified), 1e-10 otherwise."""
    return 1e-8 if module == 'aragog' else 1e-10

rtol_is_set(ie)

Whether a raw [interior_energetics] table sets rtol or one of its aliases.

Source code in src/proteus/config/_interior.py
18
19
20
21
22
23
24
def rtol_is_set(ie: dict) -> bool:
    """Whether a raw ``[interior_energetics]`` table sets rtol or one of its aliases."""
    return (
        ie.get('rtol', _TOL_UNSET) != _TOL_UNSET
        or ie.get('num_tolerance', _TOL_UNSET) != _TOL_UNSET
        or ie.get('spider', {}).get('tolerance_rel', _TOL_UNSET) > 0
    )

valid_spider(instance, attribute, value)

SPIDER requires at least one energy transport term to be enabled.

Source code in src/proteus/config/_interior.py
64
65
66
67
68
69
70
71
72
73
74
75
76
def valid_spider(instance, attribute, value):
    """SPIDER requires at least one energy transport term to be enabled."""
    if instance.module != 'spider':
        return

    # at least one energy term enabled
    if not (
        instance.trans_conduction
        or instance.trans_convection
        or instance.trans_mixing
        or instance.trans_grav_sep
    ):
        raise ValueError('Must enable at least one energy transport term in SPIDER')

valid_interiorboundary(instance, attribute, value)

Validate Boundary backend's solidus/liquidus ordering.

Only fires when module == 'boundary'; otherwise the subclass is constructed with defaults and never exercised.

Source code in src/proteus/config/_interior.py
79
80
81
82
83
84
85
86
87
88
89
90
91
def valid_interiorboundary(instance, attribute, value):
    """Validate Boundary backend's solidus/liquidus ordering.

    Only fires when ``module == 'boundary'``; otherwise the subclass is
    constructed with defaults and never exercised.
    """
    if instance.module != 'boundary':
        return

    tsol = instance.boundary.T_solidus
    tliq = instance.boundary.T_liquidus
    if tliq <= tsol:
        raise ValueError(f'Boundary liquidus ({tliq}K) must be greater than solidus ({tsol}K)')

valid_aragog(instance, attribute, value)

Aragog requires at least one energy transport term to be enabled.

Source code in src/proteus/config/_interior.py
150
151
152
153
154
155
156
157
158
159
160
161
162
def valid_aragog(instance, attribute, value):
    """Aragog requires at least one energy transport term to be enabled."""
    if instance.module != 'aragog':
        return

    # at least one energy term enabled (uses top-level interior fields)
    if not (
        instance.trans_conduction
        or instance.trans_convection
        or instance.trans_mixing
        or instance.trans_grav_sep
    ):
        raise ValueError('Must enable at least one energy transport term in Aragog')

valid_interiordummy(instance, attribute, value)

Dummy interior requires the liquidus to sit above the solidus.

Source code in src/proteus/config/_interior.py
452
453
454
455
456
457
458
459
460
461
462
def valid_interiordummy(instance, attribute, value):
    """Dummy interior requires the liquidus to sit above the solidus."""
    if instance.module != 'dummy':
        return

    pass  # dummy uses planet.tsurf_init for initial temperature

    tliq = instance.dummy.mantle_tliq
    tsol = instance.dummy.mantle_tsol
    if tliq <= tsol:
        raise ValueError(f'Dummy liquidus ({tliq}K) must be greater than solidus ({tsol}K)')

Atmosphere climate

Agni

AGNI atmosphere module (AGNI-specific parameters only).

Grid, spectral, and pressure parameters are shared with JANUS and live on the parent AtmosClim class.

Attributes:

Name Type Description
verbosity int

Logging and output verbosity for agni (0:none, 1:info, 2:debug).

surf_material str

Surface scattering material: the file name of a Hammond et al. (2024) spectrum, e.g. 'lunarmarebasalt.dat', or 'greybody' to use surf_greyalbedo.

chemistry str | None

Atmospheric chemistry treatment. Choices: 'none', 'eq' (FastChem).

solve_energy bool

Solve for an energy-conserving atmosphere solution.

solution_atol float

Absolute tolerance on the atmosphere solution [W/m2].

solution_rtol float

Relative tolerance on the atmosphere solution.

surf_roughness float

Characteristic surface roughness scale [metres].

surf_windspeed float

Characteristic surface wind speed [m/s].

phs_timescale float

Relaxation timescale for phase changes (condensation/evaporation) [seconds].

evap_efficiency float

Efficiency of raindrop re-evaporation (0 to 1).

rainout bool

Enable volatile condensation and evaporation in the atmosphere.

oceans bool

Enable volatile ocean formation at the surface.

latent_heat bool

Account for latent heat from condense/evap when solving temperature profile. Requires condensation=true.

convection bool

Account for convective heat transport, using MLT.

conduction bool

Account for conductive heat transport, using Fourier's law.

sens_heat bool

Include sensible heat flux at surface

real_gas bool

Use real gas equations of state in atmosphere, where possible.

thermo_functions bool

Use temperature-dependent thermodynamic properties.

psurf_thresh float

Use the transparent-atmosphere solver when P_surf is less than this value [bar].

dx_max float

Nominal maximum step size to T(p) during the solver process, although this is dynamic.

dx_max_ini float

Initial maximum step size to T(p) when AGNI is called in the first few PROTEUS loops.

max_steps int

Maximum number of iterations before giving up.

perturb_all bool

Recalculate entire jacobian matrix at every iteration?

mlt_criterion str

Convection criterion. Options: (l)edoux, (s)chwarzschild.

fastchem_floor float

Minimum temperature allowed to be sent to FC

fastchem_maxiter_chem int

Maximum FC iterations (chemistry)

fastchem_maxiter_solv int

Maximum FC iterations (internal solver)

fastchem_xtol_chem float

FC solver tolerance (chemistry)

fastchem_xtol_elem float

FC solver tolerance (elemental)

ini_profile str

Shape of initial T(p) guess: 'loglinear', 'isothermal', 'dry_adiabat', 'analytic'.

ls_default int

Default linesearch method. 0: disabled, 1: goldensection, 2: backtracking.

fdo int

Numerical order of finite-difference for jacobian. 2 or 4.

spectral_file str | None

Path to AGNI spectral file, or 'greygas' to enable the grey-gas RT scheme. If None, will use atmos_clim.spectral_group and atmos_clim.spectral_bands.

grey_opacity_lw float

Grey longwave opacity [m2 kg-1], used when spectral_file='greygas'.

grey_opacity_sw float

Grey shortwave opacity [m2 kg-1], used when spectral_file='greygas'.

hydrograv_steps int

Number of steps to use when calculating heights and gravity.

hydrograv_maxdr float

Maximum step size to use when calculating heights [m]

hydrograv_hilldr float

Maximum step size to use beyond the Hill radius [m]

hydrograv_mindr float

Minimum step size to use when calculating heights [m]

hydrograv_ming float

Minimum allowed gravity in the atmosphere [m/s^2]

hydrograv_constg bool

Constant gravity with height?

hydrograv_selfg bool

Include self-gravity of the atmosphere?

aerosol_r_eff float

Effective radius of log-normal size distribution for Mie aerosols [m].

aerosol_sigma_g float

Standard deviation of log-normal particle-size distribution for Mie aerosols.

tau_obs float

Reference vertical optical depth defining the photosphere, used when atmos_clim.p_obs='none'.

wl_obs float

Reference wavelength [m] at which tau_obs is evaluated, used when atmos_clim.p_obs='none'.

Janus

JANUS atmosphere module (JANUS-specific parameters only).

Grid, spectral, and pressure parameters are shared with AGNI and live on the parent AtmosClim class.

Attributes:

Name Type Description
F_atm_bc int

Outgoing flux boundary: 0 (TOA) or 1 (surface).

tropopause str | None

Tropopause scheme. Choices: 'none', 'skin', 'dynamic'.

cloud_alpha float

Condensate retention fraction (0 = full rainout, 1 = fully retained).

tmp_maximum float

Solver temperature ceiling [K].

Dummy

Dummy atmosphere module.

A parametrised model of the atmosphere designed for debugging. The greenhouse effect is captured by gamma which produces a transparent atmosphere when 0, and a completely opaque atmosphere when 1. The height of the atmosphere equals the scale height times the height_factor variable.

When fixed_flux is set to a positive value, the dummy module bypasses the grey-body calculation entirely and returns that constant flux as F_atm at every coupling step. This is useful for controlled parity comparisons between interior solvers where the surface BC must be identical.

Attributes:

Name Type Description
gamma float

Atmosphere opacity factor between 0 and 1.

height_factor float

A multiplying factor applied to the ideal-gas scale height.

fixed_flux float

If > 0, return this constant flux [W/m2] instead of computing it. Default -1 (disabled, use the grey-body calculation).

AtmosClim

Atmosphere parameters, model selection.

Attributes:

Name Type Description
module str

Which atmosphere module to use. Choices: 'agni', 'janus', 'dummy'.

spectral_group str

Spectral file group defining gas opacities. See https://proteus-framework.org/SOCRATES/Reference/proteus_spectral_file_reference.html

spectral_bands str

Number of wavenumber bands in k-table.

spectral_cache str | None

Folder in which to reuse prepared spectral files across runs that share a stellar spectrum. None disables the cache and every run builds its own.

num_levels int

Number of vertical atmosphere levels.

p_top float

Top-of-atmosphere pressure [bar].

p_obs float | Literal['none']

Observation pressure level [bar] (transit radius). Set to 'none' (-> None) to instead let AGNI determine this pressure level self-consistently from the optical depth (see agni.tau_obs, agni.wl_obs); AGNI only.

overlap_method str

Gas overlap method. Choices: 'ro', 'rorr', 'ee'.

surface_d float

Conductive skin thickness [m].

surface_k float

Conductive skin thermal conductivity [W m-1 K-1].

aerosols_enabled bool

Enable aerosol radiative effects.

cloud_enabled bool

Enable water cloud radiative effects (AGNI, JANUS only).

surf_state str

Surface energy balance scheme. Choices: 'mixed_layer', 'fixed', 'skin'.

surf_greyalbedo float

Grey surface albedo.

albedo_pl float

Planetary bond albedo (0 to 1).

cloud_alpha float

Condensate retention fraction (0 = full rainout, 1 = fully retained).

rayleigh bool

Include Rayleigh scattering (AGNI, JANUS only).

tmp_minimum float

Minimum temperature throughout the atmosphere [K] (AGNI, JANUS only).

agni Agni

Config parameters for AGNI atmosphere module.

janus Janus

Config parameters for JANUS atmosphere module.

dummy Dummy

Config parameters for dummy atmosphere module.

surf_state_int property

Return integer surface boundary condition for agni.

warn_if_dummy(instance, attribute, value)

Reject enabling this option when the dummy atmos_clim module is selected.

Source code in src/proteus/config/_atmos_clim.py
14
15
16
17
def warn_if_dummy(instance, attribute, value):
    """Reject enabling this option when the dummy atmos_clim module is selected."""
    if (instance.module == 'dummy') and value:
        raise ValueError(f'Dummy atmos_clim module is incompatible with {attribute.name}=True')

valid_rayleigh(instance, attribute, value)

Reject Rayleigh scattering with the dummy module or AGNI in grey-gas mode.

Source code in src/proteus/config/_atmos_clim.py
20
21
22
23
24
25
26
27
28
29
def valid_rayleigh(instance, attribute, value):
    """Reject Rayleigh scattering with the dummy module or AGNI in grey-gas mode."""
    if not value:
        return

    if instance.module == 'dummy':
        raise ValueError('Dummy atmos_clim is incompatible with Rayleigh scattering')

    if instance.module == 'agni' and (str(instance.agni.spectral_file).lower() == 'greygas'):
        raise ValueError('AGNI grey gas is incompatible with Rayleigh scattering')

valid_aerosols_enabled(instance, attribute, value)

Aerosol scattering needs band-resolved RT.

Reuses the dummy guard from warn_if_dummy and rejects the AGNI grey-gas combination: grey gas has no spectral bands, so a Mie-scattering aerosol library is either silently ignored or crashes on the Julia side. The check fires only when aerosols are enabled (value is True).

Source code in src/proteus/config/_atmos_clim.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def valid_aerosols_enabled(instance, attribute, value):
    """Aerosol scattering needs band-resolved RT.

    Reuses the dummy guard from ``warn_if_dummy`` and rejects the
    AGNI grey-gas combination: grey gas has no spectral bands, so a
    Mie-scattering aerosol library is either silently ignored or
    crashes on the Julia side. The check fires only when aerosols
    are enabled (``value is True``).
    """
    if not value:
        return

    if instance.module == 'dummy':
        raise ValueError(f'Dummy atmos_clim module is incompatible with {attribute.name}=True')

    if instance.module == 'agni' and (str(instance.agni.spectral_file).lower() == 'greygas'):
        raise ValueError(
            'AGNI grey gas is incompatible with aerosols (band-resolved Mie '
            'scattering requires a real spectral file)'
        )

valid_p_obs(instance, attribute, value)

p_obs is either a positive pressure [bar], or None ('none' in the config file), which lets AGNI determine the photosphere self-consistently from optical depth instead. The latter is only meaningful for AGNI.

Source code in src/proteus/config/_atmos_clim.py
54
55
56
57
58
59
60
61
62
63
64
65
def valid_p_obs(instance, attribute, value):
    """p_obs is either a positive pressure [bar], or None ('none' in the
    config file), which lets AGNI determine the photosphere self-consistently
    from optical depth instead. The latter is only meaningful for AGNI.
    """
    if value is None:
        if instance.module != 'agni':
            raise ValueError("`p_obs='none'` requires atmos_clim.module='agni'")
        return

    if value <= 0:
        raise ValueError('`p_obs` must be > 0')

valid_agni(instance, attribute, value)

Validate AGNI settings: pressure ordering, surface-state support, rainout, spectral file.

Source code in src/proteus/config/_atmos_clim.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def valid_agni(instance, attribute, value):
    """Validate AGNI settings: pressure ordering, surface-state support, rainout, spectral file."""
    if instance.module != 'agni':
        return

    # ensure psurf_thresh is greater than p_top, to avoid upside-down atmosphere in transparent mode
    if instance.p_top > instance.agni.psurf_thresh:
        raise ValueError('Must set `p_top` to be less than `agni.psurf_thresh`')

    # ensure p_obs is greater than p_top
    if instance.p_obs is not None and instance.p_top > instance.p_obs:
        raise ValueError('Must set `p_top` to be less than `p_obs`')

    # agni does not support mixed_layer surface state
    if instance.surf_state == 'mixed_layer':
        raise ValueError(
            "AGNI does not support `surf_state='mixed_layer'`. Use 'fixed' or 'skin'."
        )

    # agni must solve_energy=true if surf_state=skin
    if (not instance.agni.solve_energy) and (instance.surf_state == 'skin'):
        raise ValueError("Must set `agni.solve_energy=true` if using `surf_state='skin'`")

    if instance.agni.latent_heat and not instance.agni.rainout:
        raise ValueError(
            '`atmos_clim.agni`: Must set `rainout=true` if setting `latent_heat=true`'
        )

    # set spectral files?
    if instance.agni.spectral_file is not None:
        if instance.agni.spectral_file.lower() == 'greygas':
            # grey gas: no SOCRATES spectral file needed
            pass
        elif not os.path.isfile(instance.agni.spectral_file):
            raise FileNotFoundError(
                f'AGNI spectral file not found at specified path: {instance.agni.spectral_file}'
            )
    else:
        # provided by group and bands at parent level
        if not instance.spectral_group:
            raise ValueError('Must set atmos_clim.spectral_group')
        if not instance.spectral_bands:
            raise ValueError('Must set atmos_clim.spectral_bands')

    # fastchem installed?
    if instance.agni.chemistry == 'eq':
        FC_DIR = os.environ.get('FC_DIR')
        if FC_DIR:
            if not os.path.isdir(FC_DIR):
                raise FileNotFoundError(f'Fastchem not found at FC_DIR={FC_DIR}')
        else:
            raise EnvironmentError(
                'Chemistry is enabled but environment variable `FC_DIR` is not set'
            )

valid_janus(instance, attribute, value)

Validate JANUS settings: spectral file selection and temperature-bound ordering.

Source code in src/proteus/config/_atmos_clim.py
315
316
317
318
319
320
321
322
323
324
325
326
327
328
def valid_janus(instance, attribute, value):
    """Validate JANUS settings: spectral file selection and temperature-bound ordering."""
    if instance.module != 'janus':
        return

    # set spectral files?
    if not instance.spectral_group:
        raise ValueError('Must set atmos_clim.spectral_group')
    if not instance.spectral_bands:
        raise ValueError('Must set atmos_clim.spectral_bands')

    # tmp_maximum must exceed tmp_minimum
    if instance.janus.tmp_maximum <= instance.tmp_minimum:
        raise ValueError("'janus.tmp_maximum' has to be bigger than 'tmp_minimum'.")

Atmospheric escape

Zephyrus

Parameters for Zephyrus module.

Attributes:

Name Type Description
Pxuv float

Pressure at which XUV radiation become opaque in the planetary atmosphere [bar]

efficiency float

Escape efficiency factor

tidal bool

Tidal contribution enabled

EscapeDummy

Dummy module.

Attributes:

Name Type Description
rate float

Bulk unfractionated escape rate [kg s-1]

EscapeBoreas

BOREAS escape module.

Attributes:

Name Type Description
fractionate bool

Enable elemental fractionation in outflow?

efficiency float

Energy efficiency factor.

sigma_H float

Absorption cross-section of H in XUV [cm2]

sigma_O float

Absorption cross-section of O in XUV [cm2]

sigma_C float

Absorption cross-section of C in XUV [cm2]

sigma_N float

Absorption cross-section of N in XUV [cm2]

sigma_S float

Absorption cross-section of S in XUV [cm2]

kappa_H2O float

Grey H2O opacity in IR [cm2 g-1]

kappa_H2 float

Grey H2 opacity in IR [cm2 g-1]

kappa_O2 float

Grey O2 opacity in IR [cm2 g-1]

kappa_CO2 float

Grey CO2 opacity in IR [cm2 g-1]

kappa_CO float

Grey CO opacity in IR [cm2 g-1]

kappa_CH4 float

Grey CH4 opacity in IR [cm2 g-1]

kappa_N2 float

Grey N2 opacity in IR [cm2 g-1]

kappa_NH3 float

Grey NH3 opacity in IR [cm2 g-1]

kappa_H2S float

Grey H2S opacity in IR [cm2 g-1]

kappa_SO2 float

Grey SO2 opacity in IR [cm2 g-1]

kappa_S2 float

Grey S2 opacity in IR [cm2 g-1]

Escape

Escape parameters and module selection.

Attributes:

Name Type Description
reservoir str

Escaping composition when not doing fractionation. Choices: bulk, outgas, pxuv.

module str | None

Escape module to use. Choices: None, "dummy", "zephyrus", "boreas".

zephyrus Zephyrus

Parameters for zephyrus module.

dummy EscapeDummy

Parameters for dummy escape module.

boreas EscapeBoreas

Parameters for BOREAS escape module.

hill_clamp bool

Limit the XUV level to the Hill radius. Gas beyond the Hill radius is not bound to the planet, so an XUV radius outside it sizes the escape cross-section with material the planet does not hold.

hill_clamp_frac float

Fraction of the Hill radius used as that limit, when hill_clamp is enabled.

step_max_frac float

Largest share of the escapable reservoir a single step may remove. The bulk rate is sized without reference to how much mass remains, so over a long step it can ask for many times the reservoir.

The default guards reduced compositions rather than limiting the grid. Measured on production cases, every one at IW-5 asks for more than it at some step and the worst for eighty times the reservoir, while cases from IW-1 to IW+5 stay under a thousandth of it and never meet the cap at all. A larger value lets one step take more of what is left, so a reduced case draws its reservoir down in fewer and coarser steps. The cap keeps binding at every setting in this range, because an unchanged rate settles at max_frac / (1 - max_frac), which is above the cap for every value it accepts.

step_dt_floor_frac float

How far below params.dt.minimum a capped step may shorten the next one. The floor otherwise overrides the shortened step on 94 % of the steps the cap binds on, leaving the reduction inert where it is needed.

xuv_defined_by_radius property

Does Rxuv define the escape level?

If the escape level is defined by constant Pxuv, then return False. This depends on the escape module used. BOREAS calculates both Pxuv and Rxuv, while the default assumes that Pxuv is constant, which is used to find Rxuv from the r(p) profile.

valid_zephyrus(instance, attribute, value)

Validate ZEPHYRUS settings: Pxuv within (0, 10] bar and efficiency within [0, 1].

Source code in src/proteus/config/_escape.py
 9
10
11
12
13
14
15
16
17
18
19
20
def valid_zephyrus(instance, attribute, value):
    """Validate ZEPHYRUS settings: Pxuv within (0, 10] bar and efficiency within [0, 1]."""
    if instance.module != 'zephyrus':
        return

    Pxuv = instance.zephyrus.Pxuv
    if Pxuv is None or Pxuv <= 0 or Pxuv > 10:
        raise ValueError('`zephyrus.Pxuv` must be >0 and <= 10 bar')

    efficiency = instance.zephyrus.efficiency
    if efficiency is None or efficiency < 0 or efficiency > 1:
        raise ValueError('`zephyrus.efficiency` must be >=0 and <=1')

valid_escapedummy(instance, attribute, value)

Dummy escape requires a non-negative escape rate.

Source code in src/proteus/config/_escape.py
42
43
44
45
46
47
48
49
def valid_escapedummy(instance, attribute, value):
    """Dummy escape requires a non-negative escape rate."""
    if instance.module != 'dummy':
        return

    rate = instance.dummy.rate
    if rate is None or rate < 0:
        raise ValueError('`escape.dummy.rate` must be >= 0')

valid_escapeboreas(instance, attribute, value)

Hook for BOREAS cross-field checks; no active constraints at present.

Source code in src/proteus/config/_escape.py
65
66
67
68
def valid_escapeboreas(instance, attribute, value):
    """Hook for BOREAS cross-field checks; no active constraints at present."""
    if instance.module != 'boreas':
        return

valid_reservoir(instance, attribute, value)

Escape reservoir must be one of 'bulk', 'outgas', or 'pxuv'.

Source code in src/proteus/config/_escape.py
137
138
139
140
141
def valid_reservoir(instance, attribute, value):
    """Escape reservoir must be one of 'bulk', 'outgas', or 'pxuv'."""
    ress = ('bulk', 'outgas', 'pxuv')
    if instance.reservoir not in ress:
        raise ValueError(f'Escape reservoir must be one of: {ress}')

Atmospheric chemistry

Vulcan

VULCAN chemistry module.

Attributes:

Name Type Description
clip_fl float

Stellar flux floor [ergs cm-2 s-1 nm-1].

clip_vmr float

Neglect species with surface VMR < clip_vmr.

make_funs bool

Make functions from chemical network.

ini_mix str

Initial mixing ratios. Options: profile, outgas.

fix_surf bool

Fix the surface mixing ratios based on outgassed composition.

network str

Chemical network. Options: CHO, NCHO, SNCHO.

save_frames bool

Save simulation state as plots.

yconv_cri float

Steady state - max change in mixing ratio over test period

slope_cri float

Steady state - max rate of change of mixing ratio over test period

AtmosChem

Atmosphere chemistry parameters, model selection.

Attributes:

Name Type Description
module str

Chemistry module

vulcan Vulcan

VULCAN module options

when str

When to run the chemistry module. Options: manually, offline, online.

photo_on bool

Use photochemistry.

Kzz_on bool

Use Kzz.

Kzz_const float

Constant Kzz value [cm2/s]. If 'none', Kzz is read from NetCDF file.

moldiff_on bool

Use molecular diffusion.

updraft_const float

Updraft velocity [cm/s].

Volatile outgassing

Calliope

Module parameters for Calliope.

Attributes:

Name Type Description
include_H2O bool

If True, include H2O.

include_CO2 bool

If True, include CO2.

include_N2 bool

If True, include N2.

include_S2 bool

If True, include S2.

include_SO2 bool

If True, include SO2.

include_H2S bool

If True, include H2S.

include_NH3 bool

If True, include NH3.

include_H2 bool

If True, include H2.

include_CH4 bool

If True, include CH4.

include_CO bool

If True, include CO.

include_He bool

If True, include He (noble gas; budget set in planet.elements).

include_Ne bool

If True, include Ne (noble gas; budget set in planet.elements).

include_Ar bool

If True, include Ar (noble gas; budget set in planet.elements).

include_Kr bool

If True, include Kr (noble gas; budget set in planet.elements).

include_Xe bool

If True, include Xe (noble gas; budget set in planet.elements).

solubility bool

Enable solubility of volatiles into melt.

nguess int

Maximum number of initial-guess samples for the CALLIOPE equilibrium solver. The samples come from a fixed seed, so identical inputs draw the same guesses, and an input that exhausts the samples fails the same way on a rerun. Default 1000.

nsolve int

Maximum number of iterations of the CALLIOPE equilibrium solver per call. Default 3000.

p_guess_max float

Upper bound [bar] of the CALLIOPE Monte-Carlo cold-start surface- pressure draw. Sets where the cold start samples, so raising it helps the solver find a high-pressure (e.g. sub-Neptune) basin faster. It does NOT raise the maximum pressure the solver can accept (CALLIOPE's fixed 1e7 bar box), so it is bounded to (0, 1e7]. Default 1e5.

is_included(vol)

Helper method for getting flag if vol is included in outgassing.

Source code in src/proteus/config/_outgas.py
112
113
114
def is_included(self, vol: str) -> bool:
    """Helper method for getting flag if `vol` is included in outgassing."""
    return getattr(self, f'include_{vol}')

Atmodeller

Module parameters for Atmodeller (Bower+2025, ApJ 995:59).

JAX-based volatile partitioning with real gas EOS, non-ideal solubility laws, and condensation. Replaces CALLIOPE for thermodynamically consistent magma-atmosphere equilibrium.

Attributes:

Name Type Description
solver_mode str

Root-finding mode: 'robust' (slower compile, better convergence) or 'basic' (faster compile, less robust).

solver_max_steps int

Maximum iterations for the root-finder.

solver_multistart int

Number of random restarts for the root-finder.

include_condensates bool

Enable condensate phases (graphite, etc.) in the equilibrium.

solubility_H2O str

Solubility law for H2O. See atmodeller.solubility.library.

solubility_CO2 str

Solubility law for CO2.

solubility_H2 str

Solubility law for H2.

solubility_N2 str

Solubility law for N2.

solubility_S2 str

Solubility law for S2.

solubility_CO str

Solubility law for CO. 'none' = no solubility.

solubility_CH4 str

Solubility law for CH4. 'none' = no solubility.

eos_H2O str

Real gas EOS for H2O. 'none' = ideal gas.

eos_CO2 str

Real gas EOS for CO2. 'none' = ideal gas.

eos_H2 str

Real gas EOS for H2. 'none' = ideal gas.

eos_CH4 str

Real gas EOS for CH4. 'none' = ideal gas.

eos_CO str

Real gas EOS for CO. 'none' = ideal gas.

Lavatmos

Module parameters for LavAtmos rock vapourisation.

Attributes:

Name Type Description
T_min float

Minimum surface temperature [K] used by LavAtmos.

melt_comp_name str

Name of the melt composition file (without extension).

P_melt float

Pressure used for melt activities [bar].

xatol float

Absolute tolerance for LavAtmos fO2 solve.

fO2_buffer_model str

IW buffer model used for LavAtmos fO2 solve. One of 'oneill', 'fischer'.

Outgas

Outgassing parameters (fO2) and included volatiles.

Attributes:

Name Type Description
module str

Outgassing module to be used. Choices: 'calliope', 'atmodeller', 'dummy'.

fO2_shift_IW float

Oxygen fugacity relative to Iron-Wustite [log10 units].

mass_thresh float

Minimum threshold for element mass [kg]. Outgassing treats H, C, N or S below it as absent; escape sets a non-noble element total below it to zero, except on a mantle frozen with params.stop.solid.freeze_volatiles. Noble gases are exempt.

h2_binodal bool

Enable binodal-controlled H2 partitioning between atmosphere and magma ocean using the Rogers+2025 H2-MgSiO3 miscibility model.

T_floor float

Temperature floor [K]. The outgassing temperature is clamped to this value from below before the chemistry solve.

solver_rtol float

Relative tolerance for the volatile equilibrium solver.

solver_atol float

Absolute tolerance for the volatile equilibrium solver.

calliope Calliope

Parameters for CALLIOPE module.

atmodeller Atmodeller

Parameters for atmodeller module.

vapourise bool

Enable rock vapourisation via LavAtmos/ThermoEngineLite. Requires LAVA_DIR and FC_DIR to be set; see the optional modules installation guide. LavAtmos parameters are set in outgas.lavatmos.

lavatmos Lavatmos

Parameters for the LavAtmos rock-vapour module.

Elemental delivery and accretion

Morrigan

Parameters for the Morrigan giant-impact module.

Morrigan evolves a system of embryos after disk dispersal, following Kimura et al. (2025), and reports the impacts experienced by one selected survivor. The stellar mass is taken from star.mass rather than repeated here, so the dynamical model and the rest of PROTEUS cannot disagree about the host star.

Attributes:

Name Type Description
seed int

Random seed for the Monte Carlo. Fixing it makes an impact history reproducible; sweeping it samples the outcome distribution.

num_planets int

Number of embryos the system starts with.

masses list of float

Initial embryo masses [M_earth], one per embryo. An empty list starts every embryo at mass_equal.

mass_equal float

Initial mass of every embryo [M_earth], used when masses is empty.

eccentricity_init float

Initial eccentricity shared by all embryos.

inner_edge float

Semi-major axis of the innermost embryo [AU].

spacing float

Initial separation between adjacent embryos, in mutual Hill radii. Typical values are 5 to 15; beyond roughly 30 the system does not go unstable within any useful evolution time, so the run finishes with no impacts. Capped at 50 purely to catch an order-of-magnitude mistake at configuration load.

The cap is not the physical limit and does not track it. The layout condition has a pole where the requested gap approaches the span it is measured across, and its position scales with the embryo masses and with the cube root of the stellar mass: near 74 mutual Hill radii for a pair of ten-Earth-mass embryos around a solar-mass star, but near 34 for the same pair around a 0.1-solar-mass host. A spacing this validator accepts can therefore still be too wide for a compact, low-mass-host system. The dynamical model applies the exact condition and refuses such a layout by name, so that check, not this cap, is what guarantees a valid layout.

density float

Uniform bulk density used to convert embryo mass to radius [kg m-3].

impact_angle float

Impact angle [deg]. The impact parameter is its sine.

evolution_time float

Duration of the dynamical evolution [Gyr].

inner_cutoff float

Perihelion inside which an embryo counts as lost to the star [AU].

selector str

Which survivor's impact history PROTEUS follows. 'match_config' picks the survivor whose initial mass and orbit are closest to the PROTEUS configuration, 'mass' the most massive survivor, 'semimajoraxis' the survivor whose final orbit is nearest selector_value [AU], and 'id' the embryo with index selector_value.

selector_value float or int or str or None

Target value for the 'semimajoraxis' and 'id' selectors. Ignored otherwise. The field converter parses numeric strings to numbers.

AccretionTimeline

Impact timeline replayed from a file.

Applies a pre-written sequence of impacts instead of deriving one from a dynamical model. Every impact consequence is computed exactly as it is for a model-derived timeline, so this reproduces a published impact history, drives PROTEUS from a history computed elsewhere, or applies a hand-written sequence for a controlled experiment.

Attributes:

Name Type Description
timeline_path str or None

Path to the impact timeline file. Environment variables and ~ are expanded.

AccretionDummy

Analytical stand-in for a dynamical giant-impact model.

Builds a self-consistent accretion history from scaling laws rather than integrating a system of embryos, in the same spirit as the other dummy modules in PROTEUS: fast, deterministic, and dependency-free, at the cost of the dynamics.

The planet approaches an asymptotic mass exponentially, the standard picture of an accretion rate that decays as the feeding zone empties. Impacts are placed at evenly spaced times and each one delivers the mass the law accretes over its interval, so the increments decay with time and the largest impact is the first. Radii follow the Noack & Lasbleis (2020) mass-radius scaling, collision velocities combine the pair's mutual escape velocity with an encounter velocity set by eccentricity, and each merged orbit follows from conserving linear momentum through the collision.

Attributes:

Name Type Description
mass_accreted float

Total mass delivered over the whole timeline [M_earth]. The growth law sets how this is distributed in time and between impacts; the increments are scaled so they sum to exactly this value.

num_impacts int

Number of impacts in the timeline.

timescale float

E-folding time of the accretion law [yr]. Short compared with time_last concentrates the mass in the first impacts; long compared with it spreads the mass evenly.

time_last float

Time of the final impact [yr]. Impacts are spaced evenly from time_last / num_impacts up to this time.

eccentricity float

Encounter eccentricity [1], setting both the approach velocity that adds to the mutual escape velocity and the impactor's orbit.

impact_parameter float

Impact parameter of every collision [1], the sine of the impact angle. Zero is head-on, one is grazing.

timeline_path str or None

Not a parameter of this module. Present only to reject a configuration that sets it here, which asks to replay a file and would otherwise be served a generated timeline at default settings. Use accretion.module = "timeline" and accretion.timeline.timeline_path.

Accretion

Giant-impact accretion, delivery, and module selection.

An impact grows the planet, delivers volatiles, re-melts the mantle, strips part of the atmosphere, and moves the orbit. The impactor volatile content is set by impactor_volatiles: "dry" impactors (the default) add silicate and iron mass only, "match_planet" impactors carry the planet's own formation composition, and "ppmw" impactors carry the per-element budgets configured below.

The mantle re-melt is a thermodynamic reset, not an energy deposition. It raises the mantle to the run's planet.temperature_mode initial condition, and hotter parts keep their state, so the heat it injects is set by the mantle's own state and mass rather than by the energy the collision carried. The two agree in order of magnitude for a large impact onto a mantle that has cooled appreciably, which is the regime this coupling targets, and diverge outside it: a mantle already near the initial condition absorbs almost nothing, and a cool mantle struck by a small impactor absorbs far more than the impact supplied. On the Aragog interior, which resolves an entropy profile and so can quantify the injection, the run reports it as a fraction of the impact kinetic energy and warns when that fraction leaves the physically expected band. That report is the only check available, because the conservation residual adds the injection to both of its sides and so stays closed for any value. The scalar interiors reset a temperature rather than a profile and book no injection, so neither the report nor the warning applies there. Interpret the thermal response to an impact as a property of the chosen initial condition rather than of the collision.

Attributes:

Name Type Description
module str or None

Accretion module to use. A "timeline" or "morrigan" history is borrowed from its dynamical bodies: impactor masses, collision loss fractions and impact energies are theirs, not rescaled to this planet. A warning is logged when the timeline's target mass differs from the planet mass by more than 10 %, a round threshold (about 3.5 % in escape speed, which scales as M^0.36), at load and per impact. Choices: None, "dummy", "timeline", "morrigan".

morrigan Morrigan

Parameters for the Morrigan giant-impact module.

dummy AccretionDummy

Parameters for the analytical dummy module.

timeline AccretionTimeline

Parameters for replaying an impact timeline from file.

time_offset float

Offset applied to every impact time when mapping the timeline onto the PROTEUS time axis [yr]. A dynamical model measures time from disk dispersal, while PROTEUS measures it from the start of its own evolution. Impacts that still land at or before the start of the run are discarded with a warning, and their mass is not applied anywhere: the configured planet mass and orbit define the initial state on their own.

impactor_volatiles str

Where each impactor's volatile content comes from. Choices: "dry" (impactors carry rock and iron only), "match_planet" (every impactor carries the planet's own initial fractional volatile abundances, scaled to the impactor mass, on the assumption that all embryos co-formed from the same disk material), "ppmw" (the per-element impactor_<e>_ppmw budgets below). The content is split into an atmospheric and a dissolved part by mirroring the planet's own partitioning at impact time; the atmospheric part loses the same collision fraction that strips the target's atmosphere and the remainder of the content is delivered.

impactor_H_ppmw float

Hydrogen carried by each impactor [ppmw of impactor mass].

impactor_C_ppmw float

Carbon carried by each impactor [ppmw of impactor mass].

impactor_N_ppmw float

Nitrogen carried by each impactor [ppmw of impactor mass].

impactor_S_ppmw float

Sulfur carried by each impactor [ppmw of impactor mass].

impactor_O_ppmw float

Oxygen carried by each impactor [ppmw of impactor mass]. With CALLIOPE or atmodeller at a fixed fO2 (planet.fO2_source = 'user_constant') the outgassing derives the O budget, so the delivered part counts as rock; otherwise it joins the O budget.

atmloss_module str or None

How impact atmosphere loss is computed. Choices: None (no impact atmosphere loss at all: the target keeps its atmosphere and a volatile-bearing impactor delivers its whole content), "constant" (the fixed fraction below), "zephyrus" (the giant-impact erosion scaling laws evaluated by zephyrus.collision.impact_loss from each impact's collision parameters). One fraction governs both bodies at each impact: the target loses that fraction of its atmosphere, and a volatile-bearing impactor loses the same fraction of its atmospheric part and delivers the remainder. PROTEUS itself ships no impact loss physics.

atmloss_law str

Which erosion scaling law to evaluate when atmloss_module = 'zephyrus'. Choices: "roche2026" (Roche et al. 2026, arXiv:2610.06077, parameterized by impact angle, mass ratio, contact velocity, target mass, radius ratio, and target atmospheric fraction), "kegerreis2020" (Kegerreis et al. 2020, doi:10.3847/2041-8213/abb5fb, parameterized by impact angle, contact velocity, mass ratio, radii, and bulk densities). The default is "roche2026". Both laws are calibrated on H2-He atmospheres (Kegerreis et al. 2020 used the HM80 equation of state; Roche et al. 2026, Sect. 2.1). For atmospheres of higher mean molecular weight (H2O, CO2, O2), the shock-driven loss is an upper limit for a given envelope mass (Roche et al. 2026, Sect. 4.3). The law covers the immediate shock- and vapour-plume-driven loss only; it neglects later thermally driven loss (an outflow driven by heat from the post-impact interior, Biersteker & Schlichting 2021), so for primordial H2-He envelopes the total loss can be higher (Roche et al. 2026, Sect. 4.1); that later loss becomes negligible for envelopes of higher mean molecular weight. Conversely, envelope miscibility may lead to overestimating loss from massive young envelopes (Roche et al. 2026, Sect. 4.1). The atmospheric mass fraction f_atm is computed from the running planet state as the sum of <e>_kg_atm over M_planet across all elements including rock vapour. Rock vapour adds to the envelope mass the scaling law sees, but PROTEUS does not debit stripped rock vapour because its inventory re-equilibrates with the magma ocean at each step. With roche2026, an airless target (m_atm = 0) returns f_loss = 0 so an impactor delivers its full volatile content. An atmosphere with f_atm between 1e-6 and below 0.0099 (1 % tolerance) is flagged and extrapolated without a clamp; f_atm below the 1e-6 stability bound is evaluated at 1e-6, and the resulting fraction depends on the collision parameters. When parameters are clamped, only the fit terms use the clamped value while v_esc, Q'_R, and the mass ratio use the raw collision state. When the far-field term predicts loss without impact energy, the 'X_FF_zero_energy' flag is set; this flag can fire inside the fitted range (for high gamma at f_atm 0.01 to 0.1) and is common below f_atm 0.01.

atmloss_frac float

Fraction of the atmosphere removed by each impact when atmloss_module = "constant" [0-1]. Applies to the target's atmosphere and to the impactor's atmospheric part alike.

delivers_volatiles property

Can an impactor carry any volatile mass under the selected mode?

valid_morrigan(instance, attribute, value)

The Morrigan module requires as many embryo masses as planets, all positive, and a valid selector value when the selector is a semi-major axis or planet id.

Validate Morrigan configuration and check selector constraints.

Source code in src/proteus/config/_accretion.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def valid_morrigan(instance, attribute, value):
    """The Morrigan module requires as many embryo masses as planets, all positive, and a valid selector value when the selector is a semi-major axis or planet id.

    Validate Morrigan configuration and check selector constraints.
    """
    if instance.module != 'morrigan':
        return

    mor = value

    if mor.masses and len(mor.masses) != mor.num_planets:
        raise ValueError(
            f'`accretion.morrigan.masses` has {len(mor.masses)} entries but '
            f'num_planets = {mor.num_planets}; they must match'
        )

    if any(m <= 0 for m in mor.masses):
        raise ValueError('All `accretion.morrigan.masses` entries must be > 0')

    if mor.selector == 'semimajoraxis':
        if mor.selector_value is None:
            raise ValueError(
                '`accretion.morrigan.selector_value` must be set (target orbit in AU) '
                "when selector = 'semimajoraxis'"
            )
        val = _as_finite_float(mor.selector_value)
        if val is None or val <= 0 or not math.isfinite(val * AU):
            raise ValueError(
                '`accretion.morrigan.selector_value` must be a finite positive number '
                "when selector = 'semimajoraxis'"
            )

    if mor.selector == 'id':
        if mor.selector_value is None:
            raise ValueError(
                "`accretion.morrigan.selector_value` must be set (planet id) when selector = 'id'"
            )
        val = _as_finite_float(mor.selector_value)
        if val is None or val < 0 or not val.is_integer():
            raise ValueError(
                '`accretion.morrigan.selector_value` must be a finite non-negative integer '
                "when selector = 'id'"
            )

valid_accretiondummy(instance, attribute, value)

Refuse a timeline path aimed at the module that generates its own.

Source code in src/proteus/config/_accretion.py
187
188
189
190
191
192
193
194
195
196
197
def valid_accretiondummy(instance, attribute, value):
    """Refuse a timeline path aimed at the module that generates its own."""
    if instance.dummy.timeline_path is None:
        return

    raise ValueError(
        '`accretion.dummy.timeline_path` is not a parameter of the analytical '
        'accretion module, which derives its own impact history. To replay a '
        "timeline from a file, set `accretion.module = 'timeline'` and move the "
        'path to `accretion.timeline.timeline_path`.'
    )

valid_accretiontimeline(instance, attribute, value)

The timeline accretion module requires a path to an impact timeline file.

Source code in src/proteus/config/_accretion.py
200
201
202
203
204
205
206
207
208
209
def valid_accretiontimeline(instance, attribute, value):
    """The timeline accretion module requires a path to an impact timeline file."""
    if instance.module != 'timeline':
        return

    if instance.timeline.timeline_path is None:
        raise ValueError(
            '`accretion.timeline.timeline_path` must point at an impact timeline file '
            "when accretion.module = 'timeline'"
        )

valid_impactor_volatiles(instance, attribute, value)

Refuse ppmw budgets that the selected content mode would ignore.

Source code in src/proteus/config/_accretion.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
def valid_impactor_volatiles(instance, attribute, value):
    """Refuse ppmw budgets that the selected content mode would ignore."""
    if instance.impactor_volatiles == 'ppmw':
        return
    set_fields = [
        f'impactor_{e}_ppmw'
        for e in ('H', 'C', 'N', 'S', 'O')
        if getattr(instance, f'impactor_{e}_ppmw') > 0.0
    ]
    if set_fields:
        raise ValueError(
            f'`accretion.{"`, `accretion.".join(set_fields)}` set, but the ppmw '
            f"budgets are read only when accretion.impactor_volatiles = 'ppmw' "
            f"(currently '{instance.impactor_volatiles}'). Select the ppmw mode "
            'or remove the budgets.'
        )

valid_impactor_budget_total(instance, attribute, value)

Refuse a volatile budget that exceeds the impactor's own mass.

The budgets are fractions of the impactor mass, and the rock the impact adds to the interior anchor is what is left once they are taken out. A total at or above 1e6 ppmw leaves nothing or less than nothing, so the impact would shrink the anchor while still crediting the full volatile mass to the planet. Whole-planet mass stays self-consistent through that, so nothing downstream can detect it.

Cross-field checks only see a complete object, so this rides on the last ppmw field and covers construction, which is where every entry point arrives: a config read from TOML, and a grid case, which is written out and read back before it runs. Assigning to one of the earlier budgets on a live object does not re-run it, so apply_impact refuses a negative rock remainder as well rather than relying on this alone.

Source code in src/proteus/config/_accretion.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def valid_impactor_budget_total(instance, attribute, value):
    """Refuse a volatile budget that exceeds the impactor's own mass.

    The budgets are fractions of the impactor mass, and the rock the impact
    adds to the interior anchor is what is left once they are taken out. A
    total at or above 1e6 ppmw leaves nothing or less than nothing, so the
    impact would shrink the anchor while still crediting the full volatile
    mass to the planet. Whole-planet mass stays self-consistent through that,
    so nothing downstream can detect it.

    Cross-field checks only see a complete object, so this rides on the last
    ppmw field and covers construction, which is where every entry point
    arrives: a config read from TOML, and a grid case, which is written out
    and read back before it runs. Assigning to one of the earlier budgets on
    a live object does not re-run it, so ``apply_impact`` refuses a negative
    rock remainder as well rather than relying on this alone.
    """
    if instance.impactor_volatiles != 'ppmw':
        return
    budgets = {e: getattr(instance, f'impactor_{e}_ppmw') for e in ('H', 'C', 'N', 'S', 'O')}
    total = sum(budgets.values())
    if total >= 1.0e6:
        named = ', '.join(f'{e}={v:g}' for e, v in budgets.items() if v > 0.0)
        raise ValueError(
            f'The impactor volatile budgets sum to {total:g} ppmw ({total / 1.0e4:.3g}% '
            f'of the impactor mass), which leaves no rock to accrete ({named}). '
            'Each budget is a fraction of the impactor mass, so they must total '
            'below 1e6 ppmw.'
        )

Synthetic observations

PetitRADTRANS

Parameters for the petitRADTRANS module.

Attributes:

Name Type Description
line_opacity_mode str

Opacity treatment: 'c-k' (correlated-k) or 'lbl' (line-by-line).

include_rayleigh bool

Include Rayleigh scattering contributions.

include_cia bool

Include collision-induced absorption contributions.

silent bool

Suppress petitRADTRANS stdout/stderr during Radtrans initialization.

Note

Input data is discovered at runtime from dirs['fwl']/prt/input_data, where dirs['fwl'] is populated from the FWL_DATA environment variable during PROTEUS startup.

Observe

Synthetic observations.

Attributes:

Name Type Description
module str

Module to use for calculating synthetic spectra.

clip_vmr float

Minimum VMR to include a species in radiative transfer.

reference_pressure float

Reference pressure for synthetic spectrum generation [bar].

source str

Composition source selection: 'all', 'outgas', 'profile', or 'offchem'.

spectrum_type str

Synthetic spectrum products to compute: 'both', 'transit', or 'eclipse'.

remove_one_gas bool

If True, generate additional leave-one-out spectra with each gas removed.


For developers: adding a new parameter

So, you are developing a new model and want to add some parameters? Follow these steps:

  1. Decide on a good parameter name (e.g. my_star_var), and under which section to place it (e.g. star). Add the new variable to the config submodule.
  2. Add the type for your variable, e.g. float, int, str. You can also add complex types, please check the code for inspiration.
  3. Add a validator! If your variable has a maximum value (e.g. 10), you can add a validator to make sure that any values above 10 are rejected: my_star_var: float = field(validator=attrs.validators.le(10))
  4. Add a description for your new variable under Attributes in the docstring. The documentation uses the description to generate this documentation.
  5. Update the example input configs. Proteus checks tests all input configs in this directory are valid.
  6. Use your parameter in your code, i.e.: config.star.my_star_var
src/proteus/config/_star.py
class Star:
    """Stellar parameters.

    Attributes
    ----------
    my_star_var: float
        Star variable, must be 10 or lower!
    """
    my_star_var: float = field(validator=attrs.validators.le(10))

Proteus uses attrs for its parameter handling. Please see the examples for more information how to work with attrs.