An interactive 2D soil–fluid interaction sandbox with a live science layer, built with pygame, pymunk and numpy. Pour sand, gravel, soil, water and oil into a tank with a mouse-driven nozzle; two coupled physics solvers simulate the interactions while a dashboard records, charts and exports everything (infiltration, wetting front, porosity, flow fields, …).
Two solvers, coupled every frame (details & limitations in
docs/PHYSICS.md):
- Granular (Sand, Gravel, Soil) — rigid-body dynamics (pymunk): area-based mass in kg/m³, real gravity, 6× sub-stepping, spatial hashing, per-material friction/elasticity/damping.
- Fluids (Water, Oil) — multiphase Position-Based Fluids (Macklin & Müller 2013): incompressibility enforced by an iterative density constraint, so liquids are calm and genuinely incompressible (settled water holds its rest density within ±9 % and comes to rest). Per-phase rest density makes oil float on water — they never blend.
- Two-way coupling — grains act as fluid boundary particles (liquid ponds on a soil bed and infiltrates its pore space), and fluid pushes back: Archimedes buoyancy + drag mean light grains float, dense grains sink, and currents carry grains.
- Field overlays (
O): saturation heat-map of the bed (dry brown → wet blue — watch the wetting front advance) and a flow-field quiver plot of fluid velocity. - Live metrics + charts: particle counts, mean speed per phase, infiltration %, wetting-front depth, bed porosity, and a moisture-vs-depth profile.
- Logging: every metric sampled at 4 Hz to
sim_log.csv. - Export report (
E): timestamped PNG snapshot of the full lab.
python3 -m pip install -r requirements.txt
python3 main.pyUses pygame-ce (required by
pygame_gui0.6+). Do not install the legacypygamepackage alongside it — they share a namespace and clash.
Headless self-test (steps both solvers, prints physics diagnostics):
python3 main.py --selftest 300Regenerate the documentation screenshots:
python3 scripts/make_screenshots.py| Action | Control |
|---|---|
| Pour material | Hold left-click in the tank (at the cursor) |
| Pick material | Material dropdown (Sand/Gravel/Soil/Water/Oil) |
| Material properties | Density, friction, elasticity, grain size (granular) / density, viscosity, cohesion (fluids) — auto-set per material, freely adjustable |
| Pour rate / nozzle | sliders |
| Field overlays | Overlay button or O |
| Colour mode | Material / speed heat-map toggle |
| Export report | Export button or E |
| Reset tank | Reset button or R |
| Quit | window close or Esc |
- Pour a Soil bed; let it settle. Press O → Saturation.
- Pour Water on top: watch it pond, then infiltrate — the saturation field turns blue downward and the Infiltration & wetting front chart records the curve.
- Add Oil: it stratifies above the water.
- Press E to export the report; reset, rebuild the bed with a larger Grain size, repeat — compare infiltration curves (grain size vs permeability).
ParticlesInteraction/
├── main.py # the whole application
├── theme.json # dark UI theme (pygame_gui)
├── requirements.txt
├── docs/
│ ├── PHYSICS.md # models, assumptions, tuned constants, limits
│ └── screenshots/
├── scripts/
│ └── make_screenshots.py # regenerate docs/screenshots headlessly
├── assets/ # logo, background
└── sim_log.csv # generated at runtime (git-ignored)
This is a qualitatively correct interactive demonstrator — the numerical
methods are real (PBF, rigid-body DEM-lite) but several coupling constants are
tuned for plausibility rather than derived (documented in
docs/PHYSICS.md). Planned path to a quantitative tool:
- Verification & SI units — validation suite vs analytical benchmarks (hydrostatics, dam break, angle of repose, Kozeny–Carman permeability).
- GPU solver core (Taichi) — DFSPH + proper DEM contacts, 100× particles, 3D-capable.
- Validated science modules — erosion/corrosion, sediment transport (Shields), unsaturated flow (Green–Ampt).
- Platform features — experiment configs, parameter sweeps, batch runs, reproducible reports.


