Skip to content

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.

Error against step size: the correct solver follows the slope-2 line, the broken one levels off

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:

  1. 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.
  2. How to run your code: a function integrate(source, y0, t_end, n_steps) that runs it for n_steps fixed steps and adds source(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.