-
Notifications
You must be signed in to change notification settings - Fork 455
Move modular design of newton-actuators to Newton repo #2449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
4f8f617
2d8eefd
e23b3ed
9738c81
ca82871
79cc869
36437b5
08541f0
48810f5
74112db
2b56eef
997900c
387ef13
875284f
cce0824
d2a885d
a067fe4
206f624
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| .. SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers | ||
| .. SPDX-License-Identifier: CC-BY-4.0 | ||
|
|
||
| .. currentmodule:: newton.actuators | ||
|
|
||
| Actuators | ||
| ========= | ||
|
|
||
| Newton provides composable actuator implementations that read physics simulation | ||
| state, compute actuator forces, and write the forces back to control arrays for | ||
| application to the simulation. The simulator does not need to be part of | ||
| Newton: the library is designed to be reusable anywhere the caller can provide | ||
| state arrays and consume forces. | ||
|
|
||
| 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. The actuator subsystem is 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 control targets by N timesteps) | ||
| ├── Controller (control law that computes raw forces) | ||
| └── Clamping[] (clamps raw forces based on motor-limit modeling) | ||
| ├── ClampingMaxForce (±max_force box clamp) | ||
| ├── ClampingDCMotor (velocity-dependent saturation) | ||
| └── ClampingPositionBased (angle-dependent lookup table) | ||
|
|
||
| **Delay** | ||
| Optionally delays the control targets (e.g. position or velocity) by *N* | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The question I had in the USD PR applies here too, does delay apply also to FF terms and should we therefore prefer the control inputs instead of specific target language? I'm aware I initially suggested the more narrow control targets, sorry.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, delay is applied to all the inputs. |
||
| timesteps before they reach the controller, allowing the actuator to model | ||
| communication or processing latency. While the delay buffer is still | ||
| filling, no forces are produced. | ||
|
|
||
| **Controller** | ||
| Computes raw forces or torques from the current simulator state and control | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given my comments on the USD PR and having looked at PhysX perf envelope and lab API docs, should we standardize to effort to mean force/torque in this PR also? Not going to comment further in this PR we can decide first then make a single refactor.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. I can use the word effort |
||
| 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 forces based on motor-limit modeling. This applies | ||
| post-controller output limits to the computed forces or torques to model | ||
| motor limits such as saturation, back-EMF losses, performance envelopes, or | ||
| angle-dependent torque limits. Multiple clamping stages can be combined on | ||
| a single actuator. | ||
|
|
||
| The per-step pipeline is: | ||
|
|
||
| .. code-block:: text | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per Newton docs convention ( Could we convert the first four (builder-level usage, manual construction, USD loading, selection API) to |
||
| Delay → Controller → Clamping → Scatter-add to output | ||
|
|
||
| Controllers and clamping objects are pluggable: implement the | ||
| :class:`Controller` or :class:`Clamping` base class to add new models. | ||
|
|
||
| .. note:: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would make this more prominent in the intro. Should this also have an experimental feature disclaimer as we discussed? We don't need it if you are fairly sure about the future-proofness of the API with upcoming changes like transmission. |
||
|
|
||
| **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: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| import newton | ||
| from newton.actuators import ControllerPD, ClampingMaxForce, Delay | ||
|
|
||
| builder = newton.ModelBuilder() | ||
| # ... add links, joints, articulations ... | ||
|
|
||
| builder.add_actuator( | ||
| controller_type=ControllerPD, | ||
| index=dof_index, | ||
| kp=100.0, | ||
| kd=10.0, | ||
| clamping_types=[ClampingMaxForce], | ||
| max_force=50.0, | ||
| ) | ||
|
|
||
| model = builder.finalize() | ||
|
|
||
| For manual construction (outside of :class:`~newton.ModelBuilder`), compose the | ||
| components directly: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| import warp as wp | ||
| from newton.actuators import Actuator, ControllerPD, ClampingMaxForce, Delay | ||
|
|
||
| indices = wp.array([0, 1, 2], dtype=wp.uint32, device="cuda:0") | ||
| kp = wp.array([100.0, 100.0, 100.0], dtype=wp.float32, device="cuda:0") | ||
| kd = wp.array([10.0, 10.0, 10.0], dtype=wp.float32, device="cuda:0") | ||
| max_f = wp.array([50.0, 50.0, 50.0], dtype=wp.float32, device="cuda:0") | ||
|
|
||
| actuator = Actuator( | ||
| indices, | ||
| delay=Delay(delay=5), | ||
| controller=ControllerPD(kp=kp, kd=kd), | ||
| clamping=[ClampingMaxForce(max_force=max_f)], | ||
| ) | ||
|
|
||
| # In the simulation loop: | ||
| actuator.step(sim_state, sim_control, state_a, state_b, dt=0.01) | ||
|
|
||
|
|
||
| Stateful Actuators | ||
| ------------------ | ||
|
|
||
| Controllers that maintain internal state (e.g. :class:`ControllerPID` with an | ||
| integral accumulator, or :class:`ControllerNetLSTM` 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: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| state_a = actuator.state() | ||
| state_b = actuator.state() | ||
|
|
||
| for step in range(num_steps): | ||
| actuator.step(sim_state, sim_control, state_a, state_b, dt=dt) | ||
| state_a, state_b = state_b, state_a # swap | ||
|
|
||
| Stateless actuators (e.g. a plain PD controller without delay) do not require | ||
| state objects — pass ``None`` for both. | ||
|
|
||
| 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:`ControllerNetMLP`, | ||
| :class:`ControllerNetLSTM`) 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:`ControllerNetMLP` — MLP neural-network controller (requires | ||
| PyTorch, stateful: position/velocity history buffers). | ||
| * :class:`ControllerNetLSTM` — LSTM neural-network controller (requires | ||
| PyTorch, stateful: hidden/cell state). | ||
|
|
||
| See the API documentation for each controller's control-law equations. | ||
|
|
||
| Clamping | ||
| ^^^^^^^^ | ||
|
|
||
| * :class:`ClampingMaxForce` — symmetric box clamp to ±max_force per actuator. | ||
| * :class:`ClampingDCMotor` — velocity-dependent torque saturation using the DC | ||
| motor torque-speed characteristic. | ||
| * :class:`ClampingPositionBased` — angle-dependent torque limits via | ||
| interpolated lookup table (e.g. for linkage-driven joints). | ||
|
|
||
| Multiple clamping objects can be stacked on a single actuator; they are applied | ||
| 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` and :meth:`~Controller.resolve_arguments`: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| 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 compute(self, positions, velocities, target_pos, target_vel, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example signature here is stale vs. def compute(self, positions, velocities, target_pos, target_vel,
feedforward, input_indices, target_indices, forces,
state, dt, device=None):The real signature uses |
||
| feedforward, input_indices, target_indices, forces, | ||
| num_actuators, state, dt): | ||
| # Launch a Warp kernel that writes 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`. | ||
|
|
||
| See Also | ||
| -------- | ||
|
|
||
| * :mod:`newton.actuators` — full API reference | ||
| * :meth:`newton.ModelBuilder.add_actuator` — registering actuators during | ||
| model construction | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ Newton Physics | |
| Custom Attributes <concepts/custom_attributes> | ||
| Extended Attributes <concepts/extended_attributes> | ||
| Collisions and Contacts <concepts/collisions> | ||
| Actuators <concepts/actuators> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't forget to add |
||
|
|
||
| .. toctree:: | ||
| :maxdepth: 1 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| from .actuator import Actuator | ||
| from .clamping import Clamping, ClampingDCMotor, ClampingMaxForce, ClampingPositionBased | ||
| from .controllers import Controller, ControllerNetLSTM, ControllerNetMLP, ControllerPD, ControllerPID | ||
| from .delay import Delay | ||
| from .usd_parser import ActuatorParsed, parse_actuator_prim | ||
|
|
||
| __all__ = [ | ||
| "Actuator", | ||
| "ActuatorParsed", | ||
| "Clamping", | ||
| "ClampingDCMotor", | ||
| "ClampingMaxForce", | ||
| "ClampingPositionBased", | ||
| "Controller", | ||
| "ControllerNetLSTM", | ||
| "ControllerNetMLP", | ||
| "ControllerPD", | ||
| "ControllerPID", | ||
| "Delay", | ||
| "parse_actuator_prim", | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per
AGENTS.md, user-facing changes (especially Changed/Removed) require a CHANGELOG entry with migration guidance. This PR (a) deletes thenewton-actuatorsdependency, (b) replaces the oldModelBuilder.add_actuator(actuator_class=..., input_indices=..., output_indices=...)signature with a very differentadd_actuator(controller_class, index, clamping=[...], delay=..., pos_index=..., **ctrl_kwargs)API, and (c) changesArticulationView.get_actuator_parameter/set_actuator_parameterto require acomponentargument.All three are breaking for any existing user. Could we add entries under
Changed/Removedwith explicit before/after snippets? e.g.