-
Notifications
You must be signed in to change notification settings - Fork 1
Review draft for upstream #2449 #1
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
Changes from all commits
4f8f617
2d8eefd
e23b3ed
9738c81
ca82871
79cc869
36437b5
08541f0
48810f5
74112db
2b56eef
997900c
387ef13
875284f
cce0824
d2a885d
a067fe4
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,237 @@ | ||
| .. SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers | ||
| .. SPDX-License-Identifier: CC-BY-4.0 | ||
|
|
||
| .. currentmodule:: newton.actuators | ||
|
|
||
| Actuators | ||
| ========= | ||
|
|
||
| Actuators provide composable implementations that read physics simulation | ||
| state, compute forces, and write the forces back to control arrays for | ||
| application to the simulation. 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 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. 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 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* | ||
| timesteps before they reach the controller, allowing the actuator to model | ||
| communication or processing latency. The delay always produces output; | ||
| when the buffer is still filling, the lag is clamped to the available | ||
| history so the most recent data is returned. | ||
|
|
||
| **Controller** | ||
| Computes raw forces or torques 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 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 | ||
|
|
||
|
Owner
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. all of these should use test-code, right?
Owner
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. Missed your question on my first pass — my grouping only looked at threads I started. Fixing that in the skill now (see below). On the substance: yes, confirmed. One nuance worth flagging: the custom-controller example at line 206 has a stub body ( 📝 Review comment (will ship upstream) — v1Per Newton docs convention ( Could we convert the first four (builder-level usage, manual construction, USD loading, selection API) to |
||
| 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:: | ||
|
|
||
| **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 ClampingMaxForce, ControllerPD | ||
|
|
||
| builder = newton.ModelBuilder() | ||
| # ... add links, joints, articulations ... | ||
|
|
||
| builder.add_actuator( | ||
| ControllerPD, | ||
| index=dof_index, | ||
| kp=100.0, | ||
| kd=10.0, | ||
| delay=5, | ||
| clamping=[(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, | ||
| controller=ControllerPD(kp=kp, kd=kd), | ||
| delay=Delay(delay=wp.array([5, 5, 5], dtype=wp.int32, device="cuda:0"), max_delay=5), | ||
| 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 — simply omit them: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| actuator.step(sim_state, sim_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:`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, | ||
|
Owner
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. 📝 Review comment (will ship upstream) — v1The 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
Owner
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. please double-check for accuracy here.
Owner
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. Verified. def compute(self, positions, velocities, target_pos, target_vel,
feedforward, pos_indices, vel_indices,
target_pos_indices, target_vel_indices,
forces, state, dt, device=None):The rst example at line 219 still uses the old 2-index shape (
Owner
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. Confirmation: the verification above stands. Re-posting identical content as v2. 📝 Review comment (will ship upstream) — v2The 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, | ||
| state, dt, device=None): | ||
| # 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> | ||
|
Owner
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. 📝 Review comment (will ship upstream) — v1
Owner
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. please reword, make it less verbose. something like don't forget to add to the toctree, and direct the author to the right place
Owner
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. Shorter version: 📝 Review comment (will ship upstream) — v2Don'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", | ||
| ] |
Uh oh!
There was an error while loading. Please reload this page.
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.
📝 Review comment (will ship upstream) — v1
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.