env-ssl-wrapper 0.5.4__tar.gz → 0.5.6__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.4 → env_ssl_wrapper-0.5.6}/PKG-INFO +31 -1
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/README.md +30 -0
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/__init__.py +14 -0
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/action_chunk.py +6 -4
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/evaluate.py +34 -22
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/memory_trace.py +21 -2
- env_ssl_wrapper-0.5.6/env_ssl_wrapper/sgs.py +490 -0
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/action_transform_wrapper.py +16 -17
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/adapters.py +23 -95
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/auto_batched_wrapper.py +4 -11
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/episode_padding_wrapper.py +15 -5
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/flatten_obs_wrapper.py +1 -1
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/helpers.py +16 -27
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/image_wrapper.py +8 -9
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/spaces.py +2 -9
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/standardize_env_wrapper.py +7 -1
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/standardize_wrapper.py +3 -6
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/tensor_wrapper.py +2 -4
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/time_limit_wrapper.py +1 -1
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/utils.py +5 -1
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/vector.py +63 -38
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/pyproject.toml +1 -1
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/.gitignore +0 -0
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/LICENSE +0 -0
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/__init__.py +0 -0
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/done_tracker_wrapper.py +0 -0
- {env_ssl_wrapper-0.5.4 → env_ssl_wrapper-0.5.6}/env_ssl_wrapper/standardize/mocks.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.6
|
|
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
|
|
@@ -180,6 +180,26 @@ uv run test_action_chunk.py --sweep=True # PPO over several chun
|
|
|
180
180
|
uv run test_action_chunk.py --verify=True --sweep=True # chunked rollouts match per-step reference
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
### Success-Guided Sampling
|
|
184
|
+
|
|
185
|
+
Adaptive curriculum over a bank of reset states, keeping episodes near the policy's frontier of competence — the configurations it currently solves about `target` of the time ([Octi Zhang et al., 2026](https://arxiv.org/abs/2603.15999))
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
import gymnasium as gym
|
|
189
|
+
import torch
|
|
190
|
+
from env_ssl_wrapper import StandardizeEnvWrapper, SuccessGuidedSamplingWrapper
|
|
191
|
+
|
|
192
|
+
reset_states = [torch.zeros(4), torch.ones(4)] # bank of reset states to sample over
|
|
193
|
+
|
|
194
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'))
|
|
195
|
+
env = SuccessGuidedSamplingWrapper(env, reset_configs = reset_states, target = 0.5, kappa = 2.)
|
|
196
|
+
|
|
197
|
+
obs, info = env.reset()
|
|
198
|
+
# info['sgs_config_id'] -> the config this episode started from (success rates update on episode end)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`reset_configs` holds reset states — or callables drawing one (`lambda rng: ...`) — passed through `options`, or built by a `reset_options_fn` / `state_transform`. Every stage of the sampling loop is hookable: score equation (`score_fn`), target schedule, uniform floor (`uniform_prob`), env-native fallback (`default_prob`), success signal (`success_fn`).
|
|
202
|
+
|
|
183
203
|
## Wrappers
|
|
184
204
|
|
|
185
205
|
Pass wrappers as strings (default config) or `(name, dict)` tuples (custom config), in any order.
|
|
@@ -195,6 +215,7 @@ Pass wrappers as strings (default config) or `(name, dict)` tuples (custom confi
|
|
|
195
215
|
| `tensor` | NumPy → torch on a device, torch actions → numpy for the sim. |
|
|
196
216
|
| `flatten_obs` | Flattens dict/tuple observations into a single vector. |
|
|
197
217
|
| `action_chunk` | Executes actions in open-loop chunks of length k. `('action_chunk', dict(chunk_len = 2, gamma = 0.99))` |
|
|
218
|
+
| `sgs` | Adaptive frontier curriculum over reset configs. `('sgs', dict(reset_configs = configs, target = 0.5, kappa = 2.))` |
|
|
198
219
|
|
|
199
220
|
Every env emits the same contract: obs `torch.float32`, rewards `torch.float32`, `terminated`/`truncated` `torch.bool`. `env.seed(n)` works on every sim.
|
|
200
221
|
|
|
@@ -312,3 +333,12 @@ uv run pytest
|
|
|
312
333
|
year = {2025}
|
|
313
334
|
}
|
|
314
335
|
```
|
|
336
|
+
|
|
337
|
+
```bibtex
|
|
338
|
+
@article{zhang2026omnireset,
|
|
339
|
+
title = {OmniReset: Solving Dexterous Manipulation from Sparse Rewards via Reset States},
|
|
340
|
+
author = {Octi Zhang and others},
|
|
341
|
+
journal = {arXiv preprint arXiv:2603.15999},
|
|
342
|
+
year = {2026}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
@@ -120,6 +120,26 @@ uv run test_action_chunk.py --sweep=True # PPO over several chun
|
|
|
120
120
|
uv run test_action_chunk.py --verify=True --sweep=True # chunked rollouts match per-step reference
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
### Success-Guided Sampling
|
|
124
|
+
|
|
125
|
+
Adaptive curriculum over a bank of reset states, keeping episodes near the policy's frontier of competence — the configurations it currently solves about `target` of the time ([Octi Zhang et al., 2026](https://arxiv.org/abs/2603.15999))
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import gymnasium as gym
|
|
129
|
+
import torch
|
|
130
|
+
from env_ssl_wrapper import StandardizeEnvWrapper, SuccessGuidedSamplingWrapper
|
|
131
|
+
|
|
132
|
+
reset_states = [torch.zeros(4), torch.ones(4)] # bank of reset states to sample over
|
|
133
|
+
|
|
134
|
+
env = StandardizeEnvWrapper(gym.make('CartPole-v1'))
|
|
135
|
+
env = SuccessGuidedSamplingWrapper(env, reset_configs = reset_states, target = 0.5, kappa = 2.)
|
|
136
|
+
|
|
137
|
+
obs, info = env.reset()
|
|
138
|
+
# info['sgs_config_id'] -> the config this episode started from (success rates update on episode end)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`reset_configs` holds reset states — or callables drawing one (`lambda rng: ...`) — passed through `options`, or built by a `reset_options_fn` / `state_transform`. Every stage of the sampling loop is hookable: score equation (`score_fn`), target schedule, uniform floor (`uniform_prob`), env-native fallback (`default_prob`), success signal (`success_fn`).
|
|
142
|
+
|
|
123
143
|
## Wrappers
|
|
124
144
|
|
|
125
145
|
Pass wrappers as strings (default config) or `(name, dict)` tuples (custom config), in any order.
|
|
@@ -135,6 +155,7 @@ Pass wrappers as strings (default config) or `(name, dict)` tuples (custom confi
|
|
|
135
155
|
| `tensor` | NumPy → torch on a device, torch actions → numpy for the sim. |
|
|
136
156
|
| `flatten_obs` | Flattens dict/tuple observations into a single vector. |
|
|
137
157
|
| `action_chunk` | Executes actions in open-loop chunks of length k. `('action_chunk', dict(chunk_len = 2, gamma = 0.99))` |
|
|
158
|
+
| `sgs` | Adaptive frontier curriculum over reset configs. `('sgs', dict(reset_configs = configs, target = 0.5, kappa = 2.))` |
|
|
138
159
|
|
|
139
160
|
Every env emits the same contract: obs `torch.float32`, rewards `torch.float32`, `terminated`/`truncated` `torch.bool`. `env.seed(n)` works on every sim.
|
|
140
161
|
|
|
@@ -252,3 +273,12 @@ uv run pytest
|
|
|
252
273
|
year = {2025}
|
|
253
274
|
}
|
|
254
275
|
```
|
|
276
|
+
|
|
277
|
+
```bibtex
|
|
278
|
+
@article{zhang2026omnireset,
|
|
279
|
+
title = {OmniReset: Solving Dexterous Manipulation from Sparse Rewards via Reset States},
|
|
280
|
+
author = {Octi Zhang and others},
|
|
281
|
+
journal = {arXiv preprint arXiv:2603.15999},
|
|
282
|
+
year = {2026}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
@@ -7,6 +7,14 @@ from .standardize import * # noqa: F403
|
|
|
7
7
|
|
|
8
8
|
from .memory_trace import MemoryTraceWrapper
|
|
9
9
|
from .action_chunk import ActionChunkWrapper
|
|
10
|
+
from .sgs import (
|
|
11
|
+
SuccessGuidedSamplingWrapper,
|
|
12
|
+
SGSWrapper,
|
|
13
|
+
SGSSampler,
|
|
14
|
+
sgs_score_fn,
|
|
15
|
+
variance_score_fn,
|
|
16
|
+
uniform_score_fn,
|
|
17
|
+
)
|
|
10
18
|
from .evaluate import evaluate_actor, EpisodeStats
|
|
11
19
|
|
|
12
20
|
# backwards compatibility — the standardize wrappers used to live at the top
|
|
@@ -38,6 +46,12 @@ __all__ = [
|
|
|
38
46
|
*standardize.__all__,
|
|
39
47
|
'MemoryTraceWrapper',
|
|
40
48
|
'ActionChunkWrapper',
|
|
49
|
+
'SuccessGuidedSamplingWrapper',
|
|
50
|
+
'SGSWrapper',
|
|
51
|
+
'SGSSampler',
|
|
52
|
+
'sgs_score_fn',
|
|
53
|
+
'variance_score_fn',
|
|
54
|
+
'uniform_score_fn',
|
|
41
55
|
'evaluate_actor',
|
|
42
56
|
'EpisodeStats',
|
|
43
57
|
]
|
|
@@ -7,15 +7,17 @@ from torch import is_tensor
|
|
|
7
7
|
|
|
8
8
|
from .standardize.helpers import (
|
|
9
9
|
EnvWrapper,
|
|
10
|
+
any_true,
|
|
10
11
|
copy_leaf,
|
|
11
12
|
default,
|
|
12
|
-
any_true,
|
|
13
13
|
dones_of,
|
|
14
14
|
env_takes_torch,
|
|
15
15
|
first_existing,
|
|
16
16
|
get_adapter,
|
|
17
17
|
get_attr,
|
|
18
18
|
is_vectorized,
|
|
19
|
+
normalize_reset_out,
|
|
20
|
+
normalize_step_out,
|
|
19
21
|
to_numpy,
|
|
20
22
|
)
|
|
21
23
|
|
|
@@ -72,7 +74,7 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
72
74
|
|
|
73
75
|
# vectorized gym envs expose a batched action space - the single env space defines the chunk layout
|
|
74
76
|
|
|
75
|
-
action_space = first_existing(env, 'single_action_space', 'action_space')
|
|
77
|
+
action_space = default(first_existing(env, 'single_action_space', 'action_space'), get_adapter(env).action_space)
|
|
76
78
|
self.action_shape = tuple(get_attr(action_space, 'shape', ()) or ())
|
|
77
79
|
|
|
78
80
|
self.expects_batch = is_vectorized(env)
|
|
@@ -83,7 +85,7 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
83
85
|
return (self.chunk_len, *self.action_shape)
|
|
84
86
|
|
|
85
87
|
def reset(self, **kwargs):
|
|
86
|
-
return self.env.reset(**kwargs)
|
|
88
|
+
return normalize_reset_out(self.env.reset(**kwargs))
|
|
87
89
|
|
|
88
90
|
def step(self, actions):
|
|
89
91
|
if isinstance(actions, dict):
|
|
@@ -114,7 +116,7 @@ class ActionChunkWrapper(EnvWrapper):
|
|
|
114
116
|
if not self.expects_batch:
|
|
115
117
|
action = unbatch_step(action)
|
|
116
118
|
|
|
117
|
-
out = self.env.step(action)
|
|
119
|
+
out = normalize_step_out(self.env.step(action))
|
|
118
120
|
rewards.append(copy_leaf(out[1]))
|
|
119
121
|
|
|
120
122
|
if any_true(dones_of(out[2], out[3])):
|
|
@@ -6,12 +6,11 @@ from typing import NamedTuple
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import torch
|
|
8
8
|
from torch import nn, is_tensor
|
|
9
|
-
from torch.distributions import Categorical, Distribution
|
|
10
|
-
from torch.utils._pytree import
|
|
9
|
+
from torch.distributions import Bernoulli, Categorical, Distribution
|
|
10
|
+
from torch.utils._pytree import tree_map
|
|
11
11
|
|
|
12
12
|
from .standardize.spaces import action_space_is_discrete
|
|
13
13
|
from .standardize.helpers import (
|
|
14
|
-
EnvWrapper,
|
|
15
14
|
dones_of,
|
|
16
15
|
env_autoresets,
|
|
17
16
|
env_num_envs,
|
|
@@ -97,6 +96,12 @@ def to_action(env, out, deterministic = True):
|
|
|
97
96
|
if not deterministic:
|
|
98
97
|
return val.sample()
|
|
99
98
|
|
|
99
|
+
if isinstance(val, Bernoulli):
|
|
100
|
+
return (val.probs > 0.5).long()
|
|
101
|
+
|
|
102
|
+
if isinstance(val, Categorical):
|
|
103
|
+
return val.probs.argmax(dim = -1)
|
|
104
|
+
|
|
100
105
|
probs = get_attr(val, 'probs')
|
|
101
106
|
|
|
102
107
|
# categorical-style probs carry a trailing action axis to argmax over
|
|
@@ -171,24 +176,26 @@ def to_env_action(env, action):
|
|
|
171
176
|
if not env_takes_torch(env):
|
|
172
177
|
action = tree_map(to_numpy, action)
|
|
173
178
|
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
# single non-vectorized envs get the leading batch dim stripped, while discrete
|
|
180
|
+
# actions additionally collapse to scalars
|
|
176
181
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if a.ndim > 1 and a.shape[0] == 1:
|
|
180
|
-
a = a[0]
|
|
182
|
+
if not is_vectorized(env) and not exists(chunk_shape):
|
|
183
|
+
is_discrete = action_space_is_discrete(get_adapter(env).action_space)
|
|
181
184
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
def strip_leaf(a):
|
|
186
|
+
if a.ndim > 1 and a.shape[0] == 1:
|
|
187
|
+
a = a[0]
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
if is_discrete:
|
|
190
|
+
if a.ndim > 0 and a.shape[0] == 1:
|
|
191
|
+
a = a[0]
|
|
192
|
+
|
|
193
|
+
if a.ndim == 0:
|
|
194
|
+
a = a.item()
|
|
188
195
|
|
|
189
|
-
|
|
196
|
+
return a
|
|
190
197
|
|
|
191
|
-
|
|
198
|
+
action = tree_map(strip_leaf, action)
|
|
192
199
|
|
|
193
200
|
return action
|
|
194
201
|
|
|
@@ -292,7 +299,7 @@ def evaluate_actor(
|
|
|
292
299
|
assert not (record and num_envs > 1), 'video recording requires a single env'
|
|
293
300
|
|
|
294
301
|
obs, info = safe_reset(env, seed)
|
|
295
|
-
batched = is_vectorized(env)
|
|
302
|
+
batched = is_vectorized(env)
|
|
296
303
|
|
|
297
304
|
goal = goal.expand(num_envs, -1) if exists(goal) and goal.shape[0] == 1 and num_envs > 1 else goal
|
|
298
305
|
|
|
@@ -326,10 +333,12 @@ def evaluate_actor(
|
|
|
326
333
|
if reward.numel() > num_envs:
|
|
327
334
|
reward = reward.reshape(num_envs, -1).sum(dim = -1)
|
|
328
335
|
|
|
329
|
-
|
|
336
|
+
reward = reward if reward.numel() == num_envs else reward.expand(num_envs)
|
|
337
|
+
active_t = torch.from_numpy(active)
|
|
330
338
|
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
returns += torch.where(active_t, reward, 0.)
|
|
340
|
+
lengths += torch.where(active_t, float(info.get('chunk_length', 1) if isinstance(info, dict) else 1), 0.)
|
|
341
|
+
steps += torch.where(active_t, 1, 0)
|
|
333
342
|
|
|
334
343
|
done = to_numpy(dones_of(terminated, truncated)).astype(bool).reshape(-1)
|
|
335
344
|
|
|
@@ -367,15 +376,18 @@ def evaluate_actor(
|
|
|
367
376
|
steps[i] = 0
|
|
368
377
|
active[i] = False
|
|
369
378
|
|
|
370
|
-
if done.any() and len(done_returns) < episodes:
|
|
379
|
+
if (done.any() or not active.any()) and len(done_returns) < episodes:
|
|
371
380
|
if record:
|
|
372
381
|
frames = []
|
|
373
382
|
|
|
374
383
|
# autoresetting envs reset themselves, everything else resets here
|
|
375
384
|
|
|
376
|
-
if not env_autoresets(env) and (num_envs == 1 or done.all() or bool(get_attr(env, 'needs_reset', False))):
|
|
385
|
+
if not env_autoresets(env) and (num_envs == 1 or not active.any() or done.all() or bool(get_attr(env, 'needs_reset', False))):
|
|
377
386
|
reset_seed = seed + len(done_returns) if exists(seed) and num_envs == 1 else None
|
|
378
387
|
obs, info = safe_reset(env, reset_seed)
|
|
388
|
+
returns.zero_()
|
|
389
|
+
lengths.zero_()
|
|
390
|
+
steps.zero_()
|
|
379
391
|
active[:] = True
|
|
380
392
|
elif env_autoresets(env):
|
|
381
393
|
active[newly_done] = True
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from functools import partial
|
|
2
3
|
from typing import Sequence
|
|
3
4
|
|
|
4
5
|
import torch
|
|
@@ -9,9 +10,12 @@ from .standardize.helpers import (
|
|
|
9
10
|
any_true,
|
|
10
11
|
cast_tuple,
|
|
11
12
|
default,
|
|
13
|
+
dones_of,
|
|
12
14
|
exists,
|
|
13
15
|
is_array_like,
|
|
14
16
|
is_scalar,
|
|
17
|
+
maybe_transform_final_observation,
|
|
18
|
+
normalize_step_out,
|
|
15
19
|
)
|
|
16
20
|
|
|
17
21
|
# helper functions
|
|
@@ -63,7 +67,7 @@ class MemoryTraceWrapper(TransformObservationWrapper):
|
|
|
63
67
|
prefix = self.trace_key if key == self.obs_key else f'{key}_{self.trace_key}'
|
|
64
68
|
return f'{prefix}_{lam}' if len(self.lambdas) > 1 else prefix
|
|
65
69
|
|
|
66
|
-
def transform_obs(self, obs, done = None):
|
|
70
|
+
def transform_obs(self, obs, done = None, update_state: bool = True):
|
|
67
71
|
if isinstance(obs, (tuple, list)):
|
|
68
72
|
out = {f'{self.obs_key}_{i}': elem for i, elem in enumerate(obs)}
|
|
69
73
|
elif isinstance(obs, dict):
|
|
@@ -96,7 +100,8 @@ class MemoryTraceWrapper(TransformObservationWrapper):
|
|
|
96
100
|
else:
|
|
97
101
|
traces = [prev.lerp(val_float, calc_lerp_weight(lam, done, val_float)) for lam, prev in zip(self.lambdas, self.traces[key])]
|
|
98
102
|
|
|
99
|
-
|
|
103
|
+
if update_state:
|
|
104
|
+
self.traces[key] = traces
|
|
100
105
|
|
|
101
106
|
for lam, trace in zip(self.lambdas, traces):
|
|
102
107
|
out[self.trace_key_for(key, lam)] = trace
|
|
@@ -106,3 +111,17 @@ class MemoryTraceWrapper(TransformObservationWrapper):
|
|
|
106
111
|
def reset(self, **kwargs):
|
|
107
112
|
self.traces = dict()
|
|
108
113
|
return super().reset(**kwargs)
|
|
114
|
+
|
|
115
|
+
def step(self, action):
|
|
116
|
+
obs, reward, terminated, truncated, info = normalize_step_out(self.env.step(action))
|
|
117
|
+
done = dones_of(terminated, truncated)
|
|
118
|
+
|
|
119
|
+
# terminal observations are transformed against pre-reset traces without mutating state
|
|
120
|
+
|
|
121
|
+
maybe_transform_final_observation(
|
|
122
|
+
info,
|
|
123
|
+
partial(self.transform_obs, done = None, update_state = False)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
out = self.transform_obs(obs, done = done, update_state = True)
|
|
127
|
+
return out, reward, terminated, truncated, info
|