Skip to content

novaphy.solvers.SolverBase

Newton-aligned base class for every NovaPhy SolverBase subclass. Defines the canonical 5-argument forward-dynamics contract plus notification and introspection hooks.

class SolverBase:
    def __init__(self, model: Model): ...
    def step(self, state_in: SimState, state_out: SimState,
             control: Control | None, contacts: Contacts | None,
             dt: float) -> None: ...
    def notify_model_changed(self, flags: SolverNotifyFlags) -> None: ...
    def joint_support(self) -> JointSupportMatrix: ...
    def backend_info(self) -> SolverBackendInfo: ...

Construction

The model argument is mandatory and positional. There is no settings= kwarg — solver-specific configuration objects (PBFConfig, IPCConfig, …) are passed where they apply, and shared settings are tuned post-construction via solver.settings.

solver = novaphy.solvers.SolverSemiImplicit(model)

Step Contract

solver.step(state_in, state_out, control, contacts, dt)
Argument Description
state_in Input SimState. May alias state_out for in-place stepping.
state_out Output SimState. After return, holds the integrated state.
control Control (or None to use the defaults baked into Model).
contacts Contacts aggregate produced by CollisionPipeline.collide or Model.collide. Passing None runs without contact constraints.
dt Time step in seconds. Some backends (e.g. SolverIPC) require a fixed dt matching their config.

Collision detection is explicit. Populate contacts before stepping when the solver should enforce rigid / soft-point contacts:

pipeline = novaphy.CollisionPipeline(model, broad_phase="sap")
contacts = pipeline.contacts()

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

Aliasing state_in and state_out lets the same buffer be advanced in-place. Pass distinct buffers when the input must be preserved:

state_a = model.state()
state_b = model.state()
pipeline = novaphy.CollisionPipeline(model)
contacts = pipeline.contacts()

for _ in range(steps):
    pipeline.collide(state_a, contacts)
    solver.step(state_a, state_b, control, contacts, dt)
    state_a, state_b = state_b, state_a

Other Methods

Method Purpose
notify_model_changed(flags) Invalidate cached data after mutating Model properties (gravity, drives, filters). Pass an SolverNotifyFlags bitmask.
joint_support() Returns a JointSupportMatrix describing which joint types and joint properties this solver actually enforces.
backend_info() Returns a SolverBackendInfo describing device, fixed-dt requirement, and state ownership.

Properties

Property Description
model The constructor-bound Model. Read-only Newton-aligned attribute.
settings Mutable per-solver settings struct exposed for post-construction tuning.

Multi-instance / Multi-Env Sharding

Solvers carry no per-instance state of their own; model.state() returns a fresh buffer per call. Multi-env rollouts are therefore N independent (SimState, SolverBase) pairs constructed from the same Model:

solver_per_env = [novaphy.solvers.SolverSemiImplicit(model) for _ in range(n_envs)]
state_per_env  = [model.state() for _ in range(n_envs)]
ctrl_per_env   = [model.control() for _ in range(n_envs)]
pipe_per_env   = [novaphy.CollisionPipeline(model, broad_phase="sap") for _ in range(n_envs)]
cont_per_env   = [pipe.contacts() for pipe in pipe_per_env]

for solver, s, c, pipe, k in zip(solver_per_env, state_per_env,
                                  ctrl_per_env, pipe_per_env, cont_per_env):
    pipe.collide(s, k)
    solver.step(s, s, c, k, dt)

Subclasses

See novaphy.solvers for the full list of solver classes. NovaPhy currently ships:

SolverSemiImplicit, SolverFeatherstone, SolverXPBD, SolverPBF, SolverSPH, SolverIPC (real implementations) plus SolverMPM, SolverLBM (scaffolds).

See Also