Skip to content

novaphy.solvers.SolverPBF

Position Based Fluids (PBF) solver following Macklin & Muller 2013, with two-way rigid-fluid coupling via Akinci-style boundary contacts. SolverPBF is a Newton-aligned SolverBase subclass.

Particle / rigid contacts are generated by CollisionPipeline.collide into the Contacts.soft_contact_* channel. PBF consumes that channel; it does not create a public Contacts.particle object channel.

Pipeline

graph LR
    A[Predict] --> B[Neighbor search<br/>spatial hash]
    B --> C[Density constraint<br/>iterative]
    C --> D[XSPH viscosity<br/>Vorticity confinement]
    D --> E[Update positions<br/>and velocities]

Steps:

  1. Predict — apply gravity and predict particle positions.
  2. Neighbor search — spatial hash grid.
  3. Density constraint — iteratively project particles to satisfy rest density.
  4. Corrections — XSPH viscosity and vorticity confinement.
  5. Update — recover velocities and finalize positions.

Constructor

novaphy.solvers.SolverPBF(
    model: Model,
    config: PBFConfig = PBFConfig(),
    fluid_boundary_extent: float = 1.0,
)
Parameter Description
model Required. Model with particles added via ModelBuilder.add_particles.
config Optional PBFConfig instance.
fluid_boundary_extent Half-extent used when sampling plane boundary particles.

Lifecycle

SolverPBF requires a one-time state initialization before stepping:

solver = novaphy.solvers.SolverPBF(model, pbf_config)
state  = model.state()
solver.initialize_state(state)

Two-Stage Rigid + Fluid Chain

Run collision explicitly before each solver stage that needs contact data:

import novaphy

pbf_solver   = novaphy.solvers.SolverPBF(model, pbf_config)
rigid_solver = novaphy.solvers.SolverSemiImplicit(model)

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

dt = 1.0 / 120.0
for _ in range(500):
    pipeline.collide(state, contacts)
    pbf_solver.step(state, state, None, contacts, dt)

    pipeline.collide(state, contacts)
    rigid_solver.step(state, state, control, contacts, dt)

Pure-fluid simulations can pass contacts=None when no particle / rigid coupling is needed.

Configuration Highlights

Tune via PBFConfig:

Field Description
kernel_radius SPH kernel support radius. Common: 4.0 * particle_spacing.
solver_iterations Density-constraint iterations per step.
rest_density Target rest density [kg / m^3].
xsph_viscosity XSPH viscosity coefficient.
vorticity_epsilon Vorticity confinement strength.

Example

import numpy as np
import novaphy

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

block = novaphy.FluidBlockDef()
block.lower = np.array([0, 0, 0], dtype=np.float32)
block.upper = np.array([1, 1, 1], dtype=np.float32)
block.particle_spacing = 0.05

positions = novaphy.generate_fluid_block(block)
mass = block.rest_density * block.particle_spacing ** 3
radius = block.particle_spacing * 0.5
builder.add_particles(
    positions,
    [block.initial_velocity] * len(positions),
    [mass] * len(positions),
    [radius] * len(positions),
)

model = builder.finalize()
cfg = novaphy.solvers.PBFConfig()
cfg.kernel_radius = 4.0 * block.particle_spacing
cfg.solver_iterations = 4

solver = novaphy.solvers.SolverPBF(model, cfg)
state = model.state()
solver.initialize_state(state)
pipeline = novaphy.CollisionPipeline(model)
contacts = pipeline.contacts()

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

When To Use

Scenario Recommendation
Visually plausible water / dam break / sloshing PBF is the right tradeoff.
Two-way rigid-fluid coupling Pair with a rigid solver through the shared Contacts aggregate.
Physically rigorous fluid pressure Consider SolverSPH with appropriate config.

Demos

Demo What it shows
python/demos/demo_dam_break.py Rectangular fluid block collapse.
python/demos/demo_fluid_box.py Particles sloshing in a moving box.
python/demos/demo_ball_in_water.py Ball dropping into water.
python/demos/demo_fluid_coupling.py Boxes and spheres splashing into water.

See Also