Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/pymemsim/README.md
Original file line number Diff line number Diff line change
@@ -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.
120 changes: 120 additions & 0 deletions examples/pymemsim/configuring_an_hfm_module/code.py
Original file line number Diff line number Diff line change
@@ -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 <code>pymemsim</code>:")
display(HTML("<pre>" + "\n".join(candidates) + "</pre>"), 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 border='1' cellpadding='4' style='border-collapse: collapse;'>"]
table_html.append("<tr><th align='left'>Setting</th><th align='left'>Value</th></tr>")
for key, val in rows:
table_html.append(f"<tr><td><code>{key}</code></td><td><code>{val}</code></td></tr>")
table_html.append("</table>")
display(HTML("".join(table_html)), append=True)

note(
"In a full script you'd pass these into "
"<code>HollowFiberMembraneOptions(...)</code> and a model-input "
"object, then call <code>pms.create_hfm_module(options=..., "
"inputs=...)</code> and run the resulting module's simulate "
"method. The example files <code>gas-hfm-exp-1.py</code> "
"(co-current, IVP) and <code>gas-hfm-exp-2.py</code> "
"(counter-current, BVP via <code>solver_bvp</code>) 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)
1 change: 1 addition & 0 deletions examples/pymemsim/configuring_an_hfm_module/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["numpy", "scipy", "matplotlib", "pymemsim"]
19 changes: 19 additions & 0 deletions examples/pymemsim/configuring_an_hfm_module/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

70 changes: 70 additions & 0 deletions examples/pymemsim/hollow_fiber_intro/code.py
Original file line number Diff line number Diff line change
@@ -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 <strong>{pms.__version__}</strong> 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 <code>pymemsim</code>:")
display(HTML("<pre>" + ", ".join(public_names) + "</pre>"), 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(
"<ul>"
"<li><strong>Co-current</strong>: feed and permeate flow in the "
"same direction. Solved as an initial-value problem.</li>"
"<li><strong>Counter-current</strong>: feed and permeate flow in "
"opposite directions. Solved as a boundary-value problem.</li>"
"</ul>"
), 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)
1 change: 1 addition & 0 deletions examples/pymemsim/hollow_fiber_intro/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["numpy", "scipy", "matplotlib", "pymemsim"]
37 changes: 37 additions & 0 deletions examples/pymemsim/hollow_fiber_intro/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)
4 changes: 4 additions & 0 deletions examples/pymemsim/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"hollow_fiber_intro",
"configuring_an_hfm_module"
]