env-ssl-wrapper 0.4.6__tar.gz → 0.5.0__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.6 → env_ssl_wrapper-0.5.0}/PKG-INFO +4 -2
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/README.md +1 -1
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/__init__.py +3 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/action_chunk.py +12 -1
- env_ssl_wrapper-0.5.0/env_ssl_wrapper/evaluate.py +337 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/pyproject.toml +3 -1
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/.gitignore +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/LICENSE +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/memory_trace.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/__init__.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/action_transform_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/adapters.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/auto_batched_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/done_tracker_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/episode_padding_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/helpers.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/image_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/mocks.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/spaces.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/standardize_env_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/standardize_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/tensor_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/time_limit_wrapper.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/utils.py +0 -0
- {env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/vector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: env-ssl-wrapper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
|
@@ -37,6 +37,8 @@ Requires-Python: >=3.10
|
|
|
37
37
|
Requires-Dist: discrete-continuous-embed-readout
|
|
38
38
|
Requires-Dist: einops>=0.8.1
|
|
39
39
|
Requires-Dist: einx>=0.3.0
|
|
40
|
+
Requires-Dist: imageio
|
|
41
|
+
Requires-Dist: imageio-ffmpeg
|
|
40
42
|
Requires-Dist: memmap-replay-buffer
|
|
41
43
|
Requires-Dist: torch-einops-utils>=0.0.29
|
|
42
44
|
Requires-Dist: torch>=2.5
|
|
@@ -161,7 +163,7 @@ while True:
|
|
|
161
163
|
obs, info = env.reset()
|
|
162
164
|
```
|
|
163
165
|
|
|
164
|
-
Chunks are shaped `(num_envs, chunk_len, *action_shape)` (`(num_envs, chunk_len)` for discrete actions).
|
|
166
|
+
Chunks are shaped `(num_envs, chunk_len, *action_shape)` (`(num_envs, chunk_len)` for discrete actions). The wrapper can also be applied directly to raw gymnasium environments, single or vectorized — torch action chunks are converted to numpy for the underlying simulator.
|
|
165
167
|
|
|
166
168
|
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
169
|
|
|
@@ -98,7 +98,7 @@ while True:
|
|
|
98
98
|
obs, info = env.reset()
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
Chunks are shaped `(num_envs, chunk_len, *action_shape)` (`(num_envs, chunk_len)` for discrete actions).
|
|
101
|
+
Chunks are shaped `(num_envs, chunk_len, *action_shape)` (`(num_envs, chunk_len)` for discrete actions). The wrapper can also be applied directly to raw gymnasium environments, single or vectorized — torch action chunks are converted to numpy for the underlying simulator.
|
|
102
102
|
|
|
103
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
104
|
|
|
@@ -6,6 +6,7 @@ from .standardize import *
|
|
|
6
6
|
|
|
7
7
|
from .memory_trace import MemoryTraceWrapper
|
|
8
8
|
from .action_chunk import ActionChunkWrapper
|
|
9
|
+
from .evaluate import evaluate_actor, EpisodeStats
|
|
9
10
|
|
|
10
11
|
# Wire backwards-compatibility aliases in sys.modules and module globals
|
|
11
12
|
# so imports like `from env_ssl_wrapper.done_tracker_wrapper import DoneTrackerWrapper`
|
|
@@ -40,6 +41,8 @@ __all__ = [
|
|
|
40
41
|
*standardize.__all__,
|
|
41
42
|
'MemoryTraceWrapper',
|
|
42
43
|
'ActionChunkWrapper',
|
|
44
|
+
'evaluate_actor',
|
|
45
|
+
'EpisodeStats',
|
|
43
46
|
]
|
|
44
47
|
|
|
45
48
|
def __getattr__(name):
|
|
@@ -11,8 +11,10 @@ from .standardize.helpers import (
|
|
|
11
11
|
default,
|
|
12
12
|
any_true,
|
|
13
13
|
dones_of,
|
|
14
|
+
first_existing,
|
|
14
15
|
get_attr,
|
|
15
16
|
is_vectorized,
|
|
17
|
+
to_numpy,
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
# helpers
|
|
@@ -62,9 +64,13 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
62
64
|
self.gamma = float(gamma)
|
|
63
65
|
self.reward_mode = reward_mode
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
# vectorized gym envs expose a batched action space - the single env space defines the chunk layout
|
|
68
|
+
|
|
69
|
+
action_space = first_existing(env, 'single_action_space', 'action_space')
|
|
66
70
|
self.action_shape = tuple(get_attr(action_space, 'shape', ()) or ())
|
|
71
|
+
|
|
67
72
|
self.expects_batch = is_vectorized(env)
|
|
73
|
+
self.raw_env = not isinstance(env, EnvWrapper)
|
|
68
74
|
|
|
69
75
|
@property
|
|
70
76
|
def chunk_action_shape(self):
|
|
@@ -80,6 +86,11 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
80
86
|
if not (is_tensor(actions) or isinstance(actions, np.ndarray)):
|
|
81
87
|
actions = np.asarray(actions)
|
|
82
88
|
|
|
89
|
+
# raw simulators only understand numpy - wrapper chains from this package handle torch actions
|
|
90
|
+
|
|
91
|
+
if self.raw_env and is_tensor(actions):
|
|
92
|
+
actions = to_numpy(actions)
|
|
93
|
+
|
|
83
94
|
chunk_axis = actions.ndim - 1 - len(self.action_shape)
|
|
84
95
|
|
|
85
96
|
assert chunk_axis >= 1, (
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import NamedTuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import torch
|
|
8
|
+
from torch import nn, is_tensor
|
|
9
|
+
from torch.distributions import Categorical, Distribution
|
|
10
|
+
from torch.utils._pytree import tree_flatten, tree_map
|
|
11
|
+
|
|
12
|
+
from .standardize.helpers import (
|
|
13
|
+
EnvWrapper,
|
|
14
|
+
env_autoresets,
|
|
15
|
+
env_num_envs,
|
|
16
|
+
env_render,
|
|
17
|
+
exists,
|
|
18
|
+
first_existing,
|
|
19
|
+
get_adapter,
|
|
20
|
+
get_attr,
|
|
21
|
+
is_array,
|
|
22
|
+
is_vectorized,
|
|
23
|
+
normalize_reset_out,
|
|
24
|
+
normalize_step_out,
|
|
25
|
+
to_numpy,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# results
|
|
29
|
+
|
|
30
|
+
class EpisodeStats(NamedTuple):
|
|
31
|
+
returns: torch.Tensor
|
|
32
|
+
lengths: torch.Tensor
|
|
33
|
+
videos: tuple[str, ...] = ()
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def num_episodes(self):
|
|
37
|
+
return self.returns.numel()
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def mean(self):
|
|
41
|
+
return self.returns.mean().item()
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def std(self):
|
|
45
|
+
return self.returns.std(unbiased = False).item()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def min(self):
|
|
49
|
+
return self.returns.min().item()
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def max(self):
|
|
53
|
+
return self.returns.max().item()
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def video_path(self):
|
|
57
|
+
return self.videos[0] if self.videos else None
|
|
58
|
+
|
|
59
|
+
def success_rate(self, threshold):
|
|
60
|
+
return (self.returns >= threshold).float().mean().item()
|
|
61
|
+
|
|
62
|
+
def summary(self):
|
|
63
|
+
return dict(episodes = self.num_episodes, mean = self.mean, std = self.std, min = self.min, max = self.max)
|
|
64
|
+
|
|
65
|
+
# actor output -> action
|
|
66
|
+
|
|
67
|
+
def resolve_actor(actor):
|
|
68
|
+
# a callable `.dist` takes precedence, sb3 style
|
|
69
|
+
|
|
70
|
+
module = actor if isinstance(actor, nn.Module) else None
|
|
71
|
+
policy = get_attr(actor, 'dist')
|
|
72
|
+
|
|
73
|
+
if not callable(policy):
|
|
74
|
+
policy = actor
|
|
75
|
+
|
|
76
|
+
assert callable(policy), 'actor must be callable, or expose a callable `.dist`'
|
|
77
|
+
|
|
78
|
+
return policy, module
|
|
79
|
+
|
|
80
|
+
def is_distribution(out):
|
|
81
|
+
if isinstance(out, Distribution):
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
if is_tensor(out):
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
return callable(get_attr(out, 'sample')) and (exists(get_attr(out, 'mean')) or exists(get_attr(out, 'probs')))
|
|
88
|
+
|
|
89
|
+
def to_action(env, out, deterministic = True):
|
|
90
|
+
# distributions collapse to their mode, raw floating logits over a discrete space to their argmax
|
|
91
|
+
|
|
92
|
+
if is_distribution(out):
|
|
93
|
+
if not deterministic:
|
|
94
|
+
return out.sample()
|
|
95
|
+
|
|
96
|
+
return out.probs.argmax(dim = -1) if exists(get_attr(out, 'probs')) else out.mean
|
|
97
|
+
|
|
98
|
+
if is_tensor(out) and out.is_floating_point() and out.ndim > 0:
|
|
99
|
+
num_actions = get_attr(get_adapter(env).action_space, 'n')
|
|
100
|
+
|
|
101
|
+
if exists(num_actions) and out.shape[-1] == num_actions:
|
|
102
|
+
return out.argmax(dim = -1) if deterministic else Categorical(logits = out).sample()
|
|
103
|
+
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
# states and actions
|
|
107
|
+
|
|
108
|
+
def to_actor_obs(obs, device, batched):
|
|
109
|
+
def convert(x):
|
|
110
|
+
if not (is_array(x) or callable(get_attr(x, '__array__'))):
|
|
111
|
+
return x
|
|
112
|
+
|
|
113
|
+
tensor = x if is_tensor(x) else torch.as_tensor(np.asarray(x))
|
|
114
|
+
|
|
115
|
+
if tensor.is_floating_point():
|
|
116
|
+
tensor = tensor.float()
|
|
117
|
+
|
|
118
|
+
tensor = tensor.to(device)
|
|
119
|
+
|
|
120
|
+
return tensor if batched or tensor.ndim == 0 else tensor[None]
|
|
121
|
+
|
|
122
|
+
return tree_map(convert, obs)
|
|
123
|
+
|
|
124
|
+
def to_env_action(env, action):
|
|
125
|
+
if not is_tensor(action):
|
|
126
|
+
action = torch.as_tensor(action)
|
|
127
|
+
|
|
128
|
+
device = get_attr(env, 'device')
|
|
129
|
+
action = action.to(device) if exists(device) else action
|
|
130
|
+
|
|
131
|
+
# raw simulators take unbatched numpy
|
|
132
|
+
|
|
133
|
+
if not isinstance(env, EnvWrapper):
|
|
134
|
+
action = to_numpy(action)
|
|
135
|
+
|
|
136
|
+
if action.ndim > 1 and action.shape[0] == 1:
|
|
137
|
+
action = action[0]
|
|
138
|
+
|
|
139
|
+
return action.item() if action.ndim == 0 else action
|
|
140
|
+
|
|
141
|
+
# wrappers take batched torch actions - pad up to the declared rank
|
|
142
|
+
|
|
143
|
+
chunk_shape = get_attr(env, 'chunk_action_shape')
|
|
144
|
+
|
|
145
|
+
if exists(chunk_shape):
|
|
146
|
+
rank = len(chunk_shape) + 1
|
|
147
|
+
else:
|
|
148
|
+
space = first_existing(env, 'single_action_space', 'action_space')
|
|
149
|
+
rank = len(get_attr(space, 'shape', ())) + 1
|
|
150
|
+
|
|
151
|
+
while action.ndim < rank:
|
|
152
|
+
action = action[None]
|
|
153
|
+
|
|
154
|
+
return action
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# video
|
|
158
|
+
|
|
159
|
+
def capture_frame(env):
|
|
160
|
+
render = get_attr(env, 'render')
|
|
161
|
+
frame = render() if callable(render) else None
|
|
162
|
+
|
|
163
|
+
if not exists(frame):
|
|
164
|
+
try:
|
|
165
|
+
frame = env_render(env, 256, 256)
|
|
166
|
+
except Exception:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
if is_tensor(frame):
|
|
170
|
+
frame = frame.detach().cpu().numpy()
|
|
171
|
+
|
|
172
|
+
frame = np.asarray(frame)
|
|
173
|
+
|
|
174
|
+
if frame.ndim == 4:
|
|
175
|
+
frame = frame[0]
|
|
176
|
+
|
|
177
|
+
if frame.ndim == 3 and frame.shape[0] in (3, 4) and frame.shape[-1] not in (3, 4):
|
|
178
|
+
frame = np.moveaxis(frame, 0, -1)
|
|
179
|
+
|
|
180
|
+
if frame.ndim == 3 and frame.shape[-1] == 4:
|
|
181
|
+
frame = frame[..., :3]
|
|
182
|
+
|
|
183
|
+
if frame.dtype != np.uint8:
|
|
184
|
+
scale = 255. if frame.max() <= 1. else 1.
|
|
185
|
+
frame = np.clip(frame * scale, 0., 255.).astype(np.uint8)
|
|
186
|
+
|
|
187
|
+
return frame
|
|
188
|
+
|
|
189
|
+
def save_video(frames, path, fps = 30):
|
|
190
|
+
import imageio
|
|
191
|
+
|
|
192
|
+
path = Path(path)
|
|
193
|
+
path.parent.mkdir(parents = True, exist_ok = True)
|
|
194
|
+
imageio.mimsave(path, frames, fps = fps, macro_block_size = None)
|
|
195
|
+
|
|
196
|
+
def video_path_at(path, index):
|
|
197
|
+
path = Path(path)
|
|
198
|
+
return str(path if index == 0 else path.with_name(f'{path.stem}_ep{index}{path.suffix}'))
|
|
199
|
+
|
|
200
|
+
# evaluate
|
|
201
|
+
|
|
202
|
+
@torch.no_grad()
|
|
203
|
+
def evaluate_actor(
|
|
204
|
+
actor,
|
|
205
|
+
env,
|
|
206
|
+
episodes = 10,
|
|
207
|
+
max_steps = 1000,
|
|
208
|
+
seed = None,
|
|
209
|
+
goal = None,
|
|
210
|
+
device = None,
|
|
211
|
+
deterministic = True,
|
|
212
|
+
video_path = None,
|
|
213
|
+
video_episodes = 1,
|
|
214
|
+
fps = 30,
|
|
215
|
+
verbose = False
|
|
216
|
+
):
|
|
217
|
+
# seed fixes the episode seeds, so checkpoints are compared on identical episodes
|
|
218
|
+
# video_path records the first video_episodes episodes
|
|
219
|
+
|
|
220
|
+
policy, module = resolve_actor(actor)
|
|
221
|
+
|
|
222
|
+
if not exists(device):
|
|
223
|
+
params = next(module.parameters(), None) if exists(module) else None
|
|
224
|
+
device = get_attr(params, 'device', torch.device('cpu'))
|
|
225
|
+
|
|
226
|
+
was_training = get_attr(module, 'training')
|
|
227
|
+
|
|
228
|
+
if exists(module):
|
|
229
|
+
module.eval()
|
|
230
|
+
|
|
231
|
+
if exists(goal):
|
|
232
|
+
goal = torch.as_tensor(goal) if not is_tensor(goal) else goal
|
|
233
|
+
goal = goal.to(device)
|
|
234
|
+
goal = goal[None] if goal.ndim == 1 else goal
|
|
235
|
+
|
|
236
|
+
num_envs = env_num_envs(env)
|
|
237
|
+
record = exists(video_path)
|
|
238
|
+
|
|
239
|
+
assert not (record and num_envs > 1), 'video recording requires a single env'
|
|
240
|
+
|
|
241
|
+
obs, info = normalize_reset_out(env.reset(seed = seed) if exists(seed) else env.reset())
|
|
242
|
+
batched = is_vectorized(env) or all(map(is_tensor, tree_flatten(obs)[0]))
|
|
243
|
+
|
|
244
|
+
goal = goal.expand(num_envs, -1) if exists(goal) and goal.shape[0] == 1 and num_envs > 1 else goal
|
|
245
|
+
|
|
246
|
+
returns = torch.zeros(num_envs)
|
|
247
|
+
lengths = torch.zeros(num_envs)
|
|
248
|
+
steps = torch.zeros(num_envs, dtype = torch.long)
|
|
249
|
+
|
|
250
|
+
done_returns, done_lengths, videos, frames = [], [], [], []
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
while len(done_returns) < episodes:
|
|
254
|
+
if record and len(done_returns) < video_episodes:
|
|
255
|
+
frame = capture_frame(env)
|
|
256
|
+
if exists(frame):
|
|
257
|
+
frames.append(frame)
|
|
258
|
+
|
|
259
|
+
state = to_actor_obs(obs, device, batched)
|
|
260
|
+
out = policy(state, goal) if exists(goal) else policy(state)
|
|
261
|
+
action = to_env_action(env, to_action(env, out, deterministic))
|
|
262
|
+
|
|
263
|
+
obs, reward, terminated, truncated, info = normalize_step_out(env.step(action))
|
|
264
|
+
|
|
265
|
+
# reward chunks collapse to one scalar per env
|
|
266
|
+
|
|
267
|
+
reward = torch.as_tensor(to_numpy(reward), dtype = torch.float32)
|
|
268
|
+
|
|
269
|
+
while reward.ndim > 1:
|
|
270
|
+
reward = reward.sum(dim = -1)
|
|
271
|
+
|
|
272
|
+
if reward.numel() > num_envs:
|
|
273
|
+
reward = reward.reshape(num_envs, -1).sum(dim = -1)
|
|
274
|
+
|
|
275
|
+
returns += reward if reward.numel() == num_envs else reward.expand(num_envs)
|
|
276
|
+
|
|
277
|
+
lengths += info.get('chunk_length', 1) if isinstance(info, dict) else 1
|
|
278
|
+
steps += 1
|
|
279
|
+
|
|
280
|
+
done = to_numpy(terminated | truncated).astype(bool).reshape(-1)
|
|
281
|
+
|
|
282
|
+
if exists(max_steps):
|
|
283
|
+
done |= to_numpy(steps >= max_steps).reshape(-1)
|
|
284
|
+
|
|
285
|
+
for i in np.where(done)[0]:
|
|
286
|
+
if len(done_returns) >= episodes:
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
episode = len(done_returns)
|
|
290
|
+
done_returns.append(returns[i].item())
|
|
291
|
+
done_lengths.append(lengths[i].item())
|
|
292
|
+
|
|
293
|
+
if verbose:
|
|
294
|
+
print(f'episode {episode:3d} | return {done_returns[-1]:8.1f} | length {done_lengths[-1]:7.0f}', flush = True)
|
|
295
|
+
|
|
296
|
+
if record and episode < video_episodes:
|
|
297
|
+
frame = capture_frame(env)
|
|
298
|
+
if exists(frame):
|
|
299
|
+
frames.append(frame)
|
|
300
|
+
|
|
301
|
+
if frames:
|
|
302
|
+
path = video_path_at(video_path, episode)
|
|
303
|
+
save_video(frames, path, fps)
|
|
304
|
+
videos.append(path)
|
|
305
|
+
|
|
306
|
+
returns[i] = 0.
|
|
307
|
+
lengths[i] = 0.
|
|
308
|
+
steps[i] = 0
|
|
309
|
+
|
|
310
|
+
if done.any() and len(done_returns) < episodes:
|
|
311
|
+
if record:
|
|
312
|
+
frames = []
|
|
313
|
+
|
|
314
|
+
# autoresetting envs reset themselves, everything else resets here
|
|
315
|
+
|
|
316
|
+
if not env_autoresets(env) and (num_envs == 1 or done.all() or bool(get_attr(env, 'needs_reset', False))):
|
|
317
|
+
reset_seed = seed + len(done_returns) if exists(seed) and num_envs == 1 else None
|
|
318
|
+
obs, info = normalize_reset_out(env.reset(seed = reset_seed) if exists(reset_seed) else env.reset())
|
|
319
|
+
finally:
|
|
320
|
+
if exists(module) and exists(was_training):
|
|
321
|
+
module.train(was_training)
|
|
322
|
+
|
|
323
|
+
return EpisodeStats(
|
|
324
|
+
returns = torch.tensor(done_returns, dtype = torch.float32),
|
|
325
|
+
lengths = torch.tensor(done_lengths, dtype = torch.float32),
|
|
326
|
+
videos = tuple(videos)
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if __name__ == '__main__':
|
|
330
|
+
import gymnasium as gym
|
|
331
|
+
from env_ssl_wrapper import StandardizeEnvWrapper
|
|
332
|
+
|
|
333
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'))
|
|
334
|
+
|
|
335
|
+
stats = evaluate_actor(lambda obs: torch.zeros(obs.shape[0], 2), env, episodes = 10, seed = 0, verbose = True)
|
|
336
|
+
|
|
337
|
+
print(stats.summary())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "env-ssl-wrapper"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "One torch-native interface for any MDP environment"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Phil Wang", email = "lucidrains@gmail.com" }
|
|
@@ -27,6 +27,8 @@ dependencies = [
|
|
|
27
27
|
"discrete-continuous-embed-readout",
|
|
28
28
|
"einx>=0.3.0",
|
|
29
29
|
"einops>=0.8.1",
|
|
30
|
+
"imageio",
|
|
31
|
+
"imageio-ffmpeg",
|
|
30
32
|
"memmap-replay-buffer",
|
|
31
33
|
"torch>=2.5",
|
|
32
34
|
"torch-einops-utils>=0.0.29",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/auto_batched_wrapper.py
RENAMED
|
File without changes
|
{env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/done_tracker_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/image_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/standardize_wrapper.py
RENAMED
|
File without changes
|
{env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/tensor_wrapper.py
RENAMED
|
File without changes
|
{env_ssl_wrapper-0.4.6 → env_ssl_wrapper-0.5.0}/env_ssl_wrapper/standardize/time_limit_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|