Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4f8f617
new lib design
jvonmuralt Apr 14, 2026
2d8eefd
formatting and types
jvonmuralt Apr 15, 2026
e23b3ed
renames
jvonmuralt Apr 15, 2026
9738c81
cleanup
jvonmuralt Apr 15, 2026
ca82871
cleanup
jvonmuralt Apr 15, 2026
79cc869
cleanup
jvonmuralt Apr 15, 2026
36437b5
comments
jvonmuralt Apr 16, 2026
08541f0
add documentation and minor restructuring
jvonmuralt Apr 16, 2026
48810f5
improve documentation
jvonmuralt Apr 16, 2026
74112db
cleanup, refactor of position based clamping
jvonmuralt Apr 16, 2026
2b56eef
cleanup
jvonmuralt Apr 16, 2026
997900c
refactor delay to allow for randomization of delay
jvonmuralt Apr 16, 2026
387ef13
delay refactor
jvonmuralt Apr 17, 2026
875284f
add pos and frc optional indices
jvonmuralt Apr 17, 2026
cce0824
bring scales to the mlp
jvonmuralt Apr 17, 2026
d2a885d
cleanup
jvonmuralt Apr 17, 2026
a067fe4
restructure tests
jvonmuralt Apr 17, 2026
206f624
comments
jvonmuralt Apr 20, 2026
2f32b38
refactor
jvonmuralt Apr 21, 2026
3ef0178
fix dc motor
jvonmuralt Apr 21, 2026
f3d81ae
rename, improve docs and errors
jvonmuralt Apr 22, 2026
9a55c51
fix
jvonmuralt Apr 22, 2026
1af4b3e
Merge remote-tracking branch 'origin/main' into actuators-move
jvonmuralt Apr 22, 2026
1f5efb3
fix naming
jvonmuralt Apr 22, 2026
081a5a0
cleanup
jvonmuralt Apr 22, 2026
39b28aa
cleanup
jvonmuralt Apr 22, 2026
5359884
add schema, address comments
jvonmuralt Apr 23, 2026
3deb9c2
Merge remote-tracking branch 'origin/main' into actuators-move
jvonmuralt Apr 23, 2026
ce2f3cb
remining comments and docs fixes
jvonmuralt Apr 23, 2026
9dc2c8c
fix year
jvonmuralt Apr 23, 2026
7978ad8
minor
jvonmuralt Apr 23, 2026
3a7dc02
minor
jvonmuralt Apr 23, 2026
096c598
minor
jvonmuralt Apr 23, 2026
4d2c21a
minor
jvonmuralt Apr 23, 2026
ccc409d
comments
jvonmuralt Apr 23, 2026
08d0f95
comments
jvonmuralt Apr 23, 2026
74d4084
raise on unsupported target joints
jvonmuralt Apr 23, 2026
42fd7d8
add a 1-DOF guard in import_usd.py
jvonmuralt Apr 23, 2026
381c53c
remove box
jvonmuralt Apr 23, 2026
c4e9d2e
remove box
jvonmuralt Apr 23, 2026
03fe947
fix
jvonmuralt Apr 23, 2026
0ba0da3
comments
jvonmuralt Apr 23, 2026
974b6c6
add NotImplementedError
jvonmuralt Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

- Add composable actuator subsystem with pluggable `Controller` (`ControllerPD`, `ControllerPID`, `ControllerNeuralMLP`, `ControllerNeuralLSTM`), `Clamping` (`ClampingMaxEffort`, `ClampingDCMotor`, `ClampingPositionBased`), and `Delay` components; supports per-DOF delays, CUDA graph capture, and masked environment reset
- Add heatmap rendering for scalar arrays logged through `ViewerGL.log_array()`
- Add `SolverXPBD.update_contacts()` to populate `contacts.force` with per-contact spatial forces (linear force and torque) derived from XPBD constraint impulses
- Raise process priority automatically in `--benchmark` mode for more stable measurements; add `--realtime` for maximum priority.
Expand Down Expand Up @@ -124,6 +125,8 @@
- Add optional `state` parameter to `SolverBase.update_contacts()` to align the base-class signature with Kamino and MuJoCo solvers
- Use `Literal` types for `SolverImplicitMPM.Config` string fields with fixed option sets (`solver`, `warmstart_mode`, `collider_velocity_mode`, `grid_type`, `transfer_scheme`, `integration_scheme`)
- Migrate `wp.array(dtype=X)` type annotations to `wp.array[X]` bracket syntax (Warp 1.12+).
- Replace `ModelBuilder.add_actuator(actuator_class, input_indices=..., output_indices=..., **kwargs)` with `ModelBuilder.add_actuator(controller_class, index=..., clamping=[...], delay_steps=..., pos_index=..., **ctrl_kwargs)` where each call registers a single DOF
- Change `ArticulationView.get_actuator_parameter(actuator, name)` and `set_actuator_parameter(actuator, name, values)` to require a `component` argument identifying the owning `Controller`, `Clamping`, or `Delay` instance: `get_actuator_parameter(actuator, actuator.controller, "kp")`
- Align articulated `State.body_qd` / FK / IK / Jacobian / mass-matrix linear velocity with COM-referenced motion. If you were comparing `body_qd[:3]` against finite-differenced body-origin motion, recover origin velocity via `v_origin = v_com - omega x r_com_world`. Descendant `FREE` / `DISTANCE` `joint_qd` remains parent-frame and `joint_f` remains a world-frame COM wrench.

### Deprecated
Expand All @@ -142,6 +145,7 @@
- Deprecate `ModelBuilder.add_shape_ellipsoid()` parameters `a`, `b`, `c` in favor of `rx`, `ry`, `rz`
- Deprecate passing a `Gaussian` as the second positional argument to `ModelBuilder.add_shape_gaussian()`; use the `gaussian=` keyword argument instead
- Deprecate `SensorTiledCamera.utils.assign_random_colors_per_world()` and `assign_random_colors_per_shape()` in favor of per-shape colors via `ModelBuilder.add_shape_*(color=...)`
- Deprecate `newton-actuators` package dependency; all actuator functionality is now built into `newton.actuators`. The dependency is kept for backward compatibility and will be removed in a future release; migrate imports from `newton_actuators` to `newton.actuators`

### Removed

Expand Down
2 changes: 2 additions & 0 deletions docs/api/newton.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ newton
.. toctree::
:hidden:

newton_actuators
newton_geometry
newton_ik
newton_math
Expand All @@ -22,6 +23,7 @@ newton

.. rubric:: Submodules

- :doc:`newton.actuators <newton_actuators>`
- :doc:`newton.geometry <newton_geometry>`
- :doc:`newton.ik <newton_ik>`
- :doc:`newton.math <newton_math>`
Expand Down
45 changes: 45 additions & 0 deletions docs/api/newton_actuators.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.. SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers
.. SPDX-License-Identifier: CC-BY-4.0

newton.actuators
================

GPU-accelerated actuator models for physics simulations.

This module provides a modular library of actuator components — controllers,
clamping, and delay — that compute joint effort from simulation state and
control targets. Components are composed into an :class:`Actuator` instance
and registered with :meth:`~newton.ModelBuilder.add_actuator` during model
construction.

.. py:module:: newton.actuators
.. currentmodule:: newton.actuators

.. rubric:: Classes

.. autosummary::
:toctree: _generated
:nosignatures:

Actuator
ActuatorParsed
Clamping
ClampingDCMotor
ClampingMaxEffort
ClampingPositionBased
ComponentKind
Controller
ControllerNeuralLSTM
ControllerNeuralMLP
ControllerPD
ControllerPID
Delay

.. rubric:: Functions

.. autosummary::
:toctree: _generated
:signatures: long

parse_actuator_prim
register_actuator_component
267 changes: 267 additions & 0 deletions docs/concepts/actuators.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
.. SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers
Comment thread
jvonmuralt marked this conversation as resolved.
.. SPDX-License-Identifier: CC-BY-4.0

.. currentmodule:: newton.actuators

Actuators
=========

.. warning::

The actuator API is **experimental** and may change in future releases.
Feedback is welcome — please file issues or discussion threads.

Actuators provide composable implementations that read physics simulation
state, compute effort, and **accumulate** (scatter-add) the effort into
control arrays for application to the simulation. The caller must zero the
output array before stepping actuators each frame. The simulator does not
need to be part of Newton: actuators are designed to be reusable anywhere the
caller can provide state arrays and consume effort.

Each :class:`Actuator` instance is **vectorized**: a single actuator object
operates on a batch of DOF indices in global state and control arrays, allowing
efficient integration into RL workflows with many parallel environments.

The goal is to provide canonical actuator models with support for
**differentiability** and **graphable execution** where the underlying
controller implementation supports it. Actuators are designed to be easy to
customize and extend for specific actuator models.

Architecture
------------

An actuator is composed from three building blocks, applied in this order:

.. code-block:: text

Actuator
├── Delay (optional: delays command inputs by N actuator timesteps)
├── Controller (control law that computes raw effort)
└── Clamping[] (clamps raw effort based on motor-limit modeling)
├── ClampingMaxEffort (±max_effort symmetric clamp)
├── ClampingDCMotor (velocity-dependent saturation)
└── ClampingPositionBased (position-dependent lookup table)

**Delay**
Optionally delays command inputs (control targets and feedforward terms)
by *N* actuator timesteps before they reach the controller, modeling
communication or processing latency. The delay always produces output;
when the buffer is empty or a DOF has ``delay_steps == 0``, the current
command inputs are used directly. When underfilled, the lag is clamped
to the available history so the oldest available entry is returned.

**Controller**
Computes raw actuator effort [N or N·m] from the current simulator state
and control targets. This is the actuator's control law — for example PD,
PID, or neural-network-based control. See the individual controller class
documentation for the control-law equations.

**Clamping**
Clamps raw effort based on motor-limit modeling. This applies
post-controller output limits to the computed effort to model motor limits
such as saturation, back-EMF losses, performance envelopes, or
position-dependent effort limits. Multiple clamping stages can be combined
on a single actuator.

The per-step pipeline is:

.. code-block:: text

Comment thread
jvonmuralt marked this conversation as resolved.
Delay read → Controller → Clamping → Scatter-add → State updates (controller + delay write)

Controllers and clamping objects are pluggable: implement the
:class:`Controller` or :class:`Clamping` base class to add new models.

.. note::
Comment thread
preist-nvidia marked this conversation as resolved.

**Current limitations:** the first version does not include a transmission
model (gear ratios / linkage transforms), supports only single-input
single-output (SISO) actuators (one DOF per actuator), and does not model
actuator dynamics (inertia, friction, thermal effects).

Usage
-----

Actuators are registered during model construction with
:meth:`~newton.ModelBuilder.add_actuator` and are instantiated automatically
when the model is finalized:

.. testsetup:: actuator-usage

import warp as wp
import newton
from newton.actuators import (
Actuator, ClampingMaxEffort, ControllerPD, Delay,
)

builder = newton.ModelBuilder()
link = builder.add_link()
joint = builder.add_joint_revolute(parent=-1, child=link, axis=newton.Axis.Z)
builder.add_articulation([joint])
dof_index = builder.joint_qd_start[joint]

.. testcode:: actuator-usage

builder.add_actuator(
ControllerPD,
index=dof_index,
kp=100.0,
kd=10.0,
delay_steps=5,
clamping=[(ClampingMaxEffort, {"max_effort": 50.0})],
)
Comment thread
jvonmuralt marked this conversation as resolved.

model = builder.finalize()

For manual construction (outside of :class:`~newton.ModelBuilder`), compose the
components directly:

.. testcode:: actuator-usage

indices = wp.array([0], dtype=wp.uint32)
kp = wp.array([100.0], dtype=wp.float32)
kd = wp.array([10.0], dtype=wp.float32)
max_e = wp.array([50.0], dtype=wp.float32)

actuator = Actuator(
indices,
controller=ControllerPD(kp=kp, kd=kd),
delay=Delay(delay_steps=wp.array([5], dtype=wp.int32), max_delay=5),
clamping=[ClampingMaxEffort(max_effort=max_e)],
)


Stateful Actuators
------------------

Controllers that maintain internal state (e.g. :class:`ControllerPID` with an
integral accumulator, or :class:`ControllerNeuralLSTM` with hidden/cell state) and
actuators with a :class:`Delay` require explicit double-buffered state
management. Create two state objects with :meth:`Actuator.state` and swap them
after each step:

.. testcode:: actuator-usage

state_0 = model.actuators[0].state()
state_1 = model.actuators[0].state()
state = model.state()
control = model.control()

for step in range(3):
control.joint_f.zero_() # zero output before stepping actuators
model.actuators[0].step(state, control, state_0, state_1, dt=0.01)
state_0, state_1 = state_1, state_0

Stateless actuators (e.g. a plain PD controller without delay) do not require
state objects — simply omit them:

.. testcode:: actuator-usage

# Build a stateless actuator (no delay, stateless controller)
b2 = newton.ModelBuilder()
lk = b2.add_link()
jt = b2.add_joint_revolute(parent=-1, child=lk, axis=newton.Axis.Z)
b2.add_articulation([jt])
b2.add_actuator(ControllerPD, index=b2.joint_qd_start[jt], kp=50.0)
m2 = b2.finalize()

m2.actuators[0].step(m2.state(), m2.control())

Differentiability and Graph Capture
-----------------------------------

Whether an actuator supports differentiability and CUDA graph capture depends on
its controller. :class:`ControllerPD` and :class:`ControllerPID` are fully
graphable. Neural-network controllers (:class:`ControllerNeuralMLP`,
:class:`ControllerNeuralLSTM`) require PyTorch and are not graphable due to
framework interop overhead.

:meth:`Actuator.is_graphable` returns ``True`` when all components can be
captured in a CUDA graph.

Available Components
--------------------

Delay
^^^^^

* :class:`Delay` — circular-buffer delay for control targets (stateful).

Controllers
^^^^^^^^^^^

* :class:`ControllerPD` — proportional-derivative control law (stateless).
* :class:`ControllerPID` — proportional-integral-derivative control law
(stateful: integral accumulator with anti-windup clamp).
* :class:`ControllerNeuralMLP` — MLP neural-network controller (requires
PyTorch, stateful: position/velocity history buffers).
* :class:`ControllerNeuralLSTM` — LSTM neural-network controller (requires
PyTorch, stateful: hidden/cell state).

See the API documentation for each controller's control-law equations.

Clamping
^^^^^^^^

* :class:`ClampingMaxEffort` — symmetric clamp to ±max_effort per actuator.
* :class:`ClampingDCMotor` — velocity-dependent effort saturation using the DC
motor effort-speed characteristic.
* :class:`ClampingPositionBased` — position-dependent effort limits via
interpolated lookup table (e.g. for linkage-driven joints).

Multiple clamping objects can be stacked on a single actuator; they are applied
Comment thread
preist-nvidia marked this conversation as resolved.
in sequence.

Customization
-------------

Any actuator can be assembled from the existing building blocks — mix and
match controllers, clamping stages, and delay to fit a specific use case.
When the built-in components are not sufficient, implement new ones by
subclassing :class:`Controller` or :class:`Clamping`.

For example, a custom controller needs to implement
:meth:`~Controller.compute`, :meth:`~Controller.resolve_arguments`,
:meth:`~Controller.is_stateful`, and :meth:`~Controller.is_graphable`:

.. code-block:: python
:caption: Skeleton — the ``compute`` body is omitted; see existing
controllers for complete examples.

import warp as wp
from newton.actuators import Controller

class MyController(Controller):
@classmethod
def resolve_arguments(cls, args):
return {"gain": args.get("gain", 1.0)}

def __init__(self, gain: wp.array):
self.gain = gain

def is_stateful(self):
return False

def is_graphable(self):
return True

def compute(self, positions, velocities, target_pos, target_vel,
Comment thread
jvonmuralt marked this conversation as resolved.
feedforward, pos_indices, vel_indices,
target_pos_indices, target_vel_indices,
forces, state, dt, device=None):
# Launch a Warp kernel that writes effort into `forces`
...

``resolve_arguments`` maps user-provided keyword arguments (from
:meth:`~newton.ModelBuilder.add_actuator` or USD schemas) to constructor
parameters, filling in defaults where needed.

Similarly, a custom clamping stage subclasses :class:`Clamping` and implements
:meth:`~Clamping.modify_forces` (which reads effort from a source buffer and writes bounded effort to a destination buffer).

See Also
--------

* :mod:`newton.actuators` — full API reference
* :meth:`newton.ModelBuilder.add_actuator` — registering actuators during
model construction
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ def _ensure_pandoc_on_path() -> str | None:
"UsdGeom.Mesh": "pxr.UsdGeom.Mesh",
"UsdShade.Material": "pxr.UsdShade.Material",
"UsdShade.Shader": "pxr.UsdShade.Shader",
"State": "newton.State",
}


Expand Down Expand Up @@ -224,7 +225,7 @@ def _ensure_pandoc_on_path() -> str | None:
"member-order": "groupwise",
"special-members": "__init__",
"undoc-members": False,
"exclude-members": "__weakref__",
"exclude-members": "__weakref__, State",
"imported-members": True,
"autosummary": True,
}
Expand Down
1 change: 1 addition & 0 deletions docs/generate_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
# Modules for which we want API pages. Feel free to modify.
MODULES: list[str] = [
"newton",
"newton.actuators",
"newton.geometry",
"newton.ik",
"newton.math",
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Pre-release planning
- Determine target version (``X.Y.Z``).
* - ☐
- Confirm dependency versions and availability: warp-lang, mujoco,
mujoco-warp, newton-usd-schemas, newton-actuators.
mujoco-warp, newton-usd-schemas.
* - ☐
- Set timeline: code freeze → RC1 → testing window → GA.
* - ☐
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Newton Physics
Custom Attributes <concepts/custom_attributes>
Extended Attributes <concepts/extended_attributes>
Collisions and Contacts <concepts/collisions>
Actuators <concepts/actuators>
Comment thread
jvonmuralt marked this conversation as resolved.

.. toctree::
:maxdepth: 1
Expand Down
Loading
Loading