env-ssl-wrapper 0.5.0__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/PKG-INFO +25 -7
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/README.md +23 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/__init__.py +7 -11
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/action_chunk.py +4 -3
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/evaluate.py +59 -8
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/memory_trace.py +5 -2
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/__init__.py +5 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/action_transform_wrapper.py +16 -10
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/adapters.py +50 -27
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/auto_batched_wrapper.py +8 -19
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/episode_padding_wrapper.py +1 -2
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py +2 -11
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/helpers.py +2 -3
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/image_wrapper.py +1 -5
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/mocks.py +47 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/spaces.py +1 -1
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/standardize_env_wrapper.py +1 -1
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/tensor_wrapper.py +26 -6
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/vector.py +4 -2
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/pyproject.toml +2 -7
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/.gitignore +0 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/LICENSE +0 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/done_tracker_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/standardize_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/time_limit_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: env-ssl-wrapper
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: One torch-native interface for any MDP environment
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/env-ssl-wrapper/
|
|
6
6
|
Project-URL: Repository, https://codeberg.org/lucidrains/env-ssl-wrapper
|
|
@@ -34,16 +34,11 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
34
34
|
Classifier: Programming Language :: Python :: 3.10
|
|
35
35
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
36
36
|
Requires-Python: >=3.10
|
|
37
|
-
Requires-Dist: discrete-continuous-embed-readout
|
|
38
37
|
Requires-Dist: einops>=0.8.1
|
|
39
|
-
Requires-Dist: einx>=0.3.0
|
|
40
38
|
Requires-Dist: imageio
|
|
41
39
|
Requires-Dist: imageio-ffmpeg
|
|
42
|
-
Requires-Dist:
|
|
43
|
-
Requires-Dist: torch-einops-utils>=0.0.29
|
|
40
|
+
Requires-Dist: numpy>=1.24
|
|
44
41
|
Requires-Dist: torch>=2.5
|
|
45
|
-
Requires-Dist: x-mlps-pytorch
|
|
46
|
-
Requires-Dist: x-transformers
|
|
47
42
|
Provides-Extra: examples
|
|
48
43
|
Requires-Dist: fire; extra == 'examples'
|
|
49
44
|
Provides-Extra: test
|
|
@@ -277,6 +272,29 @@ with MultiprocessingVecEnv('CartPole-v1', num_envs = 8) as env:
|
|
|
277
272
|
obs, info = env.reset()
|
|
278
273
|
```
|
|
279
274
|
|
|
275
|
+
## Evaluation
|
|
276
|
+
|
|
277
|
+
`evaluate_actor` rolls a policy through any standardized env and returns per-episode stats, optionally recording videos.
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
import gymnasium as gym
|
|
281
|
+
from env_ssl_wrapper import StandardizeEnvWrapper, evaluate_actor
|
|
282
|
+
|
|
283
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'))
|
|
284
|
+
|
|
285
|
+
stats = evaluate_actor(policy, env, episodes = 10, seed = 0)
|
|
286
|
+
|
|
287
|
+
stats.mean # mean return
|
|
288
|
+
stats.std # std of returns
|
|
289
|
+
stats.min, stats.max
|
|
290
|
+
stats.success_rate(475.) # fraction of episodes above a threshold
|
|
291
|
+
stats.summary() # dict(episodes, mean, std, min, max)
|
|
292
|
+
|
|
293
|
+
# deterministic = False samples from distributions instead of collapsing to the mode
|
|
294
|
+
# an actor exposing `.dist` takes precedence over calling it directly
|
|
295
|
+
# video_path = 'eval.mp4' records the first video_episodes episodes (single env only)
|
|
296
|
+
```
|
|
297
|
+
|
|
280
298
|
## Tests
|
|
281
299
|
|
|
282
300
|
```bash
|
|
@@ -212,6 +212,29 @@ with MultiprocessingVecEnv('CartPole-v1', num_envs = 8) as env:
|
|
|
212
212
|
obs, info = env.reset()
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
## Evaluation
|
|
216
|
+
|
|
217
|
+
`evaluate_actor` rolls a policy through any standardized env and returns per-episode stats, optionally recording videos.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import gymnasium as gym
|
|
221
|
+
from env_ssl_wrapper import StandardizeEnvWrapper, evaluate_actor
|
|
222
|
+
|
|
223
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'))
|
|
224
|
+
|
|
225
|
+
stats = evaluate_actor(policy, env, episodes = 10, seed = 0)
|
|
226
|
+
|
|
227
|
+
stats.mean # mean return
|
|
228
|
+
stats.std # std of returns
|
|
229
|
+
stats.min, stats.max
|
|
230
|
+
stats.success_rate(475.) # fraction of episodes above a threshold
|
|
231
|
+
stats.summary() # dict(episodes, mean, std, min, max)
|
|
232
|
+
|
|
233
|
+
# deterministic = False samples from distributions instead of collapsing to the mode
|
|
234
|
+
# an actor exposing `.dist` takes precedence over calling it directly
|
|
235
|
+
# video_path = 'eval.mp4' records the first video_episodes episodes (single env only)
|
|
236
|
+
```
|
|
237
|
+
|
|
215
238
|
## Tests
|
|
216
239
|
|
|
217
240
|
```bash
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
+
|
|
4
5
|
from . import standardize
|
|
5
|
-
from .standardize import *
|
|
6
|
+
from .standardize import * # noqa: F403
|
|
6
7
|
|
|
7
8
|
from .memory_trace import MemoryTraceWrapper
|
|
8
9
|
from .action_chunk import ActionChunkWrapper
|
|
9
10
|
from .evaluate import evaluate_actor, EpisodeStats
|
|
10
11
|
|
|
11
|
-
#
|
|
12
|
-
# so
|
|
13
|
-
# or `import env_ssl_wrapper.mocks` continue to work without breaking.
|
|
12
|
+
# backwards compatibility — the standardize wrappers used to live at the top
|
|
13
|
+
# level, so keep the old module paths (e.g. `env_ssl_wrapper.mocks`) importable
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
_LEGACY_MODULES = (
|
|
16
16
|
'adapters',
|
|
17
17
|
'auto_batched_wrapper',
|
|
18
18
|
'action_transform_wrapper',
|
|
@@ -31,11 +31,8 @@ _STANDARDIZE_SUBMODULES = (
|
|
|
31
31
|
'vector',
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
for _name in
|
|
35
|
-
|
|
36
|
-
if _mod is not None:
|
|
37
|
-
sys.modules[f'{__name__}.{_name}'] = _mod
|
|
38
|
-
globals()[_name] = _mod
|
|
34
|
+
for _name in _LEGACY_MODULES:
|
|
35
|
+
sys.modules[f'{__name__}.{_name}'] = getattr(standardize, _name)
|
|
39
36
|
|
|
40
37
|
__all__ = [
|
|
41
38
|
*standardize.__all__,
|
|
@@ -49,4 +46,3 @@ def __getattr__(name):
|
|
|
49
46
|
if hasattr(standardize, name):
|
|
50
47
|
return getattr(standardize, name)
|
|
51
48
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
52
|
-
|
|
@@ -12,6 +12,7 @@ from .standardize.helpers import (
|
|
|
12
12
|
any_true,
|
|
13
13
|
dones_of,
|
|
14
14
|
first_existing,
|
|
15
|
+
get_adapter,
|
|
15
16
|
get_attr,
|
|
16
17
|
is_vectorized,
|
|
17
18
|
to_numpy,
|
|
@@ -70,7 +71,7 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
70
71
|
self.action_shape = tuple(get_attr(action_space, 'shape', ()) or ())
|
|
71
72
|
|
|
72
73
|
self.expects_batch = is_vectorized(env)
|
|
73
|
-
self.
|
|
74
|
+
self.convert_in = not isinstance(env, EnvWrapper) and not get_adapter(env).torch_native
|
|
74
75
|
|
|
75
76
|
@property
|
|
76
77
|
def chunk_action_shape(self):
|
|
@@ -86,9 +87,9 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
86
87
|
if not (is_tensor(actions) or isinstance(actions, np.ndarray)):
|
|
87
88
|
actions = np.asarray(actions)
|
|
88
89
|
|
|
89
|
-
# raw simulators only understand numpy
|
|
90
|
+
# raw simulators only understand numpy — wrapper chains and torch-native sims take torch
|
|
90
91
|
|
|
91
|
-
if
|
|
92
|
+
if is_tensor(actions) and self.convert_in:
|
|
92
93
|
actions = to_numpy(actions)
|
|
93
94
|
|
|
94
95
|
chunk_axis = actions.ndim - 1 - len(self.action_shape)
|
|
@@ -9,6 +9,7 @@ from torch import nn, is_tensor
|
|
|
9
9
|
from torch.distributions import Categorical, Distribution
|
|
10
10
|
from torch.utils._pytree import tree_flatten, tree_map
|
|
11
11
|
|
|
12
|
+
from .standardize.spaces import action_space_is_discrete
|
|
12
13
|
from .standardize.helpers import (
|
|
13
14
|
EnvWrapper,
|
|
14
15
|
env_autoresets,
|
|
@@ -93,7 +94,19 @@ def to_action(env, out, deterministic = True):
|
|
|
93
94
|
if not deterministic:
|
|
94
95
|
return out.sample()
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
probs = get_attr(out, 'probs')
|
|
98
|
+
|
|
99
|
+
# categorical-style probs carry a trailing action axis to argmax over
|
|
100
|
+
|
|
101
|
+
if exists(probs) and probs.ndim >= 2 and probs.shape[-1] > 1:
|
|
102
|
+
return probs.argmax(dim = -1)
|
|
103
|
+
|
|
104
|
+
# bernoulli-style probabilities threshold at 0.5
|
|
105
|
+
|
|
106
|
+
if exists(probs):
|
|
107
|
+
return (probs > 0.5).long()
|
|
108
|
+
|
|
109
|
+
return out.mean
|
|
97
110
|
|
|
98
111
|
if is_tensor(out) and out.is_floating_point() and out.ndim > 0:
|
|
99
112
|
num_actions = get_attr(get_adapter(env).action_space, 'n')
|
|
@@ -128,15 +141,29 @@ def to_env_action(env, action):
|
|
|
128
141
|
device = get_attr(env, 'device')
|
|
129
142
|
action = action.to(device) if exists(device) else action
|
|
130
143
|
|
|
131
|
-
# raw simulators take unbatched numpy
|
|
144
|
+
# raw simulators take unbatched numpy — torch-native sims take torch as-is (zero-copy)
|
|
132
145
|
|
|
133
146
|
if not isinstance(env, EnvWrapper):
|
|
147
|
+
if get_adapter(env).torch_native:
|
|
148
|
+
return action
|
|
149
|
+
|
|
134
150
|
action = to_numpy(action)
|
|
135
151
|
|
|
136
|
-
|
|
137
|
-
|
|
152
|
+
# single raw sims get the leading batch dim stripped, while discrete
|
|
153
|
+
# actions additionally collapse to scalars
|
|
154
|
+
|
|
155
|
+
if not is_vectorized(env):
|
|
156
|
+
if action.ndim > 1 and action.shape[0] == 1:
|
|
157
|
+
action = action[0]
|
|
158
|
+
|
|
159
|
+
if action_space_is_discrete(get_adapter(env).action_space):
|
|
160
|
+
if action.ndim > 0 and action.shape[0] == 1:
|
|
161
|
+
action = action[0]
|
|
162
|
+
|
|
163
|
+
if action.ndim == 0:
|
|
164
|
+
action = action.item()
|
|
138
165
|
|
|
139
|
-
return action
|
|
166
|
+
return action
|
|
140
167
|
|
|
141
168
|
# wrappers take batched torch actions - pad up to the declared rank
|
|
142
169
|
|
|
@@ -166,6 +193,9 @@ def capture_frame(env):
|
|
|
166
193
|
except Exception:
|
|
167
194
|
return None
|
|
168
195
|
|
|
196
|
+
if not exists(frame):
|
|
197
|
+
return None
|
|
198
|
+
|
|
169
199
|
if is_tensor(frame):
|
|
170
200
|
frame = frame.detach().cpu().numpy()
|
|
171
201
|
|
|
@@ -197,6 +227,17 @@ def video_path_at(path, index):
|
|
|
197
227
|
path = Path(path)
|
|
198
228
|
return str(path if index == 0 else path.with_name(f'{path.stem}_ep{index}{path.suffix}'))
|
|
199
229
|
|
|
230
|
+
def safe_reset(env, seed = None):
|
|
231
|
+
# gymnasium resets accept seed; legacy sims reset without it
|
|
232
|
+
|
|
233
|
+
if exists(seed):
|
|
234
|
+
try:
|
|
235
|
+
return normalize_reset_out(env.reset(seed = seed))
|
|
236
|
+
except TypeError:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
return normalize_reset_out(env.reset())
|
|
240
|
+
|
|
200
241
|
# evaluate
|
|
201
242
|
|
|
202
243
|
@torch.no_grad()
|
|
@@ -238,7 +279,7 @@ def evaluate_actor(
|
|
|
238
279
|
|
|
239
280
|
assert not (record and num_envs > 1), 'video recording requires a single env'
|
|
240
281
|
|
|
241
|
-
obs, info =
|
|
282
|
+
obs, info = safe_reset(env, seed)
|
|
242
283
|
batched = is_vectorized(env) or all(map(is_tensor, tree_flatten(obs)[0]))
|
|
243
284
|
|
|
244
285
|
goal = goal.expand(num_envs, -1) if exists(goal) and goal.shape[0] == 1 and num_envs > 1 else goal
|
|
@@ -246,6 +287,7 @@ def evaluate_actor(
|
|
|
246
287
|
returns = torch.zeros(num_envs)
|
|
247
288
|
lengths = torch.zeros(num_envs)
|
|
248
289
|
steps = torch.zeros(num_envs, dtype = torch.long)
|
|
290
|
+
active = np.ones(num_envs, dtype = bool)
|
|
249
291
|
|
|
250
292
|
done_returns, done_lengths, videos, frames = [], [], [], []
|
|
251
293
|
|
|
@@ -282,7 +324,12 @@ def evaluate_actor(
|
|
|
282
324
|
if exists(max_steps):
|
|
283
325
|
done |= to_numpy(steps >= max_steps).reshape(-1)
|
|
284
326
|
|
|
285
|
-
|
|
327
|
+
# a slot is only recorded once per episode — non-autoreset slots
|
|
328
|
+
# latch their done flag until the whole vector env resets
|
|
329
|
+
|
|
330
|
+
newly_done = done & active
|
|
331
|
+
|
|
332
|
+
for i in np.where(newly_done)[0]:
|
|
286
333
|
if len(done_returns) >= episodes:
|
|
287
334
|
break
|
|
288
335
|
|
|
@@ -306,6 +353,7 @@ def evaluate_actor(
|
|
|
306
353
|
returns[i] = 0.
|
|
307
354
|
lengths[i] = 0.
|
|
308
355
|
steps[i] = 0
|
|
356
|
+
active[i] = False
|
|
309
357
|
|
|
310
358
|
if done.any() and len(done_returns) < episodes:
|
|
311
359
|
if record:
|
|
@@ -315,7 +363,10 @@ def evaluate_actor(
|
|
|
315
363
|
|
|
316
364
|
if not env_autoresets(env) and (num_envs == 1 or done.all() or bool(get_attr(env, 'needs_reset', False))):
|
|
317
365
|
reset_seed = seed + len(done_returns) if exists(seed) and num_envs == 1 else None
|
|
318
|
-
obs, info =
|
|
366
|
+
obs, info = safe_reset(env, reset_seed)
|
|
367
|
+
active[:] = True
|
|
368
|
+
elif env_autoresets(env):
|
|
369
|
+
active[newly_done] = True
|
|
319
370
|
finally:
|
|
320
371
|
if exists(module) and exists(was_training):
|
|
321
372
|
module.train(was_training)
|
|
@@ -3,7 +3,6 @@ from typing import Sequence
|
|
|
3
3
|
|
|
4
4
|
import torch
|
|
5
5
|
from torch import is_tensor
|
|
6
|
-
from torch_einops_utils import pad_right_ndim_to
|
|
7
6
|
|
|
8
7
|
from .standardize.helpers import (
|
|
9
8
|
TransformObservationWrapper,
|
|
@@ -27,7 +26,11 @@ def calc_lerp_weight(lam, done, x):
|
|
|
27
26
|
done = torch.as_tensor(done, device = x.device)
|
|
28
27
|
|
|
29
28
|
weight = torch.where(done, 1., 1. - lam).to(x)
|
|
30
|
-
|
|
29
|
+
|
|
30
|
+
if x.ndim <= weight.ndim:
|
|
31
|
+
return weight
|
|
32
|
+
|
|
33
|
+
return weight.reshape(*weight.shape, *((1,) * (x.ndim - weight.ndim)))
|
|
31
34
|
|
|
32
35
|
# Partially Observable Reinforcement Learning with Memory Traces - Eberhard et al.
|
|
33
36
|
# https://arxiv.org/abs/2503.15200
|
|
@@ -34,6 +34,7 @@ from .adapters import (
|
|
|
34
34
|
BaseEnvAdapter,
|
|
35
35
|
get_adapter,
|
|
36
36
|
register_adapter,
|
|
37
|
+
MjlabAdapter,
|
|
37
38
|
MujocoWarpAdapter,
|
|
38
39
|
IsaacAdapter,
|
|
39
40
|
PyBulletAdapter,
|
|
@@ -68,6 +69,7 @@ from .mocks import (
|
|
|
68
69
|
AutoresetVectorMockEnv,
|
|
69
70
|
PufferVectorMockEnv,
|
|
70
71
|
PufferTensorMockEnv,
|
|
72
|
+
MjlabMockEnv,
|
|
71
73
|
ManiSkillMockEnv,
|
|
72
74
|
BraxMockEnv,
|
|
73
75
|
MjxMockEnv,
|
|
@@ -118,9 +120,11 @@ __all__ = [
|
|
|
118
120
|
'MultiprocessingVecEnv',
|
|
119
121
|
'StandardizeEnvWrapper',
|
|
120
122
|
'StandardizeEnv',
|
|
123
|
+
'StandardizedEnv',
|
|
121
124
|
'BaseEnvAdapter',
|
|
122
125
|
'get_adapter',
|
|
123
126
|
'register_adapter',
|
|
127
|
+
'MjlabAdapter',
|
|
124
128
|
'MujocoWarpAdapter',
|
|
125
129
|
'IsaacAdapter',
|
|
126
130
|
'PyBulletAdapter',
|
|
@@ -149,6 +153,7 @@ __all__ = [
|
|
|
149
153
|
'AutoresetVectorMockEnv',
|
|
150
154
|
'PufferVectorMockEnv',
|
|
151
155
|
'PufferTensorMockEnv',
|
|
156
|
+
'MjlabMockEnv',
|
|
152
157
|
'ManiSkillMockEnv',
|
|
153
158
|
'BraxMockEnv',
|
|
154
159
|
'MjxMockEnv',
|
|
@@ -5,7 +5,6 @@ import torch
|
|
|
5
5
|
from torch import is_tensor
|
|
6
6
|
from torch.utils._pytree import tree_map
|
|
7
7
|
from functools import partial
|
|
8
|
-
from einops import rearrange
|
|
9
8
|
|
|
10
9
|
from .helpers import EnvWrapper, copy_leaf, default, exists, first_existing, get_attr
|
|
11
10
|
from .spaces import space_from_action_spec
|
|
@@ -95,7 +94,7 @@ class ActionTransformWrapper(EnvWrapper):
|
|
|
95
94
|
was_scalar = t.ndim == 0
|
|
96
95
|
|
|
97
96
|
if was_scalar:
|
|
98
|
-
t =
|
|
97
|
+
t = t[None]
|
|
99
98
|
|
|
100
99
|
# bounds may be unbatched (single_action_space) or already batched
|
|
101
100
|
# (vector envs exposing only a batched action_space)
|
|
@@ -105,22 +104,28 @@ class ActionTransformWrapper(EnvWrapper):
|
|
|
105
104
|
low = np.broadcast_to(low, (dim,))
|
|
106
105
|
high = np.broadcast_to(high, (dim,))
|
|
107
106
|
|
|
107
|
+
# invalid (unbounded) dims pass through — neutral bounds keep inf out of the arithmetic
|
|
108
|
+
|
|
108
109
|
valid = np.isfinite(low) & np.isfinite(high)
|
|
109
110
|
|
|
110
111
|
if is_tensor(t):
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
valid_t = torch.as_tensor(valid, device = t.device)
|
|
113
|
+
low = torch.as_tensor(np.where(valid, low, 0.), device = t.device, dtype = t.dtype)
|
|
114
|
+
high = torch.as_tensor(np.where(valid, high, 1.), device = t.device, dtype = t.dtype)
|
|
115
|
+
else:
|
|
116
|
+
valid_t = valid
|
|
117
|
+
low = np.where(valid, low, 0.)
|
|
118
|
+
high = np.where(valid, high, 1.)
|
|
114
119
|
|
|
115
120
|
rescaled = rescale(t, self.from_range, (low, high))
|
|
116
121
|
|
|
117
122
|
if is_tensor(t):
|
|
118
|
-
rescaled = torch.where(
|
|
123
|
+
rescaled = torch.where(valid_t, torch.clamp(rescaled, low, high), t)
|
|
119
124
|
else:
|
|
120
|
-
rescaled = np.where(
|
|
125
|
+
rescaled = np.where(valid_t, np.clip(rescaled, low, high), t)
|
|
121
126
|
|
|
122
127
|
if was_scalar:
|
|
123
|
-
rescaled =
|
|
128
|
+
rescaled = rescaled[0]
|
|
124
129
|
|
|
125
130
|
return rescaled
|
|
126
131
|
|
|
@@ -129,8 +134,6 @@ class ActionTransformWrapper(EnvWrapper):
|
|
|
129
134
|
if not is_float_dtype(t):
|
|
130
135
|
return t
|
|
131
136
|
|
|
132
|
-
t = copy_leaf(t)
|
|
133
|
-
|
|
134
137
|
for ind, transform in enumerate(self.transforms):
|
|
135
138
|
indices = transform.get('indices', ind if len(self.transforms) > 1 else None)
|
|
136
139
|
|
|
@@ -141,6 +144,9 @@ class ActionTransformWrapper(EnvWrapper):
|
|
|
141
144
|
fn = partial(rescale, from_range = from_range, to_range = to_range)
|
|
142
145
|
|
|
143
146
|
if exists(indices):
|
|
147
|
+
# copy only when writing in place, so the caller's action is never mutated
|
|
148
|
+
|
|
149
|
+
t = copy_leaf(t)
|
|
144
150
|
t[..., indices] = fn(t[..., indices])
|
|
145
151
|
else:
|
|
146
152
|
t = fn(t)
|
|
@@ -6,18 +6,20 @@ from .helpers import (
|
|
|
6
6
|
exists,
|
|
7
7
|
first_existing,
|
|
8
8
|
get_attr,
|
|
9
|
-
is_time_step,
|
|
10
9
|
normalize_reset_out,
|
|
11
10
|
normalize_step_out,
|
|
12
11
|
safe_close,
|
|
13
12
|
truthy_attr,
|
|
14
|
-
zero_like,
|
|
15
13
|
)
|
|
16
14
|
from .spaces import space_from_action_spec
|
|
17
15
|
|
|
18
16
|
# base adapter
|
|
19
17
|
|
|
20
18
|
class BaseEnvAdapter:
|
|
19
|
+
# torch-native sims consume torch actions directly on device — no numpy roundtrip
|
|
20
|
+
|
|
21
|
+
torch_native = False
|
|
22
|
+
|
|
21
23
|
@classmethod
|
|
22
24
|
def matches(cls, env) -> bool:
|
|
23
25
|
return False
|
|
@@ -148,6 +150,10 @@ class WrapperAdapter(BaseEnvAdapter):
|
|
|
148
150
|
val = first_existing(self.env, 'autoreset', 'autoresets', 'autoreset_mode')
|
|
149
151
|
return truthy_attr(val) if exists(val) else self.inner_adapter.autoresets
|
|
150
152
|
|
|
153
|
+
@property
|
|
154
|
+
def torch_native(self) -> bool:
|
|
155
|
+
return self.inner_adapter.torch_native
|
|
156
|
+
|
|
151
157
|
@property
|
|
152
158
|
def action_space(self):
|
|
153
159
|
return default(
|
|
@@ -194,13 +200,8 @@ class DMControlAdapter(BaseEnvAdapter):
|
|
|
194
200
|
def action_space(self):
|
|
195
201
|
return default(super().action_space, space_from_action_spec(self.env))
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return False
|
|
200
|
-
|
|
201
|
-
@property
|
|
202
|
-
def autoresets(self) -> bool:
|
|
203
|
-
return False
|
|
203
|
+
is_vectorized = False
|
|
204
|
+
autoresets = False
|
|
204
205
|
|
|
205
206
|
# pybullet adapter
|
|
206
207
|
|
|
@@ -227,17 +228,32 @@ class PyBulletAdapter(BaseEnvAdapter):
|
|
|
227
228
|
return
|
|
228
229
|
super().seed(seed)
|
|
229
230
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
is_vectorized = False
|
|
232
|
+
autoresets = False
|
|
233
|
+
|
|
234
|
+
# mjlab adapter (manager-based rl envs on mujoco warp)
|
|
235
|
+
|
|
236
|
+
class MjlabAdapter(BaseEnvAdapter):
|
|
237
|
+
torch_native = True
|
|
238
|
+
is_vectorized = True
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def matches(cls, env):
|
|
242
|
+
mod = getattr(type(env), '__module__', '')
|
|
243
|
+
name = type(env).__name__
|
|
244
|
+
return 'mjlab' in mod.lower() or name == 'ManagerBasedRlEnv'
|
|
233
245
|
|
|
234
246
|
@property
|
|
235
247
|
def autoresets(self) -> bool:
|
|
236
|
-
return
|
|
248
|
+
return truthy_attr(get_attr(get_attr(self.env, 'cfg'), 'auto_reset', True))
|
|
237
249
|
|
|
238
250
|
# isaac sim adapter (isaac gym / isaac lab / omniverse)
|
|
239
251
|
|
|
240
252
|
class IsaacAdapter(BaseEnvAdapter):
|
|
253
|
+
torch_native = True
|
|
254
|
+
is_vectorized = True
|
|
255
|
+
autoresets = True
|
|
256
|
+
|
|
241
257
|
@classmethod
|
|
242
258
|
def matches(cls, env):
|
|
243
259
|
mod = getattr(type(env), '__module__', '')
|
|
@@ -245,14 +261,6 @@ class IsaacAdapter(BaseEnvAdapter):
|
|
|
245
261
|
isaac_kw = ('isaac', 'omni.isaac', 'isaacgym', 'isaaclab')
|
|
246
262
|
return any(k in mod.lower() for k in isaac_kw) or 'Isaac' in name or exists(get_attr(env, 'sim_device')) or exists(get_attr(env, 'physics_sim_view'))
|
|
247
263
|
|
|
248
|
-
@property
|
|
249
|
-
def is_vectorized(self) -> bool:
|
|
250
|
-
return True
|
|
251
|
-
|
|
252
|
-
@property
|
|
253
|
-
def autoresets(self) -> bool:
|
|
254
|
-
return True
|
|
255
|
-
|
|
256
264
|
# mujoco warp / warp / brax / mjx adapter
|
|
257
265
|
|
|
258
266
|
class MujocoWarpAdapter(BaseEnvAdapter):
|
|
@@ -276,16 +284,14 @@ class MujocoWarpAdapter(BaseEnvAdapter):
|
|
|
276
284
|
# pufferlib adapter
|
|
277
285
|
|
|
278
286
|
class PufferLibAdapter(BaseEnvAdapter):
|
|
287
|
+
is_vectorized = True
|
|
288
|
+
|
|
279
289
|
@classmethod
|
|
280
290
|
def matches(cls, env):
|
|
281
291
|
mod = getattr(type(env), '__module__', '')
|
|
282
292
|
name = type(env).__name__
|
|
283
293
|
return 'Puffer' in name or 'pufferlib' in mod or exists(get_attr(env, 'puffer_env'))
|
|
284
294
|
|
|
285
|
-
@property
|
|
286
|
-
def is_vectorized(self) -> bool:
|
|
287
|
-
return True
|
|
288
|
-
|
|
289
295
|
@property
|
|
290
296
|
def autoresets(self) -> bool:
|
|
291
297
|
return truthy_attr(first_existing(self.env, 'autoreset', 'autoresets', 'autoreset_mode'))
|
|
@@ -350,6 +356,18 @@ class GymnasiumAdapter(BaseEnvAdapter):
|
|
|
350
356
|
pass
|
|
351
357
|
return self.num_envs > 1
|
|
352
358
|
|
|
359
|
+
def unwrapped_adapter(self):
|
|
360
|
+
# foreign gym wrappers (TimeLimit, RecordEpisodeStatistics, ...) inherit
|
|
361
|
+
# the unwrapped sim's adapter traits
|
|
362
|
+
|
|
363
|
+
inner = get_attr(self.env, 'unwrapped')
|
|
364
|
+
return get_adapter(inner) if exists(inner) and inner is not self.env else None
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def torch_native(self) -> bool:
|
|
368
|
+
inner = self.unwrapped_adapter()
|
|
369
|
+
return exists(inner) and inner.torch_native
|
|
370
|
+
|
|
353
371
|
@property
|
|
354
372
|
def autoresets(self) -> bool:
|
|
355
373
|
mode = first_existing(self.env, 'autoreset', 'autoresets', 'autoreset_mode')
|
|
@@ -359,9 +377,13 @@ class GymnasiumAdapter(BaseEnvAdapter):
|
|
|
359
377
|
|
|
360
378
|
try:
|
|
361
379
|
from gymnasium.vector import VectorEnv
|
|
362
|
-
|
|
380
|
+
if isinstance(self.env, VectorEnv):
|
|
381
|
+
return True
|
|
363
382
|
except ImportError:
|
|
364
|
-
|
|
383
|
+
pass
|
|
384
|
+
|
|
385
|
+
inner = self.unwrapped_adapter()
|
|
386
|
+
return exists(inner) and inner.autoresets
|
|
365
387
|
|
|
366
388
|
def seed(self, seed: int):
|
|
367
389
|
try:
|
|
@@ -400,6 +422,7 @@ class DefaultAdapter(BaseEnvAdapter):
|
|
|
400
422
|
|
|
401
423
|
ADAPTER_REGISTRY: list[type[BaseEnvAdapter]] = [
|
|
402
424
|
WrapperAdapter,
|
|
425
|
+
MjlabAdapter,
|
|
403
426
|
MujocoWarpAdapter,
|
|
404
427
|
IsaacAdapter,
|
|
405
428
|
PyBulletAdapter,
|
{env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/auto_batched_wrapper.py
RENAMED
|
@@ -5,7 +5,6 @@ from functools import partial
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import torch
|
|
7
7
|
from torch.utils._pytree import tree_map
|
|
8
|
-
from einops import rearrange
|
|
9
8
|
|
|
10
9
|
from .helpers import (
|
|
11
10
|
FINAL_OBSERVATION_KEYS,
|
|
@@ -19,7 +18,6 @@ from .helpers import (
|
|
|
19
18
|
is_tensor,
|
|
20
19
|
is_vectorized,
|
|
21
20
|
)
|
|
22
|
-
from .spaces import space_from_action_spec
|
|
23
21
|
|
|
24
22
|
# helper functions
|
|
25
23
|
|
|
@@ -41,22 +39,12 @@ def maybe_expand_dim(x):
|
|
|
41
39
|
|
|
42
40
|
def _expand(t):
|
|
43
41
|
arr = to_numeric_array(t)
|
|
42
|
+
return arr[None] if is_array(arr) else t
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if is_array(x):
|
|
45
|
+
return x[None]
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
return rearrange(arr, '-> 1')
|
|
50
|
-
|
|
51
|
-
return rearrange(arr, '... -> 1 ...')
|
|
52
|
-
|
|
53
|
-
if is_tensor(x):
|
|
54
|
-
return rearrange(x, '-> 1') if x.ndim == 0 else rearrange(x, '... -> 1 ...')
|
|
55
|
-
|
|
56
|
-
if isinstance(x, np.ndarray) and x.dtype.kind in 'biufc':
|
|
57
|
-
return rearrange(x, '-> 1') if x.ndim == 0 else rearrange(x, '... -> 1 ...')
|
|
58
|
-
|
|
59
|
-
return tree_map(_expand, x)
|
|
47
|
+
return tree_map(_expand, x) if isinstance(x, (dict, list, tuple)) else _expand(x)
|
|
60
48
|
|
|
61
49
|
def is_integer_dtype(t):
|
|
62
50
|
if is_tensor(t):
|
|
@@ -121,11 +109,11 @@ def heuristic_leaf(t, is_vector = False):
|
|
|
121
109
|
return arr
|
|
122
110
|
|
|
123
111
|
if not is_vector and arr.ndim > 1 and arr.shape[0] == 1:
|
|
124
|
-
arr =
|
|
112
|
+
arr = arr.reshape(*arr.shape[1:])
|
|
125
113
|
|
|
126
114
|
if is_integer_dtype(arr):
|
|
127
115
|
while arr.ndim > 1 and arr.shape[-1] == 1:
|
|
128
|
-
arr =
|
|
116
|
+
arr = arr.reshape(*arr.shape[:-1])
|
|
129
117
|
|
|
130
118
|
if not is_vector and (arr.numel() if is_tensor(arr) else arr.size) == 1:
|
|
131
119
|
return arr.item()
|
|
@@ -207,7 +195,8 @@ class AutoBatchedWrapper(EnvWrapper):
|
|
|
207
195
|
if self.is_vector:
|
|
208
196
|
return out
|
|
209
197
|
|
|
210
|
-
obs, reward, terminated, truncated, info =
|
|
198
|
+
obs, reward, terminated, truncated, info = out
|
|
199
|
+
obs, reward, terminated, truncated = (maybe_expand_dim(t) for t in (obs, reward, terminated, truncated))
|
|
211
200
|
|
|
212
201
|
if isinstance(info, dict):
|
|
213
202
|
for key in FINAL_OBSERVATION_KEYS:
|
|
@@ -6,7 +6,6 @@ import numpy as np
|
|
|
6
6
|
import torch
|
|
7
7
|
from torch import is_tensor
|
|
8
8
|
from torch.utils._pytree import tree_map
|
|
9
|
-
from einops import rearrange
|
|
10
9
|
|
|
11
10
|
from .helpers import (
|
|
12
11
|
EnvWrapper,
|
|
@@ -28,7 +27,7 @@ def broadcast_mask(mask, x):
|
|
|
28
27
|
m = torch.as_tensor(mask, device = x.device, dtype = torch.bool)
|
|
29
28
|
diff = x.ndim - m.ndim
|
|
30
29
|
|
|
31
|
-
return
|
|
30
|
+
return m.reshape(*m.shape, *((1,) * diff)) if diff > 0 else m
|
|
32
31
|
|
|
33
32
|
def zero_mask(x, mask, fill_scalar = None):
|
|
34
33
|
if is_tensor(x):
|
{env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py
RENAMED
|
@@ -4,7 +4,6 @@ import numpy as np
|
|
|
4
4
|
import torch
|
|
5
5
|
from torch import is_tensor
|
|
6
6
|
from torch.utils._pytree import tree_flatten
|
|
7
|
-
from einops import rearrange
|
|
8
7
|
|
|
9
8
|
from .helpers import (
|
|
10
9
|
TransformObservationWrapper,
|
|
@@ -30,17 +29,9 @@ def flatten_leaf(t, is_vector = False):
|
|
|
30
29
|
t = np.asarray(t)
|
|
31
30
|
|
|
32
31
|
if not is_vector:
|
|
33
|
-
if t.ndim == 0
|
|
34
|
-
return rearrange(t, '-> 1')
|
|
35
|
-
return rearrange(t, '... -> (...)')
|
|
32
|
+
return t.reshape(1) if t.ndim == 0 else t.reshape(-1)
|
|
36
33
|
|
|
37
|
-
if t.ndim == 0
|
|
38
|
-
return rearrange(t, '-> 1 1')
|
|
39
|
-
|
|
40
|
-
if t.ndim == 1:
|
|
41
|
-
return rearrange(t, 'b -> b 1')
|
|
42
|
-
|
|
43
|
-
return rearrange(t, 'b ... -> b (...)')
|
|
34
|
+
return t.reshape(1, 1) if t.ndim == 0 else t.reshape(t.shape[0], -1)
|
|
44
35
|
|
|
45
36
|
def concat_leaves(leaves, is_vector = False):
|
|
46
37
|
axis = -1 if is_vector else 0
|
|
@@ -84,9 +84,6 @@ def copy_leaf(x):
|
|
|
84
84
|
|
|
85
85
|
return x
|
|
86
86
|
|
|
87
|
-
def copy_tree(tree):
|
|
88
|
-
return tree_map(copy_leaf, tree)
|
|
89
|
-
|
|
90
87
|
def dones_of(terminated, truncated):
|
|
91
88
|
if not isinstance(terminated, (dict, list, tuple)):
|
|
92
89
|
return terminated | truncated
|
|
@@ -138,6 +135,8 @@ def _zero_leaf(x):
|
|
|
138
135
|
return False
|
|
139
136
|
if isinstance(x, (int, float, np.number)):
|
|
140
137
|
return np.zeros_like(x)
|
|
138
|
+
if exists(get_attr(x, '__array__')):
|
|
139
|
+
return np.zeros_like(np.asarray(x))
|
|
141
140
|
return 0
|
|
142
141
|
|
|
143
142
|
def _stack_leaves(leaves):
|
{env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/image_wrapper.py
RENAMED
|
@@ -39,14 +39,10 @@ def render_frame(env, image_size = (64, 64), camera = None):
|
|
|
39
39
|
|
|
40
40
|
img = env.render()
|
|
41
41
|
|
|
42
|
-
if isinstance(img, (list, tuple)):
|
|
43
|
-
img = np.ascontiguousarray(img)
|
|
44
|
-
|
|
45
42
|
if isinstance(img, torch.Tensor):
|
|
46
43
|
img = img.detach().cpu().numpy()
|
|
47
44
|
|
|
48
|
-
img = np.ascontiguousarray(img)
|
|
49
|
-
img = torch.from_numpy(img)
|
|
45
|
+
img = torch.from_numpy(np.ascontiguousarray(img))
|
|
50
46
|
|
|
51
47
|
if img.ndim == 4:
|
|
52
48
|
return rearrange(img, 'b h w c -> b c h w'), True
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from types import SimpleNamespace
|
|
3
4
|
from typing import NamedTuple
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
@@ -391,6 +392,52 @@ class IsaacLabMockEnv(MockEnv):
|
|
|
391
392
|
|
|
392
393
|
return self.obs(), torch.randn(self.num_envs), dones, torch.zeros(self.num_envs, dtype = torch.bool), {}
|
|
393
394
|
|
|
395
|
+
# mjlab — manager-based RL on mujoco warp: always vectorized, torch-native
|
|
396
|
+
# (step takes torch tensors only), autoresets within step, dict obs of torch tensors
|
|
397
|
+
|
|
398
|
+
class MjlabMockEnv(MockEnv):
|
|
399
|
+
num_envs = 4
|
|
400
|
+
is_vector = True
|
|
401
|
+
is_vector_env = True
|
|
402
|
+
|
|
403
|
+
def __init__(self, num_envs = 4, auto_reset = True, seed = 0):
|
|
404
|
+
self.num_envs = num_envs
|
|
405
|
+
self.cfg = SimpleNamespace(auto_reset = auto_reset)
|
|
406
|
+
super().__init__(seed)
|
|
407
|
+
|
|
408
|
+
def reset(self, *, seed = None, env_ids = None, options = None):
|
|
409
|
+
if seed is not None:
|
|
410
|
+
self.seed(seed)
|
|
411
|
+
else:
|
|
412
|
+
self.reset_state()
|
|
413
|
+
return self.obs(), {}
|
|
414
|
+
|
|
415
|
+
def obs(self):
|
|
416
|
+
return dict(
|
|
417
|
+
policy = torch.randn(self.num_envs, self.obs_dim),
|
|
418
|
+
critic = torch.randn(self.num_envs, self.obs_dim * 2)
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def step(self, action):
|
|
422
|
+
if not is_tensor(action):
|
|
423
|
+
raise TypeError(f'mjlab envs step on torch tensors, got {type(action).__name__}')
|
|
424
|
+
|
|
425
|
+
self.advance(action)
|
|
426
|
+
self.last_action = action
|
|
427
|
+
|
|
428
|
+
dones = torch.from_numpy(self.is_done())
|
|
429
|
+
|
|
430
|
+
if self.cfg.auto_reset:
|
|
431
|
+
for ind in np.where(dones.numpy())[0]:
|
|
432
|
+
self.t[ind] = 0
|
|
433
|
+
self.state[ind] = 0
|
|
434
|
+
|
|
435
|
+
return self.obs(), torch.ones(self.num_envs), dones, torch.zeros(self.num_envs, dtype = torch.bool), {}
|
|
436
|
+
|
|
437
|
+
# live in the real module path so the adapter recognizes the mock
|
|
438
|
+
|
|
439
|
+
MjlabMockEnv.__module__ = 'mjlab.envs.manager_based_rl_env'
|
|
440
|
+
|
|
394
441
|
# maniskill — gymnasium-compliant, but always batched even at num_envs = 1:
|
|
395
442
|
# obs / reward / dones are torch tensors with a leading batch dim, and the
|
|
396
443
|
# env exposes single_action_space / single_observation_space (unbatched)
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from torch.utils._pytree import tree_map
|
|
5
5
|
|
|
6
|
-
from .helpers import default, exists,
|
|
6
|
+
from .helpers import default, exists, get_attr, to_numpy
|
|
7
7
|
|
|
8
8
|
# stand-in for environments that do not expose gymnasium spaces
|
|
9
9
|
|
{env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/tensor_wrapper.py
RENAMED
|
@@ -12,6 +12,7 @@ from .helpers import (
|
|
|
12
12
|
FINAL_OBSERVATION_KEYS,
|
|
13
13
|
FINAL_OBSERVATION_MASK_KEYS,
|
|
14
14
|
exists,
|
|
15
|
+
get_adapter,
|
|
15
16
|
get_attr,
|
|
16
17
|
is_scalar,
|
|
17
18
|
unpack_vector_observations,
|
|
@@ -31,6 +32,10 @@ def to_torch_leaf(t, device, cast_obs_to_float = True):
|
|
|
31
32
|
return t
|
|
32
33
|
|
|
33
34
|
dtype = torch.float32 if cast_obs_to_float and t.dtype != torch.bool else t.dtype
|
|
35
|
+
|
|
36
|
+
if t.dtype == dtype and t.device == device:
|
|
37
|
+
return t
|
|
38
|
+
|
|
34
39
|
return t.to(device = device, dtype = dtype)
|
|
35
40
|
|
|
36
41
|
def numpy_to_torch(x, device, cast_obs_to_float = True):
|
|
@@ -71,7 +76,11 @@ def torch_to_numpy(x):
|
|
|
71
76
|
def contract(t, to_float = False):
|
|
72
77
|
if not is_tensor(t):
|
|
73
78
|
return t
|
|
74
|
-
|
|
79
|
+
|
|
80
|
+
if to_float:
|
|
81
|
+
return t if t.dtype == torch.float32 else t.float()
|
|
82
|
+
|
|
83
|
+
return t if t.dtype == torch.bool else t.bool()
|
|
75
84
|
|
|
76
85
|
# class
|
|
77
86
|
|
|
@@ -96,10 +105,15 @@ class TensorWrapper(EnvWrapper):
|
|
|
96
105
|
self.convert_in = convert_in
|
|
97
106
|
self.convert_out = convert_out
|
|
98
107
|
self.cast_obs_to_float = cast_obs_to_float
|
|
108
|
+
|
|
109
|
+
# torch-native sims (mjlab, isaac) take torch actions as-is — zero-copy passthrough
|
|
110
|
+
|
|
111
|
+
self.torch_native = get_adapter(env).torch_native
|
|
112
|
+
|
|
99
113
|
self.cast = partial(numpy_to_torch, device = self.device, cast_obs_to_float = self.cast_obs_to_float)
|
|
100
114
|
|
|
101
115
|
def cast_info(self, info):
|
|
102
|
-
if not isinstance(info, dict):
|
|
116
|
+
if not isinstance(info, dict) or not info:
|
|
103
117
|
return
|
|
104
118
|
|
|
105
119
|
for key in FINAL_OBSERVATION_KEYS:
|
|
@@ -120,13 +134,19 @@ class TensorWrapper(EnvWrapper):
|
|
|
120
134
|
return obs, info
|
|
121
135
|
|
|
122
136
|
def to_contract(self, t, to_float = False):
|
|
123
|
-
|
|
124
|
-
leaf = to_torch_leaf(
|
|
137
|
+
def convert(leaf):
|
|
138
|
+
leaf = to_torch_leaf(leaf, self.device, cast_obs_to_float = False)
|
|
125
139
|
return contract(leaf, to_float = to_float)
|
|
126
|
-
|
|
140
|
+
|
|
141
|
+
if not isinstance(t, (dict, list, tuple)):
|
|
142
|
+
return convert(t)
|
|
143
|
+
|
|
144
|
+
return tree_map(convert, t)
|
|
127
145
|
|
|
128
146
|
def step(self, action):
|
|
129
|
-
|
|
147
|
+
if self.convert_in and not self.torch_native:
|
|
148
|
+
action = torch_to_numpy(action)
|
|
149
|
+
|
|
130
150
|
obs, reward, terminated, truncated, info = self.env.step(action)
|
|
131
151
|
|
|
132
152
|
if not self.convert_out:
|
|
@@ -2,9 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from multiprocessing import get_context
|
|
4
4
|
import numpy as np
|
|
5
|
-
import torch
|
|
6
5
|
from torch import is_tensor
|
|
7
|
-
from torch.utils._pytree import
|
|
6
|
+
from torch.utils._pytree import tree_map
|
|
8
7
|
|
|
9
8
|
from .helpers import (
|
|
10
9
|
any_true,
|
|
@@ -119,6 +118,9 @@ class _InlineConn:
|
|
|
119
118
|
def recv(self):
|
|
120
119
|
return self._result
|
|
121
120
|
|
|
121
|
+
def close(self):
|
|
122
|
+
pass
|
|
123
|
+
|
|
122
124
|
def _safe_send(conn, msg):
|
|
123
125
|
"""Send that swallows broken-pipe — for broadcast patterns where _recv_all reports the error."""
|
|
124
126
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "env-ssl-wrapper"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.2"
|
|
4
4
|
description = "One torch-native interface for any MDP environment"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Phil Wang", email = "lucidrains@gmail.com" }
|
|
@@ -24,16 +24,11 @@ classifiers=[
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
dependencies = [
|
|
27
|
-
"discrete-continuous-embed-readout",
|
|
28
|
-
"einx>=0.3.0",
|
|
29
27
|
"einops>=0.8.1",
|
|
30
28
|
"imageio",
|
|
31
29
|
"imageio-ffmpeg",
|
|
32
|
-
"
|
|
30
|
+
"numpy>=1.24",
|
|
33
31
|
"torch>=2.5",
|
|
34
|
-
"torch-einops-utils>=0.0.29",
|
|
35
|
-
"x-transformers",
|
|
36
|
-
"x-mlps-pytorch",
|
|
37
32
|
]
|
|
38
33
|
|
|
39
34
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/done_tracker_wrapper.py
RENAMED
|
File without changes
|
{env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/standardize_wrapper.py
RENAMED
|
File without changes
|
{env_ssl_wrapper-0.5.0 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/time_limit_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|