66 "TorqueActuators" ,
77 "PositionActuators" ,
88 "PositionVelocityActuator" ,
9- "BiasedPositionActuators" ,
109]
1110
1211import logging
1312from abc import ABC , abstractmethod
14- from typing import TypedDict
1513
1614import chex
1715import jax
1816import jax .numpy as jnp
1917from jaxtyping import Array , PRNGKeyArray , PyTree
2018
21- from ksim .noise import Noise , NoNoise , RandomVariable , UniformRandomVariable
22- from ksim .types import Metadata , PhysicsData , PhysicsModel
19+ from ksim .noise import Noise , NoNoise
20+ from ksim .types import Metadata , PhysicsModel
2321from ksim .utils .mujoco import get_ctrl_data_idx_by_name
2422
2523logger = logging .getLogger (__name__ )
@@ -29,28 +27,32 @@ class Actuators(ABC):
2927 """Collection of actuators."""
3028
3129 @abstractmethod
32- def get_ctrl (self , action : Array , physics_data : PhysicsData , curriculum_level : Array , rng : PRNGKeyArray ) -> Array :
30+ def get_ctrl (
31+ self ,
32+ action : Array ,
33+ qpos : Array ,
34+ qvel : Array ,
35+ curriculum_level : Array ,
36+ rng : PRNGKeyArray ,
37+ ) -> Array :
3338 """Get the control signal from the action vector."""
3439
35- def get_default_action (self , physics_data : PhysicsData ) -> Array :
36- """Get the default action for the actuators."""
37- return physics_data .ctrl
38-
3940
4041class StatefulActuators (Actuators ):
4142 @abstractmethod
4243 def get_stateful_ctrl (
4344 self ,
4445 action : Array ,
45- physics_data : PhysicsData ,
46+ qpos : Array ,
47+ qvel : Array ,
4648 curriculum_level : Array ,
4749 actuator_state : PyTree ,
4850 rng : PRNGKeyArray ,
4951 ) -> tuple [Array , PyTree ]:
5052 """Get the control signal from the action vector."""
5153
5254 @abstractmethod
53- def get_initial_state (self , physics_data : PhysicsData , rng : PRNGKeyArray ) -> PyTree :
55+ def get_initial_state (self , qpos : Array , qvel : Array , rng : PRNGKeyArray ) -> PyTree :
5456 """Get the initial state for the actuator."""
5557
5658
@@ -62,7 +64,14 @@ def __init__(self, noise: Noise | None = None) -> None:
6264
6365 self .noise = NoNoise () if noise is None else noise
6466
65- def get_ctrl (self , action : Array , physics_data : PhysicsData , curriculum_level : Array , rng : PRNGKeyArray ) -> Array :
67+ def get_ctrl (
68+ self ,
69+ action : Array ,
70+ qpos : Array ,
71+ qvel : Array ,
72+ curriculum_level : Array ,
73+ rng : PRNGKeyArray ,
74+ ) -> Array :
6675 """Just use the action as the torque, the simplest actuator model."""
6776 return self .noise .add_noise (action , curriculum_level , rng )
6877
@@ -132,13 +141,22 @@ def get_actuator_name(self, joint_name: str) -> str:
132141 # This can be overridden if necessary.
133142 return f"{ joint_name } _ctrl"
134143
135- def get_ctrl (self , action : Array , physics_data : PhysicsData , curriculum_level : Array , rng : PRNGKeyArray ) -> Array :
144+ def get_ctrl (
145+ self ,
146+ action : Array ,
147+ qpos : Array ,
148+ qvel : Array ,
149+ curriculum_level : Array ,
150+ rng : PRNGKeyArray ,
151+ ) -> Array :
136152 """Get the control signal from the (position) action vector."""
137153 scaled = action * self .action_scale
138154
139155 pos_rng , tor_rng = jax .random .split (rng )
140- current_pos = physics_data .qpos [7 :] # First 7 are always root pos.
141- current_vel = physics_data .qvel [6 :] # First 6 are always root vel.
156+
157+ # Calling function removes root position and velocity.
158+ current_pos = qpos
159+ current_vel = qvel
142160
143161 # Add position and velocity noise
144162 target_position = self .action_noise .add_noise (scaled , curriculum_level , pos_rng )
@@ -172,12 +190,20 @@ def __init__(
172190
173191 self .vel_action_noise = NoNoise () if vel_action_noise is None else vel_action_noise
174192
175- def get_ctrl (self , action : Array , physics_data : PhysicsData , curriculum_level : Array , rng : PRNGKeyArray ) -> Array :
193+ def get_ctrl (
194+ self ,
195+ action : Array ,
196+ qpos : Array ,
197+ qvel : Array ,
198+ curriculum_level : Array ,
199+ rng : PRNGKeyArray ,
200+ ) -> Array :
176201 """Get the control signal from the (position and velocity) action vector."""
177202 pos_rng , vel_rng , tor_rng = jax .random .split (rng , 3 )
178203
179- current_pos = physics_data .qpos [7 :] # First 7 are always root pos.
180- current_vel = physics_data .qvel [6 :] # First 6 are always root vel.
204+ # Calling function removes root position and velocity.
205+ current_pos = qpos
206+ current_vel = qvel
181207
182208 # Extract position and velocity targets
183209 target_position = action [: len (current_pos )]
@@ -197,78 +223,3 @@ def get_ctrl(self, action: Array, physics_data: PhysicsData, curriculum_level: A
197223 - self .ctrl_clip ,
198224 self .ctrl_clip ,
199225 )
200-
201- def get_default_action (self , physics_data : PhysicsData ) -> Array :
202- """Get the default action (zeros) with the correct shape."""
203- qpos_dim = len (physics_data .qpos [7 :])
204- return jnp .zeros (qpos_dim * 2 )
205-
206-
207- class TorqueBias (TypedDict ):
208- action : Array
209- torque : Array
210-
211-
212- class BiasedPositionActuators (PositionActuators , StatefulActuators ):
213- """Adds some random bias to the position action to simulate imperfect actuators."""
214-
215- def __init__ (
216- self ,
217- physics_model : PhysicsModel ,
218- metadata : Metadata ,
219- action_bias : RandomVariable | float ,
220- torque_bias : RandomVariable | float ,
221- action_noise : Noise | None = None ,
222- torque_noise : Noise | None = None ,
223- action_scale : float = 1.0 ,
224- ) -> None :
225- super ().__init__ (
226- physics_model = physics_model ,
227- metadata = metadata ,
228- action_noise = action_noise ,
229- torque_noise = torque_noise ,
230- action_scale = action_scale ,
231- )
232-
233- if not isinstance (action_bias , RandomVariable ):
234- action_bias = UniformRandomVariable (mean = 0.0 , mag = action_bias )
235- if not isinstance (torque_bias , RandomVariable ):
236- torque_bias = UniformRandomVariable (mean = 0.0 , mag = torque_bias )
237- self .action_bias = action_bias
238- self .torque_bias = torque_bias
239-
240- def get_stateful_ctrl (
241- self ,
242- action : Array ,
243- physics_data : PhysicsData ,
244- curriculum_level : Array ,
245- actuator_state : TorqueBias ,
246- rng : PRNGKeyArray ,
247- ) -> tuple [Array , TorqueBias ]:
248- """Get the control signal from the (position) action vector."""
249- action_bias = actuator_state ["action" ]
250- torque_bias = actuator_state ["torque" ]
251-
252- scaled = action * self .action_scale
253-
254- pos_rng , tor_rng = jax .random .split (rng )
255- current_pos = physics_data .qpos [7 :] # First 7 are always root pos.
256- current_vel = physics_data .qvel [6 :] # First 6 are always root vel.
257-
258- # Add position and velocity noise
259- target_position = self .action_noise .add_noise (scaled , curriculum_level , pos_rng ) + action_bias
260- target_velocity = jnp .zeros_like (action )
261-
262- pos_delta = target_position - current_pos
263- vel_delta = target_velocity - current_vel
264-
265- ctrl = self .kps * pos_delta + self .kds * vel_delta
266- ctrl = self .torque_noise .add_noise (ctrl , curriculum_level , tor_rng ) + torque_bias
267- return jnp .clip (ctrl , - self .ctrl_clip , self .ctrl_clip ), actuator_state
268-
269- def get_initial_state (self , physics_data : PhysicsData , rng : PRNGKeyArray ) -> TorqueBias :
270- shape = physics_data .qpos [..., 7 :].shape
271- return {
272- "action" : self .action_bias .get_random_variable (shape , rng ),
273- "torque" : self .torque_bias .get_random_variable (shape , rng ),
274- }
0 commit comments