Operator invariants¶
Four properties every correct discretisation has, whatever its order of accuracy. None of them needs an exact solution, and each takes a few lines to set up.
| Check | Property | Typical bug it catches |
|---|---|---|
check_nullspace |
A discrete Laplacian, gradient or divergence maps a constant field to zero at every interior point. | Stencil weights stacked in the wrong order, a missing metric factor in one direction, an off-by-one neighbour index. |
check_uniform_state |
A uniform state with no flow and no source is unchanged by time stepping. | A scheme that returns an updated value where the caller expects a rate (or the reverse), a source that is never zero, a wrong-signed boundary term. |
check_advection_direction |
A feature carried by velocity u moves by u t. | A solver that integrates U_t - F_x = 0: densities and pressures from a state at rest look right, but anything with an initial velocity runs mirrored. |
check_force_gradient |
A conservative force equals minus the derivative of its potential. | A wrong factor in one term of a hand-written derivative. |
import numpy as np
from physverify.operators import (check_nullspace, check_uniform_state,
check_advection_direction, check_force_gradient)
check_nullspace(A, (nx, ny)) # A: the code's matrix (dense or sparse), or a function field -> field
check_uniform_state(solver.step, np.full(n, 300.0))
check_advection_direction(lambda f, t: run_solver(f, t), x, u=1.0, t=0.2)
check_force_gradient(lambda r: pot(r)[0], lambda r: pot(r)[1], np.linspace(0.9, 3, 20))
Notes:
check_nullspaceignores one cell at every edge by default (boundary rows hold boundary conditions). Passinterior=to change that, andfield=for other null-space fields (a linear field for a second-derivative operator on a uniform grid).check_advection_directionneeds a uniform periodic grid and u t below half the domain. On a solver without periodic boundaries, run it for a short time on a domain large enough that nothing reaches the boundary.check_force_gradientuses the radial convention: positive F pushes the particles apart.
Where these came from¶
In the corpus sessions of 2026-11-01 these four found four bugs: a scrambled polar-grid diffusion matrix (interior row sums 22 and 44 instead of 0), a Lax-Wendroff option that heated still water by 0.909 K in 5 s, an Euler solver that moved fluid at -u while its shock-tube magnitudes were right, and a screened-Coulomb force at 36-59 % of -dU/dr. They passed on the correct codes they were tried on (pylj, magnelio, Glimy, tdyno and five other pair potentials). These are counts within that sample only.
Stretched grids¶
operators.stretched_grid(n, a, b, ratio=1.1) gives nodes whose spacings grow from cell to cell. Run the invariants on it as well as on a uniform grid: a west-face coefficient built from the east spacing is exact whenever the spacings are equal. For a second-derivative operator, also pass a linear field (field=x): it is in the null space on any grid, and a wrong spacing shows there even when a constant still maps to zero.