env-ssl-wrapper 0.4.1__tar.gz → 0.4.3__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.4.1 → env_ssl_wrapper-0.4.3}/PKG-INFO +53 -2
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/README.md +49 -1
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/__init__.py +2 -0
- env_ssl_wrapper-0.4.3/env_ssl_wrapper/action_chunk.py +117 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/action_transform_wrapper.py +8 -3
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/adapters.py +13 -41
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/done_tracker_wrapper.py +2 -20
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/episode_padding_wrapper.py +16 -19
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/helpers.py +56 -6
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/image_wrapper.py +39 -9
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/standardize_env_wrapper.py +12 -5
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/standardize_wrapper.py +1 -33
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/time_limit_wrapper.py +18 -3
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/utils.py +28 -18
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/vector.py +4 -6
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/pyproject.toml +4 -1
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/.gitignore +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/LICENSE +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/memory_trace.py +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/__init__.py +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/auto_batched_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/mocks.py +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/spaces.py +0 -0
- {env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/tensor_wrapper.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: env-ssl-wrapper
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
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
|
|
@@ -47,14 +47,17 @@ Requires-Dist: fire; extra == 'examples'
|
|
|
47
47
|
Provides-Extra: test
|
|
48
48
|
Requires-Dist: box2d-py>=2.3.5; extra == 'test'
|
|
49
49
|
Requires-Dist: dm-control; extra == 'test'
|
|
50
|
+
Requires-Dist: gym-pusht; extra == 'test'
|
|
50
51
|
Requires-Dist: gymnasium-robotics; extra == 'test'
|
|
51
52
|
Requires-Dist: gymnasium>=0.29.1; extra == 'test'
|
|
52
53
|
Requires-Dist: imageio; extra == 'test'
|
|
54
|
+
Requires-Dist: mean-conc-beta; extra == 'test'
|
|
53
55
|
Requires-Dist: mujoco; extra == 'test'
|
|
54
56
|
Requires-Dist: pillow; extra == 'test'
|
|
55
57
|
Requires-Dist: populora; extra == 'test'
|
|
56
58
|
Requires-Dist: pybullet; extra == 'test'
|
|
57
59
|
Requires-Dist: pygame; extra == 'test'
|
|
60
|
+
Requires-Dist: pymunk<7; extra == 'test'
|
|
58
61
|
Requires-Dist: pytest; extra == 'test'
|
|
59
62
|
Description-Content-Type: text/markdown
|
|
60
63
|
|
|
@@ -81,7 +84,7 @@ env = StandardizeEnvWrapper(any_env)
|
|
|
81
84
|
obs, info = env.reset() # torch.float32, batched
|
|
82
85
|
|
|
83
86
|
while not env.all_done:
|
|
84
|
-
actions = torch.randint(0, 2, (
|
|
87
|
+
actions = torch.randint(0, 2, (1,))
|
|
85
88
|
obs, reward, terminated, truncated, info = env.step(actions)
|
|
86
89
|
```
|
|
87
90
|
|
|
@@ -131,6 +134,53 @@ Run the PPO benchmark on POMDP LunarLander:
|
|
|
131
134
|
uv run test_memory_trace.py
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
### Action Chunk
|
|
138
|
+
|
|
139
|
+
Open-loop action chunking: one `step` executes a whole chunk of actions and returns only the final state, temporally compressing the environment by the chunk length.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import gymnasium as gym
|
|
143
|
+
import torch
|
|
144
|
+
from env_ssl_wrapper import StandardizeEnvWrapper, ActionChunkWrapper
|
|
145
|
+
|
|
146
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'))
|
|
147
|
+
env = ActionChunkWrapper(env, chunk_len = 2, gamma = 0.99)
|
|
148
|
+
|
|
149
|
+
obs, info = env.reset()
|
|
150
|
+
|
|
151
|
+
while True:
|
|
152
|
+
actions = torch.randint(0, 2, (1, 2)) # (num_envs, chunk_len)
|
|
153
|
+
obs, reward, terminated, truncated, info = env.step(actions)
|
|
154
|
+
|
|
155
|
+
# reward -> discounted sum of substeps (r0 + gamma * r1 + ...)
|
|
156
|
+
# info['discount'] -> gamma ** chunk_length (macro-step discount for GAE / Bellman target)
|
|
157
|
+
# info['chunk_length'] -> env steps actually executed (drops below chunk_len on terminal chunk)
|
|
158
|
+
# info['chunk_rewards'] -> (1, chunk_length) raw per-step rewards
|
|
159
|
+
|
|
160
|
+
if bool((terminated | truncated).item()):
|
|
161
|
+
obs, info = env.reset()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Chunks are shaped `(num_envs, chunk_len, *action_shape)` (`(num_envs, chunk_len)` for discrete actions).
|
|
165
|
+
|
|
166
|
+
Execution stops early the moment any env terminates or truncates mid-chunk — the terminal state and done flags of that substep are returned and the rest of the chunk is dropped, so a new episode is never silently advanced.
|
|
167
|
+
|
|
168
|
+
Pass `gamma` (default `1.`) to discount intra-chunk rewards $r = \sum_{i=0}^{L-1} \gamma^i r_i$. The macro-transition discount factor to the next state is provided as `info['discount'] = gamma ** chunk_length`. `reward_mode` can also be `'mean'` or `'last'`.
|
|
169
|
+
|
|
170
|
+
Can also be passed directly to `StandardizeEnvWrapper` or `compose_env`:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'), chunk_len = 2, chunk_gamma = 0.99)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Run the chunked PPO benchmark on CartPole, or check chunked rollouts against a per-step reference:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
uv run test_action_chunk.py --chunk_len=2 # PPO on CartPole
|
|
180
|
+
uv run test_action_chunk.py --sweep=True # PPO over several chunk lengths
|
|
181
|
+
uv run test_action_chunk.py --verify=True --sweep=True # chunked rollouts match per-step reference
|
|
182
|
+
```
|
|
183
|
+
|
|
134
184
|
## Wrappers
|
|
135
185
|
|
|
136
186
|
Pass wrappers as strings (default config) or `(name, dict)` tuples (custom config), in any order.
|
|
@@ -145,6 +195,7 @@ Pass wrappers as strings (default config) or `(name, dict)` tuples (custom confi
|
|
|
145
195
|
| `action_transform` | Rescales actions from a canonical `(0, 1)` range to the env's bounds. |
|
|
146
196
|
| `tensor` | NumPy → torch on a device, torch actions → numpy for the sim. |
|
|
147
197
|
| `flatten_obs` | Flattens dict/tuple observations into a single vector. |
|
|
198
|
+
| `action_chunk` | Executes actions in open-loop chunks of length k. `('action_chunk', dict(chunk_len = 2, gamma = 0.99))` |
|
|
148
199
|
|
|
149
200
|
Every env emits the same contract: obs `torch.float32`, rewards `torch.float32`, `terminated`/`truncated` `torch.bool`. `env.seed(n)` works on every sim.
|
|
150
201
|
|
|
@@ -21,7 +21,7 @@ env = StandardizeEnvWrapper(any_env)
|
|
|
21
21
|
obs, info = env.reset() # torch.float32, batched
|
|
22
22
|
|
|
23
23
|
while not env.all_done:
|
|
24
|
-
actions = torch.randint(0, 2, (
|
|
24
|
+
actions = torch.randint(0, 2, (1,))
|
|
25
25
|
obs, reward, terminated, truncated, info = env.step(actions)
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -71,6 +71,53 @@ Run the PPO benchmark on POMDP LunarLander:
|
|
|
71
71
|
uv run test_memory_trace.py
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### Action Chunk
|
|
75
|
+
|
|
76
|
+
Open-loop action chunking: one `step` executes a whole chunk of actions and returns only the final state, temporally compressing the environment by the chunk length.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import gymnasium as gym
|
|
80
|
+
import torch
|
|
81
|
+
from env_ssl_wrapper import StandardizeEnvWrapper, ActionChunkWrapper
|
|
82
|
+
|
|
83
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'))
|
|
84
|
+
env = ActionChunkWrapper(env, chunk_len = 2, gamma = 0.99)
|
|
85
|
+
|
|
86
|
+
obs, info = env.reset()
|
|
87
|
+
|
|
88
|
+
while True:
|
|
89
|
+
actions = torch.randint(0, 2, (1, 2)) # (num_envs, chunk_len)
|
|
90
|
+
obs, reward, terminated, truncated, info = env.step(actions)
|
|
91
|
+
|
|
92
|
+
# reward -> discounted sum of substeps (r0 + gamma * r1 + ...)
|
|
93
|
+
# info['discount'] -> gamma ** chunk_length (macro-step discount for GAE / Bellman target)
|
|
94
|
+
# info['chunk_length'] -> env steps actually executed (drops below chunk_len on terminal chunk)
|
|
95
|
+
# info['chunk_rewards'] -> (1, chunk_length) raw per-step rewards
|
|
96
|
+
|
|
97
|
+
if bool((terminated | truncated).item()):
|
|
98
|
+
obs, info = env.reset()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Chunks are shaped `(num_envs, chunk_len, *action_shape)` (`(num_envs, chunk_len)` for discrete actions).
|
|
102
|
+
|
|
103
|
+
Execution stops early the moment any env terminates or truncates mid-chunk — the terminal state and done flags of that substep are returned and the rest of the chunk is dropped, so a new episode is never silently advanced.
|
|
104
|
+
|
|
105
|
+
Pass `gamma` (default `1.`) to discount intra-chunk rewards $r = \sum_{i=0}^{L-1} \gamma^i r_i$. The macro-transition discount factor to the next state is provided as `info['discount'] = gamma ** chunk_length`. `reward_mode` can also be `'mean'` or `'last'`.
|
|
106
|
+
|
|
107
|
+
Can also be passed directly to `StandardizeEnvWrapper` or `compose_env`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'), chunk_len = 2, chunk_gamma = 0.99)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Run the chunked PPO benchmark on CartPole, or check chunked rollouts against a per-step reference:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv run test_action_chunk.py --chunk_len=2 # PPO on CartPole
|
|
117
|
+
uv run test_action_chunk.py --sweep=True # PPO over several chunk lengths
|
|
118
|
+
uv run test_action_chunk.py --verify=True --sweep=True # chunked rollouts match per-step reference
|
|
119
|
+
```
|
|
120
|
+
|
|
74
121
|
## Wrappers
|
|
75
122
|
|
|
76
123
|
Pass wrappers as strings (default config) or `(name, dict)` tuples (custom config), in any order.
|
|
@@ -85,6 +132,7 @@ Pass wrappers as strings (default config) or `(name, dict)` tuples (custom confi
|
|
|
85
132
|
| `action_transform` | Rescales actions from a canonical `(0, 1)` range to the env's bounds. |
|
|
86
133
|
| `tensor` | NumPy → torch on a device, torch actions → numpy for the sim. |
|
|
87
134
|
| `flatten_obs` | Flattens dict/tuple observations into a single vector. |
|
|
135
|
+
| `action_chunk` | Executes actions in open-loop chunks of length k. `('action_chunk', dict(chunk_len = 2, gamma = 0.99))` |
|
|
88
136
|
|
|
89
137
|
Every env emits the same contract: obs `torch.float32`, rewards `torch.float32`, `terminated`/`truncated` `torch.bool`. `env.seed(n)` works on every sim.
|
|
90
138
|
|
|
@@ -5,6 +5,7 @@ from . import standardize
|
|
|
5
5
|
from .standardize import *
|
|
6
6
|
|
|
7
7
|
from .memory_trace import MemoryTraceWrapper
|
|
8
|
+
from .action_chunk import ActionChunkWrapper
|
|
8
9
|
|
|
9
10
|
# Wire backwards-compatibility aliases in sys.modules and module globals
|
|
10
11
|
# so imports like `from env_ssl_wrapper.done_tracker_wrapper import DoneTrackerWrapper`
|
|
@@ -38,6 +39,7 @@ for _name in _STANDARDIZE_SUBMODULES:
|
|
|
38
39
|
__all__ = [
|
|
39
40
|
*standardize.__all__,
|
|
40
41
|
'MemoryTraceWrapper',
|
|
42
|
+
'ActionChunkWrapper',
|
|
41
43
|
]
|
|
42
44
|
|
|
43
45
|
def __getattr__(name):
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
from einops import reduce
|
|
6
|
+
from torch import is_tensor
|
|
7
|
+
|
|
8
|
+
from .standardize.helpers import (
|
|
9
|
+
EnvWrapper,
|
|
10
|
+
exists,
|
|
11
|
+
default,
|
|
12
|
+
any_true,
|
|
13
|
+
dones_of,
|
|
14
|
+
get_attr,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# helpers
|
|
18
|
+
|
|
19
|
+
def stack_steps(steps):
|
|
20
|
+
return torch.stack(steps, dim = -1) if is_tensor(steps[0]) else np.stack(steps, axis = -1)
|
|
21
|
+
|
|
22
|
+
def chunk_steps(actions, axis):
|
|
23
|
+
return actions.unbind(dim = axis) if is_tensor(actions) else np.moveaxis(actions, axis, 0)
|
|
24
|
+
|
|
25
|
+
# wrapper
|
|
26
|
+
|
|
27
|
+
class ActionChunkWrapper(EnvWrapper):
|
|
28
|
+
"""
|
|
29
|
+
Open-loop action chunking - executes a chunk of actions in one step,
|
|
30
|
+
temporally compressing the environment by the chunk length.
|
|
31
|
+
|
|
32
|
+
action chunks are shaped (num_envs, chunk_len, *action_shape)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
env,
|
|
38
|
+
chunk_len: int,
|
|
39
|
+
gamma: float = 1.,
|
|
40
|
+
discount: float | None = None,
|
|
41
|
+
reward_mode: str = 'sum'
|
|
42
|
+
):
|
|
43
|
+
super().__init__(env)
|
|
44
|
+
|
|
45
|
+
gamma = default(discount, gamma)
|
|
46
|
+
assert chunk_len >= 1, f'chunk_len must be at least 1, got {chunk_len}'
|
|
47
|
+
assert 0. <= gamma <= 1., f'gamma must be between 0 and 1, got {gamma}'
|
|
48
|
+
assert reward_mode in ('sum', 'mean', 'last'), f'unknown reward_mode {reward_mode!r}'
|
|
49
|
+
|
|
50
|
+
self.chunk_len = chunk_len
|
|
51
|
+
self.gamma = float(gamma)
|
|
52
|
+
self.reward_mode = reward_mode
|
|
53
|
+
|
|
54
|
+
action_space = get_attr(env, 'action_space')
|
|
55
|
+
self.action_shape = tuple(get_attr(action_space, 'shape', ()) or ())
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def chunk_action_shape(self):
|
|
59
|
+
return (self.chunk_len, *self.action_shape)
|
|
60
|
+
|
|
61
|
+
def reset(self, **kwargs):
|
|
62
|
+
return self.env.reset(**kwargs)
|
|
63
|
+
|
|
64
|
+
def step(self, actions):
|
|
65
|
+
if isinstance(actions, dict):
|
|
66
|
+
raise NotImplementedError('action chunking does not support dict actions yet')
|
|
67
|
+
|
|
68
|
+
if not (is_tensor(actions) or isinstance(actions, np.ndarray)):
|
|
69
|
+
actions = np.asarray(actions)
|
|
70
|
+
|
|
71
|
+
chunk_axis = actions.ndim - 1 - len(self.action_shape)
|
|
72
|
+
|
|
73
|
+
assert chunk_axis >= 1, (
|
|
74
|
+
f'action chunks must be shaped (num_envs, chunk_len, *action_shape), got {tuple(actions.shape)}'
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert actions.shape[chunk_axis] == self.chunk_len, (
|
|
78
|
+
f'expected chunk length {self.chunk_len}, got {actions.shape[chunk_axis]}'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
rewards = []
|
|
82
|
+
out = None
|
|
83
|
+
|
|
84
|
+
for action in chunk_steps(actions, chunk_axis):
|
|
85
|
+
out = self.env.step(action)
|
|
86
|
+
rewards.append(out[1])
|
|
87
|
+
|
|
88
|
+
if any_true(dones_of(out[2], out[3])):
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
obs, _, terminated, truncated, info = out
|
|
92
|
+
|
|
93
|
+
rewards = stack_steps(rewards)
|
|
94
|
+
executed_len = rewards.shape[-1]
|
|
95
|
+
|
|
96
|
+
if self.reward_mode == 'last':
|
|
97
|
+
reward = rewards[..., -1]
|
|
98
|
+
elif self.reward_mode == 'mean':
|
|
99
|
+
reward = reduce(rewards, '... k -> ...', 'mean')
|
|
100
|
+
elif self.gamma != 1.:
|
|
101
|
+
if is_tensor(rewards):
|
|
102
|
+
dtype = rewards.dtype if rewards.is_floating_point() else torch.float32
|
|
103
|
+
discounts = (self.gamma ** torch.arange(executed_len, device = rewards.device)).to(dtype = dtype)
|
|
104
|
+
else:
|
|
105
|
+
dtype = rewards.dtype if np.issubdtype(rewards.dtype, np.floating) else np.float32
|
|
106
|
+
discounts = (self.gamma ** np.arange(executed_len)).astype(dtype)
|
|
107
|
+
|
|
108
|
+
reward = reduce(rewards * discounts, '... k -> ...', 'sum')
|
|
109
|
+
else:
|
|
110
|
+
reward = reduce(rewards, '... k -> ...', 'sum')
|
|
111
|
+
|
|
112
|
+
info = dict(info) if isinstance(info, dict) else {}
|
|
113
|
+
info['chunk_length'] = executed_len
|
|
114
|
+
info['chunk_rewards'] = rewards
|
|
115
|
+
info['discount'] = self.gamma ** executed_len
|
|
116
|
+
|
|
117
|
+
return obs, reward, terminated, truncated, info
|
|
@@ -97,9 +97,14 @@ class ActionTransformWrapper(EnvWrapper):
|
|
|
97
97
|
if was_scalar:
|
|
98
98
|
t = rearrange(t, '-> 1')
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
# bounds may be unbatched (single_action_space) or already batched
|
|
101
|
+
# (vector envs exposing only a batched action_space)
|
|
102
|
+
|
|
103
|
+
if low.shape != t.shape and not (low.ndim > 0 and t.shape[-low.ndim:] == low.shape):
|
|
104
|
+
dim = t.shape[-1]
|
|
105
|
+
low = np.broadcast_to(low, (dim,))
|
|
106
|
+
high = np.broadcast_to(high, (dim,))
|
|
107
|
+
|
|
103
108
|
valid = np.isfinite(low) & np.isfinite(high)
|
|
104
109
|
|
|
105
110
|
if is_tensor(t):
|
|
@@ -1,32 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import numpy as np
|
|
4
|
-
import torch
|
|
5
|
-
from torch import is_tensor
|
|
6
|
-
|
|
7
3
|
from .helpers import (
|
|
8
4
|
EnvWrapper,
|
|
9
5
|
default,
|
|
10
6
|
exists,
|
|
11
7
|
first_existing,
|
|
12
8
|
get_attr,
|
|
9
|
+
is_time_step,
|
|
10
|
+
normalize_reset_out,
|
|
11
|
+
normalize_step_out,
|
|
13
12
|
safe_close,
|
|
14
13
|
truthy_attr,
|
|
14
|
+
zero_like,
|
|
15
15
|
)
|
|
16
16
|
from .spaces import space_from_action_spec
|
|
17
17
|
|
|
18
|
-
# helpers
|
|
19
|
-
|
|
20
|
-
def zero_like(x):
|
|
21
|
-
if is_tensor(x):
|
|
22
|
-
return torch.zeros_like(x, dtype = torch.bool)
|
|
23
|
-
|
|
24
|
-
arr = np.asarray(x)
|
|
25
|
-
return np.zeros_like(arr, dtype = bool) if arr.ndim > 0 else False
|
|
26
|
-
|
|
27
|
-
def is_time_step(out):
|
|
28
|
-
return exists(get_attr(out, 'step_type')) and exists(get_attr(out, 'observation'))
|
|
29
|
-
|
|
30
18
|
# base adapter
|
|
31
19
|
|
|
32
20
|
class BaseEnvAdapter:
|
|
@@ -38,27 +26,10 @@ class BaseEnvAdapter:
|
|
|
38
26
|
self.env = env
|
|
39
27
|
|
|
40
28
|
def step(self, action) -> tuple:
|
|
41
|
-
|
|
42
|
-
if is_time_step(out):
|
|
43
|
-
last = out.last() if callable(get_attr(out, 'last')) else out.step_type == 2
|
|
44
|
-
return out.observation, out.reward, last, False, dict(discount = out.discount)
|
|
45
|
-
if len(out) == 5:
|
|
46
|
-
obs, reward, term, trunc, info = out
|
|
47
|
-
return obs, reward, term, trunc, info if isinstance(info, dict) else {}
|
|
48
|
-
if len(out) in (3, 4):
|
|
49
|
-
obs, reward, done, *rest = out
|
|
50
|
-
info = rest[0] if rest and isinstance(rest[0], dict) else {}
|
|
51
|
-
return obs, reward, done, zero_like(done), info
|
|
52
|
-
raise ValueError(f'cannot standardize step output of length {len(out)}')
|
|
29
|
+
return normalize_step_out(self.env.step(action))
|
|
53
30
|
|
|
54
31
|
def reset(self, **kwargs) -> tuple:
|
|
55
|
-
|
|
56
|
-
if is_time_step(out):
|
|
57
|
-
return out.observation, {}
|
|
58
|
-
if isinstance(out, tuple) and len(out) == 2:
|
|
59
|
-
obs, info = out
|
|
60
|
-
return obs, {} if info is None else (info if isinstance(info, dict) else {})
|
|
61
|
-
return out, {}
|
|
32
|
+
return normalize_reset_out(self.env.reset(**kwargs))
|
|
62
33
|
|
|
63
34
|
def seed(self, seed: int):
|
|
64
35
|
if callable(get_attr(self.env, 'seed')):
|
|
@@ -381,15 +352,16 @@ class GymnasiumAdapter(BaseEnvAdapter):
|
|
|
381
352
|
|
|
382
353
|
@property
|
|
383
354
|
def autoresets(self) -> bool:
|
|
384
|
-
|
|
385
|
-
|
|
355
|
+
mode = first_existing(self.env, 'autoreset', 'autoresets', 'autoreset_mode')
|
|
356
|
+
|
|
357
|
+
if exists(mode):
|
|
358
|
+
return truthy_attr(mode)
|
|
359
|
+
|
|
386
360
|
try:
|
|
387
361
|
from gymnasium.vector import VectorEnv
|
|
388
|
-
|
|
389
|
-
return True
|
|
362
|
+
return isinstance(self.env, VectorEnv)
|
|
390
363
|
except ImportError:
|
|
391
|
-
|
|
392
|
-
return False
|
|
364
|
+
return False
|
|
393
365
|
|
|
394
366
|
def seed(self, seed: int):
|
|
395
367
|
try:
|
{env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/done_tracker_wrapper.py
RENAMED
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
|
-
from torch.utils._pytree import tree_flatten
|
|
6
|
-
|
|
7
5
|
from .auto_batched_wrapper import AutoBatchedWrapper
|
|
8
6
|
from .helpers import (
|
|
9
7
|
EnvWrapper,
|
|
@@ -11,27 +9,11 @@ from .helpers import (
|
|
|
11
9
|
env_autoresets,
|
|
12
10
|
env_num_envs,
|
|
13
11
|
exists,
|
|
14
|
-
|
|
15
|
-
is_array,
|
|
12
|
+
get_batch_size,
|
|
16
13
|
is_vectorized,
|
|
17
14
|
to_numpy,
|
|
18
15
|
)
|
|
19
16
|
|
|
20
|
-
# helper functions
|
|
21
|
-
|
|
22
|
-
def get_batch_size(tree) -> int | None:
|
|
23
|
-
leaves, _ = tree_flatten(tree)
|
|
24
|
-
|
|
25
|
-
if not leaves:
|
|
26
|
-
return None
|
|
27
|
-
|
|
28
|
-
first = leaves[0]
|
|
29
|
-
|
|
30
|
-
if is_array(first):
|
|
31
|
-
return len(first) if first.ndim > 0 else None
|
|
32
|
-
|
|
33
|
-
return len(first) if exists(get_attr(first, '__len__')) else None
|
|
34
|
-
|
|
35
17
|
# classes
|
|
36
18
|
|
|
37
19
|
class DoneTrackerWrapper(EnvWrapper):
|
|
@@ -113,6 +95,6 @@ class DoneTrackerWrapper(EnvWrapper):
|
|
|
113
95
|
info['episode_lengths'] = self.episode_lengths.copy()
|
|
114
96
|
|
|
115
97
|
if self.all_done:
|
|
116
|
-
info.update(needs_reset =
|
|
98
|
+
info.update(needs_reset = self.needs_reset, all_done = True)
|
|
117
99
|
|
|
118
100
|
return obs, reward, terminated, truncated, info
|
|
@@ -21,15 +21,18 @@ from .helpers import (
|
|
|
21
21
|
|
|
22
22
|
# helpers
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
if is_tensor(x):
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
def broadcast_mask(mask, x):
|
|
25
|
+
if not is_tensor(x):
|
|
26
|
+
return mask
|
|
27
|
+
|
|
28
|
+
m = torch.as_tensor(mask, device = x.device, dtype = torch.bool)
|
|
29
|
+
diff = x.ndim - m.ndim
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
m = rearrange(m, f'... -> ... {" ".join(["1"] * diff)}')
|
|
31
|
+
return rearrange(m, f'... -> ... {" ".join(["1"] * diff)}') if diff > 0 else m
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
def zero_mask(x, mask, fill_scalar = None):
|
|
34
|
+
if is_tensor(x):
|
|
35
|
+
return torch.where(broadcast_mask(mask, x), torch.zeros_like(x), x)
|
|
33
36
|
|
|
34
37
|
arr = np.asarray(x)
|
|
35
38
|
|
|
@@ -49,13 +52,7 @@ def back_to_mask_type(dones, newly):
|
|
|
49
52
|
|
|
50
53
|
def merge_final(current, value, mask):
|
|
51
54
|
if is_tensor(current):
|
|
52
|
-
|
|
53
|
-
diff = current.ndim - m.ndim
|
|
54
|
-
|
|
55
|
-
if diff > 0:
|
|
56
|
-
m = rearrange(m, f'... -> ... {" ".join(["1"] * diff)}')
|
|
57
|
-
|
|
58
|
-
return torch.where(m, value, current)
|
|
55
|
+
return torch.where(broadcast_mask(mask, current), value, current)
|
|
59
56
|
|
|
60
57
|
curr = np.asarray(current)
|
|
61
58
|
|
|
@@ -97,15 +94,15 @@ class EpisodePaddingWrapper(EnvWrapper):
|
|
|
97
94
|
if self._is_done is None or len(self._is_done) != len(mask):
|
|
98
95
|
self._is_done = np.zeros(len(mask), dtype = bool)
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
# autoreset envs revive every done slot, so each done is a new
|
|
98
|
+
# terminal transition; non-autoreset slots stay done until reset
|
|
99
|
+
|
|
100
|
+
newly = mask if self.autoreset else mask & ~self._is_done
|
|
101
|
+
self._is_done |= mask
|
|
102
102
|
|
|
103
103
|
if mask.any():
|
|
104
104
|
assert exists(self._last_obs), 'environment needs reset before calling step. call env.reset() first'
|
|
105
105
|
|
|
106
|
-
newly = mask & ~self._is_done
|
|
107
|
-
self._is_done |= mask
|
|
108
|
-
|
|
109
106
|
if newly.any():
|
|
110
107
|
final_val = first_existing(info, 'final_observation', 'final_obs')
|
|
111
108
|
value = final_val if exists(final_val) else self._last_obs
|
|
@@ -22,10 +22,15 @@ def get_attr(obj, name, default = None):
|
|
|
22
22
|
return default
|
|
23
23
|
|
|
24
24
|
def truthy_attr(value):
|
|
25
|
-
# flags arrive as None, methods, numpy scalars — only honest truths count
|
|
25
|
+
# flags arrive as None, methods, numpy scalars — only honest truths count.
|
|
26
|
+
# gymnasium's AutoresetMode.DISABLED is a truthy enum that means "off"
|
|
27
|
+
|
|
26
28
|
if not exists(value) or callable(value):
|
|
27
29
|
return False
|
|
28
30
|
|
|
31
|
+
if getattr(value, 'name', None) == 'DISABLED':
|
|
32
|
+
return False
|
|
33
|
+
|
|
29
34
|
try:
|
|
30
35
|
return bool(value)
|
|
31
36
|
except Exception:
|
|
@@ -46,6 +51,19 @@ def first_existing(obj, *names):
|
|
|
46
51
|
def is_scalar(v):
|
|
47
52
|
return isinstance(v, (int, float, bool, np.number, np.bool_))
|
|
48
53
|
|
|
54
|
+
def get_batch_size(tree) -> int | None:
|
|
55
|
+
leaves, _ = tree_flatten(tree)
|
|
56
|
+
|
|
57
|
+
if not leaves:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
first = leaves[0]
|
|
61
|
+
|
|
62
|
+
if is_array(first):
|
|
63
|
+
return len(first) if first.ndim > 0 else None
|
|
64
|
+
|
|
65
|
+
return len(first) if exists(get_attr(first, '__len__')) else None
|
|
66
|
+
|
|
49
67
|
def is_array(v):
|
|
50
68
|
return is_tensor(v) or isinstance(v, np.ndarray)
|
|
51
69
|
|
|
@@ -71,6 +89,43 @@ def dones_of(terminated, truncated):
|
|
|
71
89
|
return terminated | truncated
|
|
72
90
|
return tree_map(lambda a, b: a | b, terminated, truncated)
|
|
73
91
|
|
|
92
|
+
# sim step / reset normalization
|
|
93
|
+
|
|
94
|
+
def is_time_step(out):
|
|
95
|
+
return exists(get_attr(out, 'step_type')) and exists(get_attr(out, 'observation'))
|
|
96
|
+
|
|
97
|
+
def zero_like(x):
|
|
98
|
+
if is_tensor(x):
|
|
99
|
+
return torch.zeros_like(x, dtype = torch.bool)
|
|
100
|
+
|
|
101
|
+
arr = np.asarray(x)
|
|
102
|
+
return np.zeros_like(arr, dtype = bool) if arr.ndim > 0 else False
|
|
103
|
+
|
|
104
|
+
def normalize_reset_out(out):
|
|
105
|
+
if is_time_step(out):
|
|
106
|
+
return out.observation, {}
|
|
107
|
+
|
|
108
|
+
if isinstance(out, tuple) and len(out) == 2:
|
|
109
|
+
obs, info = out
|
|
110
|
+
return obs, {} if info is None else (info if isinstance(info, dict) else {})
|
|
111
|
+
|
|
112
|
+
return out, {}
|
|
113
|
+
|
|
114
|
+
def normalize_step_out(out):
|
|
115
|
+
if is_time_step(out):
|
|
116
|
+
last = out.last() if callable(get_attr(out, 'last')) else out.step_type == 2
|
|
117
|
+
return out.observation, out.reward, last, False, dict(discount = out.discount)
|
|
118
|
+
|
|
119
|
+
if len(out) == 5:
|
|
120
|
+
return out
|
|
121
|
+
|
|
122
|
+
if len(out) in (3, 4):
|
|
123
|
+
obs, reward, done, *rest = out
|
|
124
|
+
info = rest[0] if rest and isinstance(rest[0], dict) else {}
|
|
125
|
+
return obs, reward, done, zero_like(done), info
|
|
126
|
+
|
|
127
|
+
raise ValueError(f'could not standardize step output of length {len(out)}')
|
|
128
|
+
|
|
74
129
|
def _zero_leaf(x):
|
|
75
130
|
if is_tensor(x):
|
|
76
131
|
return torch.zeros_like(x)
|
|
@@ -272,16 +327,11 @@ class TransformObservationWrapper(EnvWrapper):
|
|
|
272
327
|
self.takes_done = accepts_done_param(self.transform_obs)
|
|
273
328
|
|
|
274
329
|
def transform_obs(self, obs, done = None):
|
|
275
|
-
if hasattr(self, 'observation'):
|
|
276
|
-
return self.observation(obs)
|
|
277
330
|
return obs
|
|
278
331
|
|
|
279
332
|
def transform(self, obs, done = None):
|
|
280
333
|
return self.transform_obs(obs, done = done) if self.takes_done else self.transform_obs(obs)
|
|
281
334
|
|
|
282
|
-
def observation(self, obs):
|
|
283
|
-
return self.transform_obs(obs)
|
|
284
|
-
|
|
285
335
|
def reset(self, **kwargs):
|
|
286
336
|
obs, info = self.env.reset(**kwargs)
|
|
287
337
|
obs = self.transform_obs(obs)
|
{env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/image_wrapper.py
RENAMED
|
@@ -6,8 +6,15 @@ import numpy as np
|
|
|
6
6
|
|
|
7
7
|
from einops import rearrange
|
|
8
8
|
|
|
9
|
-
from .helpers import
|
|
10
|
-
|
|
9
|
+
from .helpers import (
|
|
10
|
+
FINAL_OBSERVATION_KEYS,
|
|
11
|
+
TransformObservationWrapper,
|
|
12
|
+
env_render,
|
|
13
|
+
env_render_mode,
|
|
14
|
+
exists,
|
|
15
|
+
normalize_reset_out,
|
|
16
|
+
normalize_step_out,
|
|
17
|
+
)
|
|
11
18
|
|
|
12
19
|
# helper functions
|
|
13
20
|
|
|
@@ -32,12 +39,19 @@ def render_frame(env, image_size = (64, 64), camera = None):
|
|
|
32
39
|
|
|
33
40
|
img = env.render()
|
|
34
41
|
|
|
42
|
+
if isinstance(img, (list, tuple)):
|
|
43
|
+
img = np.ascontiguousarray(img)
|
|
44
|
+
|
|
35
45
|
if isinstance(img, torch.Tensor):
|
|
36
46
|
img = img.detach().cpu().numpy()
|
|
37
47
|
|
|
38
48
|
img = np.ascontiguousarray(img)
|
|
39
49
|
img = torch.from_numpy(img)
|
|
40
|
-
|
|
50
|
+
|
|
51
|
+
if img.ndim == 4:
|
|
52
|
+
return rearrange(img, 'b h w c -> b c h w'), True
|
|
53
|
+
|
|
54
|
+
return rearrange(img, 'h w c -> 1 c h w'), False
|
|
41
55
|
|
|
42
56
|
def process_image(
|
|
43
57
|
img,
|
|
@@ -61,7 +75,7 @@ def process_image(
|
|
|
61
75
|
|
|
62
76
|
# class
|
|
63
77
|
|
|
64
|
-
class ImageObservationWrapper(
|
|
78
|
+
class ImageObservationWrapper(TransformObservationWrapper):
|
|
65
79
|
def __init__(
|
|
66
80
|
self,
|
|
67
81
|
env,
|
|
@@ -81,18 +95,21 @@ class ImageObservationWrapper(EnvWrapper):
|
|
|
81
95
|
self.normalize_divisor = normalize_divisor
|
|
82
96
|
|
|
83
97
|
def render_frame(self):
|
|
84
|
-
img = render_frame(self.env, image_size = self.image_size, camera = self.camera)
|
|
85
|
-
|
|
98
|
+
img, is_batched = render_frame(self.env, image_size = self.image_size, camera = self.camera)
|
|
99
|
+
processed = process_image(
|
|
86
100
|
img,
|
|
87
101
|
image_size = self.image_size,
|
|
88
102
|
mode = self.mode,
|
|
89
103
|
normalize = self.normalize,
|
|
90
104
|
normalize_divisor = self.normalize_divisor
|
|
91
105
|
)
|
|
106
|
+
return processed, is_batched
|
|
92
107
|
|
|
93
108
|
def observation(self, obs):
|
|
94
|
-
img = self.render_frame()
|
|
95
|
-
|
|
109
|
+
img, is_batched = self.render_frame()
|
|
110
|
+
|
|
111
|
+
if not is_batched:
|
|
112
|
+
img = rearrange(img, '1 c h w -> c h w')
|
|
96
113
|
|
|
97
114
|
if not isinstance(obs, dict):
|
|
98
115
|
return dict(state = obs, **{self.image_key: img})
|
|
@@ -102,10 +119,23 @@ class ImageObservationWrapper(EnvWrapper):
|
|
|
102
119
|
|
|
103
120
|
return {**obs, self.image_key: img}
|
|
104
121
|
|
|
122
|
+
def transform_obs(self, obs, done = None):
|
|
123
|
+
return self.observation(obs)
|
|
124
|
+
|
|
105
125
|
def reset(self, **kwargs):
|
|
106
126
|
obs, info = normalize_reset_out(self.env.reset(**kwargs))
|
|
107
127
|
return self.observation(obs), info
|
|
108
128
|
|
|
109
129
|
def step(self, action):
|
|
110
130
|
obs, reward, terminated, truncated, info = normalize_step_out(self.env.step(action))
|
|
111
|
-
|
|
131
|
+
out = self.observation(obs)
|
|
132
|
+
|
|
133
|
+
# terminal obs are augmented in place, preserving whatever the sim
|
|
134
|
+
# or the padding wrapper froze as the true terminal observation
|
|
135
|
+
|
|
136
|
+
if isinstance(info, dict) and not self.autoresets:
|
|
137
|
+
for key in FINAL_OBSERVATION_KEYS:
|
|
138
|
+
if key in info and not (isinstance(info[key], dict) and self.image_key in info[key]):
|
|
139
|
+
info[key] = self.observation(info[key])
|
|
140
|
+
|
|
141
|
+
return out, reward, terminated, truncated, info
|
|
@@ -18,11 +18,14 @@ class StandardizeEnvWrapper(EnvWrapper):
|
|
|
18
18
|
pad_episodes: bool = True,
|
|
19
19
|
done_tracker: bool = True,
|
|
20
20
|
flatten_obs: bool = False,
|
|
21
|
-
action_transform: bool = False,
|
|
21
|
+
action_transform: bool | dict = False,
|
|
22
22
|
max_timesteps: int | None = None,
|
|
23
|
-
image_size: tuple[int, int] | None = None,
|
|
23
|
+
image_size: int | tuple[int, int] | None = None,
|
|
24
24
|
lambdas: tuple[float, ...] | list[float] | None = None,
|
|
25
|
-
|
|
25
|
+
keys: str | tuple[str, ...] | None = None,
|
|
26
|
+
chunk_len: int | None = None,
|
|
27
|
+
chunk_gamma: float = 1.,
|
|
28
|
+
chunk_reward_mode: str = 'sum',
|
|
26
29
|
):
|
|
27
30
|
wrappers = []
|
|
28
31
|
|
|
@@ -30,7 +33,8 @@ class StandardizeEnvWrapper(EnvWrapper):
|
|
|
30
33
|
wrappers.append(('image', dict(image_size = image_size)))
|
|
31
34
|
|
|
32
35
|
if action_transform:
|
|
33
|
-
|
|
36
|
+
transform_kwargs = dict(auto = True) if action_transform is True else action_transform
|
|
37
|
+
wrappers.append(('action_transform', transform_kwargs))
|
|
34
38
|
|
|
35
39
|
if exists(max_timesteps):
|
|
36
40
|
wrappers.append(('time_limit', dict(max_timesteps = max_timesteps)))
|
|
@@ -45,7 +49,10 @@ class StandardizeEnvWrapper(EnvWrapper):
|
|
|
45
49
|
wrappers.append('done_tracker')
|
|
46
50
|
|
|
47
51
|
if exists(lambdas):
|
|
48
|
-
wrappers.append(('memory_trace', dict(lambdas = lambdas,
|
|
52
|
+
wrappers.append(('memory_trace', dict(lambdas = lambdas, keys = keys)))
|
|
53
|
+
|
|
54
|
+
if exists(chunk_len):
|
|
55
|
+
wrappers.append(('action_chunk', dict(chunk_len = chunk_len, gamma = chunk_gamma, reward_mode = chunk_reward_mode)))
|
|
49
56
|
|
|
50
57
|
if flatten_obs:
|
|
51
58
|
wrappers.append('flatten_obs')
|
{env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/standardize_wrapper.py
RENAMED
|
@@ -1,48 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import torch
|
|
5
|
-
from torch import is_tensor
|
|
6
|
-
|
|
7
|
-
from .adapters import get_adapter, is_time_step, zero_like
|
|
3
|
+
from .adapters import get_adapter
|
|
8
4
|
from .helpers import (
|
|
9
5
|
EnvWrapper,
|
|
10
6
|
default,
|
|
11
7
|
dones_of,
|
|
12
8
|
exists,
|
|
13
|
-
get_attr,
|
|
14
9
|
instantiate_env,
|
|
15
10
|
mark_terminal_obs,
|
|
16
11
|
)
|
|
17
12
|
from .spaces import infer_observation_space, space_from_action_spec
|
|
18
13
|
|
|
19
|
-
# helpers
|
|
20
|
-
|
|
21
|
-
def normalize_reset_out(out):
|
|
22
|
-
if is_time_step(out):
|
|
23
|
-
return out.observation, {}
|
|
24
|
-
|
|
25
|
-
if isinstance(out, tuple) and len(out) == 2:
|
|
26
|
-
obs, info = out
|
|
27
|
-
return obs, {} if info is None else (info if isinstance(info, dict) else {})
|
|
28
|
-
|
|
29
|
-
return out, {}
|
|
30
|
-
|
|
31
|
-
def normalize_step_out(out):
|
|
32
|
-
if is_time_step(out):
|
|
33
|
-
last = out.last() if callable(get_attr(out, 'last')) else out.step_type == 2
|
|
34
|
-
return out.observation, out.reward, last, False, dict(discount = out.discount)
|
|
35
|
-
|
|
36
|
-
if len(out) == 5:
|
|
37
|
-
return out
|
|
38
|
-
|
|
39
|
-
if len(out) in (3, 4):
|
|
40
|
-
obs, reward, done, *rest = out
|
|
41
|
-
info = rest[0] if rest and isinstance(rest[0], dict) else {}
|
|
42
|
-
return obs, reward, done, zero_like(done), info
|
|
43
|
-
|
|
44
|
-
raise ValueError(f'could not standardize step output of length {len(out)}')
|
|
45
|
-
|
|
46
14
|
# class
|
|
47
15
|
|
|
48
16
|
class StandardizeWrapper(EnvWrapper):
|
{env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/time_limit_wrapper.py
RENAMED
|
@@ -4,8 +4,18 @@ import numpy as np
|
|
|
4
4
|
import torch
|
|
5
5
|
from torch import is_tensor
|
|
6
6
|
|
|
7
|
-
from .helpers import
|
|
8
|
-
|
|
7
|
+
from .helpers import (
|
|
8
|
+
EnvWrapper,
|
|
9
|
+
dones_of,
|
|
10
|
+
env_num_envs,
|
|
11
|
+
exists,
|
|
12
|
+
get_batch_size,
|
|
13
|
+
is_vectorized,
|
|
14
|
+
mark_terminal_obs,
|
|
15
|
+
normalize_reset_out,
|
|
16
|
+
normalize_step_out,
|
|
17
|
+
to_numpy,
|
|
18
|
+
)
|
|
9
19
|
|
|
10
20
|
def back_to_like(t, numpy_arr):
|
|
11
21
|
if is_tensor(t):
|
|
@@ -44,8 +54,13 @@ class TimeLimitWrapper(EnvWrapper):
|
|
|
44
54
|
self.t = np.zeros(self.num_envs, dtype = int)
|
|
45
55
|
|
|
46
56
|
def reset(self, **kwargs):
|
|
57
|
+
obs, info = normalize_reset_out(self.env.reset(**kwargs))
|
|
58
|
+
|
|
59
|
+
if self.is_vector and exists(batch_size := get_batch_size(obs)):
|
|
60
|
+
self.num_envs = batch_size
|
|
61
|
+
|
|
47
62
|
self.t = np.zeros(self.num_envs, dtype = int)
|
|
48
|
-
return
|
|
63
|
+
return obs, info
|
|
49
64
|
|
|
50
65
|
def step(self, action):
|
|
51
66
|
obs, reward, terminated, truncated, info = normalize_step_out(self.env.step(action))
|
|
@@ -25,29 +25,31 @@ WRAPPERS = dict(
|
|
|
25
25
|
pad_episodes = EpisodePaddingWrapper
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
+
def get_wrapper(name):
|
|
29
|
+
if name in ('standardize_env', 'master'):
|
|
30
|
+
from .standardize_env_wrapper import StandardizeEnvWrapper
|
|
31
|
+
return StandardizeEnvWrapper
|
|
32
|
+
|
|
33
|
+
if name == 'memory_trace':
|
|
34
|
+
from ..memory_trace import MemoryTraceWrapper
|
|
35
|
+
return MemoryTraceWrapper
|
|
36
|
+
|
|
37
|
+
if name in ('action_chunk', 'chunk'):
|
|
38
|
+
from ..action_chunk import ActionChunkWrapper
|
|
39
|
+
return ActionChunkWrapper
|
|
40
|
+
|
|
41
|
+
if name in WRAPPERS:
|
|
42
|
+
return WRAPPERS[name]
|
|
43
|
+
|
|
44
|
+
raise ValueError(f'unknown wrapper {name!r} — choose from {sorted([*WRAPPERS, "memory_trace", "action_chunk", "standardize_env"])}')
|
|
45
|
+
|
|
28
46
|
def parse_wrapper(wrapper):
|
|
29
47
|
if isinstance(wrapper, str):
|
|
30
|
-
|
|
31
|
-
from ..memory_trace import MemoryTraceWrapper
|
|
32
|
-
wrapper = MemoryTraceWrapper
|
|
33
|
-
elif wrapper in ('standardize_env', 'master'):
|
|
34
|
-
from .standardize_env_wrapper import StandardizeEnvWrapper
|
|
35
|
-
wrapper = StandardizeEnvWrapper
|
|
36
|
-
elif wrapper not in WRAPPERS:
|
|
37
|
-
raise ValueError(f'unknown wrapper {wrapper!r} — choose from {sorted([*WRAPPERS, "memory_trace", "standardize_env"])}')
|
|
38
|
-
else:
|
|
39
|
-
wrapper = WRAPPERS[wrapper]
|
|
48
|
+
wrapper = get_wrapper(wrapper)
|
|
40
49
|
|
|
41
50
|
if isinstance(wrapper, tuple):
|
|
42
51
|
name, kwargs = wrapper
|
|
43
|
-
if name
|
|
44
|
-
from ..memory_trace import MemoryTraceWrapper
|
|
45
|
-
wrapper = partial(MemoryTraceWrapper, **kwargs)
|
|
46
|
-
elif name in ('standardize_env', 'master'):
|
|
47
|
-
from .standardize_env_wrapper import StandardizeEnvWrapper
|
|
48
|
-
wrapper = partial(StandardizeEnvWrapper, **kwargs)
|
|
49
|
-
else:
|
|
50
|
-
wrapper = partial(WRAPPERS.get(name, name), **kwargs)
|
|
52
|
+
wrapper = partial(get_wrapper(name) if isinstance(name, str) else name, **kwargs)
|
|
51
53
|
|
|
52
54
|
elif isinstance(wrapper, dict):
|
|
53
55
|
raise ValueError("wrapper kwargs must be passed as (name, kwargs), e.g. ('tensor', dict(device = 'cpu'))")
|
|
@@ -89,6 +91,14 @@ def compose_env(env, *wrappers, pad_episodes: bool = True):
|
|
|
89
91
|
funcs.insert(idx_ten + 1, f)
|
|
90
92
|
classes.insert(idx_ten + 1, c)
|
|
91
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))
|
|
101
|
+
|
|
92
102
|
assert len(set(classes)) == len(classes), 'duplicate wrappers found'
|
|
93
103
|
|
|
94
104
|
for func in funcs:
|
|
@@ -22,8 +22,6 @@ from .standardize_wrapper import StandardizeWrapper
|
|
|
22
22
|
|
|
23
23
|
# leaf helpers
|
|
24
24
|
|
|
25
|
-
_stack_trees = stack_trees
|
|
26
|
-
|
|
27
25
|
def _merge_infos(infos):
|
|
28
26
|
infos = [info if isinstance(info, dict) else {} for info in infos]
|
|
29
27
|
|
|
@@ -38,7 +36,7 @@ def _merge_infos(infos):
|
|
|
38
36
|
for key in keys:
|
|
39
37
|
vals = [info[key] for info in infos]
|
|
40
38
|
try:
|
|
41
|
-
out[key] =
|
|
39
|
+
out[key] = stack_trees(vals)
|
|
42
40
|
except Exception:
|
|
43
41
|
out[key] = np.array(vals, dtype = object)
|
|
44
42
|
|
|
@@ -276,7 +274,7 @@ class MultiprocessingVecEnv:
|
|
|
276
274
|
_safe_send(conn, ('reset', kwargs))
|
|
277
275
|
|
|
278
276
|
results = _recv_all(self._conns)
|
|
279
|
-
return
|
|
277
|
+
return stack_trees([obs for obs, _ in results]), _merge_infos([info for _, info in results])
|
|
280
278
|
|
|
281
279
|
def step(self, actions):
|
|
282
280
|
actions = _split_actions(actions, self.num_envs)
|
|
@@ -302,10 +300,10 @@ class MultiprocessingVecEnv:
|
|
|
302
300
|
for final, obs_i in zip(final_obs, obs)
|
|
303
301
|
]
|
|
304
302
|
|
|
305
|
-
info['final_observation'] =
|
|
303
|
+
info['final_observation'] = stack_trees(final_obs)
|
|
306
304
|
info['_final_observation'] = done
|
|
307
305
|
|
|
308
|
-
return
|
|
306
|
+
return stack_trees(obs), reward, terminated, truncated, info
|
|
309
307
|
|
|
310
308
|
def close(self):
|
|
311
309
|
_shutdown(self._conns, self._procs)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "env-ssl-wrapper"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3"
|
|
4
4
|
description = "One torch-native interface for any MDP environment"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Phil Wang", email = "lucidrains@gmail.com" }
|
|
@@ -54,6 +54,9 @@ test = [
|
|
|
54
54
|
"pillow",
|
|
55
55
|
"imageio",
|
|
56
56
|
"populora",
|
|
57
|
+
"gym-pusht",
|
|
58
|
+
"pymunk<7",
|
|
59
|
+
"mean-conc-beta",
|
|
57
60
|
]
|
|
58
61
|
|
|
59
62
|
[tool.pytest.ini_options]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/auto_batched_wrapper.py
RENAMED
|
File without changes
|
{env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.4.1 → env_ssl_wrapper-0.4.3}/env_ssl_wrapper/standardize/tensor_wrapper.py
RENAMED
|
File without changes
|