Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9258563
docs: spec + plan for Structure-Preserving PINNs applications program…
davorrunje Jul 12, 2026
c4595af
feat(pinn): applications/ area + pinn package skeleton (Phase 0)
davorrunje Jul 12, 2026
80ff04e
docs(pinn-paper): manuscript scaffold + references (Phase 1)
davorrunje Jul 12, 2026
507100a
feat(pinn-core): admissibility metric + problem registry (Phase 2.1-2.2)
davorrunje Jul 12, 2026
008ab95
feat(pinn-core): closed-form entropy solutions (Phase 2.3)
davorrunje Jul 12, 2026
bcd3154
feat(pinn-core): TVD Godunov reference solver (Phase 2.4)
davorrunje Jul 12, 2026
6b9bf04
feat(pinn-core): deterministic sampling + sparse observation sampler …
davorrunje Jul 12, 2026
3917d31
feat(pinn-core): conservation-law problems + registry wiring (Phase 2.6)
davorrunje Jul 12, 2026
775eb57
feat(pinn-core): metrics + plotting; core framework-free invariant (P…
davorrunje Jul 12, 2026
2df4f8d
feat(pinn-models): PyTorch model builders for the four methods (Phase…
davorrunje Jul 12, 2026
c27aebc
chore(deps): opt-in per-application uv groups + backend-polymorphic f…
davorrunje Jul 12, 2026
e678e72
feat(pinn-models): JAX (Flax NNX) model builders (Phase 3.1 jax)
davorrunje Jul 12, 2026
3bfa4c8
feat(pinn-training): JAX PINN trainer + shared loss spec (Phase 3.3 jax)
davorrunje Jul 12, 2026
5250307
feat(pinn-training): torch trainer + cross-backend parity (Phase 3.2/…
davorrunje Jul 12, 2026
5ed3068
feat(pinn-exp): single-run orchestrator + RunConfig (Phase 4.1)
davorrunje Jul 12, 2026
1844edc
feat(pinn-exp): Optuna equal-budget search + sweep composer (Phase 4.…
davorrunje Jul 12, 2026
726d3b3
Merge remote-tracking branch 'origin/main' into spec/applications-str…
davorrunje Jul 12, 2026
aff7f94
Merge remote-tracking branch 'origin/main' into spec/applications-str…
davorrunje Jul 12, 2026
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
22 changes: 22 additions & 0 deletions applications/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Applications

Research papers built **on** `mononet` as a library. Distinct from
[`benchmarks/`](../benchmarks/) (which reproduces the `mononet` paper's own
Tables 1 & 2) — applications are *downstream* work that uses the published
layers to do new science. Nothing here ships in the PyPI wheel.

Each paper is a self-contained subpackage with its own `README.md` (abstract +
headline), `RUNBOOK.md` (exact reproduction commands), a Markdown manuscript
under `paper/`, and an executed notebook rendered into the Sphinx docs.

## Papers

| Dir | Paper | Status |
|---|---|---|
| [`pinn/`](pinn/) | Structure-Preserving PINNs — hard monotonicity as a PDE admissibility prior (inverse conservation-law / traffic flagship) | In progress (Paper 1) |

Planned follow-ups (own specs/plans): high-dim HJB (Paper 2), Fokker–Planck
(Paper 3), eikonal (Paper 4), arbitrage-free surfaces (Paper 5, regression —
not a PINN).

Design: [`docs/superpowers/specs/2026-07-12-applications-structure-preserving-pinns-design.md`](../docs/superpowers/specs/2026-07-12-applications-structure-preserving-pinns-design.md).
9 changes: 9 additions & 0 deletions applications/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
"""Research applications built on top of the ``mononet`` package.

This top-level area hosts downstream research papers that *depend on*
``mononet`` as a library. It is distinct from ``benchmarks/`` (which reproduces
the ``mononet`` paper's own results) and is **not** shipped in the PyPI wheel.
"""

from __future__ import annotations
4 changes: 4 additions & 0 deletions applications/_common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: Apache-2.0
"""Shared, framework-free helpers reused across application papers."""

from __future__ import annotations
35 changes: 35 additions & 0 deletions applications/_common/metrics_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-License-Identifier: Apache-2.0
"""JSON read/write for application result artifacts.

Result artifacts are small JSON mappings written to ``results/`` and consumed
by report/notebook code. Mirrors the conventions used in ``benchmarks/``.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any


def write_result(path: str | Path, obj: dict[str, Any]) -> None:
"""Write `obj` as sorted, pretty-printed JSON to `path`.

Parent directories are created if missing.

:param path: Destination file path.
:param obj: JSON-serializable result mapping.
"""
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(json.dumps(obj, indent=2, sort_keys=True) + "\n")


def read_result(path: str | Path) -> dict[str, Any]:
"""Read a JSON result mapping from `path`.

:param path: Source file path.
:returns: The parsed mapping.
"""
data: dict[str, Any] = json.loads(Path(path).read_text())
return data
25 changes: 25 additions & 0 deletions applications/_common/plot_theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: Apache-2.0
"""Shared matplotlib theme for application figures.

Kept as a plain rcParams mapping so importing it pulls in no plotting backend;
callers apply it via ``matplotlib.rcParams.update(theme_rc())``.
"""

from __future__ import annotations

from typing import Any


def theme_rc() -> dict[str, Any]:
"""Return the shared matplotlib rcParams overrides for figures.

:returns: A mapping of rcParam keys to values, suitable for
`matplotlib.rcParams.update`.
"""
return {
"figure.dpi": 120,
"axes.grid": True,
"grid.alpha": 0.3,
"font.size": 10,
"figure.autolayout": True,
}
34 changes: 34 additions & 0 deletions applications/_common/seeding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SPDX-License-Identifier: Apache-2.0
"""Deterministic RNG helpers shared across applications.

All randomness in the applications area flows through these helpers so that
point sampling and observation generation are reproducible and identical
across backends.
"""

from __future__ import annotations

import numpy as np


def rng(seed: int) -> np.random.Generator:
"""Return a NumPy generator seeded deterministically.

:param seed: Non-negative integer seed.
:returns: A `numpy.random.Generator` (PCG64) seeded with `seed`.
"""
return np.random.default_rng(seed)


def split_seeds(seed: int, n: int) -> list[int]:
"""Derive `n` independent integer seeds from a base `seed`.

Uses `numpy.random.SeedSequence` so the child seeds are well separated and
reproducible.

:param seed: Base seed.
:param n: Number of child seeds to produce.
:returns: A list of `n` deterministic non-negative integer seeds.
"""
state = np.random.SeedSequence(seed).generate_state(n)
return [int(s) for s in state]
16 changes: 16 additions & 0 deletions applications/pinn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Structure-Preserving PINNs (Paper 1)

Hard, expressive monotonicity (via `mononet`) as a PDE **admissibility prior**:
a Physics-Informed Neural Network whose architecture guarantees a
Total-Variation-Diminishing, entropy-admissible, oscillation-free solution **by
construction**, where soft-penalty PINNs only approximate it.

- **Forward mechanism tier:** Burgers-Riemann, Burgers smooth→shock, linear
advection, LWR — validated against exact / TVD finite-volume ground truth.
- **Deep flagship (inverse):** traffic state estimation — reconstruct a
monotone-front density field from sparse, noisy observations.

Scope: the **monotone-solution class** (single-front / queue / Riemann
scenarios). Abstract and headline result land here once experiments run
(manuscript in [`paper/paper.md`](paper/paper.md); reproduction in
[`RUNBOOK.md`](RUNBOOK.md)).
30 changes: 30 additions & 0 deletions applications/pinn/RUNBOOK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# RUNBOOK — Structure-Preserving PINNs (Paper 1)

Exact commands to regenerate every figure and table. Filled in as the
implementation lands (see the plan:
`docs/superpowers/plans/2026-07-12-applications-structure-preserving-pinns-paper1.md`).

## Environment

Application deps are opt-in uv groups (`pinn` includes shared `applications`);
backends are package extras. Sync the app env before running anything here:

```bash
uv sync --extra jax --group pinn # CPU JAX + optax + matplotlib (dev/tests)
uv sync --extra jax-gpu --group pinn # GPU JAX (heavy search); needs working CUDA
uv sync --extra all-cpu --group pinn # both backends (cross-backend equivalence)

uv run pytest tests/applications -q # unit + smoke suite
```

- **Development / unit + smoke tests:** `default` devcontainer (`all-cpu`, both
backends) or a single-backend env (the other backend's tests skip).
- **Heavy Optuna search + sweeps:** `gpu-jax` devcontainer (primary GPU backend).
- **Cross-backend equivalence:** needs both backends → an `all-cpu` sync.

## Reproduction (to be completed)

- [ ] Optuna search (equal budget per method) — `experiments/search.py`
- [ ] Forward mechanism-tier sweep — `experiments/sweep.py`
- [ ] Inverse flagship sparsity × noise sweep — `experiments/sweep.py`
- [ ] Fill manuscript results + render notebook
9 changes: 9 additions & 0 deletions applications/pinn/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
"""Structure-Preserving PINNs (Paper 1).

Hard monotonicity as a PDE admissibility prior. This package must import
without pulling in any ML backend: access backend code explicitly via
``applications.pinn.models.jax`` / ``applications.pinn.models.torch``.
"""

from __future__ import annotations
Empty file.
8 changes: 8 additions & 0 deletions applications/pinn/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
"""Backend-agnostic science for the PINN application (pure NumPy).

Single source of truth: problem definitions, exact solutions, the reference
solver, sampling, metrics, and plotting. Imports **no** ML framework.
"""

from __future__ import annotations
58 changes: 58 additions & 0 deletions applications/pinn/core/admissibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# SPDX-License-Identifier: Apache-2.0
"""Admissibility specification and violation metric.

The *admissibility condition* of a structure-preserving PINN is a monotonicity
(and, for later papers, convexity) statement about the solution field. This
module holds the declarative spec and the non-negative violation functional that
is the paper's headline metric: it is ``0`` by construction for the hard-monotone
model and ``> 0`` for unconstrained / soft-penalty baselines.
"""

from __future__ import annotations

from dataclasses import dataclass

import numpy as np
import numpy.typing as npt


@dataclass(frozen=True, slots=True)
class AdmissibilitySpec:
"""Declarative admissibility condition for a problem's solution field.

:param mask: Monotonicity sign per input axis — ``+1`` non-decreasing,
``-1`` non-increasing, ``0`` unconstrained (mirrors
`mononet.MonotonicityMask`).
:param convex_axes: Input axes in which the solution is convex.
:param concave_axes: Input axes in which the solution is concave.
"""

mask: tuple[int, ...]
convex_axes: tuple[int, ...] = ()
concave_axes: tuple[int, ...] = ()


def violation(
field: npt.NDArray[np.floating],
*,
axis: int,
sign: int,
) -> float:
"""Total wrong-sign variation of ``field`` along ``axis``.

For a non-increasing target (``sign == -1``) every *upward* step is a
violation; the returned value is the summed magnitude of those steps — i.e.
the total mass by which the field fails to be monotone. It is ``0`` iff the
field is monotone in the required direction along ``axis``.

:param field: Solution field sampled on a grid.
:param axis: Axis along which monotonicity is required.
:param sign: ``-1`` non-increasing, ``+1`` non-decreasing, ``0``
unconstrained (always ``0``).
:returns: Non-negative total wrong-sign variation.
"""
if sign == 0:
return 0.0
diffs = np.diff(field, axis=axis)
wrong = np.maximum(diffs, 0.0) if sign == -1 else np.maximum(-diffs, 0.0)
return float(wrong.sum())
Loading