env-ssl-wrapper 0.5.3__tar.gz → 0.5.4__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.3 → env_ssl_wrapper-0.5.4}/PKG-INFO +1 -1
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/action_chunk.py +8 -3
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/evaluate.py +58 -46
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/memory_trace.py +21 -9
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/action_transform_wrapper.py +2 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/auto_batched_wrapper.py +16 -19
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/done_tracker_wrapper.py +2 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/episode_padding_wrapper.py +2 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py +5 -1
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/helpers.py +31 -24
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/image_wrapper.py +3 -3
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/spaces.py +1 -1
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/standardize_wrapper.py +2 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/tensor_wrapper.py +5 -11
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/time_limit_wrapper.py +2 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/utils.py +10 -31
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/vector.py +28 -16
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/pyproject.toml +1 -1
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/.gitignore +0 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/LICENSE +0 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/README.md +0 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/__init__.py +0 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/__init__.py +0 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/adapters.py +0 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/mocks.py +0 -0
- {env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/standardize_env_wrapper.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.4
|
|
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
|
|
@@ -11,6 +11,7 @@ from .standardize.helpers import (
|
|
|
11
11
|
default,
|
|
12
12
|
any_true,
|
|
13
13
|
dones_of,
|
|
14
|
+
env_takes_torch,
|
|
14
15
|
first_existing,
|
|
15
16
|
get_adapter,
|
|
16
17
|
get_attr,
|
|
@@ -29,16 +30,20 @@ def chunk_steps(actions, axis):
|
|
|
29
30
|
def unbatch_step(action):
|
|
30
31
|
# chunks always carry a num_envs axis - drop it for plain single envs
|
|
31
32
|
if is_tensor(action):
|
|
32
|
-
|
|
33
|
+
action = action.squeeze(0)
|
|
34
|
+
return action.item() if action.ndim == 0 and not action.is_floating_point() else action
|
|
33
35
|
|
|
34
36
|
if isinstance(action, np.ndarray):
|
|
35
|
-
|
|
37
|
+
action = np.squeeze(action, axis = 0) if action.ndim > 0 else action
|
|
38
|
+
return action.item() if action.ndim == 0 and np.issubdtype(action.dtype, np.integer) else action
|
|
36
39
|
|
|
37
40
|
return action
|
|
38
41
|
|
|
39
42
|
# wrapper
|
|
40
43
|
|
|
41
44
|
class ActionChunkWrapper(EnvWrapper):
|
|
45
|
+
priority = 100
|
|
46
|
+
|
|
42
47
|
"""
|
|
43
48
|
Open-loop action chunking - executes a chunk of actions in one step,
|
|
44
49
|
temporally compressing the environment by the chunk length.
|
|
@@ -71,7 +76,7 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
71
76
|
self.action_shape = tuple(get_attr(action_space, 'shape', ()) or ())
|
|
72
77
|
|
|
73
78
|
self.expects_batch = is_vectorized(env)
|
|
74
|
-
self.convert_in = not
|
|
79
|
+
self.convert_in = not env_takes_torch(env)
|
|
75
80
|
|
|
76
81
|
@property
|
|
77
82
|
def chunk_action_shape(self):
|
|
@@ -12,9 +12,11 @@ from torch.utils._pytree import tree_flatten, tree_map
|
|
|
12
12
|
from .standardize.spaces import action_space_is_discrete
|
|
13
13
|
from .standardize.helpers import (
|
|
14
14
|
EnvWrapper,
|
|
15
|
+
dones_of,
|
|
15
16
|
env_autoresets,
|
|
16
17
|
env_num_envs,
|
|
17
18
|
env_render,
|
|
19
|
+
env_takes_torch,
|
|
18
20
|
exists,
|
|
19
21
|
first_existing,
|
|
20
22
|
get_adapter,
|
|
@@ -90,31 +92,34 @@ def is_distribution(out):
|
|
|
90
92
|
def to_action(env, out, deterministic = True):
|
|
91
93
|
# distributions collapse to their mode, raw floating logits over a discrete space to their argmax
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
if
|
|
95
|
-
|
|
95
|
+
def convert_leaf(val):
|
|
96
|
+
if is_distribution(val):
|
|
97
|
+
if not deterministic:
|
|
98
|
+
return val.sample()
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
probs = get_attr(val, 'probs')
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
# categorical-style probs carry a trailing action axis to argmax over
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
if exists(probs) and probs.ndim >= 2 and probs.shape[-1] > 1:
|
|
105
|
+
return probs.argmax(dim = -1)
|
|
103
106
|
|
|
104
|
-
|
|
107
|
+
# bernoulli-style probabilities threshold at 0.5
|
|
105
108
|
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
if exists(probs):
|
|
110
|
+
return (probs > 0.5).long()
|
|
108
111
|
|
|
109
|
-
|
|
112
|
+
return val.mean
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
if is_tensor(val) and val.is_floating_point() and val.ndim > 0:
|
|
115
|
+
num_actions = get_attr(get_adapter(env).action_space, 'n')
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
if exists(num_actions) and val.shape[-1] == num_actions:
|
|
118
|
+
return val.argmax(dim = -1) if deterministic else Categorical(logits = val).sample()
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
return val
|
|
121
|
+
|
|
122
|
+
return tree_map(convert_leaf, out)
|
|
118
123
|
|
|
119
124
|
# states and actions
|
|
120
125
|
|
|
@@ -135,48 +140,55 @@ def to_actor_obs(obs, device, batched):
|
|
|
135
140
|
return tree_map(convert, obs)
|
|
136
141
|
|
|
137
142
|
def to_env_action(env, action):
|
|
138
|
-
if not is_tensor(action):
|
|
139
|
-
action = torch.as_tensor(action)
|
|
140
|
-
|
|
141
143
|
device = get_attr(env, 'device')
|
|
142
|
-
action = action.to(device) if exists(device) else action
|
|
143
144
|
|
|
144
|
-
|
|
145
|
+
def to_torch(a):
|
|
146
|
+
if not is_tensor(a):
|
|
147
|
+
a = torch.as_tensor(a)
|
|
148
|
+
return a.to(device) if exists(device) else a
|
|
145
149
|
|
|
146
|
-
|
|
147
|
-
if get_adapter(env).torch_native:
|
|
148
|
-
return action
|
|
150
|
+
action = tree_map(to_torch, action)
|
|
149
151
|
|
|
150
|
-
|
|
152
|
+
chunk_shape = get_attr(env, 'chunk_action_shape')
|
|
151
153
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
if exists(chunk_shape):
|
|
155
|
+
rank = len(chunk_shape) + 1
|
|
156
|
+
else:
|
|
157
|
+
space = first_existing(env, 'single_action_space', 'action_space')
|
|
158
|
+
shape = get_attr(space, 'shape')
|
|
159
|
+
rank = len(shape) + 1 if exists(shape) else None
|
|
154
160
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
if exists(rank):
|
|
162
|
+
def pad_rank(a):
|
|
163
|
+
while a.ndim < rank:
|
|
164
|
+
a = a[None]
|
|
165
|
+
return a
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
if action.ndim > 0 and action.shape[0] == 1:
|
|
161
|
-
action = action[0]
|
|
167
|
+
action = tree_map(pad_rank, action)
|
|
162
168
|
|
|
163
|
-
|
|
164
|
-
action = action.item()
|
|
169
|
+
# raw simulators take unbatched numpy — torch-native sims take torch as-is (zero-copy)
|
|
165
170
|
|
|
166
|
-
|
|
171
|
+
if not env_takes_torch(env):
|
|
172
|
+
action = tree_map(to_numpy, action)
|
|
167
173
|
|
|
168
|
-
|
|
174
|
+
# single raw sims get the leading batch dim stripped, while discrete
|
|
175
|
+
# actions additionally collapse to scalars
|
|
169
176
|
|
|
170
|
-
|
|
177
|
+
if not is_vectorized(env) and not exists(chunk_shape):
|
|
178
|
+
def strip_leaf(a):
|
|
179
|
+
if a.ndim > 1 and a.shape[0] == 1:
|
|
180
|
+
a = a[0]
|
|
171
181
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
182
|
+
if action_space_is_discrete(get_adapter(env).action_space):
|
|
183
|
+
if a.ndim > 0 and a.shape[0] == 1:
|
|
184
|
+
a = a[0]
|
|
185
|
+
|
|
186
|
+
if a.ndim == 0:
|
|
187
|
+
a = a.item()
|
|
188
|
+
|
|
189
|
+
return a
|
|
177
190
|
|
|
178
|
-
|
|
179
|
-
action = action[None]
|
|
191
|
+
action = tree_map(strip_leaf, action)
|
|
180
192
|
|
|
181
193
|
return action
|
|
182
194
|
|
|
@@ -319,7 +331,7 @@ def evaluate_actor(
|
|
|
319
331
|
lengths += info.get('chunk_length', 1) if isinstance(info, dict) else 1
|
|
320
332
|
steps += 1
|
|
321
333
|
|
|
322
|
-
done = to_numpy(terminated
|
|
334
|
+
done = to_numpy(dones_of(terminated, truncated)).astype(bool).reshape(-1)
|
|
323
335
|
|
|
324
336
|
if exists(max_steps):
|
|
325
337
|
done |= to_numpy(steps >= max_steps).reshape(-1)
|
|
@@ -6,16 +6,16 @@ from torch import is_tensor
|
|
|
6
6
|
|
|
7
7
|
from .standardize.helpers import (
|
|
8
8
|
TransformObservationWrapper,
|
|
9
|
-
exists,
|
|
10
|
-
default,
|
|
11
9
|
any_true,
|
|
10
|
+
cast_tuple,
|
|
11
|
+
default,
|
|
12
|
+
exists,
|
|
13
|
+
is_array_like,
|
|
14
|
+
is_scalar,
|
|
12
15
|
)
|
|
13
16
|
|
|
14
17
|
# helper functions
|
|
15
18
|
|
|
16
|
-
def cast_tuple(val):
|
|
17
|
-
return val if isinstance(val, (tuple, list)) else (val,)
|
|
18
|
-
|
|
19
19
|
def calc_lerp_weight(lam, done, x):
|
|
20
20
|
if not exists(done) or not any_true(done):
|
|
21
21
|
return 1. - lam
|
|
@@ -23,7 +23,9 @@ def calc_lerp_weight(lam, done, x):
|
|
|
23
23
|
# reset trace on done, else decay
|
|
24
24
|
|
|
25
25
|
if not is_tensor(done):
|
|
26
|
-
done = torch.as_tensor(done, device = x.device)
|
|
26
|
+
done = torch.as_tensor(done, device = x.device, dtype = torch.bool)
|
|
27
|
+
else:
|
|
28
|
+
done = done.to(device = x.device, dtype = torch.bool)
|
|
27
29
|
|
|
28
30
|
weight = torch.where(done, 1., 1. - lam).to(x)
|
|
29
31
|
|
|
@@ -36,6 +38,7 @@ def calc_lerp_weight(lam, done, x):
|
|
|
36
38
|
# https://arxiv.org/abs/2503.15200
|
|
37
39
|
|
|
38
40
|
class MemoryTraceWrapper(TransformObservationWrapper):
|
|
41
|
+
priority = 80
|
|
39
42
|
|
|
40
43
|
def __init__(
|
|
41
44
|
self,
|
|
@@ -61,17 +64,26 @@ class MemoryTraceWrapper(TransformObservationWrapper):
|
|
|
61
64
|
return f'{prefix}_{lam}' if len(self.lambdas) > 1 else prefix
|
|
62
65
|
|
|
63
66
|
def transform_obs(self, obs, done = None):
|
|
64
|
-
|
|
67
|
+
if isinstance(obs, (tuple, list)):
|
|
68
|
+
out = {f'{self.obs_key}_{i}': elem for i, elem in enumerate(obs)}
|
|
69
|
+
elif isinstance(obs, dict):
|
|
70
|
+
out = dict(obs)
|
|
71
|
+
else:
|
|
72
|
+
out = {self.obs_key: obs}
|
|
73
|
+
|
|
65
74
|
target_keys = tuple(k for k in default(self.keys, tuple(out.keys())) if k in out)
|
|
66
75
|
|
|
67
76
|
for key in target_keys:
|
|
68
77
|
val = out.get(key)
|
|
69
78
|
|
|
70
|
-
if not exists(val):
|
|
79
|
+
if not exists(val) or not (is_array_like(val) or is_scalar(val)):
|
|
71
80
|
continue
|
|
72
81
|
|
|
73
82
|
if not is_tensor(val):
|
|
74
|
-
|
|
83
|
+
try:
|
|
84
|
+
val = torch.as_tensor(val)
|
|
85
|
+
except (TypeError, ValueError):
|
|
86
|
+
continue
|
|
75
87
|
out[key] = val
|
|
76
88
|
|
|
77
89
|
val_float = val.float() if not val.is_floating_point() else val
|
{env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/auto_batched_wrapper.py
RENAMED
|
@@ -4,7 +4,7 @@ from functools import partial
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import torch
|
|
7
|
-
from torch.utils._pytree import tree_map
|
|
7
|
+
from torch.utils._pytree import tree_flatten, tree_map, tree_structure, tree_unflatten
|
|
8
8
|
|
|
9
9
|
from .helpers import (
|
|
10
10
|
FINAL_OBSERVATION_KEYS,
|
|
@@ -35,16 +35,11 @@ def to_numeric_array(t):
|
|
|
35
35
|
return arr if arr.dtype.kind in 'biufc' else t
|
|
36
36
|
|
|
37
37
|
def maybe_expand_dim(x):
|
|
38
|
-
# add leading batch dim to every numeric leaf
|
|
39
|
-
|
|
40
38
|
def _expand(t):
|
|
41
39
|
arr = to_numeric_array(t)
|
|
42
40
|
return arr[None] if is_array(arr) else t
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
return x[None]
|
|
46
|
-
|
|
47
|
-
return tree_map(_expand, x) if isinstance(x, (dict, list, tuple)) else _expand(x)
|
|
42
|
+
return tree_map(_expand, x)
|
|
48
43
|
|
|
49
44
|
def is_integer_dtype(t):
|
|
50
45
|
if is_tensor(t):
|
|
@@ -122,20 +117,12 @@ def heuristic_leaf(t, is_vector = False):
|
|
|
122
117
|
return arr.item() if arr.ndim == 0 else arr
|
|
123
118
|
|
|
124
119
|
def rebuild_container(x, leaves):
|
|
125
|
-
|
|
126
|
-
if isinstance(x, list):
|
|
127
|
-
return leaves
|
|
128
|
-
|
|
129
|
-
if exists(get_attr(type(x), '_fields')):
|
|
130
|
-
return type(x)(*leaves)
|
|
131
|
-
|
|
132
|
-
return type(x)(leaves)
|
|
120
|
+
return tree_unflatten(leaves, tree_structure(x))
|
|
133
121
|
|
|
134
122
|
def is_numeric_container(x):
|
|
135
|
-
# purely numeric nested sequences count as one leaf when no space declares structure
|
|
136
|
-
|
|
137
123
|
if isinstance(x, (list, tuple)):
|
|
138
|
-
|
|
124
|
+
leaves, _ = tree_flatten(x)
|
|
125
|
+
return len(leaves) > 0 and all(map(is_scalar, leaves))
|
|
139
126
|
|
|
140
127
|
return is_scalar(x)
|
|
141
128
|
|
|
@@ -168,6 +155,7 @@ def maybe_squeeze_dim(x, shape_tree = None, is_vector = False, prepend_batch = F
|
|
|
168
155
|
class AutoBatchedWrapper(EnvWrapper):
|
|
169
156
|
|
|
170
157
|
is_auto_batched = True
|
|
158
|
+
priority = 50
|
|
171
159
|
|
|
172
160
|
def __init__(self, env, is_vector: bool | None = None):
|
|
173
161
|
super().__init__(env)
|
|
@@ -187,7 +175,16 @@ class AutoBatchedWrapper(EnvWrapper):
|
|
|
187
175
|
if not exists(self.action_shape_tree):
|
|
188
176
|
self.refresh_action_space()
|
|
189
177
|
|
|
190
|
-
|
|
178
|
+
if self.is_vector:
|
|
179
|
+
return obs, info
|
|
180
|
+
|
|
181
|
+
obs = maybe_expand_dim(obs)
|
|
182
|
+
if isinstance(info, dict):
|
|
183
|
+
for key in FINAL_OBSERVATION_KEYS:
|
|
184
|
+
if key in info:
|
|
185
|
+
info[key] = maybe_expand_dim(info[key])
|
|
186
|
+
|
|
187
|
+
return obs, info
|
|
191
188
|
|
|
192
189
|
def step(self, action):
|
|
193
190
|
action = maybe_squeeze_dim(action, shape_tree = self.action_shape_tree, is_vector = self.is_vector, prepend_batch = self.prepend_batch)
|
{env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py
RENAMED
|
@@ -37,7 +37,9 @@ def concat_leaves(leaves, is_vector = False):
|
|
|
37
37
|
axis = -1 if is_vector else 0
|
|
38
38
|
leaves = [flatten_leaf(t, is_vector = is_vector) for t in leaves]
|
|
39
39
|
|
|
40
|
-
if is_tensor
|
|
40
|
+
if any(map(is_tensor, leaves)):
|
|
41
|
+
device = next(t.device for t in leaves if is_tensor(t))
|
|
42
|
+
leaves = [torch.as_tensor(t, device = device) if not is_tensor(t) else t.to(device) for t in leaves]
|
|
41
43
|
return torch.cat(leaves, dim = axis)
|
|
42
44
|
|
|
43
45
|
return np.concatenate(leaves, axis = axis)
|
|
@@ -45,6 +47,8 @@ def concat_leaves(leaves, is_vector = False):
|
|
|
45
47
|
# class
|
|
46
48
|
|
|
47
49
|
class FlattenObsWrapper(TransformObservationWrapper):
|
|
50
|
+
priority = 90
|
|
51
|
+
|
|
48
52
|
def __init__(self, env, is_vector: bool | None = None):
|
|
49
53
|
super().__init__(env)
|
|
50
54
|
self.is_vector = default(is_vector, is_vectorized(env))
|
|
@@ -12,7 +12,14 @@ def exists(v):
|
|
|
12
12
|
return v is not None
|
|
13
13
|
|
|
14
14
|
def default(v, d):
|
|
15
|
-
return v if exists(v) else d
|
|
15
|
+
return v if exists(v) else (d() if callable(d) else d)
|
|
16
|
+
|
|
17
|
+
def cast_tuple(val, length = 1):
|
|
18
|
+
if isinstance(val, tuple):
|
|
19
|
+
return val
|
|
20
|
+
if isinstance(val, list):
|
|
21
|
+
return tuple(val)
|
|
22
|
+
return (val,) * length
|
|
16
23
|
|
|
17
24
|
def get_attr(obj, name, default = None):
|
|
18
25
|
# properties that raise count as missing
|
|
@@ -84,9 +91,8 @@ def to_numpy(t):
|
|
|
84
91
|
return t.detach().cpu().numpy() if is_tensor(t) else np.asarray(t)
|
|
85
92
|
|
|
86
93
|
def any_true(x):
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return bool(np.asarray(x).any())
|
|
94
|
+
leaves, _ = tree_flatten(x)
|
|
95
|
+
return any(bool(leaf.any()) if is_tensor(leaf) else bool(np.asarray(leaf).any()) for leaf in leaves)
|
|
90
96
|
|
|
91
97
|
def copy_leaf(x):
|
|
92
98
|
if is_tensor(x):
|
|
@@ -98,8 +104,6 @@ def copy_leaf(x):
|
|
|
98
104
|
return x
|
|
99
105
|
|
|
100
106
|
def dones_of(terminated, truncated):
|
|
101
|
-
if not isinstance(terminated, (dict, list, tuple)):
|
|
102
|
-
return terminated | truncated
|
|
103
107
|
return tree_map(lambda a, b: a | b, terminated, truncated)
|
|
104
108
|
|
|
105
109
|
# sim step / reset normalization
|
|
@@ -107,13 +111,16 @@ def dones_of(terminated, truncated):
|
|
|
107
111
|
def is_time_step(out):
|
|
108
112
|
return exists(get_attr(out, 'step_type')) and exists(get_attr(out, 'observation'))
|
|
109
113
|
|
|
110
|
-
def
|
|
114
|
+
def _zero_bool_leaf(x):
|
|
111
115
|
if is_tensor(x):
|
|
112
116
|
return torch.zeros_like(x, dtype = torch.bool)
|
|
113
117
|
|
|
114
118
|
arr = np.asarray(x)
|
|
115
119
|
return np.zeros_like(arr, dtype = bool) if arr.ndim > 0 else False
|
|
116
120
|
|
|
121
|
+
def zero_like(x):
|
|
122
|
+
return tree_map(_zero_bool_leaf, x)
|
|
123
|
+
|
|
117
124
|
def normalize_obs(obs):
|
|
118
125
|
# raw arrays / scalars / text / containers pass through — custom observation
|
|
119
126
|
# objects (rlbench, ...) flatten via get_low_dim_data or unpack their
|
|
@@ -148,16 +155,8 @@ def normalize_obs(obs):
|
|
|
148
155
|
return out if out else obs
|
|
149
156
|
|
|
150
157
|
def contains_array(x):
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if isinstance(x, dict):
|
|
155
|
-
return any(contains_array(v) for v in x.values())
|
|
156
|
-
|
|
157
|
-
if isinstance(x, (list, tuple)):
|
|
158
|
-
return any(contains_array(v) for v in x)
|
|
159
|
-
|
|
160
|
-
return is_array_like(x)
|
|
158
|
+
leaves, _ = tree_flatten(x)
|
|
159
|
+
return any(map(is_array_like, leaves))
|
|
161
160
|
|
|
162
161
|
def is_instruction(x):
|
|
163
162
|
if isinstance(x, str):
|
|
@@ -235,15 +234,9 @@ def stack_trees(trees):
|
|
|
235
234
|
if isinstance(first, np.ndarray):
|
|
236
235
|
return np.stack(trees)
|
|
237
236
|
|
|
238
|
-
if isinstance(first, dict):
|
|
239
|
-
return {key: stack_trees([t[key] for t in trees]) for key in first}
|
|
240
|
-
|
|
241
|
-
if isinstance(first, tuple):
|
|
242
|
-
return tuple(stack_trees([t[i] for t in trees]) for i in range(len(first)))
|
|
243
|
-
|
|
244
237
|
leaves = [tree_flatten(tree)[0] for tree in trees]
|
|
245
238
|
stacked = [_stack_leaves(col) for col in zip(*leaves)]
|
|
246
|
-
return tree_unflatten(stacked, tree_structure(
|
|
239
|
+
return tree_unflatten(stacked, tree_structure(first))
|
|
247
240
|
|
|
248
241
|
def unpack_vector_observations(arr):
|
|
249
242
|
# unpacks a 1D sequence / numpy object array of unbatched single-env observations
|
|
@@ -283,6 +276,18 @@ def env_render(env, height, width, camera = None):
|
|
|
283
276
|
def is_vectorized(env) -> bool:
|
|
284
277
|
return get_adapter(env).is_vectorized
|
|
285
278
|
|
|
279
|
+
def env_takes_torch(env) -> bool:
|
|
280
|
+
if get_adapter(env).torch_native:
|
|
281
|
+
return True
|
|
282
|
+
|
|
283
|
+
current = env
|
|
284
|
+
while isinstance(current, EnvWrapper):
|
|
285
|
+
if type(current).__name__ == 'TensorWrapper' and getattr(current, 'convert_in', True):
|
|
286
|
+
return True
|
|
287
|
+
current = getattr(current, 'env', None)
|
|
288
|
+
|
|
289
|
+
return False
|
|
290
|
+
|
|
286
291
|
# gymnasium 1.x surfaces final observations as 'final_obs' / '_final_obs', everything here uses 'final_observation' / '_final_observation'
|
|
287
292
|
|
|
288
293
|
FINAL_OBSERVATION_KEYS = ('final_observation', 'final_obs')
|
|
@@ -373,6 +378,8 @@ def safe_close(env):
|
|
|
373
378
|
# base wrapper
|
|
374
379
|
|
|
375
380
|
class EnvWrapper:
|
|
381
|
+
priority = 50
|
|
382
|
+
|
|
376
383
|
def __init__(self, env):
|
|
377
384
|
self.env = env
|
|
378
385
|
|
{env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/image_wrapper.py
RENAMED
|
@@ -9,6 +9,7 @@ from einops import rearrange
|
|
|
9
9
|
from .helpers import (
|
|
10
10
|
FINAL_OBSERVATION_KEYS,
|
|
11
11
|
TransformObservationWrapper,
|
|
12
|
+
cast_tuple,
|
|
12
13
|
env_render,
|
|
13
14
|
env_render_mode,
|
|
14
15
|
exists,
|
|
@@ -18,9 +19,6 @@ from .helpers import (
|
|
|
18
19
|
|
|
19
20
|
# helper functions
|
|
20
21
|
|
|
21
|
-
def cast_tuple(t, length = 1):
|
|
22
|
-
return t if isinstance(t, tuple) else ((t,) * length)
|
|
23
|
-
|
|
24
22
|
def render_frame(env, image_size = (64, 64), camera = None):
|
|
25
23
|
# render from any sim — the shared env_render probe knows each image
|
|
26
24
|
# surface (dm_control physics, pybullet client, robosuite sim); envs
|
|
@@ -72,6 +70,8 @@ def process_image(
|
|
|
72
70
|
# class
|
|
73
71
|
|
|
74
72
|
class ImageObservationWrapper(TransformObservationWrapper):
|
|
73
|
+
priority = 20
|
|
74
|
+
|
|
75
75
|
def __init__(
|
|
76
76
|
self,
|
|
77
77
|
env,
|
|
@@ -71,7 +71,7 @@ def space_dim(space) -> int | None:
|
|
|
71
71
|
if exists(shape):
|
|
72
72
|
return shape_dim(shape)
|
|
73
73
|
|
|
74
|
-
subspaces = get_attr(space, 'spaces')
|
|
74
|
+
subspaces = space if isinstance(space, (dict, list, tuple)) else get_attr(space, 'spaces')
|
|
75
75
|
if not exists(subspaces):
|
|
76
76
|
return None
|
|
77
77
|
|
{env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/standardize_wrapper.py
RENAMED
|
@@ -14,6 +14,8 @@ from .spaces import infer_observation_space, space_from_action_spec
|
|
|
14
14
|
# class
|
|
15
15
|
|
|
16
16
|
class StandardizeWrapper(EnvWrapper):
|
|
17
|
+
priority = 0
|
|
18
|
+
|
|
17
19
|
# normalizes any sim into (obs, reward, terminated, truncated, info) via its adapter
|
|
18
20
|
|
|
19
21
|
def __init__(self, env, adapter = None):
|
{env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/tensor_wrapper.py
RENAMED
|
@@ -43,9 +43,6 @@ def numpy_to_torch(x, device, cast_obs_to_float = True):
|
|
|
43
43
|
if isinstance(x, np.ndarray) and x.dtype == object:
|
|
44
44
|
x = unpack_vector_observations(x)
|
|
45
45
|
|
|
46
|
-
if not isinstance(x, (dict, list, tuple)):
|
|
47
|
-
return to_torch_leaf(x, device, cast_obs_to_float)
|
|
48
|
-
|
|
49
46
|
return tree_map(partial(to_torch_leaf, device = device, cast_obs_to_float = cast_obs_to_float), x)
|
|
50
47
|
|
|
51
48
|
def to_numpy_leaf(t):
|
|
@@ -66,9 +63,6 @@ def to_numpy_leaf(t):
|
|
|
66
63
|
|
|
67
64
|
def torch_to_numpy(x):
|
|
68
65
|
# torch to numpy; 0-dim collapses to scalar, float64 → float32
|
|
69
|
-
if not isinstance(x, (dict, list, tuple)):
|
|
70
|
-
return to_numpy_leaf(x)
|
|
71
|
-
|
|
72
66
|
return tree_map(to_numpy_leaf, x)
|
|
73
67
|
|
|
74
68
|
# rewards float32, dones bool
|
|
@@ -85,6 +79,8 @@ def contract(t, to_float = False):
|
|
|
85
79
|
# class
|
|
86
80
|
|
|
87
81
|
class TensorWrapper(EnvWrapper):
|
|
82
|
+
priority = 60
|
|
83
|
+
|
|
88
84
|
def __init__(
|
|
89
85
|
self,
|
|
90
86
|
env,
|
|
@@ -138,17 +134,15 @@ class TensorWrapper(EnvWrapper):
|
|
|
138
134
|
leaf = to_torch_leaf(leaf, self.device, cast_obs_to_float = False)
|
|
139
135
|
return contract(leaf, to_float = to_float)
|
|
140
136
|
|
|
141
|
-
if not isinstance(t, (dict, list, tuple)):
|
|
142
|
-
return convert(t)
|
|
143
|
-
|
|
144
137
|
return tree_map(convert, t)
|
|
145
138
|
|
|
146
139
|
def step(self, action):
|
|
147
140
|
if self.convert_in:
|
|
148
141
|
if not self.torch_native:
|
|
149
142
|
action = torch_to_numpy(action)
|
|
150
|
-
elif
|
|
151
|
-
|
|
143
|
+
elif is_tensor(action):
|
|
144
|
+
action = action.to(self.device)
|
|
145
|
+
else:
|
|
152
146
|
action = numpy_to_torch(action, device = self.device, cast_obs_to_float = False)
|
|
153
147
|
|
|
154
148
|
obs, reward, terminated, truncated, info = self.env.step(action)
|
{env_ssl_wrapper-0.5.3 → env_ssl_wrapper-0.5.4}/env_ssl_wrapper/standardize/time_limit_wrapper.py
RENAMED
|
@@ -33,6 +33,8 @@ def back_to_like(t, numpy_arr):
|
|
|
33
33
|
return numpy_arr
|
|
34
34
|
|
|
35
35
|
class TimeLimitWrapper(EnvWrapper):
|
|
36
|
+
priority = 40
|
|
37
|
+
|
|
36
38
|
"""
|
|
37
39
|
caps episode length at max_timesteps, setting truncated = True for
|
|
38
40
|
capped envs (vectorized and single alike); timers reset per episode.
|
|
@@ -60,48 +60,27 @@ def parse_wrapper(wrapper):
|
|
|
60
60
|
def compose_env(env, *wrappers, pad_episodes: bool = True):
|
|
61
61
|
env = instantiate_env(env)
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
classes =
|
|
65
|
-
|
|
66
|
-
for wrapper in wrappers:
|
|
67
|
-
func, cls = parse_wrapper(wrapper)
|
|
68
|
-
funcs.append(func)
|
|
69
|
-
classes.append(cls)
|
|
63
|
+
items = [parse_wrapper(wrapper) for wrapper in wrappers]
|
|
64
|
+
classes = {cls for _, cls in items}
|
|
70
65
|
|
|
71
66
|
if StandardizeWrapper not in classes:
|
|
72
|
-
|
|
73
|
-
classes.insert(0, StandardizeWrapper)
|
|
67
|
+
items.append((StandardizeWrapper, StandardizeWrapper))
|
|
74
68
|
|
|
75
69
|
# vectorized envs get standardized episode padding + a persistent final_observation
|
|
76
70
|
# (autoresetting envs re-emit the true terminal obs, so no padding for those)
|
|
77
71
|
|
|
78
72
|
if pad_episodes and EpisodePaddingWrapper not in classes and is_vectorized(env):
|
|
79
73
|
pad_wrapper = partial(EpisodePaddingWrapper, pad_autoreset = not env_autoresets(env))
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
idx_mem = classes.index(MemoryTraceWrapper)
|
|
86
|
-
idx_ten = classes.index(TensorWrapper)
|
|
87
|
-
if idx_mem < idx_ten:
|
|
88
|
-
f = funcs.pop(idx_mem)
|
|
89
|
-
c = classes.pop(idx_mem)
|
|
90
|
-
idx_ten = classes.index(TensorWrapper)
|
|
91
|
-
funcs.insert(idx_ten + 1, f)
|
|
92
|
-
classes.insert(idx_ten + 1, c)
|
|
93
|
-
|
|
94
|
-
# action chunking changes the step signature, so it always goes outermost
|
|
95
|
-
|
|
96
|
-
from ..action_chunk import ActionChunkWrapper
|
|
97
|
-
if ActionChunkWrapper in classes:
|
|
98
|
-
idx_chunk = classes.index(ActionChunkWrapper)
|
|
99
|
-
funcs.append(funcs.pop(idx_chunk))
|
|
100
|
-
classes.append(classes.pop(idx_chunk))
|
|
74
|
+
items.append((pad_wrapper, EpisodePaddingWrapper))
|
|
75
|
+
|
|
76
|
+
# sort by canonical pipeline priority (stable sort preserves user order for equal priorities)
|
|
77
|
+
|
|
78
|
+
items.sort(key = lambda item: getattr(item[1], 'priority', 50))
|
|
101
79
|
|
|
80
|
+
classes = [cls for _, cls in items]
|
|
102
81
|
assert len(set(classes)) == len(classes), 'duplicate wrappers found'
|
|
103
82
|
|
|
104
|
-
for func in
|
|
83
|
+
for func, _ in items:
|
|
105
84
|
env = func(env)
|
|
106
85
|
|
|
107
86
|
return env
|
|
@@ -3,20 +3,22 @@ from __future__ import annotations
|
|
|
3
3
|
from multiprocessing import get_context
|
|
4
4
|
import numpy as np
|
|
5
5
|
from torch import is_tensor
|
|
6
|
-
from torch.utils._pytree import tree_map
|
|
6
|
+
from torch.utils._pytree import tree_flatten, tree_map, tree_unflatten
|
|
7
7
|
|
|
8
8
|
from .helpers import (
|
|
9
9
|
any_true,
|
|
10
10
|
dones_of,
|
|
11
11
|
exists,
|
|
12
|
+
get_adapter,
|
|
12
13
|
get_attr,
|
|
13
14
|
instantiate_env,
|
|
14
15
|
safe_close,
|
|
15
16
|
stack_trees,
|
|
17
|
+
to_numpy,
|
|
16
18
|
truthy_attr,
|
|
17
19
|
_zero_leaf,
|
|
18
20
|
)
|
|
19
|
-
from .spaces import action_dim_of, obs_dim_of
|
|
21
|
+
from .spaces import action_dim_of, action_space_is_discrete, obs_dim_of
|
|
20
22
|
from .standardize_wrapper import StandardizeWrapper
|
|
21
23
|
|
|
22
24
|
# leaf helpers
|
|
@@ -48,6 +50,15 @@ class WorkerError(RuntimeError):
|
|
|
48
50
|
|
|
49
51
|
def _exec(env, cmd, payload):
|
|
50
52
|
if cmd == 'step':
|
|
53
|
+
if is_tensor(payload) and not get_adapter(env).torch_native:
|
|
54
|
+
payload = to_numpy(payload)
|
|
55
|
+
|
|
56
|
+
if action_space_is_discrete(env.action_space):
|
|
57
|
+
if hasattr(payload, 'item') and (not hasattr(payload, 'ndim') or payload.ndim <= 1):
|
|
58
|
+
payload = payload.item()
|
|
59
|
+
elif isinstance(payload, (list, tuple)) and len(payload) == 1:
|
|
60
|
+
payload = payload[0]
|
|
61
|
+
|
|
51
62
|
obs, reward, terminated, truncated, info = env.step(payload)
|
|
52
63
|
|
|
53
64
|
if any_true(dones_of(terminated, truncated)):
|
|
@@ -182,16 +193,14 @@ def _shutdown(conns, procs):
|
|
|
182
193
|
pass
|
|
183
194
|
|
|
184
195
|
def _split_actions(actions, num_envs):
|
|
185
|
-
if isinstance(actions,
|
|
186
|
-
|
|
187
|
-
return [{k: v[i] for k, v in actions.items()} for i in range(num_envs)]
|
|
188
|
-
|
|
189
|
-
if isinstance(actions, tuple) and not is_tensor(actions) and not isinstance(actions, np.ndarray):
|
|
190
|
-
assert all(len(v) == num_envs for v in actions), f'expected {num_envs} actions per element'
|
|
191
|
-
return [tuple(elem[i] for elem in actions) for i in range(num_envs)]
|
|
196
|
+
if isinstance(actions, list) and len(actions) == num_envs:
|
|
197
|
+
return actions
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
leaves, spec = tree_flatten(actions)
|
|
200
|
+
assert all(len(leaf) == num_envs for leaf in leaves), (
|
|
201
|
+
f'expected {num_envs} actions, but got {len(leaves[0]) if leaves else 0}'
|
|
202
|
+
)
|
|
203
|
+
return [tree_unflatten([leaf[i] for leaf in leaves], spec) for i in range(num_envs)]
|
|
195
204
|
|
|
196
205
|
# class
|
|
197
206
|
|
|
@@ -270,16 +279,19 @@ class MultiprocessingVecEnv:
|
|
|
270
279
|
_recv_all(self._conns)
|
|
271
280
|
|
|
272
281
|
def reset(self, seed = None, **kwargs):
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
_safe_send(conn, ('reset',
|
|
282
|
+
for i, conn in enumerate(self._conns):
|
|
283
|
+
worker_kwargs = dict(kwargs)
|
|
284
|
+
if exists(seed):
|
|
285
|
+
worker_kwargs['seed'] = seed + i
|
|
286
|
+
_safe_send(conn, ('reset', worker_kwargs))
|
|
278
287
|
|
|
279
288
|
results = _recv_all(self._conns)
|
|
280
289
|
return stack_trees([obs for obs, _ in results]), _merge_infos([info for _, info in results])
|
|
281
290
|
|
|
282
291
|
def step(self, actions):
|
|
292
|
+
if is_tensor(actions):
|
|
293
|
+
actions = to_numpy(actions)
|
|
294
|
+
|
|
283
295
|
actions = _split_actions(actions, self.num_envs)
|
|
284
296
|
|
|
285
297
|
for conn, action in zip(self._conns, actions):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|