Skip to content

Fluid Simulation

Overview

NovaPhy implements Position Based Fluids (PBF) following Macklin & Muller 2013, with two-way rigid-fluid coupling via the Akinci et al. 2012 boundary particle method.

PBF Solver

The PBF pipeline:

  1. Predict — Apply gravity, predict particle positions
  2. Neighbor Search — Spatial hash grid for O(1) average neighbor lookup
  3. Density Constraint — Iteratively project particles to satisfy rest density
  4. Corrections — XSPH viscosity and vorticity confinement
  5. Update — Finalize positions and velocities

Creating a Fluid Simulation

import numpy as np
import novaphy

builder = novaphy.ModelBuilder()
builder.add_ground_plane(y=0.0)

# Define a fluid block
fluid_block = novaphy.FluidBlockDef()
fluid_block.lower = np.array([0.0, 0.0, 0.0])
fluid_block.upper = np.array([1.0, 1.0, 1.0])
fluid_block.particle_spacing = 0.05
positions = novaphy.generate_fluid_block(fluid_block)
mass = fluid_block.rest_density * fluid_block.particle_spacing ** 3
radius = fluid_block.particle_spacing * 0.5
builder.add_particles(
    positions,
    [fluid_block.initial_velocity] * len(positions),
    [mass] * len(positions),
    [radius] * len(positions),
)

# Configure PBF solver
pbf_config = novaphy.solvers.SolverPBF.Config()
pbf_config.kernel_radius = 4.0 * 0.05  # Kernel radius = 4x spacing
pbf_config.solver_iterations = 4

model = builder.finalize()

# SolverPBF is a SolverBase subclass.
solver = novaphy.solvers.SolverPBF(model, pbf_config)

state    = model.state()
solver.initialize_state(state)
collision_pipeline = novaphy.CollisionPipeline(model)
contacts = collision_pipeline.contacts()

for _ in range(500):
    collision_pipeline.collide(state, contacts)
    solver.step(state, state, None, contacts, 1.0 / 120.0)

SPH Kernels

NovaPhy provides standard SPH kernel functions:

  • Poly6 kernel — for density estimation
  • Spiky kernel gradient — for pressure forces
  • Viscosity kernel Laplacian — for viscosity forces

Rigid-Fluid Coupling (Akinci)

Two-way coupling between rigid bodies and fluid particles:

  • Rigid body surfaces are sampled with boundary particles
  • Boundary particles contribute to fluid density computation
  • Particle / rigid contacts are generated into the unified Contacts.soft_contact_* channel and applied to rigid bodies / articulation links during the fluid step
# Two-stage Newton-style chain: SolverPBF (fluid) -> SolverSemiImplicit (rigid).
# Coupling forces flow through the shared Contacts aggregate.
pbf_solver   = novaphy.solvers.SolverPBF(model, pbf_config)
rigid_solver = novaphy.solvers.SolverSemiImplicit(model, novaphy.solvers.SolverSemiImplicit.Config())

state    = model.state()
pbf_solver.initialize_state(state)
control  = model.control()
collision_pipeline = novaphy.CollisionPipeline(model)
contacts = collision_pipeline.contacts()

for _ in range(500):
    collision_pipeline.collide(state, contacts)
    pbf_solver.step(state, state, None, contacts, 1.0 / 120.0)
    collision_pipeline.collide(state, contacts)
    rigid_solver.step(state, state, control, contacts, 1.0 / 120.0)

Demos

Demo Description
demo_dam_break.py Rectangular fluid block collapse
demo_fluid_box.py ~8000 particles sloshing in a moving box
demo_ball_in_water.py Ball dropping into water
demo_fluid_coupling.py Boxes and spheres splashing into water