warpSPH
(formerly compressibleSPH; the SPH core previously hosted at this URL now lives at
wi-re/warpSPHCore)
A GPU-oriented Python package for Smoothed Particle Hydrodynamics experiments, built on NVIDIA Warp and PyTorch. It covers three families of scheme against one shared infrastructure:
| family | schemes | typical cases |
|---|---|---|
| compressible | Monaghan, CompSPH, CRKSPH |
shock tubes, blast waves, instabilities |
| weakly compressible | deltaSPH |
free surfaces, dam breaks, channel flow |
| incompressible | divergenceFree (DFSPH) |
Taylor-Green, decaying turbulence |
Everything a run needs — configuration, geometry, the step loop, export, plotting, video encoding — is shared, so a case is only the physics that distinguishes it: how the geometry is built, what the initial conditions are, and what to measure.
This repository is the frontend. The kernels, neighbour search and data
types live in warpSPHCore, the time
integrators in warpSPHIntegrators,
and the visualisation in warpSPHPlotting.
Contents
- Install
- Quick start
- Running cases — the CLI, config files, output, plots, reports, watching a run (velocity alarm, stall watchdogs)
- The cases — what ships, and which example each came from
- Writing a case
- From Python and notebooks
- Configuration reference
- Package layout
- Tests
- Contributing — dev setup, nbstripout, what to run before committing
- Plans — which plan is active, priorities, per-plan progress markers
- Gallery
- Precision, and other things that bite
Install
The four repositories are developed together, so editable installs from a common parent directory are the supported setup.
mkdir warp-sph-stack && cd warp-sph-stack
git clone https://github.com/wi-re/warpSPHCore
git clone https://github.com/wi-re/warpSPHIntegrators
git clone https://github.com/wi-re/warpSPHPlotting
git clone https://github.com/wi-re/warpSPH
conda create -n warp_env python=3.14 && conda activate warp_env
pip install tqdm seaborn pandas pyglet dill numba scipy scikit-image h5py ipykernel ipympl
pip install -e warpSPHCore/ warpSPHIntegrators/ warpSPHPlotting/
pip install -e warpSPH/
A CUDA device is required for anything beyond imports. ffmpeg is optional —
without it, --video is skipped rather than failing a run that already produced
its frames.
Contributing, and in particular committing notebooks, needs one more step: CONTRIBUTING.md.
Quick start
warpsph-run sod --plot # Sod shock tube, live plot
warpsph-run # list every case, with a description
warpsph-run kelvinHelmholtz --nx 256 --store # write HDF5 as it goes
Or run the example script next to its notebook, which pins that example's settings:
python examples/compressible/12-kelvin-helmholtz.py
Both print what they are about to do, then what they did:
------------------------------------------------------------------------------
warpSPH | kelvinHelmholtz
Kelvin-Helmholtz instability (2D), compressible SPH.
------------------------------------------------------------------------------
scheme CRKSPH (CompressibleSPHScheme) | solver crkSPH_step
device cuda:0 (NVIDIA GeForce RTX 3090) | float32
particles 65,536 | dim 2 | nx 256
domain [0, 0] to [1, 1] periodic
kernel B7 | n_h 4 | targetNeighbors 50.3
integrator rungeKutta2 | support KernelMeanSymmetric
timestep dt 0.000389 | adaptive, cfl 0.3
duration 10,282 steps to t = 4
output export/12-kelvinHelmholtz
frames every 1 steps (vispy)
------------------------------------------------------------------------------
Running cases
A case is run through a shared runner rather than per-script boilerplate. Every
flag below is a field of CaseSpec, so a run is fully described by a file as
well as by a command line.
The CLI
warpsph-run <case> [flags] # the console script
python -m warpSPH.cases.sod [flags] # the same case, as a module
python examples/compressible/01-sod/sod_1d.py [flags]
warpsph-run with no case name lists everything with its description, and
warpsph-run <case> --help describes every flag at that case's own defaults —
including, for the enum-valued ones (--kernel, --supportMode,
--gradientMode, --laplacianMode, --samplingScheme, --integrationScheme),
the accepted values read straight off the enums. Every boolean flag has a
--no- twin, listed beside it, so a true in a config file stays overridable
from the command line.
warpsph-run --precision float64 ... works because the CLI selects the precision
before warpSPH is imported. The other two entry points import warpSPH
first and are stuck with whatever is already active — see
Precision.
Comparing solvers
--scheme picks the solver, so the same case can be run against every scheme in
its family and the results compared directly:
for solver in Monaghan CompSPH CRKSPH; do
warpsph-run sod --scheme $solver --store --caseName sod-$solver
done
warpsph-run <case> --help lists the valid names and the case's own default.
The banner names the step function that was actually selected
(solver compressibleSPH_Monaghan), so a comparison cannot quietly run the same
solver twice.
The three compressible solvers differ in what they conserve — over a 20-step Sod
run, CompSPH holds total energy to exactly zero drift by construction, CRKSPH
to ~5e-6, and Monaghan to ~1e-3, the last because its artificial viscosity and
conductivity are dissipative by design. tests/test_physics.py pins all three.
Config files and sweeps
warpsph-run tgv --config examples/sweeps/tgv_nu.yaml --nx 128
warpsph-run sod --saveConfig resolved.yaml # write the resolved spec out
Precedence, lowest to highest: CaseSpec defaults → the case's own defaults →
the --config file → explicit CLI flags. That works because every generated flag
defaults to "not passed", so a true in a config file is still overridable from
the command line. Unknown keys raise rather than being silently ignored, so a
typo in a sweep file fails loudly. See examples/sweeps/.
Case-specific knobs live under a params: block; list- and dict-valued ones
(shock regions, a gravity vector) are settable from a config file only, since
they have no sensible flag form.
Output
--store writes state, --plot writes frames, --video encodes them:
export/<caseName>_<YYYY-MM-DD_HH-MM-SS>/
caseSpec.json the fully resolved spec, so a run can be reproduced
config.json the simulation and scheme configuration
trajectory/ one .h5 per stored step (storeMode: states, the default)
trajectory.h5 one growing file (storeMode: trajectory)
images/frame_*.png plot frames
output.mp4, out.gif if --video and ffmpeg is present
Each run gets its own timestamped folder, so re-running a case accumulates results instead of overwriting the previous one. To pick a run back up without knowing its exact name:
from warpSPH.io import latestExportPath, findExportRuns
path = latestExportPath('01-sodShockTube') # newest run of that case
runs = findExportRuns('01-sodShockTube') # all of them, oldest first
Set WARPSPH_EXPORT_TIMESTAMP=0 (or pass timestamped=False to prepExport)
for the old flat export/<caseName>/ layout. latestExportPath falls back to
that layout, so trees written before this change still resolve.
--exportRoot (or $WARPSPH_EXPORT_ROOT) moves the whole tree; it defaults to
export/.
Plots
--plot opens a live, updating window and writes the frames. The rendering
backend is chosen by dimension — vispy for 2D, matplotlib for 1D — because a
matplotlib scatter of a large 2D particle set costs more per frame than the
physics step it is drawing. Measured on a 16k-particle Kelvin-Helmholtz run,
6 frames: matplotlib spent 19.97 s plotting against 1.65 s of physics; vispy
brings that to 3.97 s.
| flag | effect |
|---|---|
--plot |
draw, and write frame_NNNNN.png under the export directory |
--plotBackend |
matplotlib, vispy or pyVista; default is by dimension |
--plotInterval |
steps between redraws |
--no-show |
keep the frames, skip the window |
--no-holdPlot |
do not block on the final figure when the run ends |
A vispy canvas that cannot start — no GL context over ssh, or in a container —
falls back to matplotlib with a message rather than taking the run down. The
example scripts pass --plot for you, since an example is meant to be watched;
--no-plot turns it off.
What a run prints
The banner goes out once setup is finished and dt is resolved, so it shows
what will actually run rather than what was asked for. The report closes the
run:
------------------------------------------------------------------------------
kelvinHelmholtz finished in 3m 12s
------------------------------------------------------------------------------
steps 2,564 | t = 4 | final dt 0.001557
step time mean 41.2 ms | min 38.1 | max 248.2 | 1m 45s in the loop (55% of wall)
diagnostics initial final min max
kineticEnergy 0.15942 0.09318 0.09318 0.15945
totalEnergy 3.9094 3.9094 3.9094 3.9094
output export/12-kelvinHelmholtz
6 state files | 2564 frames (vispy)
------------------------------------------------------------------------------
This exists for the unattended run: coming back to a finished terminal should answer "did it work, and did it stay sane" without re-reading the scrollback. Diagnostics show min/max as well as initial/final, because an excursion that recovered before the end is invisible in the final value alone; the file counts are read off disk rather than inferred from the configured intervals.
A diverged run says so in the header, warns that the numbers are unusable, and exits non-zero, so a shell script can tell.
--quiet / -q suppresses banner, report and progress bar (and warp's
per-module load logging). The progress bar is on only when a terminal is
watching — redirected to a file it would bury the report under carriage returns
— and --progress forces it back on, also under --quiet (no banner, rows
still streaming: what the probe scripts use). The row is redrawn at most ~10
times a second; mid-run messages (stop reasons, the velocity alarm) are
written above the bar on a line of their own.
Watching a run
A run that goes wrong without going non-finite used to look healthy: one
runaway particle at 1e5 m/s pins the global CFL dt at its floor, simulated
time stops advancing, and the run reports diverged=False after hours. Two
kinds of guard exist, and they do different things:
| Guard | Fields / flags | Does |
|---|---|---|
Velocity alarm (runner/velocityAlarm.py) |
velocityAlarmFactor (default 100), velocityScale |
Flags, never stops (unless asked, below): when max |v| exceeds the factor × the expected velocity it prints a [warpSPH] VELOCITY ALARM raised … line naming the fastest particle (index, uid, position) and dt; again at every further 10×; and … cleared when it drops below half the threshold. On by default everywhere. |
| — reaction: closer frames | velocityAlarmPlotInterval |
While the alarm is active, draw a frame every N steps (needs --plot), so the video shows the runaway step by step; the normal plotInterval returns when it clears. |
| — reaction: hard ceiling | velocityAlarmStopRatio |
Stop once max |v| passes this multiple of the expected velocity. Unset by default. |
| Stall watchdogs | stallProgress (fraction of tLimit per 1000 steps), stallDtSteps (steps with dt at minDt) |
Stop a run whose simulated time has frozen. Off by default in CaseSpec. |
The expected velocity is, in order: --velocityScale; the case's own
(ctx.velocityScale, set in its setup hooks — dambreak: referenceVelocity
or sqrt(2 g H); hydrostaticColumn: sqrt(g H)); else the largest of the
initial max |v|, a compressible scheme's initial sound speed, a weakly
compressible scheme's c0 / 10, ACSPH's uChar. The banner's watch row says
which one it used, or that the alarm is off (an incompressible run started
from rest in a case that declares nothing). A case that knows its scale should
set ctx.velocityScale / ctx.velocityScaleSource in initialConditions.
The check reuses the one max |v| read the runner already does for its
non-finite check, so it adds no GPU sync per step and changes no result.
From the command line — every field above is a warpsph-run flag, and a
--config key:
warpsph-run dambreak --scheme artificialCompressible --plot --video \
--velocityAlarmPlotInterval 1 --stallProgress 1e-3
From a probe script — scripts/_runWatch.py adds the same flags with
probe defaults (alarm at 100×, frames every step while active,
stallProgress 1e-3); probe_deltaSPHMarrone.py and probe_contactLine.py
use it:
from _runWatch import addWatchArguments, watchOverrides # scripts/ is on sys.path
addWatchArguments(ap) # after the script's own flags
args = ap.parse_args()
...
r = run(case, **kw, **watchOverrides(args))
From Python — anything more than the two built-in reactions goes in a callback, called on every raise, escalation, clear and stop:
def onAlarm(ctx, state, event):
# event: kind ('raised'|'escalated'|'cleared'|'stop'), step, t, vmax, ratio,
# scale, dt, and for a raise/escalation the fastest particle's
# index, uid, particleKind, position
if event['kind'] == 'raised':
runaways.append((event['t'], event.get('uid'), event.get('position')))
return event['ratio'] > 1e5 # True stops the run
r = run(case, velocityAlarmPlotInterval=1, onVelocityAlarm=onAlarm)
r.velocityAlarms # every event, in order
r.stopReason # why it stopped early (watchdog, non-finite, alarm), else None
Hooks can also read the active event from ctx.scratch['velocityAlarm']
(None when quiet). Watching a log from outside (a second terminal, a
Claude Code Monitor): grep for the fixed prefix, together with the stop
lines and the failure signatures:
tail -f run.log | grep -E --line-buffered "VELOCITY ALARM|stopping|stops the run|non-finite|Traceback"
The cases
Every notebook under examples/ that runs a simulation has a case, plus a .py
script next to the notebook that runs it with that example's settings. The
notebooks stay for exploration; the scripts are what you run unattended.
Compressible
| case | script | notes |
|---|---|---|
sod |
01-sod/sod_1d.py | Sod shock tube, 1D; own directory (01-sod/) with a resume script/notebook, a trajectory-export demo, and sod_backprop.ipynb (backprop through the trajectory) -- see its notebooks for the pattern |
sod2d, sod3d |
01-sod/sod_2d.py, sod_3d.py | the same tube extruded into a periodic slab, sampled at equal particle mass rather than equal spacing (--no-equalMass for the comparison); sod_2d.ipynb/sod_3d.ipynb add the sampling arithmetic and a slab/slice view of the density field |
linearWave |
02-linear-wave.py | linear acoustic wave, 1D |
kidder |
03-kidder-isentropic-compression.py | isentropic compression; analytically driven boundary bands |
noh |
04-noh-implosion.py | Noh implosion, 1D |
woodwardColella |
05-woodward-colella.py | interacting blast waves, 1D |
sedov |
06-sedov/sedov_1d.py, sedov_2d.py, sedov_3d.py | Sedov-Taylor blast; --dim 1/2/3; own directory (06-sedov/); --initialization hat (default) smooths the point deposit over one smoothing scale via an SPH interpolation pass, singular is the raw spike, quadrant spreads it over the 2**dim innermost particles |
hydrostatic |
08-hydrostatic.py | hydrostatic equilibrium; the exact answer is "nothing happens" |
gresho |
09-gresho-chan-vortex.py | Gresho-Chan vortex, a steady state |
yee |
10-yee-vortex.py | Yee isentropic vortex, sampled on shells |
shearingNoh |
11-shearing-noh-implosion-2d.py | Noh implosion with transverse shear |
kelvinHelmholtz |
12-kelvin-helmholtz.py | Kelvin-Helmholtz instability |
rayleighTaylor |
13-rayleigh-taylor.py | Rayleigh-Taylor instability |
triplePoint |
14-triplePoint/ (equalSpacing, equalMass) | --equalMass or --no-equalMass sampling; own directory like sedov |
Weakly compressible (deltaSPH)
| case | script | notes |
|---|---|---|
impact |
01-impact/ (spheres, squares) | two bodies colliding; --shape circle or box |
squarePatch |
03-rotating-square-patch.py | rotating square patch of fluid |
droplet |
04-oscillating-droplet.py | oscillating droplet in a central potential |
tgv-wc |
05-taylor-green-vortex.py | Taylor-Green vortex with explicit viscosity |
randomFlow |
06-randomFlow/ (periodic, bounded) | divergence-free noise; --bounded adds walls |
kolmogorov |
08-kolmogorov-flow.py | sinusoidally forced periodic box |
ldc |
09-lid-driven-cavity.py | lid-driven cavity |
movingObstacle |
10-moving-obstacle.py | flow past a spinning rigid body |
drivenSquare |
11-driven-square.py | driven channel flow past a cylinder |
dambreak |
12-dambreak.py | dam break with optional obstacle |
openFlow |
13-open-flow.py | open channel flow past an obstacle |
Analytic walls (optional). With the optional package warpSPHBoundaries installed (pip install -e "warpSPH/[boundaries]"), dambreak --wallRepresentation analytic replaces the tank's wall particles by exact
kernel integrals over the solid (obstacles, mixed scenes, Michel / implicit shifting, free bodies and the whole-step graph included); see ANALYTIC_BOUNDARIES_PLAN.md.
Incompressible (DFSPH)
| case | script | notes |
|---|---|---|
tgv |
01-taylor-green-vortex.py | Taylor-Green vortex, divergence-free |
squarePatch |
03-rotating-square-patch.py | same case at --scheme divergenceFree |
randomFlow |
periodic-random-flow.py | same case at --scheme divergenceFree |
Several cases cover more than one notebook, because those notebooks differed
only in a flag; the scripts pin the flag. Two notebooks deliberately have no
case: weaklyCompressible/naca.ipynb is a standalone airfoil-SDF visualisation
with no simulation in it, and incompressible/1d-test.ipynb is an exploratory
scratchpad rather than a published example.
The wave system
A fourth thing in the tree that is not a fluid scheme: a scalar
wave-equation solver, d2u/dt2 = c^2 laplacian(u), with PML-style absorbing
damping. Registered and runnable (warpsph-run waveEquation), unlike earlier
in this repo's history — see docs/historic_plans/WAVE_EQUATION_PLAN.md for how it got there.
| piece | where |
|---|---|
| the scheme | schemes/waveEquation.py |
| the case | cases/waveEquation.py |
| state and system | systems/waveSystem.py |
| configuration | configurations/waveEquationConfig.py |
| case generation | caseUtils/waveEquation/ |
| system assembly | sample/waveSystem.py |
It is kept because it is a compact demo of the SPH operators applied to
unstructured, mesh-like data — a moving-neighbourhood Laplacian over scattered
points, with a heterogeneous wave speed making obstacles and walls — which is
the shape of problem graph and point-cloud ML models are usually posed on. The
randomize* flags and *Range pairs in its config exist so that one TOML
casefile describes a family of cases to sample from, rather than one case.
It also now has both implicit time integration and forward-mode AD wiring
(tests/test_implicitWaveEquation.py, tests/test_forwardModeWave.py) —
originally scoped out of docs/historic_plans/WAVE_EQUATION_PLAN.md as separate follow-ups, both
landed since.
Writing a case
A Case is a name, a scheme, and a set of hooks over a RunContext. Only
buildSystem is required.
Porting an existing example onto this, or taking a case up to 2D/3D, has a procedure and a set of things that bite: PORTING_EXAMPLES.md.
from warpSPH.runner import Case, RunContext, caseMain, registerCase
def configureScheme(ctx: RunContext) -> None:
"""Mutate ctx.schemeConfig (and ctx.config) before anything is sampled."""
ctx.schemeConfig.gamma = ctx.param('gamma')
def buildSystem(ctx: RunContext):
"""Geometry and particle sampling. Returns a SimulationSystem."""
return sampleSomething(ctx.spec.nx, ctx.config, ctx.schemeConfig,
ctx.SimulationState, ctx.SimulationSystem)
def diagnostics(ctx: RunContext, state) -> dict:
"""Scalars recorded every step; the progress bar, report and tests read these."""
return {'kineticEnergy': ...}
myCase = registerCase(Case(
name='myCase',
scheme='CRKSPH',
description='One line, shown by `warpsph-run` with no arguments.',
buildSystem=buildSystem,
configureScheme=configureScheme,
diagnostics=diagnostics,
defaults=dict(dim=2, nx=128, L=1.0, tLimit=2.0), # CaseSpec overrides
params=dict(gamma=5 / 3), # become --flags
))
if __name__ == '__main__':
caseMain(myCase)
Add the module name to CASE_MODULES in
warpSPH/cases/__init__.py and it is reachable
as warpsph-run myCase.
The full hook set, in call order:
| hook | signature | for |
|---|---|---|
configureScheme |
(ctx) |
scheme config, domain reshaping |
buildSystem |
(ctx) -> system |
required; geometry and sampling |
initialConditions |
(ctx, system) |
velocities/energies; often where dt is fixed |
setupPlot / updatePlot |
(ctx, state) / (ctx, state, handle, step) |
figures |
diagnostics |
(ctx, state) -> dict |
per-step scalars |
postStep |
(ctx, state, step) |
re-impose something after each step |
timestep |
(ctx, state) -> dt |
recompute dt; makes the run time-bounded |
extraData |
(ctx, state) -> dict |
metadata on every exported frame |
ctx.scratch is a free dict for state between hooks. A case may also replace
ctx.spec during setup — Kidder and Sedov only learn their time limit from the
analytic solution — and the loop reads it back.
Most of a case is already written: warpSPH.cases.compressible and
warpSPH.cases.weaklyCompressible carry the setup each family shares, and
warpSPH.cases.plotting turns a list of fields into a setupPlot/updatePlot
pair.
From Python and notebooks
from warpSPHBootstrap import bootstrap
bootstrap(precision='float32') # must precede any warpSPH import
from warpSPH.runner import run
from warpSPH.cases.tgv import tgvCase
result = run(tgvCase, nx=64, nSteps=200, quiet=True)
result.series('kineticEnergy') # one diagnostic across the run
result.diverged, result.wallTime, result.exportPath
Keyword arguments to run override the case defaults, which is what makes a
parameter study a for loop rather than a shell script.
At a lower level, buildScheme returns a frozen SchemeBundle with named
fields — SimulationSystem, SimulationState, SimulationConfig,
SimulationUpdate, stepFunction, exportFunction, importFunction:
from warpSPH import buildConfig, buildScheme, CompressibleSPHScheme
config, integrator = buildConfig(dim=1, dt=1e-3, adaptiveDt=True, cflFactor=0.3)
bundle = buildScheme(CompressibleSPHScheme.CRKSPH)
schemeConfig = bundle.SimulationConfig(gamma=5 / 3)
result = integrator.function(state=state, f=bundle.stepFunction, dt=config.dt,
config=config, schemeConfig=schemeConfig)
Access the bundle by name. It still unpacks positionally for old call sites, but
that ordering is pinned for compatibility, not something to rely on — and
binding SimulationConfig from a positional unpack shadows the global
SimulationConfig that from warpSPH import * provides, which is a trap the
notebooks used to fall into.
Configuration reference
buildConfig(...) returns a SimulationConfig and the matching integrator.
Every option below is also a CaseSpec field, so it is reachable as a CLI flag
of the same name.
Discretisation — dim, nx, L, n_h (neighbours per smoothing length,
converted to targetNeighbors), periodic.
Operators — kernel (Poly6, CubicSpline, QuarticSpline,
QuinticSpline, B7, Wendland2/4/6, Spiky), supportMode (Gather,
Scatter, MeanSymmetric, KernelMeanSymmetric, SuperSymmetric,
PartialSymmetric), gradientMode, laplacianMode, samplingScheme.
The reference dissipation configuration (AV_PLAN, 2026-10-08) of the Monaghan host
(scheme='Monaghan') is its default: the Rosswog (2020) entropy trigger at alpha in [0, 1], the
Price2012_98 operator with C_q = 2 coupled to alpha (beta = 2 alpha, Chen & Nixon 2025), and the
limited midpoint reconstruction of the pair velocity (Frontiere et al. 2017 / García-Senz & Cabezón 2026).
This is the reference SPH dissipation configuration against which future hydrodynamic operators are
compared. A case that names a viscositySwitch gets that switch with its own defaults; CompSPH and
CRKSPH keep their own operators (no switch by default). Evidence:
docs/av/sweep_2026-10-08/, AV_PLAN's status board.
Time integration — integrationScheme selects from 26 schemes in
warpSPHIntegrators (forwardEuler, rungeKutta2/3/4, leapFrog,
velocityVerlet, symplecticEuler, sspRK3, dormandPrince, …), with
dt, adaptiveDt, cflFactor, minDt, maxDt. Leaving dt unset means the
case derives it — compressible samplers from the acoustic CFL, weakly
compressible ones from a target timestep and the sound speed together.
Compressible scheme options, on the scheme config rather than the global one:
ViscositySwitch (Balsara1995, Colagrossi2004, CullenDehnen2010,
CullenHopkins, MorrisMonaghan1997, Rosswog2000, Rosswog2020, Sphenix2022,
Wadsley2017, ReadHayfield2012, NoneSwitch), the pair-velocity reconstruction
(DiffusionParameters.velocityPairPolicy: Raw, Linear, Limited, BalsaraLimited) and the
quadratic-coefficient policy (betaMode: Coupled, Fixed),
AdaptiveSupportScheme (NoScheme, Monaghan, Owen), and EnergyScheme
(equalWork, PdV, diminishing, monotonic, hybrid, CRK).
Package layout
src/
warpSPHBootstrap.py precision/warp setup; must be imported first
warpSPHRun.py the warpsph-run CLI
warpSPH/
runner/ everything generic to a run
case.py the Case hooks and the registry
caseSpec.py the run description, and the argparse it generates
runner.py the step loop
display.py live plot windows and their teardown
report.py the setup banner and the completion report
media.py ffmpeg encoding
cli.py the shared __main__ block
cases/ one module per case, plus what each family shares
compressible.py CRKSPH defaults, energy diagnostics
weaklyCompressible.py banded domain, SDF regions, sound-speed/dt setup
plotting.py field lists -> plot hooks
schemes/ scheme implementations and buildScheme
configurations/ simulation and scheme configuration dataclasses
systems/ state and system containers
modules/ timestep, viscosity, shifting, boundary conditions, …
sample/ particle samplers (regular, shell, per-family)
geometry/ what a sampler is defined in terms of: SDFs, NACA,
ParticleSet/PointCloud, SamplingScheme
regions/ SDF regions and filtering
math/ periodic positions, noise, scatter
utils/ domain description, support radii, timers
caseUtils/ per-case setup helpers shared with the notebooks
io/ HDF5/JSON export, import, parsing and datasets
examples/ notebooks, runnable scripts, sweeps, rendered media
datagen/ dataset generation on top of the same cases
(see datagen/README.md)
scripts/ run_tests.sh (pytest), run_sweep.py (every case,
briefly), check_imports.py (imports resolve),
render_examples.py (regenerate example media),
gradcheck_*.py (adjoint correctness, 15 of them)
tests/ pytest suite
Tests
scripts/run_tests.sh # or plain `pytest`
89 tests, about two minutes once the warp kernel cache is warm; a CUDA device
is required. Most of that is the 15 gradcheck_*.py scripts, which each run in
their own subprocess -- deselecting tests/test_gradcheck_scripts.py brings
the rest in under 30 seconds. The script just wraps pytest, silencing the
third-party warnings that otherwise bury the result; it forwards any extra
arguments (scripts/run_tests.sh -k sod -v). They assert properties rather
than golden numbers — total-energy conservation for Sod, Taylor-Green decay
against the analytic rate, density bounds and gravitational work for the dam
break — plus the runner's own invariants: that every case registers and names
a resolvable scheme, that the spec round-trips through JSON and YAML, and that
the banner, report and --quiet behave.
To exercise every case rather than the three the physics tests cover, sweep them:
scripts/run_sweep.py # every case, 5 steps each, ~4 min
scripts/run_sweep.py --full # every case to its own tLimit (long)
scripts/run_sweep.py --cases sod noh
Each case runs in its own process, sequentially — a diverged run exits
non-zero, and one crashing case must not take the sweep down with it. Logs, a
per-case export tree and a summary.json land in a timestamped
sweeps/sweep_<timestamp>/, and failures are printed with the tail of their
log. Anything after -- is forwarded to every case, so the configs in
examples/sweeps/ compose with it.
After a refactor, the matching check is:
scripts/check_imports.py # every module imports; every import resolves
It imports each module under warpSPH for real, then AST-scans every .py and
notebook cell in the repo for warpSPH* imports and verifies both the module
and the imported symbol exist — which catches function-level and notebook
imports that nothing else executes.
Gallery
Previews and embedded videos, one page per family: compressible · weakly compressible.
All of this media is produced by
scripts/render_examples.py, which re-runs each
example's .py wrapper at its shipped settings and files the GIF, MP4 and
final-frame PNG next to the notebook that references them. These are
full-length runs, not smoke tests, so re-rendering everything takes hours —
--only narrows it, and --list shows what would run and where it would land:
scripts/render_examples.py --list
scripts/render_examples.py --only dambreak ldc
(For "does every case still run", scripts/run_sweep.py is the right tool.)
Compressible
| Case | Notebook | Preview | Video |
|---|---|---|---|
| 01. Sod Shock Tube (1D) | ipynb | MP4 | |
| 02. Linear Wave | ipynb | MP4 | |
| 03. Kidder Isentropic Compression | ipynb | MP4 | |
| 04. Noh Implosion | ipynb | MP4 | |
| 05. Woodward-Colella Double Blastwave | ipynb | MP4 | |
| 06. Sedov-Taylor Blastwave (1D/2D/3D) | ipynb | MP4 | |
| 08. Hydrostatic | ipynb | MP4 | |
| 09. Gresho-Chan Vortex | ipynb | MP4 | |
| 10. Yee Vortex | ipynb | MP4 | |
| 11. Shearing Noh Implosion (2D) | ipynb | MP4 | |
| 12. Kelvin-Helmholtz | ipynb | MP4 | |
| 13. Rayleigh-Taylor | ipynb | MP4 | |
| 14. Triple Point (Equal Spacing) | ipynb | MP4 | |
| 14. Triple Point (Equal Mass) | ipynb | MP4 |
Weakly compressible
| Case | Notebook | Preview | Video |
|---|---|---|---|
| 01. Impact (spheres) | ipynb | MP4 | |
| 01. Impact (squares) | ipynb | MP4 | |
| 03. Rotating Square Patch | ipynb | MP4 | |
| 04. Oscillating Droplet | ipynb | MP4 | |
| 05. Taylor-Green Vortex | ipynb | MP4 | |
| 06. Random Flow (periodic) | ipynb | MP4 | |
| 06. Random Flow (bounded) | ipynb | MP4 | |
| 08. Kolmogorov Flow | ipynb | MP4 | |
| 09. Lid-Driven Cavity | ipynb | MP4 | |
| 10. Moving Obstacle | ipynb | MP4 | |
| 11. Driven Square | ipynb | MP4 | |
| 12. Dam Break | ipynb | MP4 | |
| 13. Open Channel Flow | ipynb | (no render yet) | — |
Precision, and other things that bite
Precision is resolved when warpSPHCore is first imported and cannot be
changed afterwards. Any warpSPH.* import pulls warpSPHCore in transitively,
so the choice has to be made first — which is why bootstrap is a top-level
module rather than part of the package:
from warpSPHBootstrap import bootstrap
runtime = bootstrap(precision='float64') # also runs wp.init(), pins TORCH_CUDA_ARCH_LIST
In a notebook, changing precision means restarting the kernel.
warpsph-run --precision float64 handles the ordering for you;
python -m warpSPH.cases.X cannot, and warns instead of silently running at the
wrong precision.
No GL context (ssh without X forwarding, containers): 2D plots fall back from vispy to matplotlib automatically. If matplotlib also has no interactive backend, the run says so once and keeps writing frames.
No ffmpeg: --video is skipped with a message rather than failing a run
that already produced its frames.
Old .h5 files written before the package renames hold dill-encoded
callables under the previous module names and will not import. They are
regenerable; there is no migration path and none is planned.
Metadata
Release files for warpSPH 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| warpsph-0.6.0.tar.gz | 1.1 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| warpsph-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.2 MB
Release files / warpsph-0.6.0.tar.gz
| Download URL | warpsph-0.6.0.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a5489305557f6bb6f6a2c44c3ad50115e107f97d7b4ba5312bc0c2cc30f2ec4a
|
|
BLAKE2b-256 checksum How to use checksums |
4e5f5200a954d26f85d2d324e578d179a1a0e0adc76937bb6c4ca7e26abeb99e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / warpsph-0.6.0-py3-none-any.whl
| Download URL | warpsph-0.6.0-py3-none-any.whl |
|---|---|
| Size | 1.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
61a57471f495b023e64bbfd9e928ca025c3d2550f11513a557ebb83681f1b048
|
|
BLAKE2b-256 checksum How to use checksums |
2f8cc025e06348cf3197ef8968353013a10e6fa7ad0d447cee78a5bdcdd0272b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|