Runge–Kutta Physics‑Informed Neural Networks (PINNs) with time‑discrete losses in PyTorch. Ships Gauss–Legendre, Radau IIA and Lobatto IIIA at 2, 3 and 4 stages — classical orders up to 8 — with a boundary‑conditioned neural ansatz, d‑dimensional operators, and end‑to‑end examples for the 1D and 2D heat equations.
See ROADMAP.md for milestones and planned features.
- Time‑discrete residual built from Runge–Kutta collocation: residuals evaluated at stage nodes and integrated with RK weights.
-
General RK backend via
ButcherTableau— nine tableaux included, andcollocation_tableau(nodes)derives a new collocation family from its nodes alone. -
Boundary conditioning through a multiplicative factor
$\Phi(x)$ to satisfy homogeneous Dirichlet BCs exactly. -
Modular PDE operators with autograd‑based derivatives:
Laplacian1D, andLaplacianNDfor any number of spatial dimensions. - 1D, 2D and 3D domains on the unit cube, with exact homogeneous Dirichlet conditions on every face.
- Practical implementation: type hints, ruff/mypy clean, tests, and GitHub Actions CI.
pip install pinn-rkThat gives you the library and its only runtime dependencies, numpy and torch.
The notebooks and the plotting flags need matplotlib, jupyter and plotly. These are
Poetry groups rather than pip extras, so they are available when working from a
clone — pip install "pinn-rk[examples]" will not work.
git clone https://github.com/DiogoRibeiro7/pinn-rk.git
cd pinn-rk
poetry install
pre-commit installRequires Python 3.10–3.12. Install a CPU or CUDA build of PyTorch appropriate for your environment.
Train on the 1D heat equation
from pinn_rk.examples.train_heat_equation import train_heat_equation, l2_error
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = train_heat_equation(
method="radau3", # gauss2|radau2|lobatto2 · gauss3|radau3|lobatto3 · gauss4|radau4|lobatto4
T=0.1,
N=20,
n_x_train=256,
steps=1000,
lr=2e-3,
device=device,
)
print("L2(T=0.1) =", l2_error(model, T=0.1, nx=1001, device=device))Expected output (ballpark): L2(T=0.1) ~ 1e-2 … 1e-1 depending on training steps and hardware.
We consider linear parabolic PDEs of the form
The time interval is partitioned into slabs
Here
Writing the semi-discrete problem as
pinn-rk imposes both directly on the network (residual="rk", the default), dividing by
This uses the full Butcher tableau — including the coupling matrix
| tableau | stages | stage order | update order | classical |
|---|---|---|---|---|
lobatto2 |
2 | 2 | ||
radau2 |
2 | 3 | ||
gauss2 |
2 | 4 | ||
lobatto3 |
3 | 4 | ||
radau3 |
3 | 5 | ||
gauss3 |
3 | 6 | ||
lobatto4 |
4 | 6 | ||
radau4 |
4 | 7 | ||
gauss4 |
4 | — | 8 |
Gauss q=4 is order 8, which is past what double precision can measure here. The update residual is tests/test_rk_order.py asserts explicitly, so the flat region is not mistaken for a convergence failure.
The update residual recovers each method's classical order, which is what makes the choice of tableau meaningful: Gauss and Radau cost the same stages, and Gauss is orders of magnitude more consistent at the same slab size.
The stage residual converges at the stage order, which for collocation methods equals the number of stages tests/test_rk_order.py, and the tableaux themselves are re-derived from their nodes and checked against the order conditions in tests/test_tableau_order.py.
Lobatto IIIA is stiffly accurate — its
The pre-0.3 formulation is retained for comparison. It reconstructs
stencil (q_aux) |
max residual at |
observed order | |
|---|---|---|---|
"same" — stage nodes only |
|||
"extend" — plus slab start |
For Gauss the RK form is roughly q_aux applies only to this setting and is ignored under residual="rk".
What consistency does and does not tell you. The tables above measure truncation error — the residual left on the exact solution — which bounds the best a perfectly trained network could do. It does not predict optimisation behaviour. In short single-seed training runs on the heat-equation example the two forms trade places depending on the tableau and the step budget, and the loss trace is non-monotonic because the spatial sampler redraws each step. Treat the training comparison as unresolved: a fair answer needs several seeds and converged runs.
residualexists so the comparison can be made rather than assumed.
The objective sums the PDE residual and the initial-condition penalty. Their relative size is not stable: on the shipped example the penalty is essentially the entire loss at initialisation (RkPinnConfig.ic_weight scales the penalty so this can be controlled; ic_weight=0.0 drops it entirely and measures the residual alone.
src/pinn_rk/
├─ tableau.py # ButcherTableau + Gauss/Radau/Lobatto factories
├─ mesh.py # TimeMesh: partition of [0,T] into slabs
├─ interpolants.py # barycentric weights, Lagrange evaluation
├─ model.py # MLP with boundary-conditioned ansatz
├─ operators.py # elliptic operators (e.g., Laplacian1D)
├─ config.py # RkPinnConfig
├─ loss.py # RkPinnLoss: the time-discrete RK objective
├─ examples/ # reference training routines (heat equation)
├─ __init__.py # public API
└─ __about__.py # version
Purpose. Encodes a Runge–Kutta method.
-
Fields:
A: Tensor [q,q],b: Tensor [q],c: Tensor [q]. -
Factories:
butcher_gauss_legendre_q2()– order 4, 2 stagesbutcher_radau_iia_q2()– order 3, 2 stagesbutcher_lobatto_iiia_q2()– trapezoidal rule, 2 stages
Purpose. Uniform or user‑defined partition of
TimeMesh.uniform(T: float, N: int, device) -> TimeMesh- Fields:
nodes: Tensor [N+1],steps: Tensor [N].
Purpose. Network
The factor of 4 per dimension normalises the peak to 1. Without it
MLP(in_dim=2, width=128, depth=4, activation="tanh")
Purpose. Configuration for assembling the time‑discrete loss.
- Key fields:
tableau,time_mesh,n_x_train,spatial_sampler,init_data. residual:"rk"(default) imposes the stage and update equations of the full Butcher tableau;"interpolant"selects the pre‑0.3 reconstruction‑derivative form.q_aux:"same"or"extend"— reconstruction stencil, used only whenresidual="interpolant".
Purpose. Computes the RK‑PINN time‑discrete objective over all slabs.
RkPinnLoss(model, Lop, f_rhs, cfg)- Call to compute scalar loss:
loss = loss_fn()
Purpose. Barycentric Lagrange machinery backing the time reconstruction
barycentric_weights(nodes) -> Tensor [q]-
lagrange_eval(t, nodes, w) -> Tensor [..., q]– basis values$L_j(t)$ , for evaluating$\hat{u}$ away from the nodes. -
differentiation_matrix(nodes, w=None) -> Tensor [q,q]–$D_{ij} = L_j'(t_i)$ , exact for polynomials of degree$\le q-1$ .
D @ U, taken analytically from the interpolant. The reconstruction stencil is selected by RkPinnConfig.q_aux: "same" interpolates the "extend" also uses the slab start
-
train_heat_equation(...) -> nn.Module– reference training routine. -
l2_error(model, T, nx=1001, device) -> float–$L^2$ error at final time.
Tableaux with the same stage count cost the same per slab, so within a row of this table accuracy is close to free. Under residual="rk" the trade‑off is the classical one between order and stability:
| scheme | stages | classical order | stability | notes |
|---|---|---|---|---|
lobatto2 (Lobatto IIIA) |
2 | 2 | A‑stable | trapezoidal rule; symmetric, stiffly accurate |
radau2 (Radau IIA) |
2 | 3 | L‑stable | damps stiff transients |
gauss2 (Gauss–Legendre) |
2 | 4 | A‑stable | most accurate per stage; symplectic |
lobatto3 |
3 | 4 | A‑stable | Simpson's rule; stiffly accurate |
radau3 |
3 | 5 | L‑stable | robust default when stiff |
gauss3 |
3 | 6 | A‑stable | most accurate per stage; symplectic |
lobatto4 |
4 | 6 | A‑stable | stiffly accurate |
radau4 |
4 | 7 | L‑stable | highest order with L‑stability |
gauss4 |
4 | 8 | A‑stable | highest order shipped; symplectic |
Prefer Gauss for accuracy on smooth problems and Radau IIA when the operator is stiff — A‑stability alone does not damp the stiffest modes, which is why Radau IIA remains the robust choice despite the lower order.
Because the stage order equals the stage count and caps the objective, moving from q=2 to q=3 raises the ceiling for every family, not just the classical order.
Switch via the method argument in train_heat_equation.
-
Add RK variants. Pass the nodes to
collocation_tableau(c)and it derivesAandbfor you — for a collocation method they are forced by the nodes. Gauss, Radau IIA and Lobatto IIIA ship at q=2, 3 and 4. No other code changes required. -
New PDE operators. Create a class implementing the
EllipticOperatorprotocol and supply it toRkPinnLoss. -
Initial/boundary data. Replace
init_dataand/or change the boundary factor$\Phi$ for different domains/BCs. -
Right‑hand side. Provide a custom
f_rhs(x,t)callable.
- Unit tests live in
tests/and cover training sanity and error bounds. - Set environment variables as needed for deterministic PyTorch runs (note some CUDA ops are non‑deterministic).
- CI runs
ruff,mypy, andpyteston Linux, macOS, and Windows across multiple Python versions.
Runnable scripts live in examples/, and two notebooks in
examples/notebooks/ are committed with their outputs, so they
render on GitHub without being run:
visualization.ipynb— training history, solution evolution against the exact solution, a space‑time error map, an interactive time slider, andresidual="rk"vs"interpolant"on one seed.benchmarking.ipynb— cost per step, scaling inNandn_x_train, memory, CPU/GPU, and the accuracy‑per‑unit‑cost study reproducing the convergence orders above.
poetry install --with examples
poetry run jupyter lab examples/notebooksEvery figure and number in them was produced by executing the notebook against the
current code. tests/test_notebooks.py asserts they parse, store no error outputs, and
were genuinely executed rather than authored by hand.
Training the heat‑equation example for ~1k–5k steps typically reaches 1e-2 and 1e-1 at T=0.1, depending on the RK scheme and batch sizes. Use radau2 for stability and increase N and training steps for tighter accuracy.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Key points:
- Follow code style (ruff, mypy)
- Add tests for new features
- Update documentation and CHANGELOG
- Use conventional commit messages
If you use this software in your research, please cite it:
@software{ribeiro_pinn_rk,
author = {Ribeiro, Diogo},
title = {pinn-rk: Runge-Kutta Physics-Informed Neural Networks},
year = {2026},
publisher = {Zenodo},
doi = {10.5281/zenodo.21839391},
url = {https://doi.org/10.5281/zenodo.21839391},
version = {0.2.0}
}The DOI above is the concept DOI: it always resolves to the latest version. To cite a specific release, use its own DOI instead — 10.5281/zenodo.21875876 for v0.6.0, 10.5281/zenodo.21871378 for v0.5.1, 10.5281/zenodo.21865023 for v0.5.0, 10.5281/zenodo.21860298 for v0.4.0, 10.5281/zenodo.21850047 for v0.3.0, 10.5281/zenodo.21843920 for v0.2.0, 10.5281/zenodo.21839392 for v0.1.0.
Or see CITATION.cff for the full citation information.
This project is licensed under the MIT License. See LICENSE for details.
See ROADMAP.md for planned features including:
- Higher-order RK methods (q≥3)
- 2D/3D operators
- Adaptive time stepping
- Additional boundary conditions
- Documentation website
This work builds upon research in Physics-Informed Neural Networks and time-stepping methods for PDEs.
- 📖 Documentation
- 🐛 Issue Tracker
- 💬 Discussions
- 📧 Contact: dfr@esmad.ipp.pt