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
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ version heading for the full notes.
base-level sliders and a single play/pause button, so a reader can watch the
long profile adjust in real time -- Lane's balance, played out dynamically. It
runs GRLP entirely in the browser via Pyodide (WebAssembly) -- no install, no
server -- and is compiled from `interactive_demo/grlp_panel.py` with Panel
(`panel convert`) at documentation build time, self-hosting the wheels it needs
beside the app (referenced by relative URLs) so it depends on no external
services at run time. A Jupyter-notebook version of the same demo is kept in
`interactive_demo/` for classroom or notebook use.
server -- and is compiled from `interactive_demo/grlp_panel.py` by
[artesian](https://github.com/MNiMORPH/artesian) at documentation build time.
The grlp, panel and bokeh wheels are self-hosted beside the app and referenced
by relative URLs, so no wheel CDN is needed at run time; the Pyodide runtime
itself is still fetched from a CDN on first load. A Jupyter-notebook version of
the same demo is kept in `interactive_demo/` for classroom or notebook use.
- **Second-order-in-time integration (BDF2)** -- now the **default** (see
*Changed*). `set_time_integration(1)` selects the previous first-order backward
Euler. BDF2 makes the final-profile error scale like Δt² rather than Δt, so
Expand Down
62 changes: 19 additions & 43 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
environment first.
"""

import glob
import os
import subprocess
import sys

# Make the package importable for autodoc when GRLP is not pip-installed
Expand All @@ -36,48 +34,13 @@
shutil.copyfile(os.path.join(_here, "..", "examples", _nb),
os.path.join(_tutorial_dst, _nb))

# Build the interactive browser demo (docs/interactive.md). `panel convert`
# compiles interactive_demo/grlp_panel.py into a standalone WebAssembly app that
# runs GRLP entirely in the reader's browser via Pyodide. The app and the wheels
# it loads (grlp, built fresh here; panel + bokeh, self-hosted from PyPI) live
# together in _static/interactive/ and are referenced by *relative* URLs, so the
# demo works wherever the docs are served with no external CDN. interactive.md
# The interactive browser demo (docs/interactive.md) is built by artesian,
# which compiles interactive_demo/grlp_panel.py into a standalone WebAssembly
# app running GRLP entirely in the reader's browser via Pyodide. It wheels
# grlp fresh from this source tree, self-hosts the panel and bokeh wheels
# beside the app, and rewrites their CDN URLs to those local copies (the
# holoviz CDN's bokeh wheel 403s). See artesian_apps below; interactive.md
# embeds _static/interactive/grlp_panel.html in an <iframe>.
import re

import bokeh
import panel

_demo_src = os.path.join(_here, "..", "interactive_demo", "grlp_panel.py")
_demo_out = os.path.join(_here, "_static", "interactive")
os.makedirs(_demo_out, exist_ok=True)
for _old in glob.glob(os.path.join(_demo_out, "*.whl")):
os.remove(_old)
# Fresh grlp wheel (matches the current source) + self-hosted panel/bokeh wheels.
subprocess.run([sys.executable, "-m", "pip", "wheel", os.path.join(_here, ".."),
"--no-deps", "-w", _demo_out], check=True)
subprocess.run([sys.executable, "-m", "pip", "download",
f"panel=={panel.__version__}", f"bokeh=={bokeh.__version__}",
"--no-deps", "-d", _demo_out], check=True)
_grlp_whl = os.path.basename(glob.glob(os.path.join(_demo_out, "grlp-*.whl"))[0])
subprocess.run([sys.executable, "-m", "panel", "convert", _demo_src,
"--to", "pyodide-worker", "--out", _demo_out,
"--requirements", _grlp_whl, "numpy", "scipy", "networkx"],
check=True, cwd=_demo_out)
# Point the panel + bokeh wheel URLs at the co-located copies (the holoviz CDN's
# bokeh wheel 403s; self-hosting also removes the run-time CDN dependency).
for _f in ("grlp_panel.js", "grlp_panel.html"):
_fp = os.path.join(_demo_out, _f)
if not os.path.exists(_fp):
continue
with open(_fp) as _fh:
_txt = _fh.read()
_txt = re.sub(r"https://cdn\.holoviz\.org/\S*?/(bokeh-[\d.]+-py3-none-any\.whl)",
r"\1", _txt)
_txt = re.sub(r"https://cdn\.holoviz\.org/\S*?/(panel-[\d.]+-py3-none-any\.whl)",
r"\1", _txt)
with open(_fp, "w") as _fh:
_fh.write(_txt)

# -- Project information ------------------------------------------------------

Expand All @@ -102,6 +65,7 @@
# -- General configuration ----------------------------------------------------

extensions = [
"artesian.sphinxext", # build the interactive WASM demo (see below)
"myst_nb", # Markdown source + executable notebooks
"sphinx.ext.autodoc", # pull docstrings from grlp
"sphinx.ext.autosummary", # summary tables
Expand All @@ -111,6 +75,18 @@
"sphinx.ext.intersphinx", # cross-link to numpy/scipy docs
]

# Compile the interactive demo into _static/interactive/ at build time. The
# grlp wheel is built from this checkout, so the demo always matches the source
# it is documenting; numpy, scipy and networkx come from Pyodide in the browser.
artesian_apps = [
{
"app": "../interactive_demo/grlp_panel.py",
"packages": [".."],
"requirements": ["numpy", "scipy", "networkx"],
"outdir": "_static/interactive",
},
]

autosummary_generate = True
autodoc_member_order = "bysource"
autodoc_default_options = {
Expand Down
11 changes: 6 additions & 5 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ sphinx>=7
myst-nb>=1
furo>=2024.1

# Interactive browser demo (docs/interactive.md). `panel convert` compiles
# Interactive browser demo (docs/interactive.md). artesian compiles
# interactive_demo/grlp_panel.py into a standalone WebAssembly app at build time
# (conf.py); GRLP then runs entirely in the reader's browser via Pyodide. Panel
# pulls bokeh. The panel + bokeh wheels are self-hosted next to the app (the
# holoviz CDN's bokeh wheel 403s), so no external CDN is required at run time.
panel>=1.9
# (see artesian_apps in conf.py); GRLP then runs entirely in the reader's
# browser via Pyodide. artesian pulls panel, which pulls bokeh; the panel and
# bokeh wheels are self-hosted next to the app because the holoviz CDN's bokeh
# wheel 403s. Pinned to a git ref: artesian is not yet released to PyPI.
artesian @ git+https://github.com/MNiMORPH/artesian@main
38 changes: 26 additions & 12 deletions interactive_demo/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
# Standalone interactive GRLP demos (experimental)
# Interactive GRLP demos

Browser-based, slider-driven demos of a single gravel-bed river responding to its
**water input**, **sediment input**, and **base level** — GRLP running entirely
in your browser via Pyodide / JupyterLite. Two notebooks:
in your browser via Pyodide.

**`grlp_panel.py` is the published demo.** It is a Panel app compiled to a
standalone WebAssembly page by [artesian](https://github.com/MNiMORPH/artesian),
which the documentation build drives through `artesian_apps` in `docs/conf.py`;
the result is embedded in
[docs/interactive.md](https://grlp.readthedocs.io/en/latest/interactive.html).
To build it by hand:

```sh
artesian build interactive_demo/grlp_panel.py -o _build -p . \
-r numpy -r scipy -r networkx --serve
```

The two notebooks below are the earlier JupyterLite versions, kept for
classroom and notebook use. They are **not** part of the documentation site.

- **`interactive_single_segment.ipynb`** — *equilibrium* response: each slider
change recomputes the steady-state profile (Lane's balance).
- **`interactive_single_segment_live.ipynb`** — *live transient*: the model runs
continuously; drag the sliders and watch the profile aggrade/incise toward the
new boundary conditions in real time.

Deliberately kept **out of the ReadTheDocs site** for now: standalone, until
tested enough.

## Run it
## Run the notebooks

One-time toolchain install (the **frontends** — `jupyterlab_widgets` for the
sliders, `ipympl` for the live canvas — must be in this env so `jupyter lite
Expand Down Expand Up @@ -52,6 +64,7 @@ notebook, run all cells, and use the sliders.

## Files

- `grlp_panel.py` — the published Panel demo, compiled by artesian (above).
- `interactive_single_segment.ipynb` — equilibrium demo (sliders + GRLP compute).
- `interactive_single_segment_live.ipynb` — live transient demo (async run loop).
- `run_demo.sh` — build the wheel + launch JupyterLite.
Expand All @@ -62,12 +75,13 @@ notebook, run all cells, and use the sliders.
## Status / notes

- **Verified natively:** the compute logic (correct Lane's-balance response,
~9 ms/update) and the pure-Python `grlp` wheel. **Not yet verified:** the
in-browser (WASM) execution and slider responsiveness — that is what this
standalone package is for.
~9 ms/update) and the pure-Python `grlp` wheel. In-browser execution is now
verified for the **Panel** demo, which is published on Read the Docs; it
remains unverified for these **notebooks**.
- If `%pip install grlp` does not resolve, confirm the freshly built wheel is in
`pypi/` (the auto-discovered wheelhouse) and that the JupyterLite build indexed
it (look for `piplite:copy:whl` in the build log).
- Once tested and trusted, the route to embed this in RTD is `jupyterlite-sphinx`
(a `{jupyterlite}`/`{voici}` directive). We are intentionally not doing that
yet.
- The route into RTD was expected to be `jupyterlite-sphinx` (a
`{jupyterlite}`/`{voici}` directive). That is not what we did: the published
demo is the Panel app, compiled by artesian at documentation build time. The
notebooks stay standalone.
8 changes: 5 additions & 3 deletions interactive_demo/grlp_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
Interactive GRLP demo — a single gravel river adjusting in real time.

This is the source for the live, in-browser demo embedded in the documentation.
It is compiled to a standalone WebAssembly app (no server, no install) with::
It is compiled to a standalone WebAssembly app (no server, no install) by
artesian, which the docs build drives through ``artesian_apps`` in
docs/conf.py. To build it by hand::

panel convert grlp_panel.py --to pyodide-worker --out <dir> \\
--requirements <grlp wheel> numpy scipy networkx
artesian build interactive_demo/grlp_panel.py -o <dir> \\
-p . -r numpy -r scipy -r networkx --serve

GRLP then runs entirely in the browser via Pyodide. Press play and drag the
sliders while it runs to change the boundary conditions and watch the long
Expand Down