Skip to content

proteus.outgas.common

The species-to-element split and the list of helpfile keys that each outgassing module writes.

For how this part works, see Outgassing, rock vapour and volatile trapping.

common

element_masses_from_species(species_kg)

Split per-species masses [kg] into per-element masses [kg].

Each species mass is apportioned across its constituent elements by their share of the formula mass, so the element masses sum back to the species mass. A species absent from VOLATILE_ELEMENT_STOICH (a noble gas, a rock vapour) is carried through under its own symbol, which is how those species already appear in element_list.

Parameters:

Name Type Description Default
species_kg dict

Mass per gas species [kg].

required

Returns:

Type Description
dict

Mass per element [kg], covering only the elements the input touches.

Source code in src/proteus/outgas/common.py
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
def element_masses_from_species(species_kg: dict[str, float]) -> dict[str, float]:
    """Split per-species masses [kg] into per-element masses [kg].

    Each species mass is apportioned across its constituent elements by their
    share of the formula mass, so the element masses sum back to the species
    mass. A species absent from ``VOLATILE_ELEMENT_STOICH`` (a noble gas, a rock
    vapour) is carried through under its own symbol, which is how those species
    already appear in ``element_list``.

    Parameters
    ----------
    species_kg : dict
        Mass per gas species [kg].

    Returns
    -------
    dict
        Mass per element [kg], covering only the elements the input touches.
    """
    from proteus.utils.constants import element_mmw

    out: dict[str, float] = {}
    for species, mass in species_kg.items():
        if not mass:
            continue
        stoich = VOLATILE_ELEMENT_STOICH.get(species)
        if stoich is None:
            # Monatomic species (noble gases, rock vapours) are their own element.
            out[species] = out.get(species, 0.0) + float(mass)
            continue
        formula_mmw = sum(n * element_mmw[el] for el, n in stoich.items())
        if formula_mmw <= 0.0:
            continue
        for el, n in stoich.items():
            out[el] = out.get(el, 0.0) + float(mass) * (n * element_mmw[el] / formula_mmw)
    return out

expected_keys()

Helpfile keys that an outgassing module writes on every call.

The pressures [bar], mixing ratios, masses [kg] and moles of each gas per reservoir, the element reservoirs, the atmospheric element mass ratios and the bulk columns. {e}_kg_total is left out for every element except O, since escape owns those totals.

Source code in src/proteus/outgas/common.py
 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def expected_keys():
    """Helpfile keys that an outgassing module writes on every call.

    The pressures [bar], mixing ratios, masses [kg] and moles of each gas per
    reservoir, the element reservoirs, the atmospheric element mass ratios and
    the bulk columns. ``{e}_kg_total`` is left out for every element except O,
    since escape owns those totals.
    """
    copy_keys = [
        'P_surf',
        'P_vol',
        'P_vap',
        'M_atm',
        'M_vol_atm',
        'M_vaps',
        'atm_kg_per_mol',
        'fO2_shift_IW_derived',
        'O_res',
    ]

    # reservoirs
    res_list = ('atm', 'liquid', 'solid', 'total')

    # gases
    for s in gas_list:
        copy_keys.append(f'{s}_bar')
        copy_keys.append(f'{s}_vmr')
        for r in res_list:
            # A noble gas is also an element; its _kg_total is escape-owned
            # (like every non-O element total), so it is not copied from the
            # backend here. Its atm/liquid/solid reservoirs are.
            if not (r == 'total' and s in noble_gases):
                copy_keys.append(f'{s}_kg_{r}')
            copy_keys.append(f'{s}_mol_{r}')

    # elements. The `_kg_total` slot is owned by escape (which debits
    # the running budget after the wrapper writes) for every element
    # EXCEPT oxygen. For O, the chemistry solver's output partitions
    # atm+liquid+solid into a fresh total each iteration; the calliope
    # wrapper restores hf_row['O_kg_total'] to the authoritative input
    # immediately after this copy when fO2_source =
    # "from_O_budget", so the escape debit chain is preserved across
    # iterations. Under user_constant the solver's O_kg_total IS the
    # authoritative value, so the copy is the correct write.
    # Some vap_list species are monatomic (e.g. 'Si', 'Na', 'Fe') and share
    # their string with the matching element_list symbol, so the gas loop
    # above already appended f'{e}_kg_{r}' for those elements. Skip keys
    # already present instead of appending a duplicate for the same
    # hf_row slot.
    for e in element_list:
        if e in noble_gases:
            continue  # noble reservoirs are handled in the gas-species loop
        for r in res_list:
            if (r != 'total') or (e == 'O'):
                key = f'{e}_kg_{r}'
                if key not in copy_keys:
                    copy_keys.append(key)

    # element mass ratios in atmosphere (must mirror the unordered-pair
    # registration in coupler.GetHelpfileKeys so run_desiccated zeros
    # them on desiccation instead of leaving stale ratios in hf_row).
    for e1 in element_list:
        for e2 in element_list:
            if (e1 == e2) or (f'{e1}/{e2}_atm' in copy_keys):
                continue
            copy_keys.append(f'{e2}/{e1}_atm')

    return copy_keys