Quickstart demonstration for how to use the easychem package

You can find an executable version of this notebook on Google Colabhere.

We begin by loading the relevant packages: numpy, matplotlib, and easychem itself.

[1]:
import matplotlib.pyplot as plt
import numpy as np

import easychem as ec

plt.rcParams['figure.figsize'] = (10, 6)

Chemistry is calculated by creating objects from easychem’s ExoAtmos class.

[2]:
exo = ec.ExoAtmos()

By default, easychem assumes solar atmospheric abundances, following Asplund et al. (2009). You can switch to any other composition, however, and then update the abundances using exo.updateAtomAbund(), see an example further below.

The reactant species which are added by default are 'H', 'H2', 'He', 'O', 'C', 'N', 'Mg', 'Si', 'Fe', 'S', 'Al', 'Ca', 'Na', 'Ni', 'P', 'K', 'Ti', 'CO', 'OH', 'SH', 'N2', 'O2', 'SiO', 'TiO', 'SiS', 'H2O', 'C2', 'CH', 'CN', 'CS', 'SiC', 'NH', 'SiH', 'NO', 'SN', 'SiN', 'SO', 'S2', 'C2H', 'HCN', 'C2H2,acetylene', 'CH4', 'AlH', 'AlOH', 'Al2O', 'CaOH', 'MgH', 'MgOH', 'PH3', 'CO2', 'TiO2', 'Si2C', 'SiO2', 'FeO', 'NH2', 'NH3', 'CH2', 'CH3', 'H2S', 'VO', 'VO2', 'NaCl', 'KCl', 'e-', 'H+', 'H-', 'Na+', 'K+', 'PH2', 'P2', 'PS', 'PO', 'P4O6', 'PH', 'V', 'VO(c)','VO(L)','MgSiO3(c)','SiC(c)', 'Fe(c)','Al2O3(c)','Na2S(c)','KCl(c)','Fe(L)','SiC(L)', 'MgSiO3(L)','H2O(L)','H2O(c)','TiO(c)','TiO(L)', 'TiO2(c)','TiO2(L)','H3PO4(c)','H3PO4(L)'. More species (molecular, ions, condensates, etc.) can be added if they have a corresponding entry in easychem’s thermo_easy_chem_simp_own.inp file. This file follows the typical CEA input data file structure (NASA polynomials), so you can add more species if you have the corresponding thermodynamic data.

This is the how to construct the path of thermo_easy_chem_simp_own.inp, so you can modify it. You can also create a local copy elsewhere, and then store its absolute path in a hidden file called .easychem in your home directory.

[3]:
thermo_file_path = ec.__file__.replace('__init__.py','thermo_easy_chem_simp_own.inp')

This is what the first few lines of the default file look like, copied from the CEA input data file. This is for the species H, so atomic hydrogen.

[4]:
# show the first few lines:
f = open(thermo_file_path, 'r')
lines = f.readlines()
f.close()

for i in range(14):
    print(lines[i][:-1])
H                 D0(H2):Herzberg,1970. Moore,1972. Gordon,1999.
 4 g 6/97 H   1.00    0.00    0.00    0.00    0.00 0    1.0079400     217998.828
     60.000    300.0007 -2.0 -1.0  0.0  1.0  2.0  3.0  4.0  0.0         6197.428
  0.00000000D+00  0.00000000D+00  0.25000000D+01  0.00000000D+00  0.00000000D+00
  0.00000000D+00  0.00000000D+00  0.00000000D+00  0.25473708D+05 -0.44668285D+00
    300.000   1000.0007 -2.0 -1.0  0.0  1.0  2.0  3.0  4.0  0.0         6197.428
 0.000000000D+00 0.000000000D+00 2.500000000D+00 0.000000000D+00 0.000000000D+00
 0.000000000D+00 0.000000000D+00                 2.547370801D+04-4.466828530D-01
   1000.000   6000.0007 -2.0 -1.0  0.0  1.0  2.0  3.0  4.0  0.0         6197.428
 6.078774250D+01-1.819354417D-01 2.500211817D+00-1.226512864D-07 3.732876330D-11
-5.687744560D-15 3.410210197D-19                 2.547486398D+04-4.481917770D-01
   6000.000  20000.0007 -2.0 -1.0  0.0  1.0  2.0  3.0  4.0  0.0         6197.428
 2.173757694D+08-1.312035403D+05 3.399174200D+01-3.813999680D-03 2.432854837D-07
-7.694275540D-12 9.644105630D-17                 1.067638086D+06-2.742301051D+02

Calculating equilibrium abundances at single pressure-temperature point:

This is how you would calculate the atmospheric composition at a single point in the atmosphere, in this case at 1 bar and 1000 K:

[5]:
%time exo.solve(1, 1000)  # pressure = 1 bar; temperature = 1000 K
CPU times: user 13.7 ms, sys: 1.01 ms, total: 14.7 ms
Wall time: 14.6 ms

You can either get the result in the form of a numpy.ndarray, or a dictionary. Here first the arrays:

[6]:
print(exo.reacMass) # mass fractions
print(exo.reacMols) # number fractions (aka volume mixing ratios, VMRs)
[9.06021531e-10 7.37035941e-01 2.49582309e-01 7.14991129e-23
 1.71120160e-31 1.32997187e-23 1.32552621e-04 1.69366974e-38
 3.59250117e-13 1.75854880e-14 9.25083891e-26 1.02065921e-05
 2.49918731e-05 7.13609915e-05 8.04922751e-14 1.28500554e-06
 7.75264035e-30 1.47737548e-04 3.29252917e-14 1.44426842e-09
 6.73058823e-04 6.02815405e-26 5.03657374e-26 1.75359098e-23
 7.51799992e-27 5.00502878e-03 1.99056606e-37 1.58180608e-27
 1.36952560e-21 8.43062896e-15 0.00000000e+00 1.11254028e-19
 2.96316322e-36 4.90846769e-19 3.12107218e-18 1.55689884e-40
 6.04280958e-16 9.55662579e-13 1.79368225e-26 3.36098596e-09
 4.47393261e-13 3.07857565e-03 6.95996723e-24 2.84105592e-17
 9.34852915e-32 7.69568806e-05 3.94895382e-10 2.14643882e-07
 6.30982696e-06 2.52917564e-07 5.60594704e-22 0.00000000e+00
 8.42650905e-32 5.61152874e-20 7.99163549e-14 2.51643916e-05
 1.05201637e-19 1.25782953e-10 3.29138971e-04 1.11000627e-19
 1.28934651e-16 1.08724834e-05 3.40209843e-06 2.44241088e-18
 0.00000000e+00 1.41372724e-21 3.19478213e-16 1.73529394e-13
 3.81727878e-08 4.80369239e-08 1.62565617e-10 3.64336846e-11
 4.15672352e-32 3.43966910e-11 7.20735796e-23 4.17410293e-07
 0.00000000e+00 2.37988650e-03 0.00000000e+00 1.29376650e-03
 1.05261688e-04 0.00000000e+00 0.00000000e+00 0.00000000e+00
 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
 0.00000000e+00 0.00000000e+00 5.21473752e-06 0.00000000e+00
 0.00000000e+00 0.00000000e+00]
[2.09757275e-09 8.53173161e-01 1.45501532e-01 1.04278054e-23
 3.32451899e-32 2.21565731e-24 1.27259043e-05 1.40715691e-39
 1.50109640e-14 1.27973232e-15 8.00038686e-27 5.94252036e-07
 2.53665534e-06 2.83705377e-06 6.06395085e-15 7.66907676e-08
 3.77928277e-31 1.23075664e-05 4.51741738e-15 1.01899393e-10
 5.60638811e-05 4.39588791e-27 2.66588430e-27 6.40695723e-25
 2.91648021e-28 6.48281235e-04 1.93363385e-38 2.83520794e-28
 1.22829369e-22 4.46330656e-16 0.00000000e+00 1.72901172e-20
 2.37660527e-37 3.81708951e-20 1.58075853e-19 8.63087307e-42
 2.93367056e-17 3.47727710e-14 1.67221750e-27 2.90196476e-10
 4.00950831e-14 4.47794912e-04 5.80242540e-25 1.50707070e-18
 3.11798877e-33 3.14571322e-06 3.64029108e-11 1.21236977e-08
 4.33078303e-07 1.34099919e-08 1.63788763e-23 0.00000000e+00
 3.27252253e-33 1.82257005e-21 1.16386162e-14 3.44792290e-06
 1.75012529e-20 1.95223298e-11 2.25354111e-05 3.86927721e-21
 3.62743806e-18 4.34104653e-07 1.06484798e-07 1.03890249e-14
 0.00000000e+00 3.27120550e-21 3.24275595e-17 1.03566006e-14
 2.70005610e-09 1.80945032e-09 6.01750859e-12 1.80987636e-12
 4.41101423e-34 2.50963799e-12 3.30141654e-24 1.45501532e-08
 0.00000000e+00 5.53181153e-05 0.00000000e+00 5.40589451e-05
 2.40896978e-06 0.00000000e+00 0.00000000e+00 0.00000000e+00
 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
 0.00000000e+00 0.00000000e+00 1.52358808e-07 0.00000000e+00
 0.00000000e+00 0.00000000e+00]

And here as dictionaries:

[7]:
print(exo.result_mass()) # mass fractions
print(exo.result_mol())  # number fractions (VMRs)
{'H': 9.060215308072591e-10, 'H2': 0.737035941363717, 'He': 0.24958230902520975, 'O': 7.149911286224232e-23, 'C': 1.7112016027774863e-31, 'N': 1.329971874554925e-23, 'Mg': 0.00013255262131084644, 'Si': 1.693669736736222e-38, 'Fe': 3.5925011694060837e-13, 'S': 1.758548798129851e-14, 'Al': 9.250838911088532e-26, 'Ca': 1.0206592126054387e-05, 'Na': 2.499187313428508e-05, 'Ni': 7.136099147838817e-05, 'P': 8.049227508563026e-14, 'K': 1.2850055396102771e-06, 'Ti': 7.752640352282779e-30, 'CO': 0.00014773754762171913, 'OH': 3.292529169406808e-14, 'SH': 1.4442684247517603e-09, 'N2': 0.0006730588233870282, 'O2': 6.028154046711741e-26, 'SiO': 5.0365737409058804e-26, 'TiO': 1.753590975315518e-23, 'SiS': 7.517999920493922e-27, 'H2O': 0.005005028779706047, 'C2': 1.9905660636964318e-37, 'CH': 1.5818060797453722e-27, 'CN': 1.3695256041318704e-21, 'CS': 8.430628961114065e-15, 'SiC': 0.0, 'NH': 1.1125402847591055e-19, 'SiH': 2.963163218353625e-36, 'NO': 4.908467691460504e-19, 'SN': 3.1210721777243077e-18, 'SiN': 1.5568988407771779e-40, 'SO': 6.04280957693754e-16, 'S2': 9.55662578551513e-13, 'C2H': 1.7936822458495732e-26, 'HCN': 3.3609859618708623e-09, 'C2H2,acetylene': 4.4739326061170604e-13, 'CH4': 0.0030785756451886057, 'AlH': 6.959967231353941e-24, 'AlOH': 2.841055916473596e-17, 'Al2O': 9.348529153791191e-32, 'CaOH': 7.695688062248463e-05, 'MgH': 3.94895382464083e-10, 'MgOH': 2.1464388230468712e-07, 'PH3': 6.3098269561914736e-06, 'CO2': 2.529175638081966e-07, 'TiO2': 5.605947041447451e-22, 'Si2C': 0.0, 'SiO2': 8.426509045047038e-32, 'FeO': 5.611528735219297e-20, 'NH2': 7.991635489035828e-14, 'NH3': 2.51643916178121e-05, 'CH2': 1.0520163693196814e-19, 'CH3': 1.2578295314971457e-10, 'H2S': 0.0003291389708739827, 'VO': 1.1100062736219158e-19, 'VO2': 1.2893465080550783e-16, 'NaCl': 1.0872483390650977e-05, 'KCl': 3.4020984300377294e-06, 'e-': 2.442410880591701e-18, 'H+': 0.0, 'H-': 1.4137272407676952e-21, 'Na+': 3.194782125264179e-16, 'K+': 1.735293941070376e-13, 'PH2': 3.8172787845697383e-08, 'P2': 4.8036923852053564e-08, 'PS': 1.6256561708343488e-10, 'PO': 3.643368458483637e-11, 'P4O6': 4.156723519087943e-32, 'PH': 3.4396690988501726e-11, 'V': 7.207357959439425e-23, 'VO(c)': 4.174102930089093e-07, 'VO(L)': 0.0, 'MgSiO3(c)': 0.0023798865025368332, 'SiC(c)': 0.0, 'Fe(c)': 0.0012937665025954154, 'Al2O3(c)': 0.00010526168807752692, 'Na2S(c)': 0.0, 'KCl(c)': 0.0, 'Fe(L)': 0.0, 'SiC(L)': 0.0, 'MgSiO3(L)': 0.0, 'H2O(L)': 0.0, 'H2O(c)': 0.0, 'TiO(c)': 0.0, 'TiO(L)': 0.0, 'TiO2(c)': 5.214737521858054e-06, 'TiO2(L)': 0.0, 'H3PO4(c)': 0.0, 'H3PO4(L)': 0.0}
{'H': 2.097572751445111e-09, 'H2': 0.8531731613832432, 'He': 0.14550153221245757, 'O': 1.0427805363204739e-23, 'C': 3.3245189926217705e-32, 'N': 2.215657306831546e-24, 'Mg': 1.2725904348609038e-05, 'Si': 1.4071569076282964e-39, 'Fe': 1.5010963963258873e-14, 'S': 1.279732322873811e-15, 'Al': 8.000386860162457e-27, 'Ca': 5.942520360950701e-07, 'Na': 2.53665534086733e-06, 'Ni': 2.8370537687356277e-06, 'P': 6.063950848282071e-15, 'K': 7.669076761288391e-08, 'Ti': 3.7792827681465778e-31, 'CO': 1.2307566351336626e-05, 'OH': 4.517417377088386e-15, 'SH': 1.0189939331478205e-10, 'N2': 5.606388106755884e-05, 'O2': 4.395887905605103e-27, 'SiO': 2.665884295834736e-27, 'TiO': 6.4069572252552075e-25, 'SiS': 2.916480214291209e-28, 'H2O': 0.0006482812347429341, 'C2': 1.9336338494791793e-38, 'CH': 2.8352079403065337e-28, 'CN': 1.2282936920805474e-22, 'CS': 4.463306559567085e-16, 'SiC': 0.0, 'NH': 1.7290117246791445e-20, 'SiH': 2.376605268636456e-37, 'NO': 3.817089507329063e-20, 'SN': 1.5807585303872394e-19, 'SiN': 8.630873065122164e-42, 'SO': 2.933670562491673e-17, 'S2': 3.4772770958471805e-14, 'C2H': 1.6722175040431014e-27, 'HCN': 2.9019647603728134e-10, 'C2H2,acetylene': 4.009508311948472e-14, 'CH4': 0.00044779491248561057, 'AlH': 5.8024254004087625e-25, 'AlOH': 1.5070706987087033e-18, 'Al2O': 3.117988772592058e-33, 'CaOH': 3.145713219052891e-06, 'MgH': 3.640291080425702e-11, 'MgOH': 1.2123697732222612e-08, 'PH3': 4.3307830275452847e-07, 'CO2': 1.3409991878781424e-08, 'TiO2': 1.6378876336305845e-23, 'Si2C': 0.0, 'SiO2': 3.2725225276537845e-33, 'FeO': 1.8225700463999145e-21, 'NH2': 1.1638616175047716e-14, 'NH3': 3.4479229020391146e-06, 'CH2': 1.750125291164255e-20, 'CH3': 1.9522329817687155e-11, 'H2S': 2.2535411131173248e-05, 'VO': 3.869277213828102e-21, 'VO2': 3.627438064406813e-18, 'NaCl': 4.3410465269872033e-07, 'KCl': 1.0648479840880163e-07, 'e-': 1.0389024904381006e-14, 'H+': 0.0, 'H-': 3.2712054951772588e-21, 'Na+': 3.242755947513347e-17, 'K+': 1.035660061611145e-14, 'PH2': 2.700056103664487e-09, 'P2': 1.809450315770265e-09, 'PS': 6.01750859118075e-12, 'PO': 1.8098763564695e-12, 'P4O6': 4.4110142301718365e-34, 'PH': 2.5096379854763146e-12, 'V': 3.301416540231318e-24, 'VO(c)': 1.455015321928534e-08, 'VO(L)': 0.0, 'MgSiO3(c)': 5.531811528558553e-05, 'SiC(c)': 0.0, 'Fe(c)': 5.405894509574202e-05, 'Al2O3(c)': 2.4089697795947237e-06, 'Na2S(c)': 0.0, 'KCl(c)': 0.0, 'Fe(L)': 0.0, 'SiC(L)': 0.0, 'MgSiO3(L)': 0.0, 'H2O(L)': 0.0, 'H2O(c)': 0.0, 'TiO(c)': 0.0, 'TiO(L)': 0.0, 'TiO2(c)': 1.523588081823083e-07, 'TiO2(L)': 0.0, 'H3PO4(c)': 0.0, 'H3PO4(L)': 0.0}

Calculating equilibrium abundances for a whole atmospheric P-T structure:

Let’s assume an isothermal (1000 K) atmospheric temperature structure (100 layers) going from 1 µbar to 100 bar (equidistantly log-spaced). We can then calculate the atmospheric structure as follows:

[8]:
press = np.logspace(-6,2,100)
temp = np.ones(100) * 1000.

%time exo.solve(press, temp)
CPU times: user 478 ms, sys: 48 ms, total: 526 ms
Wall time: 529 ms

We can plot the results as follows:

[9]:
mass_fractions = exo.result_mass()
for spec in exo.reactants:
    plt.loglog(mass_fractions[spec], press, label = spec)

plt.ylim([1e2,1e-6])
plt.xlim([1e-10, 1])

plt.xlabel('Mass fractions')
plt.ylabel('Pressure (bar)')
[9]:
Text(0, 0.5, 'Pressure (bar)')
../../_images/content_notebooks_getting_started_23_1.png

Changing metallicity

In the following example, we are changing the considered atmosphere’s metallicity. The elemental composition exo.atomAbunds will then automatically be updated with the new value of exo.metallicity taken into account. exo.metallicity is defined with respect to solar, so if you change it all elemental abundances (except H and He) are scaled by 10**exo.metallicity (a value of 0 is thus solar).

[10]:
exo.metallicity = 0.5

%time exo.solve(press, temp)
mass_fractions = exo.result_mass()
CPU times: user 472 ms, sys: 44.8 ms, total: 517 ms
Wall time: 516 ms

Sanity check: let’s compare to the chemical equilibrium interpolation of petitRADTRANS:

[11]:
from petitRADTRANS.poor_mans_nonequ_chem import interpol_abundances

COs = 0.55 * np.ones_like(press) # solar
FeHs = 0.5 * np.ones_like(press) # metals are scaled by 10^0.5 * solar

pRT_mass_fractions = interpol_abundances(COs,
            FeHs,
            temp,
            press)

Now we plot and compare the two resulting compositions:

[12]:
for i_spec, spec in enumerate(pRT_mass_fractions.keys()):

    if 'nabla' in spec or 'MMW' in spec:
        continue

    plt.loglog(pRT_mass_fractions[spec],
               press,
               label = spec,
               linestyle = '-',
               color = 'C'+str(i_spec))
    try:
        plt.loglog(mass_fractions[spec],
                   press,
                   linestyle = ':',
                   color = 'C'+str(i_spec))
    except KeyError:
        pass

plt.ylim([1e2,1e-6])
plt.xlim([1e-10, 1])

plt.xlabel('Mass fractions')
plt.ylabel('Pressure (bar)')
plt.title('Solid = pRT chemistry interpolation, dashed = easyCHEM')
plt.legend()
[12]:
<matplotlib.legend.Legend at 0x7f85635d4af0>
../../_images/content_notebooks_getting_started_30_1.png

Note that there are slight differences, because pRT interpolates in an equilibrium abundance table, while easychem calculates the abundances at every P-T point from scratch.

Changing C/O

Next we change the carbon-to-oxygen number ratio. In easyCHEM this is done by changing the oxygen content of the atmosphere. You can also change carbon instead, however, see below.

[13]:
exo.co = 1.2

%time exo.solve(press, temp)
mass_fractions = exo.result_mass()
CPU times: user 500 ms, sys: 46 ms, total: 546 ms
Wall time: 545 ms

Again we compare to pRT here:

[14]:
COs = 1.2 * np.ones_like(press)
FeHs = 0.5 * np.ones_like(press)

pRT_mass_fractions = interpol_abundances(COs,
            FeHs,
            temp,
            press)

for i_spec, spec in enumerate(pRT_mass_fractions.keys()):

    if 'nabla' in spec or 'MMW' in spec:
        continue

    plt.loglog(pRT_mass_fractions[spec],
               press,
               label = spec,
               linestyle = '-',
               color = 'C'+str(i_spec))
    try:
        plt.loglog(mass_fractions[spec],
                   press,
                   linestyle = ':',
                   color = 'C'+str(i_spec))
    except KeyError:
        pass

plt.ylim([1e2,1e-6])
plt.xlim([1e-10, 1])

plt.xlabel('Mass fractions')
plt.ylabel('Pressure (bar)')
plt.title('Solid = pRT chemistry interpolation, dashed = easyCHEM')
plt.legend()
[14]:
<matplotlib.legend.Legend at 0x7f856542fee0>
../../_images/content_notebooks_getting_started_36_1.png

Again note the differences due to pRT’s interpolation.

Varying atmospheric abundances freely

This is an example for how to modify atmospheric elemental abundances freely. Here we chose to allow the user to scale [C/H], [O/H] and [Fe/H] independently, where [X/H] is defined as \(\rm log_{10}(X/H)-log_{10}(X/H)_\odot\). “Fe” in this case stands for all elements except for C, H, O, He. Similar setups could also be used to implement atmospheric condensate rainout.

[15]:
# Start with a clean ExoAtmos object (so solar composition)
exo = ec.ExoAtmos()

# Copy the solar abundance setup
atom_abundances_solar = exo._atomAbunds.copy()

# Get atom names
atom_names = exo.atoms

# Implement function to return the modified abundances.
def update_atom_abundances(C_H, O_H, Fe_H):
  modif_abundances = atom_abundances_solar.copy()
  for i_spec, name in enumerate(atom_names):
    if name == 'C':
      modif_abundances[i_spec] = modif_abundances[i_spec]*10**C_H
    elif name == 'O':
      modif_abundances[i_spec] = modif_abundances[i_spec]*10**O_H
    elif name not in ['H', 'He', 'C', 'O']:
      modif_abundances[i_spec] = modif_abundances[i_spec]*10**Fe_H

  return modif_abundances

Let’s test how this setup behaves now! We start by changing [C/H] from -1 to 1.

[16]:
C_Hs = np.linspace(-1, 1, 10) # from 0.1 to 10 x solar

H2Os = np.zeros(10) # to store H2O mass fractions
COs = np.zeros(10)  # to store CO  mass fractions
CH4s = np.zeros(10) # to store CH4 mass fractions
CO2s = np.zeros(10) # to store CO2 mass fractions

for i, C_H in enumerate(C_Hs):
  update_abunds = update_atom_abundances(C_H,0,0) # make update abundance list
  exo.updateAtomAbunds(update_abunds) # update abundances
  exo.solve(0.001, 1200) # calculate composition at 1 mbar, 1200 K.
  mass_fractions = exo.result_mass()
  H2Os[i] = mass_fractions['H2O']
  COs[i] = mass_fractions['CO']
  CH4s[i] = mass_fractions['CH4']
  CO2s[i] = mass_fractions['CO2']

plt.plot(C_Hs, H2Os, label = 'H2O')
plt.plot(C_Hs, COs, label = 'CO')
plt.plot(C_Hs, CH4s, label = 'CH4')
plt.plot(C_Hs, CO2s, label = 'CO2')
plt.yscale('log')
plt.xlabel('[C/H]')
plt.ylabel('Mass fraction')
plt.legend()
plt.show()
../../_images/content_notebooks_getting_started_42_0.png

Now we change [O/H] from -1 to 1, [Fe/H] from -1 to 1, then all three ([C/H], [O/H], [Fe/H]) at the same time.

[17]:
O_Hs = np.linspace(-1, 1, 20) # from 0.1 to 10 x solar

H2Os = np.zeros(20) # to store H2O mass fractions
COs = np.zeros(20)  # to store CO  mass fractions
CH4s = np.zeros(20) # to store CH4 mass fractions
CO2s = np.zeros(20) # to store CO2 mass fractions

for i, O_H in enumerate(O_Hs):
  update_abunds = update_atom_abundances(0,O_H,0)
  exo.updateAtomAbunds(update_abunds)
  exo.solve(0.001, 1200)
  mass_fractions = exo.result_mass()
  H2Os[i] = mass_fractions['H2O']
  COs[i] = mass_fractions['CO']
  CH4s[i] = mass_fractions['CH4']
  CO2s[i] = mass_fractions['CO2']

plt.plot(O_Hs, H2Os, label = 'H2O')
plt.plot(O_Hs, COs, label = 'CO')
plt.plot(O_Hs, CH4s, label = 'CH4')
plt.plot(O_Hs, CO2s, label = 'CO2')
plt.yscale('log')
plt.xlabel('[O/H]')
plt.ylabel('Mass fraction')
plt.legend()
plt.show()


Fe_Hs = np.linspace(-1, 1, 20) # from 0.1 to 10 x solar


H2Os = np.zeros(20)
COs = np.zeros(20)
CH4s = np.zeros(20)
CO2s = np.zeros(20)
for i, Fe_H in enumerate(Fe_Hs):
  update_abunds = update_atom_abundances(0,0,Fe_H)
  exo.updateAtomAbunds(update_abunds)
  exo.solve(0.001, 1200)
  mass_fractions = exo.result_mass()
  H2Os[i] = mass_fractions['H2O']
  COs[i] = mass_fractions['CO']
  CH4s[i] = mass_fractions['CH4']
  CO2s[i] = mass_fractions['CO2']

plt.plot(Fe_Hs, H2Os, label = 'H2O')
plt.plot(Fe_Hs, COs, label = 'CO')
plt.plot(Fe_Hs, CH4s, label = 'CH4')
plt.plot(Fe_Hs, CO2s, label = 'CO2')
plt.yscale('log')
plt.xlabel('[Fe/H] (increasing all metals except C, O)')
plt.ylabel('Mass fraction')
plt.legend()
plt.show()

H2Os = np.zeros(20)
COs = np.zeros(20)
CH4s = np.zeros(20)
CO2s = np.zeros(20)
for i, Fe_H in enumerate(Fe_Hs):
  update_abunds = update_atom_abundances(Fe_H,Fe_H,Fe_H)
  exo.updateAtomAbunds(update_abunds)
  exo.solve(0.001, 1200)
  mass_fractions = exo.result_mass()
  H2Os[i] = mass_fractions['H2O']
  COs[i] = mass_fractions['CO']
  CH4s[i] = mass_fractions['CH4']
  CO2s[i] = mass_fractions['CO2']

plt.plot(Fe_Hs, H2Os, label = 'H2O')
plt.plot(Fe_Hs, COs, label = 'CO')
plt.plot(Fe_Hs, CH4s, label = 'CH4')
plt.plot(Fe_Hs, CO2s, label = 'CO2')
plt.yscale('log')
plt.xlabel('[Fe/H] (increasing all metals including C, O)')
plt.ylabel('Mass fraction')
plt.legend()
plt.show()
../../_images/content_notebooks_getting_started_44_0.png
../../_images/content_notebooks_getting_started_44_1.png
../../_images/content_notebooks_getting_started_44_2.png

Ancillary outputs

Adiabatic temperature gradient \(\nabla_{\rm ad}\)

easyCHEM also outputs the atmospheric’s adiabatic temperature gradient, that is, \(\nabla_{\rm ad} = (d {\rm log T}/d {\rm log P})_{\rm ad}\). This is the gradient an atmosphere follows if it is convectively unstable. Note that easyCHEM is a CEA clone, so \(\nabla_{\rm ad}\) includes changes in chemical composition as the temperature is changed at a given pressure. \(\nabla_{\rm ad}\) therefore corresponds to the moist adiabatic lapse rate. Here an example for how to access \(\nabla_{\rm ad}\) in a solar composition atmosphere:

[18]:
exo = ec.ExoAtmos()

press = np.logspace(-6,2,100)
temp = np.ones(100) * 1000.

%time exo.solve(press, temp)
CPU times: user 477 ms, sys: 45.5 ms, total: 522 ms
Wall time: 526 ms
[19]:
nabla_ad = (exo.gamma2-1.)/exo.gamma2
plt.semilogy(nabla_ad, press)

plt.xlabel(r'$\nabla_{\rm ad}$')
plt.xlim([0.2, 0.4])
plt.ylim([100, 1e-6])
plt.ylabel('Pressure (bar)')
[19]:
Text(0, 0.5, 'Pressure (bar)')
../../_images/content_notebooks_getting_started_47_1.png

Mean molecular weight (aka mean molar mass)

Can be accessed like this:

[20]:
plt.semilogy(exo.mmw, press)
plt.xlabel(r'Mean molar mass')
plt.ylim([100, 1e-6])
plt.ylabel('Pressure (bar)')
[20]:
Text(0, 0.5, 'Pressure (bar)')
../../_images/content_notebooks_getting_started_49_1.png

Condensates: a word on stability

Condensates are tricky to handle in any equilibrium chemistry code. This is because condensates cannot occur together if one can write down hypothetical reaction equations that transform them into each other while conserving the atom number. This only occurs if the formation of the involved condensates is thermodynamically favored (i.e., the temperature is cold enough and the pressure is high enough). An example would be

\[\rm Fe_2SiO_4(c) + 2 MgAl_2O_4(c) = 2 FeO(c) + Mg_2SiO_4(c) + 2 Al_2O_3(c).\]

For the Gibbs minimizer that is the backbone of easyCHEM the actual problem is that the matrix to be inverted becomes rank-deficient. easyCHEM’s standard selection is pretty stable against this problem, but things can still go bad in a retrieval when many different temperature - pressure combinations are tried. In this case easyCHEM will become very slow and will give non-sensical solutions. See an example here when we add species that trigger the described problem:

[34]:
exo = ec.ExoAtmos()
%time exo.solve(1, 800)
print()
print(exo.result_mass())
print()

reactant_list = exo.reactants.copy()
reactant_list.append('Mg2SiO4(c)')
reactant_list.append('Mg2SiO4(L)') # L = liquid
reactant_list.append('MgAl2O4(c)')
reactant_list.append('FeO(c)')
reactant_list.append('Fe2SiO4(c)')
exo.updateReactants(reactant_list)
%time exo.solve(1, 800)
print()
print(exo.result_mass())
CPU times: user 17.2 ms, sys: 884 µs, total: 18 ms
Wall time: 18.2 ms

{'H': 1.1562719305509502e-12, 'H2': 0.7369883299709381, 'He': 0.24958230894433125, 'O': 2.1911616919487047e-29, 'C': 5.024901328789189e-42, 'N': 7.592855912936709e-30, 'Mg': 0.0001323378393782409, 'Si': 0.0, 'Fe': 1.5646190348006107e-18, 'S': 1.7064638276791544e-18, 'Al': 4.159314806444928e-36, 'Ca': 5.185032613251398e-07, 'Na': 1.3697489677399486e-08, 'Ni': 7.136099145526319e-05, 'P': 5.454813210872906e-18, 'K': 9.554110876025068e-10, 'Ti': 8.295820646107013e-42, 'CO': 1.855585229495953e-07, 'OH': 6.59223202905385e-18, 'SH': 8.960064009518709e-12, 'N2': 0.0005939228960091637, 'O2': 2.1970243045110852e-32, 'SiO': 2.745268655737504e-40, 'TiO': 4.0968179035921114e-33, 'SiS': 5.860437258622254e-41, 'H2O': 0.005095621456313255, 'C2': 0.0, 'CH': 1.8529902314158584e-36, 'CN': 1.7689418087324437e-28, 'CS': 6.333792360164624e-20, 'SiC': 0.0, 'NH': 2.281467331064815e-24, 'SiH': 0.0, 'NO': 1.7827722830099404e-23, 'SN': 4.0793997788504437e-22, 'SiN': 0.0, 'SO': 1.3956794625276365e-19, 'S2': 3.86500924422296e-15, 'C2H': 3.8474057525607355e-36, 'HCN': 4.411023338101566e-12, 'C2H2,acetylene': 2.7028003940500914e-18, 'CH4': 0.0031631774651549164, 'AlH': 3.7926518279348396e-33, 'AlOH': 5.340820435212119e-24, 'Al2O': 4.1649824347375293e-45, 'CaOH': 9.075615831473653e-05, 'MgH': 3.451933972151145e-11, 'MgOH': 5.803064605426587e-07, 'PH3': 6.4001887614241945e-06, 'CO2': 9.506929033569126e-10, 'TiO2': 3.3640168271827565e-30, 'Si2C': 0.0, 'SiO2': 2.133581511848544e-46, 'FeO': 2.2877498941823566e-26, 'NH2': 2.8849305984407644e-16, 'NH3': 0.00012138592287730278, 'CH2': 6.832509858803456e-26, 'CH3': 1.3908252771067763e-13, 'H2S': 0.00031006661151307397, 'VO': 4.354175860294414e-27, 'VO2': 2.9187184480188256e-22, 'NaCl': 8.95313300336674e-06, 'KCl': 5.850855282771632e-06, 'e-': 9.272680238001707e-23, 'H+': 0.0, 'H-': 1.0671415331114668e-27, 'Na+': 8.844833751496268e-22, 'K+': 6.6072131839109835e-18, 'PH2': 1.1581055358965007e-09, 'P2': 6.015938984252903e-10, 'PS': 7.388487764174484e-13, 'PO': 4.9301528394670126e-14, 'P4O6': 9.925323678139046e-31, 'PH': 3.358538146617307e-14, 'V': 6.386665499365991e-32, 'VO(c)': 4.1741029297781614e-07, 'VO(L)': 0.0, 'MgSiO3(c)': 0.002379886501765602, 'SiC(c)': 0.0, 'Fe(c)': 0.0012937665025354033, 'Al2O3(c)': 0.0001052616880434485, 'Na2S(c)': 4.367894495983109e-05, 'KCl(c)': 0.0, 'Fe(L)': 0.0, 'SiC(L)': 0.0, 'MgSiO3(L)': 0.0, 'H2O(L)': 0.0, 'H2O(c)': 0.0, 'TiO(c)': 0.0, 'TiO(L)': 0.0, 'TiO2(c)': 5.2147375201681555e-06, 'TiO2(L)': 0.0, 'H3PO4(c)': 0.0, 'H3PO4(L)': 0.0}


 SLOW ! Press, Temp   1.0000000000000000        800.00000000000000

CPU times: user 32.4 s, sys: 893 ms, total: 33.2 s
Wall time: 33.3 s

{'H': 1.9684336459490452e-14, 'H2': 0.012546263651379166, 'He': 0.004249065446925054, 'O': 4.652509731338093e-31, 'C': 8.556937155115721e-44, 'N': 1.2927245443721003e-31, 'Mg': 1.2029841252362701e-05, 'Si': 1.1296859002355934e-07, 'Fe': 2.2025994102299282e-05, 'S': 1.3372059813014334e-20, 'Al': 9.531580524087138e-19, 'Ca': 7.090381371542072e-09, 'Na': 4.282463349545213e-07, 'Ni': 1.2148999034963767e-06, 'P': 9.288498932810395e-20, 'K': 1.7170971690720256e-08, 'Ti': 9.081698717069078e-44, 'CO': 3.94070042095599e-09, 'OH': 4.5457753212419663e-08, 'SH': 6.860366895829447e-14, 'N2': 1.011161640185868e-05, 'O2': 5.817674706480911e-34, 'SiO': 0.00010821434893440605, 'TiO': 5.593145355851912e-35, 'SiS': 8.333854699237142e-06, 'H2O': 0.00010818141696994615, 'C2': 0.0, 'CH': 3.1552607582051887e-38, 'CN': 3.0122709426632844e-30, 'CS': 4.852574809416103e-22, 'SiC': 2.6384502911981266e-40, 'NH': 3.8840643151763735e-26, 'SiH': 6.703298794326528e-18, 'NO': 3.785283561785818e-25, 'SN': 3.1247459760398593e-24, 'SiN': 1.0534508135812108e-22, 'SO': 1.3332640372165846e-21, 'S2': 1.3319775899616303e-17, 'C2H': 6.552541288034581e-38, 'HCN': 7.510885884397768e-14, 'C2H2,acetylene': 4.6028526469232625e-20, 'CH4': 5.385172890209732e-05, 'AlH': 8.690751833930731e-16, 'AlOH': 1.526244314474471e-06, 'Al2O': 1.6021007771131828e-08, 'CaOH': 1.5476303511645028e-06, 'MgH': 3.137687291484777e-12, 'MgOH': 6.578188186201669e-08, 'PH3': 1.0896133138637656e-07, 'CO2': 2.5178767909242015e-11, 'TiO2': 5.727555602565779e-32, 'Si2C': 1.0078687104036557e-25, 'SiO2': 1.0488441203267311e-10, 'FeO': 4.016401885215563e-13, 'NH2': 4.9111008262455304e-18, 'NH3': 2.066251239540588e-06, 'CH2': 1.1633588969939559e-27, 'CH3': 2.3679762687697728e-15, 'H2S': 2.374796582362191e-06, 'VO': 1.0532282174706015e-13, 'VO2': 8.804616261755816e-09, 'NaCl': 1.7806666822790585e-07, 'KCl': 6.689272298115187e-08, 'e-': 5.129791545763748e-23, 'H+': 0.0, 'H-': 5.9032038270103885e-28, 'Na+': 8.510578226665732e-22, 'K+': 3.654598196619834e-18, 'PH2': 1.971770813361958e-11, 'P2': 1.0245273870451392e-11, 'PS': 5.660291840131858e-15, 'PO': 1.0469543462056104e-15, 'P4O6': 6.36039486045353e-32, 'PH': 5.718567601795519e-16, 'V': 1.2387651578961043e-18, 'VO(c)': 0.0, 'VO(L)': 0.0, 'MgSiO3(c)': 0.0, 'SiC(c)': 0.0, 'Fe(c)': 0.0, 'Al2O3(c)': 0.0, 'Na2S(c)': 0.0, 'KCl(c)': 0.0, 'Fe(L)': 0.0, 'SiC(L)': 0.0, 'MgSiO3(L)': 0.0, 'H2O(L)': 0.0, 'H2O(c)': 0.0, 'TiO(c)': 0.0, 'TiO(L)': 0.0, 'TiO2(c)': 0.9828721327112375, 'TiO2(L)': 0.0, 'H3PO4(c)': 0.0, 'H3PO4(L)': 0.0, 'Mg2SiO4(c)': 0.0, 'Mg2SiO4(L)': 0.0, 'MgAl2O4(c)': 0.0, 'FeO(c)': 0.0, 'Fe2SiO4(c)': 0.0}
 EASY CHEM WARNING: One or more convergence criterianot satisfied! in cond Press, temp   1.0000000000000000        800.00000000000000

What to do is not entirely clear. The best thing likely is to check which species are expected to condense at a given pressure, temperature and composition, and which of these dominate the mass budget, when compared to other species. A useful starting point are papers such as Visser et al. (2010).