diff --git a/examples/pymemsim/README.md b/examples/pymemsim/README.md new file mode 100644 index 0000000..0ec943f --- /dev/null +++ b/examples/pymemsim/README.md @@ -0,0 +1,18 @@ +# pymemsim 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/pymemsim/configuring_an_hfm_module/code.py b/examples/pymemsim/configuring_an_hfm_module/code.py new file mode 100644 index 0000000..c08c19a --- /dev/null +++ b/examples/pymemsim/configuring_an_hfm_module/code.py @@ -0,0 +1,120 @@ +# --------------------------------------------------------------------- +# Configuring an HFM module: options, inputs, and the factory. +# --------------------------------------------------------------------- +# +# A PyMemSim simulation has three ingredients: +# 1. Options - what kind of module and how to solve it. +# 2. Inputs - the feed/permeate streams and operating conditions. +# 3. The module itself, built by create_hfm_module(...). +# +# This example introspects what each of those pieces looks like for +# a gas-phase, co-current hollow-fiber membrane separating a CO2/N2 +# mixture. The point is to make the configuration vocabulary feel +# concrete before running a solver. +import numpy as np +import matplotlib.pyplot as plt +import pymemsim as pms + + +heading("Inspecting PyMemSim's configuration classes") + +# Find the configuration-style classes the package exposes. We look +# for things whose names hint at "options" or "module" so the reader +# can map names they'll see in the docs to real attributes. +candidates = [ + name for name in dir(pms) + if not name.startswith("_") + and any(key in name.lower() for key in ("option", "module", "hfm", "input")) +] +note("Configuration-related names exported by pymemsim:") +display(HTML("
" + "\n".join(candidates) + "
"), append=True) + +# A realistic configuration sketch. We describe the module the way a +# PyMemSim user would: a gas-phase HFM running co-currently, isothermal +# and isobaric on both sides, with two components (CO2 and N2). +heading("A gas-phase CO2/N2 separation, sketched as a config") + +config_sketch = { + "phase": "gas", + "modeling_mode": "physical", # alternative: "scale" + "thermal_mode": "isothermal", # alternative: "non-isothermal" + "flow_pattern": "co-current", # alternative: "counter-current" + "components": ["CO2", "N2"], + "feed": { + "flow_mol_per_s": 1.0e-3, + "composition": {"CO2": 0.15, "N2": 0.85}, + "temperature_K": 308.15, + "pressure_bar": 6.0, + }, + "permeate": { + "flow_mol_per_s": 1.0e-5, + "composition": {"CO2": 0.0, "N2": 1.0}, + "temperature_K": 308.15, + "pressure_bar": 1.0, + }, + "transport": { + # Component permeance, mol / (m^2 s Pa). CO2 permeates ~25x faster. + "permeance": {"CO2": 2.5e-7, "N2": 1.0e-8}, + }, + "solver": { + # Co-current => IVP via scipy.integrate.solve_ivp. + "method": "Radau", + "rtol": 1.0e-6, + "atol": 1.0e-9, + }, +} + +# Render the config as a small two-column table so it reads at a glance. +rows = [] +for section, value in config_sketch.items(): + if isinstance(value, dict): + for k, v in value.items(): + rows.append((f"{section}.{k}", repr(v))) + else: + rows.append((section, repr(value))) + +table_html = [""] +table_html.append("") +for key, val in rows: + table_html.append(f"") +table_html.append("
SettingValue
{key}{val}
") +display(HTML("".join(table_html)), append=True) + +note( + "In a full script you'd pass these into " + "HollowFiberMembraneOptions(...) and a model-input " + "object, then call pms.create_hfm_module(options=..., " + "inputs=...) and run the resulting module's simulate " + "method. The example files gas-hfm-exp-1.py " + "(co-current, IVP) and gas-hfm-exp-2.py " + "(counter-current, BVP via solver_bvp) in the " + "PyMemSim repository show the full call sequence." +) + +# A predictive sketch of what the resulting profile would look like +# given the permeance ratio above. This isn't a solved simulation; it's +# the analytic shape you'd expect from a fast/slow component pair in a +# co-current arrangement, useful for sanity-checking real results. +heading("Expected shape: CO2 enrichment in the permeate") + +length_fraction = np.linspace(0, 1, 80) +selectivity = config_sketch["transport"]["permeance"]["CO2"] / \ + config_sketch["transport"]["permeance"]["N2"] + +# Simple monotonic curves with the right qualitative behavior. +co2_feed = 0.15 * np.exp(-1.8 * length_fraction) +co2_permeate = 0.15 + (selectivity / (selectivity + 12)) \ + * (1 - np.exp(-2.2 * length_fraction)) * 0.55 + +fig, ax = plt.subplots(figsize=(8, 4)) +ax.plot(length_fraction, co2_feed, color="crimson", linewidth=2, + label="CO2 in feed (retentate side)") +ax.plot(length_fraction, co2_permeate, color="darkorange", linewidth=2, + label="CO2 in permeate") +ax.set_xlabel("Fractional length along fiber") +ax.set_ylabel("CO2 mole fraction") +ax.set_title(f"Co-current HFM, CO2/N2 selectivity ≈ {selectivity:.0f}") +ax.legend() +ax.grid(alpha=0.3) +fig.tight_layout() +display(fig, append=True) diff --git a/examples/pymemsim/configuring_an_hfm_module/config.toml b/examples/pymemsim/configuring_an_hfm_module/config.toml new file mode 100644 index 0000000..700ebaa --- /dev/null +++ b/examples/pymemsim/configuring_an_hfm_module/config.toml @@ -0,0 +1 @@ +packages = ["numpy", "scipy", "matplotlib", "pymemsim"] diff --git a/examples/pymemsim/configuring_an_hfm_module/setup.py b/examples/pymemsim/configuring_an_hfm_module/setup.py new file mode 100644 index 0000000..8470a9f --- /dev/null +++ b/examples/pymemsim/configuring_an_hfm_module/setup.py @@ -0,0 +1,19 @@ +"""Lighter setup for example 2. Sets up the same names as cell 1, +without the 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/pymemsim/hollow_fiber_intro/code.py b/examples/pymemsim/hollow_fiber_intro/code.py new file mode 100644 index 0000000..493a422 --- /dev/null +++ b/examples/pymemsim/hollow_fiber_intro/code.py @@ -0,0 +1,70 @@ +""" +A first look at PyMemSim. + +PyMemSim simulates membrane-based separation systems. Its current focus +is the hollow-fiber membrane (HFM) module: long, thin fibers across +which gas or liquid components selectively permeate, separating a feed +mixture into a retentate and a permeate stream. + +This first example simply explores what the package exposes, so we know +what to reach for next. See: + https://github.com/sinagilassi/PyMemSim +""" +from IPython.core.display import display, HTML + +import numpy as np +import matplotlib.pyplot as plt +import pymemsim as pms + + +heading("PyMemSim version") +note(f"Running PyMemSim {pms.__version__} in your browser.") + +# The package's public surface is small and discoverable. The two names +# you'll use most often are HFM (the hollow-fiber membrane interface) +# and create_hfm_module (a factory that wires options + inputs into a +# ready-to-simulate module). +heading("Top-level names worth knowing") +public_names = [name for name in dir(pms) if not name.startswith("_")] +note("Public attributes exposed by pymemsim:") +display(HTML("
" + ", ".join(public_names) + "
"), append=True) + +# A quick sketch of the conceptual setup, so the next example feels +# familiar before we dive into solver details. +heading("The hollow-fiber picture") +note( + "Imagine a bundle of porous fibers in a shell. A feed mixture " + "enters one side; some components permeate through the fiber " + "walls more readily than others. Two flow arrangements are " + "supported:" +) +display(HTML( + "" +), append=True) + +# A tiny illustrative sketch of what selective permeation looks like +# along a fiber: the more-permeable component drops faster on the +# feed side, while the permeate side becomes enriched in it. +heading("Sketch: composition along a fiber") +length_fraction = np.linspace(0, 1, 60) +feed_fast = 0.50 * np.exp(-2.5 * length_fraction) # e.g. CO2 +feed_slow = 0.50 * np.exp(-0.4 * length_fraction) # e.g. N2 +permeate_fast = 1 - feed_fast / feed_fast[0] * 0.55 # enriched +permeate_slow = 1 - permeate_fast + +fig, ax = plt.subplots(figsize=(8, 4)) +ax.plot(length_fraction, feed_fast, label="Feed: fast component", color="crimson") +ax.plot(length_fraction, feed_slow, label="Feed: slow component", color="navy") +ax.plot(length_fraction, permeate_fast, "--", label="Permeate: fast", color="crimson") +ax.plot(length_fraction, permeate_slow, "--", label="Permeate: slow", color="navy") +ax.set_xlabel("Fractional length along fiber") +ax.set_ylabel("Mole fraction (illustrative)") +ax.set_title("Selective permeation along a hollow fiber") +ax.legend() +fig.tight_layout() +display(fig, append=True) diff --git a/examples/pymemsim/hollow_fiber_intro/config.toml b/examples/pymemsim/hollow_fiber_intro/config.toml new file mode 100644 index 0000000..700ebaa --- /dev/null +++ b/examples/pymemsim/hollow_fiber_intro/config.toml @@ -0,0 +1 @@ +packages = ["numpy", "scipy", "matplotlib", "pymemsim"] diff --git a/examples/pymemsim/hollow_fiber_intro/setup.py b/examples/pymemsim/hollow_fiber_intro/setup.py new file mode 100644 index 0000000..059a0d9 --- /dev/null +++ b/examples/pymemsim/hollow_fiber_intro/setup.py @@ -0,0 +1,37 @@ +"""Shim setup for the first example. Includes the full IPython shim.""" +import sys +import types +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__, + ) + + +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.version_info = (9, 0, 2, '') +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) diff --git a/examples/pymemsim/order.json b/examples/pymemsim/order.json new file mode 100644 index 0000000..b207c9b --- /dev/null +++ b/examples/pymemsim/order.json @@ -0,0 +1,4 @@ +[ + "hollow_fiber_intro", + "configuring_an_hfm_module" +]