@@ -41,7 +41,6 @@ class GenericMujocoEnv(Env):
41
41
"rgb_array" ,
42
42
"depth_array" ,
43
43
],
44
- "render_fps" : 20 ,
45
44
}
46
45
_t : float
47
46
_number_movable_obstacles : int
@@ -53,7 +52,8 @@ def __init__(
53
52
goals : List [SubGoal ],
54
53
sensors : Optional [List [Sensor ]] = None ,
55
54
render : Optional [Union [str , bool ]] = None ,
56
- frame_skip : int = 5 ,
55
+ dt : float = 0.01 ,
56
+ n_sub_steps : int = 1 ,
57
57
width : int = DEFAULT_SIZE ,
58
58
height : int = DEFAULT_SIZE ,
59
59
camera_id : Optional [int ] = None ,
@@ -83,11 +83,13 @@ def __init__(
83
83
self ._enforce_real_time = True
84
84
else :
85
85
self ._enforce_real_time = False
86
+ self ._sleep_offset = 0.0
86
87
87
88
self .width = width
88
89
self .height = height
89
90
90
- self .frame_skip = frame_skip
91
+ self ._dt = dt
92
+ self ._n_sub_steps = n_sub_steps
91
93
self ._initialize_simulation ()
92
94
93
95
for sensor in self ._sensors :
@@ -98,11 +100,6 @@ def __init__(
98
100
"rgb_array" ,
99
101
"depth_array" ,
100
102
], self .metadata ["render_modes" ]
101
- if "render_fps" in self .metadata :
102
- assert (
103
- int (np .round (1.0 / self .dt )) == self .metadata ["render_fps" ]
104
- ), f'Expected value: { int (np .round (1.0 / self .dt ))} , Actual value: { self .metadata ["render_fps" ]} '
105
-
106
103
self .observation_space = self .get_observation_space ()
107
104
self .action_space = self .get_action_space ()
108
105
@@ -117,6 +114,7 @@ def __init__(
117
114
height = DEFAULT_SIZE ,
118
115
width = DEFAULT_SIZE ,
119
116
)
117
+ self ._end_last_step_time = time .time ()
120
118
121
119
def render (self ):
122
120
return self .mujoco_renderer .render (self .render_mode )
@@ -190,11 +188,12 @@ def _initialize_simulation(
190
188
self .model .body_pos [0 ] = [0 , 1 , 1 ]
191
189
self .model .vis .global_ .offwidth = self .width
192
190
self .model .vis .global_ .offheight = self .height
191
+ self .model .opt .timestep = self ._dt / self ._n_sub_steps
193
192
self .data = mujoco .MjData (self .model )
194
193
195
194
@property
196
195
def dt (self ) -> float :
197
- return self .model . opt . timestep * self . frame_skip
196
+ return self ._dt
198
197
199
198
@property
200
199
def t (self ) -> float :
@@ -221,7 +220,8 @@ def update_goals_position(self):
221
220
self .data .site_xpos [i ] = goal .position (t = self .t )
222
221
223
222
def step (self , action : np .ndarray ):
224
- step_start = time .perf_counter ()
223
+ target_end_step_time = self ._end_last_step_time + self .dt
224
+
225
225
self ._t += self .dt
226
226
truncated = False
227
227
info = {}
@@ -230,7 +230,7 @@ def step(self, action: np.ndarray):
230
230
self ._done = True
231
231
info = {"action_limits" : f"{ action } not in { self .action_space } " }
232
232
233
- self .do_simulation (action , self . frame_skip )
233
+ self .do_simulation (action )
234
234
for contact in self .data .contact :
235
235
body1 = self .model .geom (contact .geom1 ).name
236
236
body2 = self .model .geom (contact .geom2 ).name
@@ -256,15 +256,20 @@ def step(self, action: np.ndarray):
256
256
except WrongObservationError as e :
257
257
self ._done = True
258
258
info = {"observation_limits" : str (e )}
259
- step_end = time .perf_counter ()
260
- step_time = step_end - step_start
261
- if self ._enforce_real_time :
262
- sleep_time = max (0.0 , self .dt - step_time )
259
+ time_before_sleep = time .time ()
260
+ sleep_time = target_end_step_time - time_before_sleep - self ._sleep_offset
261
+ if self ._enforce_real_time and sleep_time > 0 :
263
262
time .sleep (sleep_time )
264
- step_final_end = time .perf_counter ()
265
- total_step_time = step_final_end - step_start
266
- real_time_factor = self .dt / total_step_time
267
- logging .info (f"Real time factor { real_time_factor } " )
263
+ time_after_sleep = time .time ()
264
+ # Compute the real-time factor (RTF)
265
+ real_time_step = time_after_sleep - self ._end_last_step_time
266
+ rtf = self .dt / (real_time_step )
267
+ if real_time_step < self .dt :
268
+ self ._sleep_offset -= 0.0001
269
+ else :
270
+ self ._sleep_offset += 0.0001
271
+ logging .info (f"Real time factor { rtf :.4f} " )
272
+ self ._end_last_step_time = time .time ()
268
273
return (
269
274
ob ,
270
275
reward ,
@@ -297,6 +302,7 @@ def reset(
297
302
qvel = np .zeros (self .nv )
298
303
self .set_state (qpos , qvel )
299
304
self ._t = 0.0
305
+ self ._end_last_step_time = time .time ()
300
306
return self ._get_obs (), {}
301
307
302
308
def set_state (self , qpos , qvel ):
@@ -319,21 +325,18 @@ def nq(self) -> int:
319
325
def nv (self ) -> int :
320
326
return self .model .nv - 6 * self ._number_movable_obstacles
321
327
322
- def do_simulation (self , ctrl , n_frames ) -> None :
323
- """
324
- Step the simulation n number of frames and applying a control action.
325
- """
328
+ def do_simulation (self , ctrl ) -> None :
326
329
# Check control input is contained in the action space
327
330
if np .array (ctrl ).shape != (self .model .nu ,):
328
331
raise ValueError (
329
332
f"Action dimension mismatch. Expected { (self .model .nu ,)} , found { np .array (ctrl ).shape } "
330
333
)
331
- self ._step_mujoco_simulation (ctrl , n_frames )
334
+ self ._step_mujoco_simulation (ctrl )
332
335
333
- def _step_mujoco_simulation (self , ctrl , n_frames ):
336
+ def _step_mujoco_simulation (self , ctrl ):
334
337
self .data .ctrl [:] = ctrl
335
338
336
- mujoco .mj_step (self .model , self .data , nstep = n_frames )
339
+ mujoco .mj_step (self .model , self .data , nstep = self . _n_sub_steps )
337
340
338
341
# As of MuJoCo 2.0, force-related quantities like cacc are not computed
339
342
# unless there's a force sensor in the model.
0 commit comments