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:
- Predict — apply gravity and predict particle positions.
- Neighbor search — spatial hash grid.
- Density constraint — iteratively project particles to satisfy rest density.
- Corrections — XSPH viscosity and vorticity confinement.
- 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. |