Architecture¶
NovaPhy separates immutable scene topology from caller-owned runtime data and solver scratch. This is the same solver-primary shape used by Newton:
There is no World orchestrator. The application decides when to collide,
step, profile, render, record, reset, or capture a CUDA graph.
Core pipeline¶
flowchart LR
B[ModelBuilder<br/>mutable authoring] --> M[Model<br/>immutable topology]
M --> SI[SimState in]
M --> SO[SimState out]
M --> C[Control]
M --> P[CollisionPipeline]
P --> K[Contacts]
M --> S[Solver]
SI --> P
SI --> S
SO --> S
C --> S
K --> S
S --> R[Next state]
ModelBuildercollects bodies, shapes, joints, articulations, particles, materials, sites, and imported scene data.Modelis baked bybuilder.finalize(). Topology is immutable and may be shared across independent rollouts.SimState,Control, andContactshold runtime data. The caller owns them.CollisionPipelineperforms broadphase/narrowphase and writes into a pipeline-sizedContactsaggregate.Solveris bound to a model and advances through the five-argument contract.
import novaphy
model = builder.finalize()
solver = novaphy.solvers.SolverSemiImplicit(model)
state_in = model.state()
state_out = model.state()
control = model.control()
pipeline = novaphy.CollisionPipeline(model)
contacts = pipeline.contacts()
state_in.clear_forces()
pipeline.collide(state_in, contacts)
solver.step(state_in, state_out, control, contacts, dt)
state_in and state_out may alias for in-place stepping. Use distinct
buffers for checkpointing, observation-before-action pipelines, or state
comparisons.
Ownership and lifetime¶
| Object | Mutable? | Typical lifetime | Share across rollouts? |
|---|---|---|---|
ModelBuilder |
Yes | Scene authoring only | No |
Model |
No after finalize() |
Application / task lifetime | Yes |
Solver |
Internal caches mutate | One per execution lane or policy | Usually no |
SimState |
Yes | Rollout / checkpoint lifetime | No |
Control |
Yes | Rollout lifetime | No |
CollisionPipeline |
Internal caches mutate | Rollout or worker lifetime | Usually no |
Contacts |
Yes | Reused each collision step | No |
Solver scratch follows the Solver Internal Data Pipeline conventions: long-lived allocations are solver-owned, capacity growth happens outside hot loops where possible, and CPU/CUDA storage has the same logical shape.
Solver capability matrix¶
All solver classes live under novaphy.solvers. Runnable SolverBase
implementations accept
step(state_in, state_out, control, contacts, dt).
| Solver | Domain | CPU | Optional accelerator | Status |
|---|---|---|---|---|
SolverSemiImplicit |
Free rigid bodies, PGS contact | ✓ | — | Runnable |
SolverFeatherstone |
Articulated dynamics | ✓ | CUDA | Runnable |
SolverXPBD |
Maximal-coordinate constraints | ✓ | — | Runnable |
SolverMuJoCo |
Native MuJoCo-style robot dynamics | ✓ | CUDA | Runnable |
SolverVBD |
Rigid / soft VBD and AVBD | ✓ | CUDA or DLAN | Runnable |
SolverPBF |
Position Based Fluids | ✓ | — | Runnable |
SolverSPH |
Smoothed Particle Hydrodynamics | — | CUDA | Optional build |
SolverLBM |
Sparse-block Lattice Boltzmann fluids | — | CUDA | Optional build |
SolverIPC |
IPC contact through libuipc | — | NVIDIA CUDA or CoreX | Optional build |
SolverMPM |
Material Point Method parity surface | — | — | Scaffold |
The SolverMPM scaffold exposes metadata for API parity but its step()
method raises. Check it with novaphy.is_scaffold(...); do not select it for
a simulation.
Every solver also exposes capability metadata:
notify_model_changed(SolverNotifyFlags)invalidates supported caches.joint_support()returns aJointSupportMatrix.- Python's read-only
backend_infoproperty describes device, graph-capture, fixed-timestep, and scaffold properties. The corresponding C++ interface is thebackend_info()method.
Choosing a rigid/articulated path¶
- Start with
SolverSemiImplicitfor free rigid bodies and simple contact. - Use
SolverFeatherstonefor generalized-coordinate articulated systems, analytical dynamics evaluators, or its optional CUDA backend. - Use
SolverXPBDfor maximal-coordinate constraint behavior. - Use
SolverMuJoCofor its native MuJoCo-style constraint/integrator path, robot grids, and optional CUDA batching. See MuJoCo-style Solver. - Use
SolverVBDfor VBD / AVBD rigid or deformable workflows. - Use optional
SolverIPCwhen IPC's build and hardware requirements match the application.
Fluid and coupled paths¶
SolverPBF and SolverSPH use external SimState particle buffers.
Two-way PBF/rigid coupling is deliberately a user-level chain:
The shared Contacts aggregate carries coupling data. See
Fluid Simulation and
Migration: fluid coupling.
SolverLBM follows the same five-argument step signature but owns its
sparse-block fluid state internally. Its numerical path requires
NOVAPHY_WITH_LBM_CUDA=ON; construct the solver and inspect
solver.has_cuda_backend before seeding or stepping it.
Collision system¶
Collision is explicit and model-bound for most solvers:
pipeline = novaphy.CollisionPipeline(model)
contacts = pipeline.contacts()
pipeline.collide(state, contacts)
Do this before a solver step whenever NovaPhy contact constraints should be
enforced. For those solvers, contacts=None is valid for a deliberately
contact-free articulated step, such as a procedural cart-pole rollout.
SolverIPC is the exception: libuipc performs collision internally and
ignores the NovaPhy Contacts argument.
Broadphase¶
The public pipeline supports:
- Explicit pairs precomputed from model filtering data.
- SAP (sweep and prune) over shape AABBs.
- NXN / all-pairs checks for small scenes and tests.
Narrowphase¶
Specialized primitive pairs cover spheres, planes, boxes, capsules, cylinders, heightfields, meshes, and the general contact core used by current shape paths. Consult the Geometry API rather than assuming every shape pair has identical manifold behavior.
Contact convention¶
- Rigid contact normals point from
body_atobody_b. A positive normal impulse separates the pair. - Plane shapes are world-owned and use
body_index = -1.
Multi-environment layer¶
ModelBuilder.replicate(...) appends independent world slices to one model.
MultiEnvRunner is a convenience layer over a solver-bound model:
flowchart LR
E[Source ModelBuilder] --> R[replicate N worlds]
R --> M[One immutable Model]
M --> S[One Solver]
S --> X[MultiEnvRunner]
X --> A[Per-environment actions / resets / done flags]
It owns convenient state/control/contact buffers and episode bookkeeping, but
it is not a replacement World object and does not change the canonical
solver API. See Multi-environment Simulation.
Driver-layer services¶
Services that used to be tempting to hide inside a world object remain explicit:
- Profiling: wrap only the desired work in
with monitor.scoped(): .... - Viewer: call
begin_frame,log_state, andend_framethrough anovaphy.viewerbackend. - Picking: viewer input writes forces into the caller-owned state/control path.
- CUDA graph capture: warm and capture at the driver layer after model, state, contacts, and solver scratch are allocated.
- Fluid/rigid coupling: compose solver steps in the application loop.
This makes timing and ownership visible to RL runners, robotics applications, and testing harnesses.
Data conventions¶
NovaPhy engine numerics are float32. Use np.float32 for numeric arrays
crossing the Python boundary.
| Surface | Layout |
|---|---|
Internal novaphy::SpatialVector |
[linear; angular] |
Python-facing SimState.body_qd |
[linear; angular] |
Python-facing SimState.body_f |
[force; torque] |
Free-joint joint_qd |
[linear; angular] |
Free-joint Control.joint_f |
[force; torque] |
Free-joint coordinates are
[px, py, pz, qx, qy, qz, qw]; their six velocity entries are
[vx, vy, vz, wx, wy, wz].
File organization¶
NovaPhy/
├── novaphy/
│ ├── include/
│ │ ├── collision/
│ │ ├── core/
│ │ ├── dynamics/
│ │ │ ├── featherstone/
│ │ │ ├── ipc/
│ │ │ ├── mujoco/
│ │ │ ├── semi_implicit/
│ │ │ ├── vbd/
│ │ │ └── xpbd/
│ │ ├── fluid/
│ │ ├── io/
│ │ ├── math/
│ │ └── sim/
│ ├── src/ # Mirrors include/
│ └── tests/ # C++ tests
├── python/
│ ├── bindings/ # pybind11 bindings
│ ├── demos/ # Runnable examples and assets
│ ├── novaphy/
│ │ ├── ik/
│ │ ├── solvers/
│ │ └── viewer/
│ └── tests/ # pytest suite
├── docs/ # MkDocs source
└── cmake/cmake_project/ # CMake integration examples