Metadata-Version: 2.4
Name: koopman-graph
Version: 0.15.0
Summary: Topology-aware Koopman autoencoders for forecasting and analyzing networked dynamics
Author-email: Travis Kessler <travis.j.kessler@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/tjkessler/KoopmanGraph
Project-URL: Documentation, https://koopmangraph.readthedocs.io/
Project-URL: Repository, https://github.com/tjkessler/KoopmanGraph
Project-URL: Issues, https://github.com/tjkessler/KoopmanGraph/issues
Keywords: koopman,graph-neural-networks,pytorch-geometric,spatiotemporal,topological,dynamics
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.8.0
Requires-Dist: torch-geometric>=2.6.1
Requires-Dist: safetensors>=0.4
Provides-Extra: cli
Requires-Dist: pyyaml>=6; extra == "cli"
Provides-Extra: explain
Requires-Dist: captum>=0.7; extra == "explain"
Provides-Extra: dev
Requires-Dist: build>=1.2.0; extra == "dev"
Requires-Dist: pytest>=8.4.0; extra == "dev"
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
Requires-Dist: pytest-xdist>=3.6.0; extra == "dev"
Requires-Dist: ruff>=0.15.0; extra == "dev"
Requires-Dist: pre-commit>=4.0.0; extra == "dev"
Requires-Dist: nbmake>=0.5.0; extra == "dev"
Requires-Dist: matplotlib>=3.8.0; extra == "dev"
Requires-Dist: scipy>=1.11.0; extra == "dev"
Requires-Dist: ipykernel>=6.29.0; extra == "dev"
Requires-Dist: pandas>=2.0.0; extra == "dev"
Requires-Dist: gymnasium>=1.0.0; extra == "dev"
Requires-Dist: h5py>=3.10.0; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=8.0.0; extra == "docs"
Requires-Dist: furo>=2024.8.6; extra == "docs"
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "docs"
Provides-Extra: rl
Requires-Dist: gymnasium>=1.0.0; extra == "rl"
Requires-Dist: stable-baselines3>=2.3.0; extra == "rl"
Provides-Extra: symmetry
Requires-Dist: networkx>=3.0; extra == "symmetry"
Provides-Extra: mpc
Requires-Dist: osqp>=0.6.0; extra == "mpc"
Requires-Dist: scipy>=1.11.0; extra == "mpc"
Provides-Extra: lightning
Requires-Dist: lightning!=2.6.2,!=2.6.3,>=2.4.0; extra == "lightning"
Provides-Extra: ray
Requires-Dist: ray[train]>=2.9.0; extra == "ray"
Provides-Extra: dask
Requires-Dist: dask[distributed]>=2024.1.0; extra == "dask"
Provides-Extra: md
Requires-Dist: mdtraj>=1.9.9; extra == "md"
Provides-Extra: msm
Requires-Dist: deeptime>=0.4; extra == "msm"
Provides-Extra: baselines-ode
Requires-Dist: torchdiffeq>=0.2.3; extra == "baselines-ode"
Provides-Extra: baselines-graphcast
Provides-Extra: equivariance
Requires-Dist: e3nn>=0.5.1; extra == "equivariance"
Provides-Extra: distributed
Requires-Dist: lightning!=2.6.2,!=2.6.3,>=2.4.0; extra == "distributed"
Requires-Dist: ray[train]>=2.9.0; extra == "distributed"
Requires-Dist: dask[distributed]>=2024.1.0; extra == "distributed"
Provides-Extra: tda
Provides-Extra: tdl
Provides-Extra: export
Requires-Dist: onnx>=1.16.0; extra == "export"
Provides-Extra: federated
Provides-Extra: sparse
Provides-Extra: control-lmi
Requires-Dist: cvxpy>=1.4.0; extra == "control-lmi"
Provides-Extra: probabilistic
Provides-Extra: benchmark
Dynamic: license-file

<div align="center">

<a href="https://koopmangraph.readthedocs.io/">
  <img src="https://raw.githubusercontent.com/tjkessler/KoopmanGraph/main/docs/source/_static/koopmangraph_logo.png" alt="KoopmanGraph logo" width="420"/>
</a>

# KoopmanGraph

**Topology-aware Koopman autoencoders for forecasting and analyzing networked dynamics**

[![Tests](https://github.com/tjkessler/KoopmanGraph/actions/workflows/ci.yml/badge.svg)](https://github.com/tjkessler/KoopmanGraph/actions/workflows/ci.yml)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21926723.svg)](https://doi.org/10.5281/zenodo.21926723)
[![PyPI version](https://img.shields.io/pypi/v/koopman-graph)](https://pypi.org/project/koopman-graph/)
[![codecov](https://codecov.io/gh/tjkessler/KoopmanGraph/graph/badge.svg)](https://codecov.io/gh/tjkessler/KoopmanGraph)
[![Documentation Status](https://readthedocs.org/projects/koopmangraph/badge/?version=latest)](https://koopmangraph.readthedocs.io/en/latest/?badge=latest)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

**[Documentation](https://koopmangraph.readthedocs.io/)** |
**[Tutorials](https://koopmangraph.readthedocs.io/en/latest/tutorials.html)** |
**[API](https://koopmangraph.readthedocs.io/en/latest/api.html)** |
**[Contributing](https://github.com/tjkessler/KoopmanGraph/blob/main/CONTRIBUTING.md)** |
**[Citation](#community-and-citation)**

</div>

---

**KoopmanGraph** is an open-source PyTorch Geometric library for learning **topology-aware Koopman autoencoders** on graphs. GNN encoders lift node features into a latent space, a learned linear operator advances those states, and a matching decoder reconstructs physical node features for multi-step forecasting and spectral analysis.

It is aimed at researchers studying **networked dynamical systems**—smart grids, traffic networks, epidemic contact graphs, and similar domains—who want an inspectable linear latent operator instead of a flat-vector Koopman model or a purely nonlinear GNN forecaster.

## Why KoopmanGraph?

Koopman theory maps nonlinear dynamics into a linear latent domain where multi-step forecasting and spectral analysis are natural. Existing deep Koopman packages typically ignore graph structure, while spatiotemporal GNN forecasters usually lack an explicit linear latent operator.

KoopmanGraph bridges that gap with GNN lifting/decoding, an inspectable Koopman matrix **K**, row-state latent advance $z \leftarrow z K^{\top}$, and a PyTorch Geometric-native `fit` / `predict` workflow.

The library sits in the consistent Koopman autoencoder lineage and is **not claimed as a new theoretical contribution**; it packages topology-aware lifting, linear latent evolution, and analysis tooling for networked dynamical systems.

<p align="center">
  <img src="https://raw.githubusercontent.com/tjkessler/KoopmanGraph/main/docs/source/_static/architecture-overview.svg" alt="Encode → linear Koopman advance → decode architecture" width="820"/>
</p>

## Highlights

- **Topology-aware learning** — GCN/GAT/hypergraph encoders and decoders, delay embeddings, dynamic topology, optional self-adaptive edges, sheaf / cell / simplicial lifts, and a predicted-topology head (distinct from static AdaptiveAdjacency)
- **Flexible dynamics** — discrete, continuous-time, networked (`koopman="graph"`), hypergraph, multiplex hetero, global/local, Hodge-structured, switched, and mixture operators, with soft, structural, stochastic, or symplectic parameterizations
- **Forecasting stack** — multi-step rollout, consistency losses, temporal evaluation metrics, checkpointing, and restricted `torch.export` / TorchScript (fixed-topology homogeneous MVP)
- **Spectral analysis** — eigendecomposition, mode shapes, finite ResDMD on `evaluate`, optional `SpectralDiagnostics` (Nyquist $1/(2\Delta t)$ in cycles per unit time, $\kappa(V)$, aliasing flags), Kronecker dispersion, dynamical similarity, anomaly helpers, and optional 0-d TDA extras
- **Identification (opt-in)** — closed-form ridge / TLS / constrained least squares for discrete dense per-node $K$; default `fit` remains Adam; residual-aware dictionary gating and a frozen-encoding rank grid (not Ray Tune for `latent_dim`)
- **Identity-bound benchmarks** — frozen manifests and `koopman-graph benchmark run` / `verify` on hashed smoke fixtures; the runner does not train a model or invent MAE / RMSE, and it is not a LibCity / BasicTS host
- **Control and adaptation** — additive/bilinear control, iterated-QP Koopman-MPC (`[mpc]`), residual-tube tightening, online RLS adaptation, Kalman observation, and a Gymnasium RL wrapper
- **Research tooling** — classical DMD-family baselines, teaching GNN ports plus LibCity/BasicTS protocol adapters, GraphVAMP / alanine-dipeptide teaching fetch, conformal UQ, and a $K^2$ VAE MVP
- **Optional distributed trainers** — native DDP / `torchrun`, Lightning Fabric, Ray ensemble helpers, opt-in multi-node Ray recipe (`KOOPMAN_GRAPH_MULTINODE=1`), and in-tree FedAvg (`[federated]`)

Full inventory: [Capabilities](https://koopmangraph.readthedocs.io/en/latest/capabilities.html) · [Architecture](https://koopmangraph.readthedocs.io/en/latest/architecture.html)

**Scope.** KoopmanGraph targets topology-aware Koopman autoencoders on graphs and hypergraphs. Leaderboard adapters follow named protocols; they are not dedicated-library SOTA. Sheaf / cell / Hodge / TopologicX-bridge paths keep a linear Koopman head. GraphVAMP and the alanine-dipeptide fetch are teaching / diagnostic — not Folding@home-scale MD. Measured limits (finite ResDMD, restricted export, federated-not-DP, conservation on $K$ not decoded $x$) are consolidated in [Scope and limitations](https://koopmangraph.readthedocs.io/en/latest/limitations.html).

## Installation

Requires **Python 3.10+**, [PyTorch](https://pytorch.org/get-started/locally/), and [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/install/installation.html). Install those first, then:

```bash
pip install koopman-graph
# or: uv pip install koopman-graph
```

See the [installation guide](https://koopmangraph.readthedocs.io/en/latest/installation.html) for editable installs, `uv` workflows, docs builds, and platform-specific wheels. Release notes: [CHANGELOG.md](CHANGELOG.md).

## Quickstart

```python
import torch
from koopman_graph import GNNDecoder, GNNEncoder, GraphKoopmanModel
from koopman_graph.datasets import SyntheticDynamicGraphBenchmark

data_sequence = SyntheticDynamicGraphBenchmark.generate(
    num_nodes=20,
    num_timesteps=30,
    in_channels=3,
    seed=42,
    noise_std=0.01,
)

encoder = GNNEncoder(3, 64, 64)
decoder = GNNDecoder(64, 64, 3)
model = GraphKoopmanModel(
    encoder=encoder,
    decoder=decoder,
    latent_dim=64,
    time_step=0.1,
)

torch.manual_seed(0)
history = model.fit(data_sequence, epochs=20, lr=1e-3)
future_graphs = model.predict(data_sequence[0], steps=5)
spectrum = model.spectrum()

print(f"Final loss: {history.loss[-1]:.6f}")
print(f"Predicted {len(future_graphs)} snapshots, shape: {future_graphs[0].x.shape}")
print(f"K eigenvalues: {tuple(spectrum.eigenvalues.shape)}")
print(f"Top |λ|: {spectrum.magnitudes[:3].tolist()}")
```

The constructor factory-builds a discrete per-node `KoopmanOperator`. Pass `koopman="graph"` when `edge_index` should enter the linear step (defaults are otherwise unchanged):

```python
graph_model = GraphKoopmanModel(
    encoder=encoder,
    decoder=decoder,
    latent_dim=64,
    time_step=0.1,
    koopman="graph",
)
```

Expected output:

```text
Final loss: <float>
Predicted 5 snapshots, shape: torch.Size([20, 3])
K eigenvalues: (64,)
Top |λ|: [<float>, <float>, <float>]
```

More detail: [Quickstart guide](https://koopmangraph.readthedocs.io/en/latest/quickstart.html) · [API reference](https://koopmangraph.readthedocs.io/en/latest/api.html)

## See it in action

<p align="center">
  <img src="https://raw.githubusercontent.com/tjkessler/KoopmanGraph/main/docs/source/_static/epidemic-forecast.png" alt="Epidemic truth versus KoopmanGraph forecast on a ring graph" width="820"/>
</p>

<p align="center"><em>SIR epidemic on a ring: truth vs forecast from <a href="https://github.com/tjkessler/KoopmanGraph/blob/main/examples/06_epidemic_ring.ipynb">examples/06_epidemic_ring.ipynb</a>.</em></p>

<p align="center">
  <img src="https://raw.githubusercontent.com/tjkessler/KoopmanGraph/main/docs/source/_static/metrla-gnn-baselines.png" alt="METR-LA aggregate RMSE for GraphKoopman versus STGCN, DCRNN, and Graph WaveNet teaching baselines" width="640"/>
</p>

<p align="center"><em>METR-LA aggregate RMSE: GraphKoopman leads (0.6551 vs 0.7076 / 1.0754 / 0.9036) against in-repo STGCN / DCRNN / Graph WaveNet <strong>teaching baselines</strong> (unequal budgets; not dedicated-library SOTA) from <a href="https://github.com/tjkessler/KoopmanGraph/blob/main/examples/22_gnn_forecaster_comparison.ipynb">examples/22_gnn_forecaster_comparison.ipynb</a>.</em></p>

Featured tutorials: [01 synthetic](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/01_synthetic_graph.ipynb) · [03 traffic](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/03_traffic_network.ipynb) · [06 epidemic](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/06_epidemic_ring.ipynb) · [22 GNN baselines](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/22_gnn_forecaster_comparison.ipynb) · [37 topology transfer](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/37_cross_topology_transfer.ipynb) · [39 hetero RelGraph](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/39_heterogeneous_relational_koopman.ipynb) · [42 teaching baselines](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/42_traffic_teaching_baselines.ipynb) · [47 benchmark manifests](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/47_benchmark_manifest.ipynb) · [48 identification](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/48_identification_invariance.ipynb) · [full gallery](https://koopmangraph.readthedocs.io/en/latest/tutorials.html)

## Learn more

- [Quickstart](https://koopmangraph.readthedocs.io/en/latest/quickstart.html) — train / predict walkthrough
- [Capabilities](https://koopmangraph.readthedocs.io/en/latest/capabilities.html) — feature inventory and datasets
- [Scope and limitations](https://koopmangraph.readthedocs.io/en/latest/limitations.html) — when not to use; measured boundaries
- [Architecture](https://koopmangraph.readthedocs.io/en/latest/architecture.html) — public vs power-user API layers
- [Benchmarks](https://koopmangraph.readthedocs.io/en/latest/benchmarks.html) — identity-bound manifests (not trained scores)
- [Identification](https://koopmangraph.readthedocs.io/en/latest/identification.html) — opt-in closed-form $K$ versus Adam
- [FAQ / troubleshooting](https://koopmangraph.readthedocs.io/en/latest/faq.html) — install, imports, checkpoints
- [Installation](https://koopmangraph.readthedocs.io/en/latest/installation.html) — dependencies, install paths, and CI platforms
- [CLI](https://koopmangraph.readthedocs.io/en/latest/cli.html) — `koopman-graph train` / `predict` / `benchmark` config workflow
- [SECURITY.md](SECURITY.md) — supported versions and checkpoint trust boundaries
- What’s new in 0.15.0: opt-in closed-form identification, identity-bound
  benchmark manifests, polynomial graph filters, Nyquist / conditioning
  diagnostics, and graph-state / cochain / matrix-free MVPs — defaults
  unchanged vs 0.14.0; see [CHANGELOG.md](CHANGELOG.md).

## Related software

- [PyKoopman](https://pykoopman.readthedocs.io/), [PyDMD](https://pydmd.github.io/PyDMD/), [kooplearn](https://kooplearn.readthedocs.io/), and [DLKoopman](https://github.com/GaloisInc/dlkoopman) target vector-valued Koopman / DMD / operator-learning workflows; they treat the state as a flat vector rather than propagating information along graph edges.
- [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/) provides mature GNN infrastructure on irregular graphs; KoopmanGraph adds an explicit linear latent operator, consistency losses, and a documented `fit` / `predict` forecasting stack on that substrate.
- Spatiotemporal GNN forecasters such as STGCN, DCRNN, and Graph WaveNet typically learn nonlinear convolutional or recurrent maps on graphs; KoopmanGraph instead advances an inspectable linear Koopman matrix **K** (see in-repo teaching baselines in [examples/22](https://github.com/tjkessler/KoopmanGraph/blob/main/examples/22_gnn_forecaster_comparison.ipynb)).

## Community and citation

- Contribute, report issues, or seek support: [CONTRIBUTING.md](https://github.com/tjkessler/KoopmanGraph/blob/main/CONTRIBUTING.md) · [Support](https://github.com/tjkessler/KoopmanGraph/blob/main/CONTRIBUTING.md#support) · [Code of Conduct](CODE_OF_CONDUCT.md)
- Install / runtime troubleshooting: [FAQ](https://koopmangraph.readthedocs.io/en/latest/faq.html)
- Security vulnerabilities (private): [SECURITY.md](SECURITY.md)
- Development checks and release process: [CONTRIBUTING.md](https://github.com/tjkessler/KoopmanGraph/blob/main/CONTRIBUTING.md#running-checks-locally)

If you use KoopmanGraph in research, please cite:

```bibtex
@software{koopmangraph2026,
  author       = {Travis Kessler},
  title        = {KoopmanGraph: Topology-Aware Koopman Autoencoders for Networked Dynamics},
  year         = {2026},
  publisher    = {Zenodo},
  doi          = {10.5281/zenodo.21926723},
  url          = {https://github.com/tjkessler/KoopmanGraph},
  version      = {0.15.0},
}
```

## License

KoopmanGraph is released under the [Apache License 2.0](https://github.com/tjkessler/KoopmanGraph/blob/main/LICENSE).
