Architecture¶
Core pipeline¶
NovaPhy follows a Newton-aligned solver-primary pipeline:
graph LR
A[ModelBuilder<br/>mutable] --> B[Model<br/>immutable]
B --> C[State / Control / Contacts<br/>caller-owned buffers]
B --> D[Solver<br/>bound to Model]
C --> E[solver.step<br/>state_in, state_out, control, contacts, dt]
D --> E
E --> C
ModelBuilder— mutable scene description. Add bodies, shapes, joints, articulations, fluid blocks.Model— immutable, baked data. Created bybuilder.finalize(). The sameModelcan be shared across independent solvers and rollout states.State/Control/Contacts— caller-owned runtime buffers allocated from the model viamodel.state(),model.control(), andCollisionPipeline.contacts().Solver— bound to aModelat construction time and stepped throughsolver.step(state_in, state_out, control, contacts, dt).
Canonical Python shape:
import novaphy
model = builder.finalize()
solver = novaphy.solvers.SolverSemiImplicit(
model, novaphy.solvers.SolverSemiImplicit.Config()
) # or SolverXPBD / SolverFeatherstone / SolverVBD / SolverPBF / SolverSPH / SolverIPC
state = model.state()
control = model.control()
collision_pipeline = novaphy.CollisionPipeline(model)
contacts = collision_pipeline.contacts()
collision_pipeline.collide(state, contacts)
solver.step(state, state, control, contacts, dt)
state_in and state_out may alias for in-place stepping. Pass two
distinct state buffers when checkpointing, swapping rollout buffers, or
running parallel evaluations from a shared Model.
Solvers¶
| Solver | Algorithm | Header |
|---|---|---|
SolverSemiImplicit |
Semi-implicit Euler + PGS free bodies | dynamics/semi_implicit/solver_semi_implicit.h |
SolverFeatherstone |
Articulated FK → RNEA → CRBA → Cholesky | dynamics/featherstone/solver_featherstone.h |
SolverXPBD |
XPBD maximal-coordinate | dynamics/xpbd/solver_xpbd.h |
SolverPBF |
Position-Based Fluids + Akinci coupling | fluid/pbf_solver.h |
SolverSPH |
SPH (CPU default, optional CUDA) | fluid/solver_sph.h |
SolverIPC |
IPC via external libuipc submodule (NVIDIA/CoreX GPU paths) | dynamics/ipc/solver_ipc.h |
SolverVBD |
VBD / AVBD primal-dual (CPU SolverBase path) | dynamics/vbd/solver_vbd.h |
Every SolverBase solver implements:
step(state_in, state_out, control, contacts, dt)— Newton-aligned forward dynamics.notify_model_changed(SolverNotifyFlags)— cache invalidation.joint_support()—JointSupportMatrixcapability table.backend_info()—SolverBackendInfo(device, fixed-dt, etc.).
Collision is explicit and lives on CollisionPipeline
or Model.collide, not on the public Python solver binding.
SolverVBD follows the same solver-primary API while using VBD-specific
model attributes and color groups generated by ModelBuilder.color().
See Solver Internal Data Pipeline for the
per-solver scratch / DeviceArray SoA convention.
Free bodies (SolverSemiImplicit)¶
graph LR
A[Broadphase<br/>SAP] --> B[Narrowphase]
B --> C[Sequential Impulse<br/>PGS]
C --> D[Integrate]
Articulated bodies (SolverFeatherstone)¶
graph LR
A[FK] --> B[RNEA<br/>bias forces]
B --> C[CRBA<br/>mass matrix]
C --> D[Cholesky<br/>solve]
D --> E[Integrate]
PBF fluids (SolverPBF)¶
graph LR
A[Predict] --> B[Neighbor search<br/>spatial hash]
B --> C[Density constraint<br/>iterative]
C --> D[XSPH + Vorticity]
D --> E[Update]
Collision system¶
Broadphase¶
CollisionPipeline exposes three public modes:
- Explicit — uses the model's precomputed shape contact pairs.
- SAP — sweep-and-prune AABB sorting.
- NXN / all-pairs — brute-force AABB checks for small scenes and tests.
Narrowphase¶
Dispatches to specialized collision-pair algorithms:
- Sphere-sphere, sphere-plane, sphere-box.
- Box-plane, box-box (analytic contacts).
- contact core for general convex shapes.
- Cylinder collision pairs.
Contact convention¶
- Normal direction:
body_a → body_b(positive impulse separates). - Plane shapes use
body_index = -1(world-owned, always static).
Profiling¶
Optional. Wrap a step in the thread-local context manager (mirrors
Newton's event_scope / EventTracer):
monitor = novaphy.PerformanceMonitor()
monitor.enabled = True
monitor.trace_enabled = True
for _ in range(steps):
with monitor.scoped():
solver.step(state, state, control, contacts, dt)
for stat in monitor.phase_stats():
print(stat.name, stat.avg_ms)
monitor.write_trace_json("trace.json")
Outside the with block every C++ phase scope is a zero-cost no-op, so
solvers pay nothing when profiling is not in use.
Data types¶
NovaPhy uses float32 exclusively — never double.
| Type | Definition |
|---|---|
Scalar |
float |
Vec3f |
Eigen::Vector3f |
Mat3f |
Eigen::Matrix3f |
Quatf |
Eigen::Quaternionf |
VecXf |
Eigen::VectorXf |
MatXf |
Eigen::MatrixXf |
Spatial algebra helpers use the Featherstone convention:
[angular; linear] for 6D vectors. The flat rigid-body state views use
state.body_qd == [linear; angular] and state.body_f == [force; torque].
File organization¶
NovaPhy/
├── novaphy/include/ # Public C++ headers
│ ├── math/ # Vec3f, Mat3f, Quatf, spatial algebra
│ ├── core/ # Body, Shape, Joint, Model, ModelBuilder, Contacts
│ ├── collision/ # SAP, BVH, narrowphase, contact core
│ ├── dynamics/ # SolverBase, integrator
│ │ ├── semi_implicit/ # SolverSemiImplicit + free-body PGS
│ │ ├── featherstone/ # SolverFeatherstone, FK / RNEA / CRBA
│ │ ├── xpbd/ # SolverXPBD
│ │ ├── vbd/ # SolverVBD (CPU SolverBase path)
│ │ └── ipc/ # SolverIPC over libuipc (CUDA)
│ ├── fluid/ # SolverPBF, SolverSPH, SPH kernels, Akinci boundary
│ ├── io/ # URDF, MJCF, OpenUSD, scene builder
│ └── sim/ # SimState, PerformanceMonitor
├── novaphy/src/ # C++ implementations (mirrors novaphy/include/)
├── python/
│ ├── novaphy/ # Python package (sensors, actuators, solvers, viz)
│ └── bindings/ # pybind11 binding files
│ └── tests/ # pytest files
├── novaphy/tests/ # C++ unit tests
├── cmake/cmake_project/ # CMake integration sample projects
└── python/demos/ # Polyscope demo scripts