env-ssl-wrapper 0.5.1__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.1 → env_ssl_wrapper-0.5.2}/PKG-INFO +25 -7
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/README.md +23 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/__init__.py +7 -11
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/evaluate.py +55 -7
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/memory_trace.py +5 -2
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/__init__.py +1 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/action_transform_wrapper.py +13 -8
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/auto_batched_wrapper.py +8 -18
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/episode_padding_wrapper.py +1 -2
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py +2 -11
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/helpers.py +2 -3
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/image_wrapper.py +1 -5
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/tensor_wrapper.py +17 -5
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/vector.py +3 -1
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/pyproject.toml +2 -7
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/.gitignore +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/LICENSE +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/action_chunk.py +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/adapters.py +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/done_tracker_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/mocks.py +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/spaces.py +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/standardize_env_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/standardize_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/time_limit_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.1 → 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
|
-
|
|
@@ -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')
|
|
@@ -136,10 +149,21 @@ def to_env_action(env, action):
|
|
|
136
149
|
|
|
137
150
|
action = to_numpy(action)
|
|
138
151
|
|
|
139
|
-
|
|
140
|
-
|
|
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()
|
|
141
165
|
|
|
142
|
-
return action
|
|
166
|
+
return action
|
|
143
167
|
|
|
144
168
|
# wrappers take batched torch actions - pad up to the declared rank
|
|
145
169
|
|
|
@@ -169,6 +193,9 @@ def capture_frame(env):
|
|
|
169
193
|
except Exception:
|
|
170
194
|
return None
|
|
171
195
|
|
|
196
|
+
if not exists(frame):
|
|
197
|
+
return None
|
|
198
|
+
|
|
172
199
|
if is_tensor(frame):
|
|
173
200
|
frame = frame.detach().cpu().numpy()
|
|
174
201
|
|
|
@@ -200,6 +227,17 @@ def video_path_at(path, index):
|
|
|
200
227
|
path = Path(path)
|
|
201
228
|
return str(path if index == 0 else path.with_name(f'{path.stem}_ep{index}{path.suffix}'))
|
|
202
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
|
+
|
|
203
241
|
# evaluate
|
|
204
242
|
|
|
205
243
|
@torch.no_grad()
|
|
@@ -241,7 +279,7 @@ def evaluate_actor(
|
|
|
241
279
|
|
|
242
280
|
assert not (record and num_envs > 1), 'video recording requires a single env'
|
|
243
281
|
|
|
244
|
-
obs, info =
|
|
282
|
+
obs, info = safe_reset(env, seed)
|
|
245
283
|
batched = is_vectorized(env) or all(map(is_tensor, tree_flatten(obs)[0]))
|
|
246
284
|
|
|
247
285
|
goal = goal.expand(num_envs, -1) if exists(goal) and goal.shape[0] == 1 and num_envs > 1 else goal
|
|
@@ -249,6 +287,7 @@ def evaluate_actor(
|
|
|
249
287
|
returns = torch.zeros(num_envs)
|
|
250
288
|
lengths = torch.zeros(num_envs)
|
|
251
289
|
steps = torch.zeros(num_envs, dtype = torch.long)
|
|
290
|
+
active = np.ones(num_envs, dtype = bool)
|
|
252
291
|
|
|
253
292
|
done_returns, done_lengths, videos, frames = [], [], [], []
|
|
254
293
|
|
|
@@ -285,7 +324,12 @@ def evaluate_actor(
|
|
|
285
324
|
if exists(max_steps):
|
|
286
325
|
done |= to_numpy(steps >= max_steps).reshape(-1)
|
|
287
326
|
|
|
288
|
-
|
|
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]:
|
|
289
333
|
if len(done_returns) >= episodes:
|
|
290
334
|
break
|
|
291
335
|
|
|
@@ -309,6 +353,7 @@ def evaluate_actor(
|
|
|
309
353
|
returns[i] = 0.
|
|
310
354
|
lengths[i] = 0.
|
|
311
355
|
steps[i] = 0
|
|
356
|
+
active[i] = False
|
|
312
357
|
|
|
313
358
|
if done.any() and len(done_returns) < episodes:
|
|
314
359
|
if record:
|
|
@@ -318,7 +363,10 @@ def evaluate_actor(
|
|
|
318
363
|
|
|
319
364
|
if not env_autoresets(env) and (num_envs == 1 or done.all() or bool(get_attr(env, 'needs_reset', False))):
|
|
320
365
|
reset_seed = seed + len(done_returns) if exists(seed) and num_envs == 1 else None
|
|
321
|
-
obs, info =
|
|
366
|
+
obs, info = safe_reset(env, reset_seed)
|
|
367
|
+
active[:] = True
|
|
368
|
+
elif env_autoresets(env):
|
|
369
|
+
active[newly_done] = True
|
|
322
370
|
finally:
|
|
323
371
|
if exists(module) and exists(was_training):
|
|
324
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
|
|
@@ -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
|
|
{env_ssl_wrapper-0.5.1 → 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,
|
|
@@ -40,22 +39,12 @@ def maybe_expand_dim(x):
|
|
|
40
39
|
|
|
41
40
|
def _expand(t):
|
|
42
41
|
arr = to_numeric_array(t)
|
|
42
|
+
return arr[None] if is_array(arr) else t
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if is_array(x):
|
|
45
|
+
return x[None]
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
return rearrange(arr, '-> 1')
|
|
49
|
-
|
|
50
|
-
return rearrange(arr, '... -> 1 ...')
|
|
51
|
-
|
|
52
|
-
if is_tensor(x):
|
|
53
|
-
return rearrange(x, '-> 1') if x.ndim == 0 else rearrange(x, '... -> 1 ...')
|
|
54
|
-
|
|
55
|
-
if isinstance(x, np.ndarray) and x.dtype.kind in 'biufc':
|
|
56
|
-
return rearrange(x, '-> 1') if x.ndim == 0 else rearrange(x, '... -> 1 ...')
|
|
57
|
-
|
|
58
|
-
return tree_map(_expand, x)
|
|
47
|
+
return tree_map(_expand, x) if isinstance(x, (dict, list, tuple)) else _expand(x)
|
|
59
48
|
|
|
60
49
|
def is_integer_dtype(t):
|
|
61
50
|
if is_tensor(t):
|
|
@@ -120,11 +109,11 @@ def heuristic_leaf(t, is_vector = False):
|
|
|
120
109
|
return arr
|
|
121
110
|
|
|
122
111
|
if not is_vector and arr.ndim > 1 and arr.shape[0] == 1:
|
|
123
|
-
arr =
|
|
112
|
+
arr = arr.reshape(*arr.shape[1:])
|
|
124
113
|
|
|
125
114
|
if is_integer_dtype(arr):
|
|
126
115
|
while arr.ndim > 1 and arr.shape[-1] == 1:
|
|
127
|
-
arr =
|
|
116
|
+
arr = arr.reshape(*arr.shape[:-1])
|
|
128
117
|
|
|
129
118
|
if not is_vector and (arr.numel() if is_tensor(arr) else arr.size) == 1:
|
|
130
119
|
return arr.item()
|
|
@@ -206,7 +195,8 @@ class AutoBatchedWrapper(EnvWrapper):
|
|
|
206
195
|
if self.is_vector:
|
|
207
196
|
return out
|
|
208
197
|
|
|
209
|
-
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))
|
|
210
200
|
|
|
211
201
|
if isinstance(info, dict):
|
|
212
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.1 → 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.1 → 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
|
{env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/tensor_wrapper.py
RENAMED
|
@@ -32,6 +32,10 @@ def to_torch_leaf(t, device, cast_obs_to_float = True):
|
|
|
32
32
|
return t
|
|
33
33
|
|
|
34
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
|
+
|
|
35
39
|
return t.to(device = device, dtype = dtype)
|
|
36
40
|
|
|
37
41
|
def numpy_to_torch(x, device, cast_obs_to_float = True):
|
|
@@ -72,7 +76,11 @@ def torch_to_numpy(x):
|
|
|
72
76
|
def contract(t, to_float = False):
|
|
73
77
|
if not is_tensor(t):
|
|
74
78
|
return t
|
|
75
|
-
|
|
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()
|
|
76
84
|
|
|
77
85
|
# class
|
|
78
86
|
|
|
@@ -105,7 +113,7 @@ class TensorWrapper(EnvWrapper):
|
|
|
105
113
|
self.cast = partial(numpy_to_torch, device = self.device, cast_obs_to_float = self.cast_obs_to_float)
|
|
106
114
|
|
|
107
115
|
def cast_info(self, info):
|
|
108
|
-
if not isinstance(info, dict):
|
|
116
|
+
if not isinstance(info, dict) or not info:
|
|
109
117
|
return
|
|
110
118
|
|
|
111
119
|
for key in FINAL_OBSERVATION_KEYS:
|
|
@@ -126,10 +134,14 @@ class TensorWrapper(EnvWrapper):
|
|
|
126
134
|
return obs, info
|
|
127
135
|
|
|
128
136
|
def to_contract(self, t, to_float = False):
|
|
129
|
-
|
|
130
|
-
leaf = to_torch_leaf(
|
|
137
|
+
def convert(leaf):
|
|
138
|
+
leaf = to_torch_leaf(leaf, self.device, cast_obs_to_float = False)
|
|
131
139
|
return contract(leaf, to_float = to_float)
|
|
132
|
-
|
|
140
|
+
|
|
141
|
+
if not isinstance(t, (dict, list, tuple)):
|
|
142
|
+
return convert(t)
|
|
143
|
+
|
|
144
|
+
return tree_map(convert, t)
|
|
133
145
|
|
|
134
146
|
def step(self, action):
|
|
135
147
|
if self.convert_in and not self.torch_native:
|
|
@@ -2,7 +2,6 @@ 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
6
|
from torch.utils._pytree import tree_map
|
|
8
7
|
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/done_tracker_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/standardize_wrapper.py
RENAMED
|
File without changes
|
{env_ssl_wrapper-0.5.1 → env_ssl_wrapper-0.5.2}/env_ssl_wrapper/standardize/time_limit_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|