Skip to content

Commit e718d15

Browse files
committed
Refactor replay onto existing sim_state flow
1 parent ad4c4eb commit e718d15

4 files changed

Lines changed: 126 additions & 136 deletions

File tree

python/rcs/envs/sim.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,33 +44,32 @@ def reset(
4444

4545

4646
class SimStateObservationWrapper(ActObsInfoWrapper):
47-
DYNAMIC_JOINT_SCHEMA_KEY = "dynamic_joint_schema"
48-
DYNAMIC_JOINT_QPOS_KEY = "dynamic_joint_qpos"
49-
DYNAMIC_JOINT_QVEL_KEY = "dynamic_joint_qvel"
47+
STATE_KEY = "sim_state"
48+
STATE_SPEC_KEY = "sim_state_spec"
49+
STATE_SIZE_KEY = "sim_state_size"
5050

5151
def __init__(self, env):
5252
super().__init__(env)
5353
assert self.env.get_wrapper_attr("PLATFORM") == RobotPlatform.SIMULATION, "Base environment must be simulation."
5454
self.sim = cast(sim.Sim, self.get_wrapper_attr("sim"))
55-
self._dynamic_joint_schema = self.sim.get_dynamic_joint_schema()
56-
self._include_schema_in_next_step = True
55+
self._state_spec = self.sim.get_state_spec()
56+
self._include_state_spec_in_next_step = True
5757

5858
def observation(self, observation: dict[str, Any], info: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
5959
observation = dict(observation)
60-
dynamic_joint_state = self.sim.get_dynamic_joint_state()
61-
observation[self.DYNAMIC_JOINT_QPOS_KEY] = dynamic_joint_state["qpos"]
62-
observation[self.DYNAMIC_JOINT_QVEL_KEY] = dynamic_joint_state["qvel"]
63-
if self._include_schema_in_next_step:
64-
observation[self.DYNAMIC_JOINT_SCHEMA_KEY] = self._dynamic_joint_schema
65-
self._include_schema_in_next_step = False
60+
sim_state = self.sim.get_state()
61+
observation[self.STATE_KEY] = sim_state
62+
observation[self.STATE_SIZE_KEY] = sim_state.shape[0]
63+
if self._include_state_spec_in_next_step:
64+
observation[self.STATE_SPEC_KEY] = self._state_spec
65+
self._include_state_spec_in_next_step = False
6666
return observation, info
6767

6868
def reset(
6969
self, *, seed: int | None = None, options: dict[str, Any] | None = None
7070
) -> tuple[dict[str, Any], dict[str, Any]]:
7171
obs, info = super().reset(seed=seed, options=options)
72-
# Re-emit the schema on the first recorded step after each reset.
73-
self._include_schema_in_next_step = True
72+
self._include_state_spec_in_next_step = True
7473
return obs, info
7574

7675

python/rcs/sim/sim.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ def gui_loop(gui_uuid: str, close_event):
4747

4848

4949
class Sim(_Sim):
50-
STATE_SPEC = mj.mjtState.mjSTATE_INTEGRATION
51-
5250
def __init__(self, mjmdl: str | PathLike | ModelComposer, cfg: SimConfig | None = None):
5351
if isinstance(mjmdl, ModelComposer):
5452
self.model = mjmdl.get_model()
@@ -73,31 +71,38 @@ def __init__(self, mjmdl: str | PathLike | ModelComposer, cfg: SimConfig | None
7371
if cfg is not None:
7472
self.set_config(cfg)
7573

76-
def get_state_spec(self) -> int:
77-
return int(self.STATE_SPEC)
74+
def get_state_spec(self) -> dict[str, list[str] | list[int]]:
75+
return self.get_dynamic_joint_schema()
7876

79-
def get_state_size(self, spec: int | None = None) -> int:
80-
state_spec = self.STATE_SPEC if spec is None else mj.mjtState(spec)
81-
return mj.mj_stateSize(self.model, state_spec)
77+
def get_state_size(self, spec: dict[str, list[str] | list[int]] | None = None) -> int:
78+
state_spec = self.get_state_spec() if spec is None else spec
79+
qpos_size = sum(int(value) for value in state_spec["qpos_sizes"])
80+
qvel_size = sum(int(value) for value in state_spec["qvel_sizes"])
81+
return qpos_size + qvel_size
8282

83-
def get_state(self, spec: int | None = None) -> np.ndarray:
84-
state_spec = self.STATE_SPEC if spec is None else mj.mjtState(spec)
85-
state = np.empty(self.get_state_size(int(state_spec)), dtype=np.float64)
86-
mj.mj_getState(self.model, self.data, state, state_spec)
87-
return state
83+
def get_state(self, spec: dict[str, list[str] | list[int]] | None = None) -> np.ndarray:
84+
del spec
85+
dynamic_joint_state = self.get_dynamic_joint_state()
86+
return np.concatenate((dynamic_joint_state["qpos"], dynamic_joint_state["qvel"]))
8887

89-
def set_state(self, state: np.ndarray, spec: int | None = None):
90-
state_spec = self.STATE_SPEC if spec is None else mj.mjtState(spec)
88+
def set_state(
89+
self,
90+
state: np.ndarray,
91+
spec: dict[str, list[str] | list[int]] | None = None,
92+
):
93+
state_spec = self.get_state_spec() if spec is None else spec
9194
state_array = np.asarray(state, dtype=np.float64)
92-
expected_size = self.get_state_size(int(state_spec))
95+
expected_size = self.get_state_size(state_spec)
9396
if state_array.shape != (expected_size,):
94-
msg = (
95-
f"Expected MuJoCo state with shape ({expected_size},), "
96-
f"got {state_array.shape} for spec {int(state_spec)}."
97-
)
97+
msg = f"Expected state with shape ({expected_size},), got {state_array.shape}."
9898
raise ValueError(msg)
99-
mj.mj_setState(self.model, self.data, state_array, state_spec)
100-
mj.mj_forward(self.model, self.data)
99+
100+
qpos_size = sum(int(value) for value in state_spec["qpos_sizes"])
101+
dynamic_joint_state = {
102+
"qpos": state_array[:qpos_size],
103+
"qvel": state_array[qpos_size:],
104+
}
105+
self.set_dynamic_joint_state(state_spec, dynamic_joint_state)
101106

102107
def get_dynamic_joint_schema(self) -> dict[str, list[str] | list[int]]:
103108
schema = super().get_dynamic_joint_schema()

python/rcs/sim_state_replay.py

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,13 @@ class RecordedSimStep:
3939
observation: dict[str, Any]
4040

4141
@property
42-
def dynamic_joint_schema(self) -> dict[str, Any] | None:
43-
schema = self.observation.get(SimStateObservationWrapper.DYNAMIC_JOINT_SCHEMA_KEY)
44-
return dict(schema) if schema is not None else None
42+
def sim_state(self) -> np.ndarray:
43+
return np.asarray(self.observation[SimStateObservationWrapper.STATE_KEY], dtype=np.float64)
4544

4645
@property
47-
def dynamic_joint_state(self) -> dict[str, np.ndarray] | None:
48-
if (
49-
SimStateObservationWrapper.DYNAMIC_JOINT_QPOS_KEY not in self.observation
50-
or SimStateObservationWrapper.DYNAMIC_JOINT_QVEL_KEY not in self.observation
51-
):
52-
return None
53-
return {
54-
"qpos": np.asarray(self.observation[SimStateObservationWrapper.DYNAMIC_JOINT_QPOS_KEY], dtype=np.float64),
55-
"qvel": np.asarray(self.observation[SimStateObservationWrapper.DYNAMIC_JOINT_QVEL_KEY], dtype=np.float64),
56-
}
46+
def sim_state_spec(self) -> dict[str, Any] | None:
47+
schema = self.observation.get(SimStateObservationWrapper.STATE_SPEC_KEY)
48+
return dict(schema) if schema is not None else None
5749

5850

5951
class DuckDBUnavailableError(RuntimeError):
@@ -161,19 +153,15 @@ def resolve_trajectory_uuid(dataset_path: Path, trajectory_uuid: str | None, pre
161153
def restore_sim_step(
162154
env: gym.Env,
163155
recorded_step: RecordedSimStep,
164-
dynamic_joint_schema: dict[str, Any] | None = None,
156+
sim_state_spec: dict[str, Any] | None = None,
165157
):
166-
sim = env.get_wrapper_attr("sim")
167-
dynamic_joint_state = recorded_step.dynamic_joint_state
168-
if dynamic_joint_state is None:
169-
msg = "Recorded step is missing dynamic joint state data."
158+
resolved_spec = sim_state_spec or recorded_step.sim_state_spec
159+
if resolved_spec is None:
160+
msg = "Recorded sim state is missing its schema."
170161
raise ValueError(msg)
171162

172-
resolved_schema = dynamic_joint_schema or recorded_step.dynamic_joint_schema
173-
if resolved_schema is None:
174-
msg = "Recorded dynamic joint state is missing its schema."
175-
raise ValueError(msg)
176-
sim.set_dynamic_joint_state(resolved_schema, dynamic_joint_state)
163+
sim = env.get_wrapper_attr("sim")
164+
sim.set_state(recorded_step.sim_state, spec=resolved_spec)
177165

178166

179167
def collect_rgb_frames(env: gym.Env) -> dict[str, np.ndarray]:
@@ -209,17 +197,16 @@ def replay_trajectory(
209197
output_dir: Path | None = None,
210198
):
211199
if not recorded_steps:
212-
msg = "No recorded dynamic joint states found in the requested trajectory."
200+
msg = "No recorded sim states found in the requested trajectory."
213201
raise ValueError(msg)
214202

215-
dynamic_joint_schema = next(
216-
(recorded_step.dynamic_joint_schema for recorded_step in recorded_steps if recorded_step.dynamic_joint_schema),
217-
None,
203+
sim_state_spec = next(
204+
(recorded_step.sim_state_spec for recorded_step in recorded_steps if recorded_step.sim_state_spec), None
218205
)
219206

220207
env.reset()
221208
for recorded_step in recorded_steps:
222-
restore_sim_step(env, recorded_step, dynamic_joint_schema=dynamic_joint_schema)
209+
restore_sim_step(env, recorded_step, sim_state_spec=sim_state_spec)
223210
if output_dir is not None:
224211
save_rgb_frames(output_dir, recorded_step, collect_rgb_frames(env))
225212
if sleep_s > 0:

0 commit comments

Comments
 (0)