diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f2a2db..8408acb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,19 +1,14 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.4" + rev: v0.15.15 hooks: - - id: ruff - args: [--fix, --show-fixes] - types_or: [python] + - id: ruff-check + args: [--fix] + - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - - repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black diff --git a/docs/index.rst b/docs/index.rst index 7437ba4..cc50ccb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,7 +8,7 @@ The current implementation has been developed in Python 3 and tested on Windows, Installation/Usage: ******************* -EVo can be used through local installation, either through a CLI or as a library. +EVo can be used through local installation, either through a CLI, as a library, or with a webapp. To install locally, EVo must be downloaded from GitHub using :: @@ -19,7 +19,7 @@ into the project directory where you wish to use EVo. EVo must then be locally p :: cd EVO - python -m pip install . + python -m pip install ".[streamlit]" From this point, EVo can either be imported into your python scripts as a regular module using :: @@ -37,6 +37,13 @@ Or EVo can be run directly from the terminal from inside the `evo` directory: cd EVO/evo python dgs.py input/chem.yaml input/env.yaml --output input/output.yaml +Alternatively you can interact with EVo using the webapp interface, by running: +:: + + cd EVO/webapp + streamlit run streamlit-app.py + + .. toctree:: :maxdepth: 2 :caption: Contents: diff --git a/examples/evo_example.ipynb b/examples/evo_example.ipynb index de1f758..72d80e6 100644 --- a/examples/evo_example.ipynb +++ b/examples/evo_example.ipynb @@ -24,8 +24,9 @@ "metadata": {}, "outputs": [], "source": [ - "import pandas as pd\n", "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", "import evo\n", "import evo.plots as evoplots" ] @@ -47,18 +48,20 @@ "metadata": {}, "outputs": [], "source": [ - "composition = pd.Series({\n", - " \"SIO2\": 47.95,\n", - " \"TIO2\": 1.67,\n", - " \"AL2O3\": 17.32,\n", - " \"FEO\": 10.24,\n", - " \"MNO\": 0.17,\n", - " \"MGO\": 5.76,\n", - " \"CAO\": 10.93,\n", - " \"NA2O\": 3.45,\n", - " \"K2O\": 1.99,\n", - " \"P2O5\": 0.51,\n", - "})" + "composition = pd.Series(\n", + " {\n", + " \"SIO2\": 47.95,\n", + " \"TIO2\": 1.67,\n", + " \"AL2O3\": 17.32,\n", + " \"FEO\": 10.24,\n", + " \"MNO\": 0.17,\n", + " \"MGO\": 5.76,\n", + " \"CAO\": 10.93,\n", + " \"NA2O\": 3.45,\n", + " \"K2O\": 1.99,\n", + " \"P2O5\": 0.51,\n", + " }\n", + ")" ] }, { @@ -90,35 +93,40 @@ "source": [ "# Set up fixed model parameters\n", "\n", - "model = {\"COMPOSITION\": \"basalt\",\n", - " \"FIND_SATURATION\": True,\n", - " \"GAS_SYS\": \"cohs\",\n", - " \"FE_SYSTEM\": True,\n", - " \"FO2_buffer_SET\": True,\n", - " \"FH2_SET\": False,\n", - " \"WTH2O_SET\": True,\n", - " \"WTCO2_SET\": True,\n", - " \"SULFUR_SET\": True}\n", + "model = {\n", + " \"COMPOSITION\": \"basalt\",\n", + " \"FIND_SATURATION\": True,\n", + " \"GAS_SYS\": \"cohs\",\n", + " \"FE_SYSTEM\": True,\n", + " \"FO2_buffer_SET\": True,\n", + " \"FH2_SET\": False,\n", + " \"WTH2O_SET\": True,\n", + " \"WTCO2_SET\": True,\n", + " \"SULFUR_SET\": True,\n", + "}\n", "\n", "# Then merge with the input parameters for use with EVo.\n", "\n", "# Starting conditions\n", - "fo2_buffer = \"FMQ\" # reference buffer (IW, FMQ or NNO)\n", - "dfo2 = 0 # offset from buffer in log units\n", - "temp_k = 1473 # temperature, K\n", - "h2o_wt_perc = 2.0 # dissolved H2O in the melt, wt%\n", - "co2_wt_perc = 0.15 # dissolved CO2 in the melt, wt%\n", - "s_ppm = 3000 # dissolved S in the melt, ppm\n", + "fo2_buffer = \"FMQ\" # reference buffer (IW, FMQ or NNO)\n", + "dfo2 = 0 # offset from buffer in log units\n", + "temp_k = 1473 # temperature, K\n", + "h2o_wt_perc = 2.0 # dissolved H2O in the melt, wt%\n", + "co2_wt_perc = 0.15 # dissolved CO2 in the melt, wt%\n", + "s_ppm = 3000 # dissolved S in the melt, ppm\n", "\n", - "env = pd.Series(model | {\n", - " 'FO2_buffer': fo2_buffer,\n", - " 'FO2_buffer_START': dfo2,\n", - " 'T_START': temp_k,\n", - " # Volatile inputs as melt wt fractions (EVo uses wt fraction internally)\n", - " 'WTH2O_START': h2o_wt_perc / 100,\n", - " 'WTCO2_START': co2_wt_perc / 100,\n", - " 'SULFUR_START': s_ppm / 1e6\n", - "})" + "env = pd.Series(\n", + " model\n", + " | {\n", + " \"FO2_buffer\": fo2_buffer,\n", + " \"FO2_buffer_START\": dfo2,\n", + " \"T_START\": temp_k,\n", + " # Volatile inputs as melt wt fractions (EVo uses wt fraction internally)\n", + " \"WTH2O_START\": h2o_wt_perc / 100,\n", + " \"WTCO2_START\": co2_wt_perc / 100,\n", + " \"SULFUR_START\": s_ppm / 1e6,\n", + " }\n", + ")" ] }, { @@ -177,7 +185,20 @@ "outputs": [], "source": [ "# Pressure, gas fraction, major gas species, and melt residuals\n", - "df[[\"P\", \"Gas_wt\", \"Exsol_vol%\", \"mH2O\", \"mCO2\", \"mSO2\", \"mH2S\", \"H2O_melt\", \"CO2_melt\", \"Stot_melt\"]]" + "df[\n", + " [\n", + " \"P\",\n", + " \"Gas_wt\",\n", + " \"Exsol_vol%\",\n", + " \"mH2O\",\n", + " \"mCO2\",\n", + " \"mSO2\",\n", + " \"mH2S\",\n", + " \"H2O_melt\",\n", + " \"CO2_melt\",\n", + " \"Stot_melt\",\n", + " ]\n", + "]" ] }, { diff --git a/pyproject.toml b/pyproject.toml index 7ac07de..5c45a0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dependencies = ["numpy>=1.21.0", [project.optional-dependencies] dev = ["ruff", "pre-commit"] +web = ["streamlit"] test = ["pytest"] docs = ["sphinx==5.3.0", "sphinx_rtd_theme"] diff --git a/src/evo/__init__.py b/src/evo/__init__.py index a2bfe00..cb4dcde 100644 --- a/src/evo/__init__.py +++ b/src/evo/__init__.py @@ -1,9 +1,11 @@ """ EVo + A Python model for volcanic degassing, using the equilibrium constants and mass balance method. """ -from evo.dgs import run_evo as run_evo +from evo.dgs import run_evo +from evo.multirun import multirun -__all__ = ["run_evo"] +__all__ = ["multirun", "run_evo"] diff --git a/src/evo/dgs_classes.py b/src/evo/dgs_classes.py index 7dc4df6..0bdca85 100644 --- a/src/evo/dgs_classes.py +++ b/src/evo/dgs_classes.py @@ -752,9 +752,8 @@ def __init__(self, run, sys, melt, mol): self.melt = melt self.delG = 0 # Gibbs Free energy of formation self.Y = 0 # Fugacity coefficient - self.y_constants = ( - {} - ) # Stores any constants needed for the activity coefficient calculation + # Store constants needed for the activity coefficient calculation + self.y_constants = {} def get_G(self, T): """ @@ -1256,11 +1255,11 @@ def melt_composition(self, gas, mols): elif self.run.GAS_SYS == "COH": H2O, O2, H2, CO, CO2, CH4 = mols elif self.run.GAS_SYS == "SOH": - H2O, O2, H2, S2, SO2, H2S = mols + H2O, O2, H2, S2 = mols[:4] elif self.run.GAS_SYS == "COHS": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S = mols + H2O, O2, H2, CO, CO2, CH4, S2 = mols[:7] elif self.run.GAS_SYS == "COHSN": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S, N2 = mols + H2O, O2, H2, CO, CO2, CH4, S2 = mols[:7] self.h2o.append( sl.h2o_melt(gas.mH2O[-1], H2O, self.sys.P, name=self.sys.run.H2O_MODEL) @@ -1483,9 +1482,7 @@ def __init__(self, sys): self.mS2 = [] self.mN2 = [] self.atomicM = {} - self.wt = ( - {} - ) # Temporary store for the results of converting mole frac to weight frac + self.wt = {} # Store for the results of converting mole frac to weight frac self.Wt = {} # Store of weight fractions at each pressure step self.f = {} # Store the fugacity of a species at each step. self.M = [] # Stores the mean molecular weight of the gas phase @@ -1549,7 +1546,7 @@ def get_WgT(self, melt, mols): ) * sum(mjMj) elif self.sys.run.GAS_SYS == "SOH": - H2O, O2, H2, S2, SO2, H2S = mols + H2O, O2, H2 = mols[:3] lst = { "o2": self.mO2[-1], "h2": self.mH2[-1], @@ -1581,7 +1578,7 @@ def get_WgT(self, melt, mols): ) * sum(mjMj) elif self.sys.run.GAS_SYS == "COHS": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S = mols + H2O, O2, H2, CO, CO2, CH4 = mols[:6] lst = { "o2": self.mO2[-1], "h2": self.mH2[-1], @@ -1624,7 +1621,7 @@ def get_WgT(self, melt, mols): ) * sum(mjMj) elif self.sys.run.GAS_SYS == "COHSN": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S, N2 = mols + H2O, O2, H2, CO, CO2, CH4 = mols[:6] lst = { "o2": self.mO2[-1], "h2": self.mH2[-1], diff --git a/src/evo/fixed_weights.py b/src/evo/fixed_weights.py index 4984a23..59095a5 100644 --- a/src/evo/fixed_weights.py +++ b/src/evo/fixed_weights.py @@ -212,23 +212,23 @@ def sat_pressure(run, sys, gas, melt, mols): sys.atomicM["h"] = run.ATOMIC_H / 1e6 elif run.GAS_SYS == "COH": - H2O, O2, H2, CO, CO2, CH4 = mols + H2O, O2, H2, CO, CO2, _ = mols sys.atomicM["c"] = run.ATOMIC_C / 1e6 sys.atomicM["h"] = run.ATOMIC_H / 1e6 elif run.GAS_SYS == "SOH": - H2O, O2, H2, S2, SO2, H2S = mols + H2O, O2, H2, _, _, _ = mols sys.atomicM["h"] = run.ATOMIC_H / 1e6 sys.atomicM["s"] = run.ATOMIC_S / 1e6 elif run.GAS_SYS == "COHS": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S = mols + H2O, O2, H2, CO, CO2, _, _, _, _ = mols sys.atomicM["c"] = run.ATOMIC_C / 1e6 sys.atomicM["h"] = run.ATOMIC_H / 1e6 sys.atomicM["s"] = run.ATOMIC_S / 1e6 elif run.GAS_SYS == "COHSN": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S, N2 = mols + H2O, O2, H2, CO, CO2, _, _, _, _, _ = mols sys.atomicM["c"] = run.ATOMIC_C / 1e6 sys.atomicM["h"] = run.ATOMIC_H / 1e6 sys.atomicM["n"] = run.ATOMIC_N / 1e6 @@ -388,7 +388,7 @@ def fixed_weights_oh(guesses: list[float]): gamma = sg.find_Y(P_sat, sys.T, sys.SC)[:10] fugacities = get_f(P_sat, melt_h2o, melt_co2, melt_s, melt_n, sys, melt, gamma) - h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y, s2_y, so2_y, h2s_y, n2_y = gamma + h2o_y, o2_y, h2_y = gamma[:3] O2.Y = o2_y mH2O, mO2, mH2 = get_molfrac(P_sat, fugacities, gamma) @@ -448,7 +448,7 @@ def fixed_weights_coh(guesses: list[float]): gamma = sg.find_Y(P_sat, sys.T, sys.SC)[:10] fugacities = get_f(P_sat, melt_h2o, melt_co2, melt_s, melt_n, sys, melt, gamma) - h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y, s2_y, so2_y, h2s_y, n2_y = gamma + h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y = gamma[:6] O2.Y = o2_y mH2O, mO2, mH2, mCO, mCO2, mCH4 = get_molfrac(P_sat, fugacities, gamma) @@ -548,7 +548,7 @@ def fixed_weights_soh(guesses: list[float]): gamma = sg.find_Y(P_sat, sys.T, sys.SC)[:10] fugacities = get_f(P_sat, melt_h2o, melt_co2, melt_s, melt_n, sys, melt, gamma) - h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y, s2_y, so2_y, h2s_y, n2_y = gamma + h2o_y, o2_y, h2_y, _, _, _, s2_y, _, _, _ = gamma O2.Y = o2_y mH2O, mO2, mH2, mS2, mSO2, mH2S = get_molfrac(P_sat, fugacities, gamma) @@ -629,7 +629,7 @@ def fixed_weights_cohs(guesses: list[float]): gamma = sg.find_Y(P_sat, sys.T, sys.SC)[:10] fugacities = get_f(P_sat, melt_h2o, melt_co2, melt_s, melt_n, sys, melt, gamma) - h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y, s2_y, so2_y, h2s_y, n2_y = gamma + h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y, s2_y = gamma[:7] O2.Y = gamma[1] mH2O, mO2, mH2, mCO, mCO2, mCH4, mS2, mSO2, mH2S = get_molfrac( @@ -768,7 +768,7 @@ def fixed_weights_cohsn(guesses: list[float]): gamma = sg.find_Y(P_sat, sys.T, sys.SC)[:10] fugacities = get_f(P_sat, melt_h2o, melt_co2, melt_s, melt_n, sys, melt, gamma) - h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y, s2_y, so2_y, h2s_y, n2_y = gamma + h2o_y, o2_y, h2_y, co_y, co2_y, ch4_y, s2_y = gamma[:7] O2.Y = o2_y mH2O, mO2, mH2, mCO, mCO2, mCH4, mS2, mSO2, mH2S, mN2 = get_molfrac( @@ -1139,7 +1139,7 @@ def fixed_weights_cohsn(guesses: list[float]): values = [mH2O, mO2, mH2, mCO, mCO2, mCH4, mS2, mSO2, mH2S, mN2] # add gas speciation to lists to act as initial guess for first 'proper' P step - for ls, val in zip(lists, values): + for ls, val in zip(lists, values, strict=False): ls.append(val) for ls in empty_list: diff --git a/src/evo/init_cons.py b/src/evo/init_cons.py index 5d72fde..0f96787 100644 --- a/src/evo/init_cons.py +++ b/src/evo/init_cons.py @@ -45,13 +45,13 @@ def get_inputs(sys, run, melt, gas, mols) -> tuple[float, ...]: """ if run.GAS_SYS == "COH": - H2O, O2, H2, CO, CO2, CH4 = mols + H2O, O2, H2, _, CO2, _ = mols elif run.GAS_SYS == "SOH": - H2O, O2, H2, S2, SO2, H2S = mols + H2O, O2, H2, S2 = mols[:4] elif run.GAS_SYS == "COHS": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S = mols + H2O, O2, H2, _, CO2, _, S2 = mols[:7] elif run.GAS_SYS == "COHSN": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S, N2 = mols + H2O, O2, H2, _, CO2, _, S2 = mols[:7] if run.FH2_SET is True: mH2 = run.FH2_START / (H2.Y * sys.P) @@ -180,9 +180,7 @@ def get_inputs(sys, run, melt, gas, mols) -> tuple[float, ...]: ) graph_melt = ( (run.WTCO2_START - co2_melt) / cnst.m["co2"] - ) * cnst.m[ - "c" - ] # wt frac graphite in melt + ) * cnst.m["c"] # wt frac graphite in melt melt.graphite_sat = True melt.graph_current = graph_melt / cnst.m["c"] run.WTCO2_START = co2_melt diff --git a/src/evo/messages.py b/src/evo/messages.py index 0377b2e..d7f4e50 100644 --- a/src/evo/messages.py +++ b/src/evo/messages.py @@ -121,7 +121,7 @@ def query_yes_no(question, default="yes"): elif choice in valid: return valid[choice] else: - sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n") def run_chem_mismatch(mtype, lims, sio2): @@ -231,7 +231,7 @@ def sulfate_warn(s6, sys, gas, melt): closed_earlyexit(sys, gas, melt) raise ValueError( - f"The sulfate (S6+ speciated as SO4) content of the melt is {s6*100:.1f}% " + f"The sulfate (S6+ speciated as SO4) content of the melt is {s6 * 100:.1f}% " "of the total melt S content.\n" "As sulfate currently does not exsolve, gas phase sulphur-bearing species " "will now be significantly underestimated.\n" @@ -667,7 +667,7 @@ def graphite_warn(melt): "melt CO2 content at graphite saturation." ) answer = query_yes_no( - f"Continue with a melt graphite content of {graph*100} wt%?", + f"Continue with a melt graphite content of {graph * 100} wt%?", default="yes", ) @@ -727,10 +727,12 @@ def graphite_warn_saturation(melt, fCO2, fO2, CO2): ] # wt frac graphite in melt melt.graph_current = graph_melt / cnst.m["c"] - print(f"The original melt CO2 content was {melt.sys.run.WTCO2_START*100} wt%.") + print( + f"The original melt CO2 content was {melt.sys.run.WTCO2_START * 100} wt%." + ) print( "At graphite-present volatile saturation, the melt CO2 content is " - f"{co2_melt*100} wt% and the graphite content is {graph_melt*100} wt%." + f"{co2_melt * 100} wt% and the graphite content is {graph_melt * 100} wt%." ) answer = query_yes_no("Do you wish to continue?", default="yes") diff --git a/src/evo/multirun.py b/src/evo/multirun.py index dcf549d..7e1572d 100644 --- a/src/evo/multirun.py +++ b/src/evo/multirun.py @@ -3,10 +3,12 @@ from shutil import copyfile import numpy as np -import yaml +import ruamel.yaml from evo.dgs import run_evo +ryaml = ruamel.yaml.YAML() + def amend_env(file, **kwargs): """ @@ -25,7 +27,7 @@ def amend_env(file, **kwargs): """ with open(file) as f: - env_doc = yaml.full_load(f) + env_doc = ryaml.load(f) for param, val in kwargs.items(): if isinstance(val, np.float64): @@ -34,7 +36,7 @@ def amend_env(file, **kwargs): env_doc[param] = val with open("multirun.yaml", "w") as f: - yaml.dump(env_doc, f) + ryaml.dump(env_doc, f) def multirun(**kwargs): @@ -63,7 +65,7 @@ def multirun(**kwargs): onerun = {} for run in options: - for a, b in zip(keys, run): + for a, b in zip(keys, run, strict=False): onerun[a] = b run_name = "_".join([str(x) for x in run]) diff --git a/src/evo/plots.py b/src/evo/plots.py index b1e475e..982ad0a 100644 --- a/src/evo/plots.py +++ b/src/evo/plots.py @@ -80,6 +80,7 @@ def plot_gasspecies_wt(df): "pink", "grey", ], + strict=False, ): ax.plot(df[g], df["P"], c=color, label=g[1:]) ax.annotate(g[1:], xy=[df[g].iloc[-1], df["P"].iloc[-1]], color=color) @@ -119,6 +120,7 @@ def plot_meltspecies(df): "pink", "grey", ], + strict=False, ): ax.plot(df[s], df["P"], c=color, label=s.split("_")[0]) ax.annotate(s.split("_")[0], xy=[df[s].iloc[-1], df["P"].iloc[-1]], color=color) diff --git a/src/evo/readin.py b/src/evo/readin.py index 1ba3082..09cdb0e 100644 --- a/src/evo/readin.py +++ b/src/evo/readin.py @@ -6,7 +6,7 @@ import numpy as np import pandas as pd -import yaml +import ruamel.yaml from numpy import exp, log10 from numpy import log as ln @@ -19,6 +19,8 @@ # bundled scripts from evo.dgs_classes import Gas, Melt, Molecule, Output, RunDef, ThermoSystem +ryaml = ruamel.yaml.YAML() + # ------------------------------------------------------------------------ # FUNCTION DEFINTIONS # ------------------------------------------------------------------------ @@ -188,7 +190,7 @@ def readin_output(f): The instantiated Output class """ - x = yaml.full_load(f) + x = ryaml.load(f) out = Output() out.set_outputs(x) diff --git a/src/evo/sat_pressure.py b/src/evo/sat_pressure.py index 1d30efe..d2e31de 100644 --- a/src/evo/sat_pressure.py +++ b/src/evo/sat_pressure.py @@ -304,7 +304,7 @@ def get_f(P, sys, melt, gamma): elif run.GAS_SYS == "COHS": H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S = mols elif run.GAS_SYS == "COHSN": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S, N2 = mols + H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S, _ = mols def find_p(P, sys, melt): """ @@ -848,7 +848,7 @@ def quadratic(): values = [mH2O, mO2, mH2, mCO, mCO2, mCH4, mS2, mSO2, mH2S, mN2] # add gas speciation to lists to act as initial guess for first 'proper' P step - for ls, val in zip(lists, values): + for ls, val in zip(lists, values, strict=False): ls.append(val) for ls in empty_list: @@ -893,7 +893,7 @@ def satp_writeout(sys, melt, gas, P, values, gamma, mols, graph_sat=False): """ if sys.run.GAS_SYS == "OH": - H2O, O2, H2 = mols + H2O, _, H2 = mols mH2O, mO2, mH2 = tuple(values) h2oy, o2y, h2y = gamma[:3] @@ -904,7 +904,7 @@ def satp_writeout(sys, melt, gas, P, values, gamma, mols, graph_sat=False): M = cnvs.mean_mol_wt(H2O=mH2O, O2=mO2, H2=mH2) elif sys.run.GAS_SYS == "COH": - H2O, O2, H2, CO, CO2, CH4 = mols + H2O, _, H2, _, CO2, _ = mols mH2O, mO2, mH2, mCO, mCO2, mCH4 = tuple(values) h2oy, o2y, h2y, coy, co2y, ch4y = gamma[:6] @@ -915,7 +915,7 @@ def satp_writeout(sys, melt, gas, P, values, gamma, mols, graph_sat=False): M = cnvs.mean_mol_wt(H2O=mH2O, O2=mO2, H2=mH2, CO=mCO, CO2=mCO2, CH4=mCH4) elif sys.run.GAS_SYS == "SOH": - H2O, O2, H2, S2, SO2, H2S = mols + H2O, _, H2, _, _, _ = mols mH2O, mO2, mH2, mS2, mSO2, mH2S = tuple(values) h2oy, o2y, h2y, s2y, so2y, h2sy = gamma[:3] + gamma[6:9] @@ -926,7 +926,7 @@ def satp_writeout(sys, melt, gas, P, values, gamma, mols, graph_sat=False): M = cnvs.mean_mol_wt(H2O=mH2O, O2=mO2, H2=mH2, S2=mS2, SO2=mSO2, H2S=mH2S) elif sys.run.GAS_SYS == "COHS": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S = mols + H2O, _, H2, _, CO2, _, _, _, _ = mols mH2O, mO2, mH2, mCO, mCO2, mCH4, mS2, mSO2, mH2S = tuple(values) h2oy, o2y, h2y, coy, co2y, ch4y, s2y, so2y, h2sy = gamma[:9] @@ -957,7 +957,7 @@ def satp_writeout(sys, melt, gas, P, values, gamma, mols, graph_sat=False): ) elif sys.run.GAS_SYS == "COHSN": - H2O, O2, H2, CO, CO2, CH4, S2, SO2, H2S, N2 = mols + H2O, _, H2, _, CO2 = mols[:5] mH2O, mO2, mH2, mCO, mCO2, mCH4, mS2, mSO2, mH2S, mN2 = tuple(values) h2oy, o2y, h2y, coy, co2y, ch4y, s2y, so2y, h2sy, n2y = gamma diff --git a/src/evo/solubility_laws.py b/src/evo/solubility_laws.py index f8c8e59..7153d38 100644 --- a/src/evo/solubility_laws.py +++ b/src/evo/solubility_laws.py @@ -540,10 +540,7 @@ def f(fco2, fO2, T, P, melt, melt_CO2): # Check for graphite saturation logK1 = ( - 40.07639 - - 2.53932 * 10**-2 * T - + 5.27096 * 10**-6 * T**2 - + 0.0267 * (P - 1) / T + 40.07639 - 2.53932 * 10**-2 * T + 5.27096 * 10**-6 * T**2 + 0.0267 * (P - 1) / T ) graph_fco2 = 10**logK1 * 10 ** np.log10(fO2) @@ -582,10 +579,7 @@ def graphite_fco2(T, P, fO2): """ logK1 = ( - 40.07639 - - 2.53932 * 10**-2 * T - + 5.27096 * 10**-6 * T**2 - + 0.0267 * (P - 1) / T + 40.07639 - 2.53932 * 10**-2 * T + 5.27096 * 10**-6 * T**2 + 0.0267 * (P - 1) / T ) graph_fco2 = 10**logK1 * 10 ** np.log10(fO2) diff --git a/webapp/chem.yaml b/webapp/chem.yaml new file mode 100644 index 0000000..d29f49a --- /dev/null +++ b/webapp/chem.yaml @@ -0,0 +1,10 @@ +SIO2: 47.95 +TIO2: 1.67 +AL2O3: 17.32 +FEO: 10.24 +MNO: 0.17 +MGO: 5.76 +CAO: 10.93 +NA2O: 3.45 +K2O: 1.99 +P2O5: 0.51 diff --git a/webapp/env.yaml b/webapp/env.yaml new file mode 100644 index 0000000..612f7e4 --- /dev/null +++ b/webapp/env.yaml @@ -0,0 +1,103 @@ +#run type +COMPOSITION: basalt + +#RUN_TYPE: choose either closed or open +RUN_TYPE: closed + +# Runs only one pressure step, the starting pressure. +SINGLE_STEP: false +# Search for the saturation point based on melt composition; ignores P_Start and WgT +FIND_SATURATION: false +# Finds saturation pressure based on volatile contents as atomic mass fractions (H, S, N, C) + fO2 +ATOMIC_MASS_SET: false + +GAS_SYS: oh +FE_SYSTEM: true +S_SAT_WARN: false + +# model parameters +T_START: 1473.15 +P_START: 3000.0 +P_STOP: 1.0 +DP_MIN: 0.1 +DP_MAX: 100.0 +MASS: 100.0 +WgT: 1.0e-05 + +# Fraction of gas phase lost at each pressure step during open system degassing +LOSS_FRAC: 0.9999 + +# select models +DENSITY_MODEL: spera2000 +# Relate fo2 to Fe2/Fe3 ratio; pick from kc1991 (Kress & Carmicheal 1991), r2013 (Righter et al., 2013)) +FO2_MODEL: kc1991 +# Rock buffer definitions, options: frost1991; +FMQ_MODEL: frost1991 + +# select solubility laws from options. Some options do not include OH-, CO3, CH4 species. +# OH system models, pick from: burguisser2015 (H2O only); +H2O_MODEL: burguisser2015 +# H2 model, pick from: burguisser2015 (ONLY USE TO COMPARE TO DCOMPRESS; ERROR!); gaillard2003 +H2_MODEL: gaillard2003 +# C models, pick from: burguisser2015 (CO2 & CO3); eguchi2018 (CO2, CO3 & graphite) +C_MODEL: burguisser2015 +# CO model, pick either armstrong2015, or None (preferred for higher fO2 cases above ~ IW+1) +CO_MODEL: armstrong2015 +# CH4 model, pick either ardia2013, or None (preferred for higher fO2 cases above ~ IW+1) +CH4_MODEL: ardia2013 +# S models, pick from: sulfide capacity: oneill2002, oneill2020; sulfate capacity: nash2019 ; SCSS: liu2007 +SULFIDE_CAPACITY: oneill2020 +SULFATE_CAPACITY: nash2019 +SCSS: liu2007 +# N model, pick from: libourel2003 (N2 only) +N_MODEL: libourel2003 + +# fO2 can be set relative to a mineral buffer (NNO, FMQ or IW), as an absolute value, or through the FeO/Fe2O3 ratio in the chem.yaml file. +# Or it can be set implicitly in the OH system by initialising using fH2 rather than fO2. +FO2_buffer_SET: true +FO2_buffer: FMQ +FO2_buffer_START: 0.0 + +# SET TOTAL VOLATILES AS ATOMIC MASS FRACTIONS (ppm) + +ATOMIC_H: 0.0 +ATOMIC_C: 0.0 +ATOMIC_S: 0.0 +ATOMIC_N: 0.0 + +# VOLATILE SETTING OPTIONS: +# OH: Either fO2 (see above) OR fH2 +# COH: Pick 2 from the fO2 (see above), fH2, fH2O or wtH2O. You cannot set up with just fO2 and fH2, or specify both fH2O and WtH2O. +# SOH: Pick 2 from the fO2 (see above), fH2, fH2O or wtH2O. You cannot set up with just fO2 and fH2, or specify both fH2O and WtH2O +# COHS: Pick 3 from the fO2 (see above), fH2, fH2O, wtH2O fCO2 or WtCO2. You cannot set up with just fO2 and fH2, or specify either both fH2O and WtH2O or both fCO2 and WtCO2 +# COHSN: Pick 4 from the fO2 (see above), fH2, fH2O, wtH2O fCO2 or WtCO2, plus WtN. You cannot set up with just fO2 and fH2, or specify either both fH2O and WtH2O or both fCO2 and WtCO2 + +# To select an option, switch the boolean specified by 'set' to 'true' and provide a value in the corresponding 'start' option. +# Switch all other volatile setting options to false to avoid triggering an error. + +# select initial volatile compositions, either as gas fugacities + +FH2_SET: false +FH2_START: 0.0 + +FH2O_SET: false +FH2O_START: 0.0 + +FCO2_SET: false +FCO2_START: 0.0 + +# or melt wt fractions - a wt FRACTION still dissolved in the melt at the starting pressure (NOT WHEN EVERYTHING IS DISSOLVED), so 0.1 wt% = 0.001 +WTH2O_SET: false +WTH2O_START: 0.0 + +WTCO2_SET: false +WTCO2_START: 0.0 + +SULFUR_SET: false +SULFUR_START: 0.0 + +NITROGEN_SET: false +NITROGEN_START: 0.0 + +GRAPHITE_SATURATED: false +GRAPHITE_START: 0.0 diff --git a/webapp/evo_logo.png b/webapp/evo_logo.png new file mode 100644 index 0000000..1efd25c Binary files /dev/null and b/webapp/evo_logo.png differ diff --git a/webapp/output.yaml b/webapp/output.yaml new file mode 100644 index 0000000..e0274dc --- /dev/null +++ b/webapp/output.yaml @@ -0,0 +1,7 @@ +# Graphical output selection + +Plt_melt_species: True +Plt_gas_species_wt: False +Plt_gas_species_mol: True +Plt_gas_fraction: True +Plt_fo2_dFMQ: False \ No newline at end of file diff --git a/webapp/streamlit-app.py b/webapp/streamlit-app.py new file mode 100644 index 0000000..b7e9b4e --- /dev/null +++ b/webapp/streamlit-app.py @@ -0,0 +1,619 @@ +"""Contains code to enable a web interface for EVo, using StreamLit.""" + +from pathlib import Path + +import numpy as np +import plotly.express as px +import plotly.graph_objects as go +import ruamel.yaml +import streamlit as st + +import evo + +ryaml = ruamel.yaml.YAML() + + +def amend_chem(file, chem_dict): + """ + Duplicates the chem.yaml file, then edits with the gridsearch values relevant + to the specific run. + """ + + with open(file, "r") as f: + chem_doc = ryaml.load(f) + + for param, val in chem_dict.items(): + if type(val) is np.float64: + chem_doc[param] = float(val) + elif val == 0.0: + pass + else: + chem_doc[param] = val + + with open(file, "w") as f: + ryaml.dump(chem_doc, f) + + +def amend_env(file, env_dict): + """ + Duplicates the env.yaml file, then edits with the gridsearch values relevant + to the specific run. + """ + + with open(file, "r") as f: + env_doc = ryaml.load(f) + + for param, val in env_dict.items(): + # forces the scientific notation to be output correctly to be read as a float + if type(val) is np.float64: + env_doc[param] = float(val) + else: + env_doc[param] = val + + with open(file, "w") as f: + ryaml.dump(env_doc, f) + + +oxides = {} +env_options = {} +default_laws = { + "FO2_MODEL": "kc1991", + "H2_MODEL": "gaillard2003", + "C_MODEL": "burguisser2015", + "CO_MODEL": "armstrong2015", + "CH4_MODEL": "ardia2013", + "SULFIDE_CAPACITY": "oneill2020", +} +volatile_params = {} +models = {} + +st.set_page_config(layout="wide") + +# # Use this to control which things are visible. +# # i.e. use 'disabled' to grey out options which are not +# # valid based on the run options. +# https://docs.streamlit.io/library/api-reference/widgets/st.radio +# if "startType" not in st.session_state: +# # st.session_state.visibility = "visible" +# # st.session_state.disabled = False +# # st.session_state.horizontal = False + +if "default_sollaws" not in st.session_state: + st.session_state.default_sollaws = True + st.session_state.no_loaded_data = True + +title_image, _, title_col = st.columns([4, 1, 15]) + +with title_image: + st.image("webapp/evo_logo.png") + +with title_col: + st.title("EVo: A volcanic degassing model") + + st.text( + "This is a web interface for the EVo volcanic degassing model.\n" + + "Use the input options below to select setup options and load your " + + "input options by clicking 'Load data'.\n" + + "Then click 'Run EVo' to generate a downloadable dataframe of results " + + "and example plots." + ) + +col1, _, col2, col3 = st.columns([2, 0.5, 3, 3], gap="large") + +with col1: + st.header("Major Oxide Composition") + with st.expander("Major Oxide Composition", expanded=False): + sio2 = st.number_input("SiO2", min_value=0.0, max_value=100.0, value=47.95) + tio2 = st.number_input("TiO2", min_value=0.0, max_value=100.0, value=1.67) + al2o3 = st.number_input("Al2O3", min_value=0.0, max_value=100.0, value=17.32) + feo = st.number_input("FeO", min_value=0.0, max_value=100.0, value=10.24) + # set this to zero and greyed out if fo2 is set + fe2o3 = st.number_input("Fe2O3", min_value=0.0, max_value=100.0, value=0.0) + mno = st.number_input("MnO", min_value=0.0, max_value=100.0, value=0.17) + mgo = st.number_input("MgO", min_value=0.0, max_value=100.0, value=5.76) + cao = st.number_input("CaO", min_value=0.0, max_value=100.0, value=10.93) + na2o = st.number_input("Na2O", min_value=0.0, max_value=100.0, value=3.45) + k2o = st.number_input("K2O", min_value=0.0, max_value=100.0, value=1.99) + p2o5 = st.number_input("P2O5", min_value=0.0, max_value=100.0, value=0.51) + nio = st.number_input("NiO", min_value=0.0, max_value=100.0, value=0.0) + cr2o3 = st.number_input("Cr2O3", min_value=0.0, max_value=100.0, value=0.0) + + oxides = { + "SIO2": sio2, + "TIO2": tio2, + "AL2O3": al2o3, + "FEO": feo, + "FE2O3": fe2o3, + "MNO": mno, + "MGO": mgo, + "CAO": cao, + "NA2O": na2o, + "K2O": k2o, + "P2O5": p2o5, + "NIO": nio, + "CR2O3": cr2o3, + } + + st.subheader("Starting conditions") + with st.expander("Starting conditions", expanded=True): + t_start = st.number_input( + "Temperature (K)", min_value=600.0, max_value=2000.0, value=1473.15 + ) + # work out how to grey this out if you don't need to select a starting pressure + p_start = st.number_input( + "Starting Pressure (bar)", min_value=1.0, max_value=10000.0, value=3000.0 + ) + p_stop = st.number_input( + "Final Pressure (bar)", min_value=1e-5, max_value=10000.0, value=1.0 + ) + dp_max = st.number_input( + "Maximum pressure step size (bar)", + min_value=1.0, + max_value=100.0, + value=100.0, + ) + dp_min = st.number_input( + "Minimum pressure step size (bar)", + min_value=1e-5, + max_value=10.0, + value=0.1, + ) + # grey this one out too + wgt = st.number_input( + "Gas weight fraction at starting pressure", + min_value=0.000000001, + max_value=1.0, + value=0.00001, + ) + mass = st.number_input( + "Mass of the magma packet (g)", min_value=0.0, value=100.0 + ) + + env_options.update( + { + "T_START": t_start, + "P_START": p_start, + "P_STOP": p_stop, + "DP_MAX": dp_max, + "DP_MIN": dp_min, + "WgT": wgt, + "MASS": mass, + } + ) + +with col2: + st.header("Model setup options") + rock_type = st.selectbox("Magma type", ("basalt", "phonolite", "rhyolite")) + run_type = st.selectbox( + "Run Type - Closed System or Open System", ("closed", "open") + ) + if run_type == "Open system": + loss_frac = st.number_input( + "Fraction of gas phase lost at each P step during open system degassing", + min_value=0.0, + max_value=0.9999, + value=0.99, + ) + else: + loss_frac = 0.0 + + single_step = st.checkbox("Run for a single pressure?") + + env_options.update( + {"COMPOSITION": rock_type, "RUN_TYPE": run_type, "SINGLE_STEP": single_step} + ) + + saturation = st.radio( + "Choose how to find the starting pressure:", + ( + "Set manually", + "Find volatile saturation pressure", + "Set volatile element mass, and find volatile saturation pressure", + ), + ) + if saturation == "Set manually": + env_options["FIND_SATURATION"] = False + env_options["ATOMIC_MASS_SET"] = False + elif saturation == "Find volatile saturation pressure": + env_options["FIND_SATURATION"] = True + env_options["ATOMIC_MASS_SET"] = False + else: + env_options["FIND_SATURATION"] = False + env_options["ATOMIC_MASS_SET"] = True + + # Eventually use this to control which volatile input options are shown + gas_sys = st.selectbox( + "Select the volatile element system:", ("OH", "COH", "SOH", "COHS", "COHSN") + ) + fe_equil = st.checkbox("Allow redox equilibration between gas and melt?") + s_sat_warn = st.checkbox( + "Stop the run if sulfide saturation is reached? " + + "(EVo is not suitable for use under sulfide-saturated conditions)" + ) + + env_options.update( + { + "GAS_SYS": gas_sys, + "FE_SYSTEM": fe_equil, + "S_SAT_WARN": s_sat_warn, + } + ) + + sol_laws = st.checkbox( + "Use default solubility laws? (if unchecked, dropdown options will be offered)", + value=True, + key="default_sollaws", + ) + # These will be greyed out if default solubility laws are chosen + + with st.expander("Solubility laws", expanded=not (sol_laws)): + fo2_model = st.selectbox( + "Ferric/Ferrous -> fO2 conversions model", + ("kc1991", "righter2015"), + disabled=st.session_state.default_sollaws, + ) + h2o_model = st.selectbox( + "H2O solubility law", + ("burguisser2015",), + disabled=st.session_state.default_sollaws, + ) + h2_model = st.selectbox( + "H2 solubility law", + ("gaillard2003", "burguisser2015"), + disabled=st.session_state.default_sollaws, + ) + co2_model = st.selectbox( + "CO2 solubility law", + ("burguisser2015", "eguchi2018"), + disabled=st.session_state.default_sollaws, + ) + co_model = st.selectbox( + "CO solubility law", + ("armstrong2015", "None"), + disabled=st.session_state.default_sollaws, + ) + ch4_model = st.selectbox( + "CH4 solubility law", + ("ardia2013", "None"), + disabled=st.session_state.default_sollaws, + ) + sulfide_model = st.selectbox( + "sulfide capacity law", + ("oneill2020", "oneill2002"), + disabled=st.session_state.default_sollaws, + ) + n2_model = st.selectbox( + "N2 solubility law", + ("libourel2003",), + disabled=st.session_state.default_sollaws, + ) + + if sol_laws is True: + models = default_laws + else: + models = { + "FO2_MODEL": fo2_model, + "H2_MODEL": h2_model, + "C_MODEL": co2_model, + "CO_MODEL": co_model, + "CH4_MODEL": ch4_model, + "SULFIDE_CAPACITY": sulfide_model, + } + +with col3: + st.header("Volatile content") + buffer_type = st.selectbox( + "Select the mineral buffer for the oxygen fugacity:", ("FMQ", "IW", "NNO") + ) + fo2 = st.number_input("Starting fO2 relative to the mineral buffer:", value=0.0) + + volatile_params["FO2_buffer"] = buffer_type + volatile_params["FO2_buffer_START"] = fo2 + + vol_setup = st.radio( + "Choose a way to set the system volatile content:", + ("Set gas phase", "Set melt content", "Set atomic weight fractions"), + ) + + if vol_setup == "Set gas phase": + st.subheader("Set gas phase fugacities") + fh2_set = st.checkbox("Set fH2 (bar)", value=False) + fh2_start = st.number_input("fh2", value=0.0, label_visibility="collapsed") + + fh2o_set = st.checkbox("Set fH2O (bar)", value=False) + fh2o_start = st.number_input("fh2o", value=0.0, label_visibility="collapsed") + + fco2_set = st.checkbox("Set fCO2 (bar)", value=False) + fco2_start = st.number_input("fco2", value=0.0, label_visibility="collapsed") + + volatile_params.update( + { + "FH2_SET": fh2_set, + "FH2_START": fh2_start, + "FH2O_SET": fh2_set, + "FH2O_START": fh2o_start, + "FCO2_SET": fco2_set, + "FCO2_START": fco2_start, + } + ) + + for param in [ + "wth2o_set", + "wtco2_set", + "sulfur_set", + "nitrogen_set", + "graphite_saturated", + ]: + volatile_params[param.upper()] = False + + for param in [ + "wth2o_start", + "wtco2_start", + "sulfur_start", + "nitrogen_start", + "graphite_start", + "atomic_h", + "atomic_c", + "atomic_s", + "atomic_n", + ]: + volatile_params[param.upper()] = 0.0 + + elif vol_setup == "Set melt content": + st.subheader("Set melt volatile content") + wth2o_set = st.checkbox("Set melt H2O content (wt fraction)", value=False) + wth2o_start = st.number_input("wth2o", value=0.0, label_visibility="collapsed") + + wtco2_set = st.checkbox("Set melt CO2 content (wt fraction)", value=False) + wtco2_start = st.number_input("wtco2", value=0.0, label_visibility="collapsed") + + sulfur_set = st.checkbox("Set melt sulfur content (wt fraction)", value=False) + sulfur_start = st.number_input( + "sulfur", value=0.0, label_visibility="collapsed" + ) + + nitrogen_set = st.checkbox("Set melt N2 content (wt fraction)", value=False) + nitrogen_start = st.number_input( + "nitrogen", value=0.0, label_visibility="collapsed" + ) + + graphite_saturated = st.checkbox("Is the melt graphite saturated?", value=False) + graphite_start = st.number_input( + "Weight fraction of graphite in the melt", value=0.0 + ) + + volatile_params.update( + { + "WTH2O_SET": wth2o_set, + "WTH2O_START": wth2o_start, + "WTCO2_SET": wtco2_set, + "WTCO2_START": wtco2_start, + "SULFUR_SET": sulfur_set, + "SULFUR_START": sulfur_start, + "NITROGEN_SET": nitrogen_set, + "NITROGEN_START": nitrogen_start, + "GRAPHITE_SATURATED": graphite_saturated, + "GRAPHITE_START": graphite_start, + } + ) + + for param in ["fh2_set", "fh2o_set", "fco2_set"]: + volatile_params[param.upper()] = False + + for param in [ + "fh2_start", + "fh2o_start", + "fco2_start", + "atomic_h", + "atomic_c", + "atomic_s", + "atomic_n", + ]: + volatile_params[param.upper()] = 0.0 + + elif vol_setup == "Set atomic weight fractions": + st.subheader( + "Set the mass fraction of each volatile element in the system (ppm)" + ) + + atomic_h = st.number_input("Weight fraction of H in the system", value=0.0) + atomic_c = st.number_input("Weight fraction of C in the system", value=0.0) + atomic_s = st.number_input("Weight fraction of S in the system", value=0.0) + atomic_n = st.number_input("Weight fraction of N in the system", value=0.0) + + volatile_params.update( + { + "ATOMIC_H": atomic_h, + "ATOMIC_C": atomic_c, + "ATOMIC_S": atomic_s, + "ATOMIC_N": atomic_n, + } + ) + + for param in [ + "fh2_set", + "fh2o_set", + "fco2_set", + "wth2o_set", + "wtco2_set", + "sulfur_set", + "nitrogen_set", + "graphite_saturated", + ]: + volatile_params[param.upper()] = False + + for param in [ + "fh2_start", + "fh2o_start", + "fco2_start", + "wth2o_start", + "wtco2_start", + "sulfur_start", + "nitrogen_start", + "graphite_start", + ]: + volatile_params[param.upper()] = 0.0 + +_, col4, _, col5, _ = st.columns([1, 1, 1, 1, 1]) + +with col4: + load_data = st.button("Load input data", use_container_width=True) + if load_data: + st.session_state.no_loaded_data = False + filepath = filepath = Path(__file__).parent + amend_chem(filepath / "chem.yaml", oxides) + env_params = env_options | models | volatile_params + amend_env(filepath / "env.yaml", env_params) # update to get all options + +with col5: + # Want to grey this out as an option until 'load data' has been pressed... + run_evo = st.button( + "Run EVo", + type="primary", + disabled=st.session_state.no_loaded_data, + use_container_width=True, + ) + if run_evo: + st.session_state.no_loaded_data = True + filepath = filepath = Path(__file__).parent + df = evo.main( + filepath / "chem.yaml", filepath / "env.yaml", filepath / "output.yaml" + ) + +if run_evo: + st.header("Results:") + st.dataframe(df.style.format("{:.3e}")) + st.download_button( + "Download results", df.to_csv(index=False), "results.csv", "text/csv" + ) + + col1, col2 = st.columns(2, gap="large") + + with col1: + colors = px.colors.qualitative.Prism + fig = go.Figure() + fig.add_traces( + go.Scatter( + x=df["mH2O"], + y=df["P"], + mode="lines", + line={"color": colors[0]}, + name="H2O", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mH2"], + y=df["P"], + mode="lines", + line={"color": colors[1]}, + name="H2", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mO2"], + y=df["P"], + mode="lines", + line={"color": colors[2]}, + name="O2", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mCO2"], + y=df["P"], + mode="lines", + line={"color": colors[3]}, + name="CO2", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mCO"], + y=df["P"], + mode="lines", + line={"color": colors[4]}, + name="CO", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mCH4"], + y=df["P"], + mode="lines", + line={"color": colors[5]}, + name="CH4", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mH2S"], + y=df["P"], + mode="lines", + line={"color": colors[6]}, + name="H2S", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mSO2"], + y=df["P"], + mode="lines", + line={"color": colors[7]}, + name="SO2", + ) + ) + fig.add_traces( + go.Scatter( + x=df["mS2"], + y=df["P"], + mode="lines", + line={"color": colors[8]}, + name="S2", + ) + ) + if not (df["mN2"] == 0).all(): + fig.add_traces( + go.Scatter( + x=df["mN2"], + y=df["P"], + mode="lines", + line={"color": colors[9]}, + name="N2", + ) + ) + fig.update_xaxes(type="log") + fig.update_yaxes(autorange="reversed") + + fig.update_layout( + title="Gas composition vs pressure", + xaxis_title="Gas composition (mol fraction)", + yaxis_title="Pressure (bar)", + legend_title="Species", + ) + + st.plotly_chart(fig) + + with col2: + colors = px.colors.qualitative.Plotly + fig = go.Figure() + fig.add_traces( + go.Scatter( + x=df["Exsol_vol%"], + y=df["P"], + mode="lines", + line={"color": colors[0]}, + ) + ) + + fig.update_xaxes(type="log") + fig.update_yaxes(autorange="reversed") + + fig.update_layout( + title="Gas volume vs pressure", + xaxis_title="Gas volume %", + yaxis_title="Pressure (bar)", + ) + + st.plotly_chart(fig)