Getting started¶
You need Python 3.10 or newer. physverify depends only on NumPy (SymPy is optional, for deriving source terms).
pip install physverify
1. Run the demo¶
physverify demo
It runs the checks on a correct midpoint solver for a damped oscillator (all pass), then on a copy with the damping 10% too large (all fail). No files are needed.
To see the same example as files you can edit, clone the repository:
git clone https://github.com/rcgiri-physics/physverify.git
cd physverify
physverify run examples/verify_damped_oscillator.py
mms PASS midpoint damped oscillator: observed order 2.04 (expected 2)
reference PASS midpoint damped oscillator: observed order 2.01 (expected 2)
mms PASS midpoint damped oscillator (whole trajectory): observed order 2.02 (expected 2)
patch EXACT midpoint damped oscillator: reproduced to round-off (largest error 2.7e-15), as designed
4 of 4 checks passed
Now open examples/damped_oscillator.py, change the damping by 10%, and run the command again: the checks fail, because the code no longer solves the equation the spec describes.
2. Read the spec¶
A spec is a Python file that defines checks(). It tells physverify two things:
- The equations your code is meant to solve, written again from the physics, independently of your code. If you copy them from the code, a mistake in the code is copied too and the checks pass.
- How to run your code: a function
integrate(source, y0, t_end, n_steps)that runs it forn_stepsfixed steps and addssource(t)to the right-hand side.
"""A physverify spec: the checks for examples/damped_oscillator.py.
physverify run examples/verify_damped_oscillator.py --html report.html
pytest examples/verify_damped_oscillator.py (physverify installed)
pytest -p physverify.pytest_plugin examples/verify_damped_oscillator.py (not installed; do not use -p when installed)
The reference right-hand side below is written independently of the solver.
"""
import numpy as np
import damped_oscillator as solver
from physverify import ManufacturedODE, ode_checks
OMEGA, GAMMA = 3.0, 0.4
def reference_rhs(t, y):
return np.array([y[1], -(OMEGA**2) * y[0] - 2.0 * GAMMA * y[1]])
def checks():
mms = ManufacturedODE(lambda t: np.array([np.sin(1.3 * t) + 0.2, 1.04 * np.cos(1.3 * t)]), reference_rhs)
return ode_checks(
"midpoint damped oscillator",
solver.integrate,
rhs_ref=reference_rhs,
mms=mms,
y0_free=np.array([1.0, 0.0]),
t_end=1.0,
ns=(10, 20, 40, 80, 160),
order=2.0,
patch=(np.array([0.5, -0.3]), np.array([0.8, 1.1])),
integrate_trajectory=lambda s, y0, t, n: solver.integrate(s, y0, t, n, trajectory=True),
)
ode_checks bundles the standard checks for an ODE solver: a manufactured solution, a reference solution, the whole-trajectory error and the exact patch test.
3. Start a spec for your own solver¶
physverify init verify_solver.py --solver mysolver --order 4
This writes a spec for mysolver.integrate with the step counts filled in. Fill in the two places marked TODO (the equations and a starting state); until then every check reports ERROR.
4. Other ways to run it¶
physverify run my_spec.py --html report.html # a web page with convergence plots
physverify run my_spec.py --json - # machine-readable output
physverify run my_spec.py --budget 60 # at most about a minute per check
pytest my_spec.py # each check becomes a pytest test
What if my code has no source-term hook?¶
Write a small wrapper in the spec that adds one; Recipes shows how. If even that is hard, start with the checks that need no hook: reference solutions, conserved quantities, exact references and the Runge-Kutta table check.
If a check fails¶
See Troubleshooting: it may be the spec or the adapter rather than your code.