Skip to content

Commit b2796e8

Browse files
authored
implement zero offset (#538)
* implement zero offset * thought for a bit * remove biased actuator model * remove biased thing * fix shape mismatch * lint
1 parent 30dae33 commit b2796e8

7 files changed

Lines changed: 176 additions & 157 deletions

File tree

examples/kbot/train.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -452,11 +452,9 @@ def get_actuators(
452452
metadata: ksim.Metadata | None = None,
453453
) -> ksim.Actuators:
454454
assert metadata is not None, "Metadata is required"
455-
return ksim.BiasedPositionActuators(
455+
return ksim.PositionActuators(
456456
physics_model=physics_model,
457457
metadata=metadata,
458-
action_bias=ksim.UniformRandomVariable(mean=0.0, mag=math.radians(3.0)),
459-
torque_bias=ksim.UniformRandomVariable(mean=0.0, mag=3.0),
460458
action_noise=ksim.AdditiveGaussianNoise(std=0.01),
461459
torque_noise=ksim.AdditiveGaussianNoise(std=0.01),
462460
)
@@ -891,6 +889,8 @@ def sample_action(
891889
ctrl_dt=0.02,
892890
action_latency_range=(0.001, 0.01), # Simulate 1-10ms of latency.
893891
drop_action_prob=0.05, # Drop 5% of commands.
892+
zero_offset_std=math.radians(3.0),
893+
zero_offset_mag=math.radians(3.0),
894894
# Visualization parameters.
895895
# If running this on Mac and you are getting segfaults,
896896
# you might need to disable `render_markers`

examples/walking.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ class WalkingConfig(ksim.PPOConfig):
301301
help="The acceleration for the angular velocity command, in rad/s^2.",
302302
)
303303
angular_velocity_range: tuple[float, float] = xax.field(
304-
value=(-0.2, 0.2),
304+
value=(0.0, 0.2),
305305
help="The range for the angular velocity command.",
306306
)
307307
angular_velocity_zero_prob: float = xax.field(
@@ -534,7 +534,7 @@ def get_rewards(self, physics_model: ksim.PhysicsModel) -> dict[str, ksim.Reward
534534
force_obs="feet_force",
535535
ctrl_dt=self.config.ctrl_dt,
536536
expected_weight=10.0,
537-
scale=-1e-1,
537+
scale=1e-1,
538538
),
539539
"motionless_at_rest": ksim.MotionlessAtRestPenalty(scale=1e-2),
540540
}
@@ -779,6 +779,8 @@ def sample_action(
779779
# Engine parameters.
780780
dt=0.004,
781781
ctrl_dt=0.02,
782+
zero_offset_std=math.radians(3.0),
783+
zero_offset_mag=math.radians(3.0),
782784
# Simulation parameters.
783785
iterations=8,
784786
ls_iterations=8,

ksim/actuators.py

Lines changed: 44 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,18 @@
66
"TorqueActuators",
77
"PositionActuators",
88
"PositionVelocityActuator",
9-
"BiasedPositionActuators",
109
]
1110

1211
import logging
1312
from abc import ABC, abstractmethod
14-
from typing import TypedDict
1513

1614
import chex
1715
import jax
1816
import jax.numpy as jnp
1917
from 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
2321
from ksim.utils.mujoco import get_ctrl_data_idx_by_name
2422

2523
logger = 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

4041
class 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

Comments
 (0)