Skip to main content

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

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.

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)

Source distribution for warpSPH 0.6.0
File Size Uploaded
warpsph-0.6.0.tar.gz 1.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for warpSPH 0.6.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.5

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page