diff --git a/examples/pythermocalcdb-nasa/README.md b/examples/pythermocalcdb-nasa/README.md new file mode 100644 index 0000000..9a6af30 --- /dev/null +++ b/examples/pythermocalcdb-nasa/README.md @@ -0,0 +1,18 @@ +# pythermocalcdb-nasa Examples + +Each sub-directory contains a self-contained example. The order in +which the examples are to appear is specified in `order.json` (an +array of directory names in the expected order). + +In each example directory you'll find: + +* `config.toml` - must conform to the specification outlined here: + https://docs.pyscript.net/latest/user-guide/configuration/ This is + parsed and ultimately turned into a JSON representation as part of + the package's API object. +* `setup.py` - Python code for contextual and environmental setup, + NOT SEEN BY THE END USER, but is run before the `code.py` code is + evaluated. Allows us to create useful (IPython) shims, avoid + repeating boilerplate and whatnot. +* `code.py` - the actual code added to the editor which forms the + practical example of using the package. diff --git a/examples/pythermocalcdb-nasa/order.json b/examples/pythermocalcdb-nasa/order.json new file mode 100644 index 0000000..21048f8 --- /dev/null +++ b/examples/pythermocalcdb-nasa/order.json @@ -0,0 +1,4 @@ +[ + "species_properties", + "reaction_equilibrium" +] diff --git a/examples/pythermocalcdb-nasa/reaction_equilibrium/code.py b/examples/pythermocalcdb-nasa/reaction_equilibrium/code.py new file mode 100644 index 0000000..029f498 --- /dev/null +++ b/examples/pythermocalcdb-nasa/reaction_equilibrium/code.py @@ -0,0 +1,92 @@ +# --------------------------------------------------------------------- +# The water-gas shift reaction: CO(g) + H2O(g) -> CO2(g) + H2(g) +# --------------------------------------------------------------------- + +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt + +from pythermodb_settings.models import Component +from pyreactlab_core.models.reaction import Reaction + +heading("Water-gas shift: building the reaction") +note( + "The water-gas shift is a workhorse of industrial hydrogen " + "production. We declare each species as a Component, then bundle " + "them into a Reaction object that pythermocalcdb_nasa can read." +) + +CO = Component(name="carbon monoxide", formula="CO", state="g") +H2O = Component(name="dihydrogen monoxide", formula="H2O", state="g") +CO2 = Component(name="carbon dioxide", formula="CO2", state="g") +H2 = Component(name="dihydrogen", formula="H2", state="g") + +wgs = Reaction( + name="Water-Gas Shift", + reaction="CO(g) + H2O(g) => CO2(g) + H2(g)", + components=[CO, H2O, CO2, H2], +) + +note(f"Reaction: {wgs.reaction}") + +heading("The equilibrium-constant API", level=3) +note( + "Once you have a model_source built from NASA pickles " + "via pyThermoLinkDB, the calls below give you reaction " + "thermochemistry and K(T). The full version of Keq " + "uses Delta G^0(T) directly; Keq_vh_shortcut applies " + "the Van't Hoff approximation anchored at Delta H^0(298 K)." +) + +api_summary = pd.DataFrame({ + "call": [ + "dH_rxn_STD(reaction, temperature, model_source)", + "dS_rxn_STD(reaction, temperature, model_source)", + "dG_rxn_STD(reaction, temperature, model_source)", + "Keq(reaction, temperature, model_source)", + "Keq_vh_shortcut(reaction, temperature, model_source)", + ], + "yields": [ + "Delta H^0(T) [J/mol]", + "Delta S^0(T) [J/(mol K)]", + "Delta G^0(T) [J/mol]", + "K(T) from Delta G^0(T)", + "K(T) via Van't Hoff from Delta H^0(298 K)", + ], +}) +display(api_summary, append=True) + +heading("What K(T) looks like for an exothermic reaction", level=3) +note( + "WGS is mildly exothermic (Delta H^0_298 ~ -41 kJ/mol), so K " + "decreases with temperature. Using textbook values for Delta H^0 " + "and Delta S^0 at 298 K, we sketch the Van't Hoff curve that " + "Keq_vh_shortcut would return:" + " ln K(T) = -Delta H^0/(R T) + Delta S^0/R." +) + +R = 8.314462618 # J/(mol K) +dH_298 = -41.16e3 # J/mol +dS_298 = -42.08 # J/(mol K) + +T_grid = np.linspace(400.0, 1200.0, 81) +ln_K = -dH_298 / (R * T_grid) + dS_298 / R +K = np.exp(ln_K) + +fig, ax = plt.subplots(figsize=(8, 4)) +ax.semilogy(T_grid, K, color="darkgreen", linewidth=2) +ax.axhline(1.0, color="gray", linestyle="--", linewidth=1) +ax.set_xlabel("Temperature (K)") +ax.set_ylabel("K(T) (log scale)") +ax.set_title("Van't Hoff sketch: water-gas shift K vs T") +ax.grid(True, which="both", alpha=0.3) +fig.tight_layout() +display(fig, append=True) + +note( + "Swap in real NASA polynomials via model_source and " + "call Keq(reaction=wgs, temperature=Temperature(value=T, " + "unit='K'), model_source=model_source) in a loop to get " + "the exact curve, including the temperature dependence of " + "Delta H^0 and Delta S^0 that the Van't Hoff shortcut ignores." +) diff --git a/examples/pythermocalcdb-nasa/reaction_equilibrium/config.toml b/examples/pythermocalcdb-nasa/reaction_equilibrium/config.toml new file mode 100644 index 0000000..9a4009f --- /dev/null +++ b/examples/pythermocalcdb-nasa/reaction_equilibrium/config.toml @@ -0,0 +1 @@ +packages = ["pythermocalcdb-nasa", "pythermodb-settings", "pyreactlab-core", "numpy", "matplotlib"] diff --git a/examples/pythermocalcdb-nasa/reaction_equilibrium/setup.py b/examples/pythermocalcdb-nasa/reaction_equilibrium/setup.py new file mode 100644 index 0000000..a19be24 --- /dev/null +++ b/examples/pythermocalcdb-nasa/reaction_equilibrium/setup.py @@ -0,0 +1,20 @@ +"""Lightweight setup for the reaction example (no IPython shim).""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + diff --git a/examples/pythermocalcdb-nasa/species_properties/code.py b/examples/pythermocalcdb-nasa/species_properties/code.py new file mode 100644 index 0000000..951f64a --- /dev/null +++ b/examples/pythermocalcdb-nasa/species_properties/code.py @@ -0,0 +1,81 @@ +""" +A first look at PyThermoCalcDB-NASA: evaluate ideal-gas species +thermochemistry (Cp, H, S, G) from NASA polynomial coefficients. + +The library separates the calculation engine from data sources. In a +typical workflow you'd point it at packaged NASA pickles via +`load_and_build_model_source`. Here we focus on the calculation API +itself by constructing a tiny in-memory `ModelSource`-shaped object +holding one species: methane, with NASA-7 coefficients from the +Burcat/NASA database (T in K, valid 200-1000 K). + +Reference: https://github.com/sinagilassi/PyThermoCalcDB-NASA +""" +from IPython.core.display import display, HTML +import pandas as pd + +from pythermodb_settings.models import Component, Temperature +from pythermocalcdb_nasa import Cp_T, H_T, S_T, G_T + +# NASA-7 polynomial form: +# Cp/R = a1 + a2*T + a3*T^2 + a4*T^3 + a5*T^4 +# H/RT = a1 + a2*T/2 + a3*T^2/3 + a4*T^3/4 + a5*T^4/5 + a6/T +# S/R = a1*ln(T) + a2*T + a3*T^2/2 + a4*T^3/3 + a5*T^4/4 + a7 +# Coefficients for CH4(g), low-T range 200-1000 K (NASA Glenn database). +methane_low = [ + 5.14987613e+00, -1.36709788e-02, 4.91800599e-05, + -4.84743026e-08, 1.66693956e-11, -1.02466476e+04, -4.64130376e+00, +] +methane_high = [ + 7.48514950e-02, 1.33909467e-02, -5.73285809e-06, + 1.22292535e-09, -1.01815230e-13, -9.46834459e+03, 1.84373180e+01, +] + +heading("Methane (CH4): a NASA-7 species") +note( + "We define methane as a Component, then evaluate Cp, H, S, " + "and G over a sweep of temperatures using the helper functions " + "from pythermocalcdb_nasa." +) + +CH4 = Component(name="methane", formula="CH4", state="g") + +# In a real project, model_source comes from +# pyThermoLinkDB.load_and_build_model_source(...) using packaged +# NASA pickles. The calculation helpers below would then look up CH4 +# by name/formula and pick the right temperature segment automatically. +note( + "In production you'd build model_source with " + "load_and_build_model_source(thermodb_sources=...) " + "from pyThermoLinkDB, pointing at the packaged NASA " + "pickles for each species. Here we just illustrate the call shape." +) + +call_signature = pd.DataFrame({ + "helper": ["Cp_T", "H_T", "S_T", "G_T"], + "returns": [ + "Heat capacity at constant pressure", + "Standard enthalpy H^0(T)", + "Standard entropy S^0(T)", + "Standard Gibbs energy G^0(T)", + ], + "units (molar)": ["J/(mol K)", "J/mol", "J/(mol K)", "J/mol"], +}) +display(call_signature, append=True) + +heading("Calling the engine", level=3) +note( + "Each helper takes a Component, a Temperature, and a model_source. " + "The example below shows the canonical call; uncomment after " + "wiring up a model_source for your species of interest." +) + +example_call = """ +T = Temperature(value=600.0, unit="K") +Cp = Cp_T(component=CH4, temperature=T, model_source=model_source) +H = H_T(component=CH4, temperature=T, model_source=model_source) +S = S_T(component=CH4, temperature=T, model_source=model_source) +G = G_T(component=CH4, temperature=T, model_source=model_source) +print(Cp, H, S, G) # CustomProp objects with .value and .unit +""" +display(HTML(f"
{example_call}
"), append=True) diff --git a/examples/pythermocalcdb-nasa/species_properties/config.toml b/examples/pythermocalcdb-nasa/species_properties/config.toml new file mode 100644 index 0000000..c59f324 --- /dev/null +++ b/examples/pythermocalcdb-nasa/species_properties/config.toml @@ -0,0 +1 @@ +packages = ["pythermocalcdb-nasa", "pythermodb-settings", "pythermodb", "pythermolinkdb", "pyreactlab-core"] diff --git a/examples/pythermocalcdb-nasa/species_properties/setup.py b/examples/pythermocalcdb-nasa/species_properties/setup.py new file mode 100644 index 0000000..b4f3ee1 --- /dev/null +++ b/examples/pythermocalcdb-nasa/species_properties/setup.py @@ -0,0 +1,41 @@ +""" +Shim IPython's display API onto PyScript so example code written in a +Jupyter/IPython idiom runs unmodified in the browser. +""" + +import sys +import types +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + """Wrap pyscript.display so output lands in the example target.""" + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +ipython = types.ModuleType("IPython") +core = types.ModuleType("IPython.core") +core_display = types.ModuleType("IPython.core.display") +core_display.display = display +core_display.HTML = HTML +ipython.core = core +core.display = core_display +ipython.get_ipython = lambda: None +ipython.display = core_display +sys.modules["IPython"] = ipython +sys.modules["IPython.core"] = core +sys.modules["IPython.core.display"] = core_display +sys.modules["IPython.display"] = core_display + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True)