ZhengyangZhang commited on
Commit
661cda2
·
verified ·
1 Parent(s): f9c25e8

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/ray/rllib/evaluation/__init__.py +20 -0
  2. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/env_runner_v2.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/episode_v2.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/metrics.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/observation_function.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/postprocessing.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/rollout_worker.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/sample_batch_builder.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/sampler.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/worker_set.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__init__.py +0 -0
  13. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/__init__.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/agent_collector.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/sample_collector.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/simple_list_collector.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/agent_collector.py +688 -0
  18. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/sample_collector.py +298 -0
  19. lib/python3.12/site-packages/ray/rllib/evaluation/collectors/simple_list_collector.py +699 -0
  20. lib/python3.12/site-packages/ray/rllib/evaluation/env_runner_v2.py +1233 -0
  21. lib/python3.12/site-packages/ray/rllib/evaluation/episode_v2.py +379 -0
  22. lib/python3.12/site-packages/ray/rllib/evaluation/metrics.py +267 -0
  23. lib/python3.12/site-packages/ray/rllib/evaluation/observation_function.py +87 -0
  24. lib/python3.12/site-packages/ray/rllib/evaluation/postprocessing.py +328 -0
  25. lib/python3.12/site-packages/ray/rllib/evaluation/rollout_worker.py +2006 -0
  26. lib/python3.12/site-packages/ray/rllib/evaluation/sample_batch_builder.py +265 -0
  27. lib/python3.12/site-packages/ray/rllib/evaluation/sampler.py +253 -0
  28. lib/python3.12/site-packages/ray/rllib/evaluation/worker_set.py +10 -0
  29. lib/python3.12/site-packages/ray/rllib/examples/__init__.py +0 -0
  30. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__init__.py +0 -0
  31. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/__init__.cpython-312.pyc +0 -0
  32. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/attention_net_supervised.cpython-312.pyc +0 -0
  33. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/parametric_actions_cartpole.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/parametric_actions_cartpole_embeddings_learnt_by_model.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/attention_net_supervised.py +78 -0
  36. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__init__.py +0 -0
  37. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/__init__.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/action_mask_model.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/autoregressive_action_dist.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/autoregressive_action_model.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/centralized_critic_models.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/custom_loss_model.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/fast_model.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/mobilenet_v2_encoder.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/mobilenet_v2_with_lstm_models.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/neural_computer.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/parametric_actions_model.cpython-312.pyc +0 -0
  48. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/shared_weights_model.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/simple_rpg_model.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/action_mask_model.py +126 -0
lib/python3.12/site-packages/ray/rllib/evaluation/__init__.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ray.rllib.evaluation.metrics import collect_metrics
2
+ from ray.rllib.evaluation.postprocessing import compute_advantages
3
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
4
+ from ray.rllib.evaluation.sample_batch_builder import (
5
+ MultiAgentSampleBatchBuilder,
6
+ SampleBatchBuilder,
7
+ )
8
+ from ray.rllib.evaluation.sampler import SyncSampler
9
+ from ray.rllib.policy.sample_batch import MultiAgentBatch, SampleBatch
10
+
11
+ __all__ = [
12
+ "RolloutWorker",
13
+ "SampleBatch",
14
+ "MultiAgentBatch",
15
+ "SampleBatchBuilder",
16
+ "MultiAgentSampleBatchBuilder",
17
+ "SyncSampler",
18
+ "compute_advantages",
19
+ "collect_metrics",
20
+ ]
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (774 Bytes). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/env_runner_v2.cpython-312.pyc ADDED
Binary file (42 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/episode_v2.cpython-312.pyc ADDED
Binary file (14.8 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/metrics.cpython-312.pyc ADDED
Binary file (10.2 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/observation_function.cpython-312.pyc ADDED
Binary file (3.78 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/postprocessing.cpython-312.pyc ADDED
Binary file (13.5 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/rollout_worker.cpython-312.pyc ADDED
Binary file (80.6 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/sample_batch_builder.cpython-312.pyc ADDED
Binary file (13 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/sampler.cpython-312.pyc ADDED
Binary file (11.1 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/__pycache__/worker_set.cpython-312.pyc ADDED
Binary file (628 Bytes). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (210 Bytes). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/agent_collector.cpython-312.pyc ADDED
Binary file (26.1 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/sample_collector.cpython-312.pyc ADDED
Binary file (13.5 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/__pycache__/simple_list_collector.cpython-312.pyc ADDED
Binary file (26.7 kB). View file
 
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/agent_collector.py ADDED
@@ -0,0 +1,688 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import logging
3
+ import math
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import numpy as np
7
+ import tree # pip install dm_tree
8
+ from gymnasium.spaces import Space
9
+
10
+ from ray.rllib.policy.sample_batch import SampleBatch
11
+ from ray.rllib.policy.view_requirement import ViewRequirement
12
+ from ray.rllib.utils.annotations import OldAPIStack
13
+ from ray.rllib.utils.framework import try_import_torch
14
+ from ray.rllib.utils.spaces.space_utils import (
15
+ flatten_to_single_ndarray,
16
+ get_dummy_batch_for_space,
17
+ )
18
+ from ray.rllib.utils.typing import (
19
+ EnvID,
20
+ EpisodeID,
21
+ TensorType,
22
+ ViewRequirementsDict,
23
+ )
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ torch, _ = try_import_torch()
28
+
29
+
30
+ def _to_float_np_array(v: List[Any]) -> np.ndarray:
31
+ if torch and torch.is_tensor(v[0]):
32
+ raise ValueError
33
+ arr = np.array(v)
34
+ if arr.dtype == np.float64:
35
+ return arr.astype(np.float32) # save some memory
36
+ return arr
37
+
38
+
39
+ def _get_buffered_slice_with_paddings(d, inds):
40
+ element_at_t = []
41
+ for index in inds:
42
+ if index < len(d):
43
+ element_at_t.append(d[index])
44
+ else:
45
+ # zero pad similar to the last element.
46
+ element_at_t.append(tree.map_structure(np.zeros_like, d[-1]))
47
+ return element_at_t
48
+
49
+
50
+ @OldAPIStack
51
+ class AgentCollector:
52
+ """Collects samples for one agent in one trajectory (episode).
53
+
54
+ The agent may be part of a multi-agent environment. Samples are stored in
55
+ lists including some possible automatic "shift" buffer at the beginning to
56
+ be able to save memory when storing things like NEXT_OBS, PREV_REWARDS,
57
+ etc.., which are specified using the trajectory view API.
58
+ """
59
+
60
+ _next_unroll_id = 0 # disambiguates unrolls within a single episode
61
+
62
+ # TODO: @kourosh add different types of padding. e.g. zeros vs. same
63
+ def __init__(
64
+ self,
65
+ view_reqs: ViewRequirementsDict,
66
+ *,
67
+ max_seq_len: int = 1,
68
+ disable_action_flattening: bool = True,
69
+ intial_states: Optional[List[TensorType]] = None,
70
+ is_policy_recurrent: bool = False,
71
+ is_training: bool = True,
72
+ _enable_new_api_stack: bool = False,
73
+ ):
74
+ """Initialize an AgentCollector.
75
+
76
+ Args:
77
+ view_reqs: A dict of view requirements for the agent.
78
+ max_seq_len: The maximum sequence length to store.
79
+ disable_action_flattening: If True, don't flatten the action.
80
+ intial_states: The initial states from the policy.get_initial_states()
81
+ is_policy_recurrent: If True, the policy is recurrent.
82
+ is_training: Sets the is_training flag for the buffers. if True, all the
83
+ timesteps are stored in the buffers until explicitly build_for_training
84
+ () is called. if False, only the content required for the last time
85
+ step is stored in the buffers. This will save memory during inference.
86
+ You can change the behavior at runtime by calling is_training(mode).
87
+ """
88
+ self.max_seq_len = max_seq_len
89
+ self.disable_action_flattening = disable_action_flattening
90
+ self.view_requirements = view_reqs
91
+ # The initial_states can be an np array
92
+ self.initial_states = intial_states if intial_states is not None else []
93
+ self.is_policy_recurrent = is_policy_recurrent
94
+ self._is_training = is_training
95
+ self._enable_new_api_stack = _enable_new_api_stack
96
+
97
+ # Determine the size of the buffer we need for data before the actual
98
+ # episode starts. This is used for 0-buffering of e.g. prev-actions,
99
+ # or internal state inputs.
100
+ view_req_shifts = [
101
+ min(vr.shift_arr)
102
+ - int((vr.data_col or k) in [SampleBatch.OBS, SampleBatch.INFOS])
103
+ for k, vr in view_reqs.items()
104
+ ]
105
+ self.shift_before = -min(view_req_shifts)
106
+
107
+ # The actual data buffers. Keys are column names, values are lists
108
+ # that contain the sub-components (e.g. for complex obs spaces) with
109
+ # each sub-component holding a list of per-timestep tensors.
110
+ # E.g.: obs-space = Dict(a=Discrete(2), b=Box((2,)))
111
+ # buffers["obs"] = [
112
+ # [0, 1], # <- 1st sub-component of observation
113
+ # [np.array([.2, .3]), np.array([.0, -.2])] # <- 2nd sub-component
114
+ # ]
115
+ # NOTE: infos and state_out... are not flattened due to them often
116
+ # using custom dict values whose structure may vary from timestep to
117
+ # timestep.
118
+ self.buffers: Dict[str, List[List[TensorType]]] = {}
119
+ # Maps column names to an example data item, which may be deeply
120
+ # nested. These are used such that we'll know how to unflatten
121
+ # the flattened data inside self.buffers when building the
122
+ # SampleBatch.
123
+ self.buffer_structs: Dict[str, Any] = {}
124
+ # The episode ID for the agent for which we collect data.
125
+ self.episode_id = None
126
+ # The unroll ID, unique across all rollouts (within a RolloutWorker).
127
+ self.unroll_id = None
128
+ # The simple timestep count for this agent. Gets increased by one
129
+ # each time a (non-initial!) observation is added.
130
+ self.agent_steps = 0
131
+ # Keep track of view requirements that have a view on columns that we gain from
132
+ # inference and also need for inference. These have dummy values appended in
133
+ # buffers to account for the missing value when building for inference
134
+ # Example: We have one 'state_in' view requirement that has a view on our
135
+ # state_outs at t=[-10, ..., -1]. At any given build_for_inference()-call,
136
+ # the buffer must contain eleven values from t=[-10, ..., 0] for us to index
137
+ # properly. Since state_out at t=0 is missing, we substitute it with a buffer
138
+ # value that should never make it into batches built for training.
139
+ self.data_cols_with_dummy_values = set()
140
+
141
+ @property
142
+ def training(self) -> bool:
143
+ return self._is_training
144
+
145
+ def is_training(self, is_training: bool) -> None:
146
+ self._is_training = is_training
147
+
148
+ def is_empty(self) -> bool:
149
+ """Returns True if this collector has no data."""
150
+ return not self.buffers or all(len(item) == 0 for item in self.buffers.values())
151
+
152
+ def add_init_obs(
153
+ self,
154
+ episode_id: EpisodeID,
155
+ agent_index: int,
156
+ env_id: EnvID,
157
+ init_obs: TensorType,
158
+ init_infos: Optional[Dict[str, TensorType]] = None,
159
+ t: int = -1,
160
+ ) -> None:
161
+ """Adds an initial observation (after reset) to the Agent's trajectory.
162
+
163
+ Args:
164
+ episode_id: Unique ID for the episode we are adding the
165
+ initial observation for.
166
+ agent_index: Unique int index (starting from 0) for the agent
167
+ within its episode. Not to be confused with AGENT_ID (Any).
168
+ env_id: The environment index (in a vectorized setup).
169
+ init_obs: The initial observation tensor (after `env.reset()`).
170
+ init_infos: The initial infos dict (after `env.reset()`).
171
+ t: The time step (episode length - 1). The initial obs has
172
+ ts=-1(!), then an action/reward/next-obs at t=0, etc..
173
+ """
174
+ # Store episode ID + unroll ID, which will be constant throughout this
175
+ # AgentCollector's lifecycle.
176
+ self.episode_id = episode_id
177
+ if self.unroll_id is None:
178
+ self.unroll_id = AgentCollector._next_unroll_id
179
+ AgentCollector._next_unroll_id += 1
180
+
181
+ # convert init_obs to np.array (in case it is a list)
182
+ if isinstance(init_obs, list):
183
+ init_obs = np.array(init_obs)
184
+
185
+ if SampleBatch.OBS not in self.buffers:
186
+ single_row = {
187
+ SampleBatch.OBS: init_obs,
188
+ SampleBatch.INFOS: init_infos or {},
189
+ SampleBatch.AGENT_INDEX: agent_index,
190
+ SampleBatch.ENV_ID: env_id,
191
+ SampleBatch.T: t,
192
+ SampleBatch.EPS_ID: self.episode_id,
193
+ SampleBatch.UNROLL_ID: self.unroll_id,
194
+ }
195
+
196
+ # TODO (Artur): Remove when PREV_ACTIONS and PREV_REWARDS get deprecated.
197
+ # Note (Artur): As long as we have these in our default view requirements,
198
+ # we should build buffers with neutral elements instead of building them
199
+ # on the first AgentCollector.build_for_inference call if present.
200
+ # This prevents us from accidentally building buffers with duplicates of
201
+ # the first incoming value.
202
+ if SampleBatch.PREV_REWARDS in self.view_requirements:
203
+ single_row[SampleBatch.REWARDS] = get_dummy_batch_for_space(
204
+ space=self.view_requirements[SampleBatch.REWARDS].space,
205
+ batch_size=0,
206
+ fill_value=0.0,
207
+ )
208
+ if SampleBatch.PREV_ACTIONS in self.view_requirements:
209
+ potentially_flattened_batch = get_dummy_batch_for_space(
210
+ space=self.view_requirements[SampleBatch.ACTIONS].space,
211
+ batch_size=0,
212
+ fill_value=0.0,
213
+ )
214
+ if not self.disable_action_flattening:
215
+ potentially_flattened_batch = flatten_to_single_ndarray(
216
+ potentially_flattened_batch
217
+ )
218
+ single_row[SampleBatch.ACTIONS] = potentially_flattened_batch
219
+ self._build_buffers(single_row)
220
+
221
+ # Append data to existing buffers.
222
+ flattened = tree.flatten(init_obs)
223
+ for i, sub_obs in enumerate(flattened):
224
+ self.buffers[SampleBatch.OBS][i].append(sub_obs)
225
+ self.buffers[SampleBatch.INFOS][0].append(init_infos or {})
226
+ self.buffers[SampleBatch.AGENT_INDEX][0].append(agent_index)
227
+ self.buffers[SampleBatch.ENV_ID][0].append(env_id)
228
+ self.buffers[SampleBatch.T][0].append(t)
229
+ self.buffers[SampleBatch.EPS_ID][0].append(self.episode_id)
230
+ self.buffers[SampleBatch.UNROLL_ID][0].append(self.unroll_id)
231
+
232
+ def add_action_reward_next_obs(self, input_values: Dict[str, TensorType]) -> None:
233
+ """Adds the given dictionary (row) of values to the Agent's trajectory.
234
+
235
+ Args:
236
+ values: Data dict (interpreted as a single row) to be added to buffer.
237
+ Must contain keys:
238
+ SampleBatch.ACTIONS, REWARDS, TERMINATEDS, TRUNCATEDS, and NEXT_OBS.
239
+ """
240
+ if self.unroll_id is None:
241
+ self.unroll_id = AgentCollector._next_unroll_id
242
+ AgentCollector._next_unroll_id += 1
243
+
244
+ # Next obs -> obs.
245
+ values = copy.copy(input_values)
246
+ assert SampleBatch.OBS not in values
247
+ values[SampleBatch.OBS] = values[SampleBatch.NEXT_OBS]
248
+ del values[SampleBatch.NEXT_OBS]
249
+
250
+ # convert obs to np.array (in case it is a list)
251
+ if isinstance(values[SampleBatch.OBS], list):
252
+ values[SampleBatch.OBS] = np.array(values[SampleBatch.OBS])
253
+
254
+ # Default to next timestep if not provided in input values
255
+ if SampleBatch.T not in input_values:
256
+ values[SampleBatch.T] = self.buffers[SampleBatch.T][0][-1] + 1
257
+
258
+ # Make sure EPS_ID/UNROLL_ID stay the same for this agent.
259
+ if SampleBatch.EPS_ID in values:
260
+ assert values[SampleBatch.EPS_ID] == self.episode_id
261
+ del values[SampleBatch.EPS_ID]
262
+ self.buffers[SampleBatch.EPS_ID][0].append(self.episode_id)
263
+ if SampleBatch.UNROLL_ID in values:
264
+ assert values[SampleBatch.UNROLL_ID] == self.unroll_id
265
+ del values[SampleBatch.UNROLL_ID]
266
+ self.buffers[SampleBatch.UNROLL_ID][0].append(self.unroll_id)
267
+
268
+ for k, v in values.items():
269
+ if k not in self.buffers:
270
+ if self.training and k.startswith("state_out"):
271
+ vr = self.view_requirements[k]
272
+ data_col = vr.data_col or k
273
+ self._fill_buffer_with_initial_values(
274
+ data_col, vr, build_for_inference=False
275
+ )
276
+ else:
277
+ self._build_buffers({k: v})
278
+ # Do not flatten infos, state_out and (if configured) actions.
279
+ # Infos/state-outs may be structs that change from timestep to
280
+ # timestep.
281
+ should_flatten_action_key = (
282
+ k == SampleBatch.ACTIONS and not self.disable_action_flattening
283
+ )
284
+ # Note (Artur) RL Modules's states need no flattening
285
+ should_flatten_state_key = (
286
+ k.startswith("state_out") and not self._enable_new_api_stack
287
+ )
288
+ if (
289
+ k == SampleBatch.INFOS
290
+ or should_flatten_state_key
291
+ or should_flatten_action_key
292
+ ):
293
+ if should_flatten_action_key:
294
+ v = flatten_to_single_ndarray(v)
295
+ # Briefly remove dummy value to add to buffer
296
+ if k in self.data_cols_with_dummy_values:
297
+ dummy = self.buffers[k][0].pop(-1)
298
+ self.buffers[k][0].append(v)
299
+ # Add back dummy value
300
+ if k in self.data_cols_with_dummy_values:
301
+ self.buffers[k][0].append(dummy)
302
+ # Flatten all other columns.
303
+ else:
304
+ flattened = tree.flatten(v)
305
+ for i, sub_list in enumerate(self.buffers[k]):
306
+ # Briefly remove dummy value to add to buffer
307
+ if k in self.data_cols_with_dummy_values:
308
+ dummy = sub_list.pop(-1)
309
+ sub_list.append(flattened[i])
310
+ # Add back dummy value
311
+ if k in self.data_cols_with_dummy_values:
312
+ sub_list.append(dummy)
313
+
314
+ # In inference mode, we don't need to keep all of trajectory in memory
315
+ # we only need to keep the steps required. We can pop from the beginning to
316
+ # create room for new data.
317
+ if not self.training:
318
+ for k in self.buffers:
319
+ for sub_list in self.buffers[k]:
320
+ if sub_list:
321
+ sub_list.pop(0)
322
+
323
+ self.agent_steps += 1
324
+
325
+ def build_for_inference(self) -> SampleBatch:
326
+ """During inference, we will build a SampleBatch with a batch size of 1 that
327
+ can then be used to run the forward pass of a policy. This data will only
328
+ include the enviornment context for running the policy at the last timestep.
329
+
330
+ Returns:
331
+ A SampleBatch with a batch size of 1.
332
+ """
333
+
334
+ batch_data = {}
335
+ np_data = {}
336
+ for view_col, view_req in self.view_requirements.items():
337
+ # Create the batch of data from the different buffers.
338
+ data_col = view_req.data_col or view_col
339
+
340
+ # if this view is not for inference, skip it.
341
+ if not view_req.used_for_compute_actions:
342
+ continue
343
+
344
+ if np.any(view_req.shift_arr > 0):
345
+ raise ValueError(
346
+ f"During inference the agent can only use past observations to "
347
+ f"respect causality. However, view_col = {view_col} seems to "
348
+ f"depend on future indices {view_req.shift_arr}, while the "
349
+ f"used_for_compute_actions flag is set to True. Please fix the "
350
+ f"discrepancy. Hint: If you are using a custom model make sure "
351
+ f"the view_requirements are initialized properly and is point "
352
+ f"only refering to past timesteps during inference."
353
+ )
354
+
355
+ # Some columns don't exist yet
356
+ # (get created during postprocessing or depend on state_out).
357
+ if data_col not in self.buffers:
358
+ self._fill_buffer_with_initial_values(
359
+ data_col, view_req, build_for_inference=True
360
+ )
361
+ self._prepare_for_data_cols_with_dummy_values(data_col)
362
+
363
+ # Keep an np-array cache, so we don't have to regenerate the
364
+ # np-array for different view_cols using to the same data_col.
365
+ self._cache_in_np(np_data, data_col)
366
+
367
+ data = []
368
+ for d in np_data[data_col]:
369
+ # if shift_arr = [0] the data will be just the last time step
370
+ # (len(d) - 1), if shift_arr = [-1] the data will be just the timestep
371
+ # before the last one (len(d) - 2) and so on.
372
+ element_at_t = d[view_req.shift_arr + len(d) - 1]
373
+ if element_at_t.shape[0] == 1:
374
+ # We'd normally squeeze here to remove the time dim, but we'll
375
+ # simply use the time dim as the batch dim.
376
+ data.append(element_at_t)
377
+ continue
378
+ # add the batch dimension with [None]
379
+ data.append(element_at_t[None])
380
+
381
+ # We unflatten even if data is empty here, because the structure might be
382
+ # nested with empty leafs and so we still need to reconstruct it.
383
+ # This is useful because we spec-check states in RLModules and these
384
+ # states can sometimes be nested dicts with empty leafs.
385
+ batch_data[view_col] = self._unflatten_as_buffer_struct(data, data_col)
386
+
387
+ batch = self._get_sample_batch(batch_data)
388
+ return batch
389
+
390
+ # TODO: @kouorsh we don't really need view_requirements anymore since it's already
391
+ # an attribute of the class
392
+ def build_for_training(
393
+ self, view_requirements: ViewRequirementsDict
394
+ ) -> SampleBatch:
395
+ """Builds a SampleBatch from the thus-far collected agent data.
396
+
397
+ If the episode/trajectory has no TERMINATED|TRUNCATED=True at the end, will
398
+ copy the necessary n timesteps at the end of the trajectory back to the
399
+ beginning of the buffers and wait for new samples coming in.
400
+ SampleBatches created by this method will be ready for postprocessing
401
+ by a Policy.
402
+
403
+ Args:
404
+ view_requirements: The viewrequirements dict needed to build the
405
+ SampleBatch from the raw buffers, which may have data shifts as well as
406
+ mappings from view-col to data-col in them.
407
+
408
+ Returns:
409
+ SampleBatch: The built SampleBatch for this agent, ready to go into
410
+ postprocessing.
411
+ """
412
+ batch_data = {}
413
+ np_data = {}
414
+ for view_col, view_req in view_requirements.items():
415
+ # Create the batch of data from the different buffers.
416
+ data_col = view_req.data_col or view_col
417
+
418
+ if data_col not in self.buffers:
419
+ is_state = self._fill_buffer_with_initial_values(
420
+ data_col, view_req, build_for_inference=False
421
+ )
422
+
423
+ # We need to skip this view_col if it does not exist in the buffers and
424
+ # is not an RNN state because it could be the special keys that gets
425
+ # added by policy's postprocessing function for training.
426
+ if not is_state:
427
+ continue
428
+
429
+ # OBS and INFOS are already shifted by -1 (the initial obs/info starts one
430
+ # ts before all other data columns).
431
+ obs_shift = -1 if data_col in [SampleBatch.OBS, SampleBatch.INFOS] else 0
432
+
433
+ # Keep an np-array cache so we don't have to regenerate the
434
+ # np-array for different view_cols using to the same data_col.
435
+ self._cache_in_np(np_data, data_col)
436
+
437
+ # Go through each time-step in the buffer and construct the view
438
+ # accordingly.
439
+ data = []
440
+ for d in np_data[data_col]:
441
+ shifted_data = []
442
+
443
+ # batch_repeat_value determines how many time steps should we skip
444
+ # before we repeat indexing the data.
445
+ # Example: batch_repeat_value=10, shift_arr = [-3, -2, -1],
446
+ # shift_before = 3
447
+ # buffer = [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
448
+ # resulting_data = [[-3, -2, -1], [7, 8, 9]]
449
+ # explanation: For t=0, we output [-3, -2, -1]. We then skip 10 time
450
+ # steps ahead and get to t=10. For t=10, we output [7, 8, 9]. We skip
451
+ # 10 more time steps and get to t=20. but since t=20 is out of bound we
452
+ # stop.
453
+
454
+ # count computes the number of time steps that we need to consider.
455
+ # if batch_repeat_value = 1, this number should be the length of
456
+ # episode so far, which is len(buffer) - shift_before (-1 if this
457
+ # value was gained during inference. This is because we keep a dummy
458
+ # value at the last position of the buffer that makes it one longer).
459
+ count = int(
460
+ math.ceil(
461
+ (
462
+ len(d)
463
+ - int(data_col in self.data_cols_with_dummy_values)
464
+ - self.shift_before
465
+ )
466
+ / view_req.batch_repeat_value
467
+ )
468
+ )
469
+ for i in range(count):
470
+ # the indices for time step t
471
+ inds = (
472
+ self.shift_before
473
+ + obs_shift
474
+ + view_req.shift_arr
475
+ + (i * view_req.batch_repeat_value)
476
+ )
477
+
478
+ # handle the case where the inds are out of bounds from the end.
479
+ # if during the indexing any of the indices are out of bounds, we
480
+ # need to use padding on the end to fill in the missing indices.
481
+ # Create padding first time we encounter data
482
+ if max(inds) < len(d):
483
+ # Simple case where we can simply pick slices from buffer
484
+ element_at_t = d[inds]
485
+ else:
486
+ # Case in which we have to pad because buffer has insufficient
487
+ # length. This branch takes more time than simply picking
488
+ # slices we try to avoid it.
489
+ element_at_t = _get_buffered_slice_with_paddings(d, inds)
490
+ element_at_t = np.stack(element_at_t)
491
+
492
+ if element_at_t.shape[0] == 1:
493
+ # Remove the T dimension if it is 1.
494
+ element_at_t = element_at_t[0]
495
+ shifted_data.append(element_at_t)
496
+
497
+ # in some multi-agent cases shifted_data may be an empty list.
498
+ # In this case we should just create an empty array and return it.
499
+ if shifted_data:
500
+ shifted_data_np = np.stack(shifted_data, 0)
501
+ else:
502
+ shifted_data_np = np.array(shifted_data)
503
+ data.append(shifted_data_np)
504
+
505
+ # We unflatten even if data is empty here, because the structure might be
506
+ # nested with empty leafs and so we still need to reconstruct it.
507
+ # This is useful because we spec-check states in RLModules and these
508
+ # states can sometimes be nested dicts with empty leafs.
509
+ batch_data[view_col] = self._unflatten_as_buffer_struct(data, data_col)
510
+
511
+ batch = self._get_sample_batch(batch_data)
512
+
513
+ # This trajectory is continuing -> Copy data at the end (in the size of
514
+ # self.shift_before) to the beginning of buffers and erase everything
515
+ # else.
516
+ if (
517
+ SampleBatch.TERMINATEDS in self.buffers
518
+ and not self.buffers[SampleBatch.TERMINATEDS][0][-1]
519
+ and SampleBatch.TRUNCATEDS in self.buffers
520
+ and not self.buffers[SampleBatch.TRUNCATEDS][0][-1]
521
+ ):
522
+ # Copy data to beginning of buffer and cut lists.
523
+ if self.shift_before > 0:
524
+ for k, data in self.buffers.items():
525
+ # Loop through
526
+ for i in range(len(data)):
527
+ self.buffers[k][i] = data[i][-self.shift_before :]
528
+ self.agent_steps = 0
529
+
530
+ # Reset our unroll_id.
531
+ self.unroll_id = None
532
+
533
+ return batch
534
+
535
+ def _build_buffers(self, single_row: Dict[str, TensorType]) -> None:
536
+ """Builds the buffers for sample collection, given an example data row.
537
+
538
+ Args:
539
+ single_row (Dict[str, TensorType]): A single row (keys=column
540
+ names) of data to base the buffers on.
541
+ """
542
+ for col, data in single_row.items():
543
+ if col in self.buffers:
544
+ continue
545
+
546
+ shift = self.shift_before - (
547
+ 1
548
+ if col
549
+ in [
550
+ SampleBatch.OBS,
551
+ SampleBatch.INFOS,
552
+ SampleBatch.EPS_ID,
553
+ SampleBatch.AGENT_INDEX,
554
+ SampleBatch.ENV_ID,
555
+ SampleBatch.T,
556
+ SampleBatch.UNROLL_ID,
557
+ ]
558
+ else 0
559
+ )
560
+
561
+ # Store all data as flattened lists, except INFOS and state-out
562
+ # lists. These are monolithic items (infos is a dict that
563
+ # should not be further split, same for state-out items, which
564
+ # could be custom dicts as well).
565
+ should_flatten_action_key = (
566
+ col == SampleBatch.ACTIONS and not self.disable_action_flattening
567
+ )
568
+ # Note (Artur) RL Modules's states need no flattening
569
+ should_flatten_state_key = (
570
+ col.startswith("state_out") and not self._enable_new_api_stack
571
+ )
572
+ if (
573
+ col == SampleBatch.INFOS
574
+ or should_flatten_state_key
575
+ or should_flatten_action_key
576
+ ):
577
+ if should_flatten_action_key:
578
+ data = flatten_to_single_ndarray(data)
579
+ self.buffers[col] = [[data for _ in range(shift)]]
580
+ else:
581
+ self.buffers[col] = [
582
+ [v for _ in range(shift)] for v in tree.flatten(data)
583
+ ]
584
+ # Store an example data struct so we know, how to unflatten
585
+ # each data col.
586
+ self.buffer_structs[col] = data
587
+
588
+ def _get_sample_batch(self, batch_data: Dict[str, TensorType]) -> SampleBatch:
589
+ """Returns a SampleBatch from the given data dictionary. Also updates the
590
+ sequence information based on the max_seq_len."""
591
+
592
+ # Due to possible batch-repeats > 1, columns in the resulting batch
593
+ # may not all have the same batch size.
594
+ batch = SampleBatch(batch_data, is_training=self.training)
595
+
596
+ # Adjust the seq-lens array depending on the incoming agent sequences.
597
+ if self.is_policy_recurrent:
598
+ seq_lens = []
599
+ max_seq_len = self.max_seq_len
600
+ count = batch.count
601
+ while count > 0:
602
+ seq_lens.append(min(count, max_seq_len))
603
+ count -= max_seq_len
604
+ batch["seq_lens"] = np.array(seq_lens)
605
+ batch.max_seq_len = max_seq_len
606
+
607
+ return batch
608
+
609
+ def _cache_in_np(self, cache_dict: Dict[str, List[np.ndarray]], key: str) -> None:
610
+ """Caches the numpy version of the key in the buffer dict."""
611
+ if key not in cache_dict:
612
+ cache_dict[key] = [_to_float_np_array(d) for d in self.buffers[key]]
613
+
614
+ def _unflatten_as_buffer_struct(
615
+ self, data: List[np.ndarray], key: str
616
+ ) -> np.ndarray:
617
+ """Unflattens the given to match the buffer struct format for that key."""
618
+ if key not in self.buffer_structs:
619
+ return data[0]
620
+
621
+ return tree.unflatten_as(self.buffer_structs[key], data)
622
+
623
+ def _fill_buffer_with_initial_values(
624
+ self,
625
+ data_col: str,
626
+ view_requirement: ViewRequirement,
627
+ build_for_inference: bool = False,
628
+ ) -> bool:
629
+ """Fills the buffer with the initial values for the given data column.
630
+ for dat_col starting with `state_out`, use the initial states of the policy,
631
+ but for other data columns, create a dummy value based on the view requirement
632
+ space.
633
+
634
+ Args:
635
+ data_col: The data column to fill the buffer with.
636
+ view_requirement: The view requirement for the view_col. Normally the view
637
+ requirement for the data column is used and if it does not exist for
638
+ some reason the view requirement for view column is used instead.
639
+ build_for_inference: Whether this is getting called for inference or not.
640
+
641
+ returns:
642
+ is_state: True if the data_col is an RNN state, False otherwise.
643
+ """
644
+ try:
645
+ space = self.view_requirements[data_col].space
646
+ except KeyError:
647
+ space = view_requirement.space
648
+
649
+ # special treatment for state_out
650
+ # add them to the buffer in case they don't exist yet
651
+ is_state = True
652
+ if data_col.startswith("state_out"):
653
+ if self._enable_new_api_stack:
654
+ self._build_buffers({data_col: self.initial_states})
655
+ else:
656
+ if not self.is_policy_recurrent:
657
+ raise ValueError(
658
+ f"{data_col} is not available, because the given policy is"
659
+ f"not recurrent according to the input model_inital_states."
660
+ f"Have you forgotten to return non-empty lists in"
661
+ f"policy.get_initial_states()?"
662
+ )
663
+ state_ind = int(data_col.split("_")[-1])
664
+ self._build_buffers({data_col: self.initial_states[state_ind]})
665
+ else:
666
+ is_state = False
667
+ # only create dummy data during inference
668
+ if build_for_inference:
669
+ if isinstance(space, Space):
670
+ # state_out assumes the values do not have a batch dimension
671
+ # (i.e. instead of being (1, d) it is of shape (d,).
672
+ fill_value = get_dummy_batch_for_space(
673
+ space,
674
+ batch_size=0,
675
+ )
676
+ else:
677
+ fill_value = space
678
+
679
+ self._build_buffers({data_col: fill_value})
680
+
681
+ return is_state
682
+
683
+ def _prepare_for_data_cols_with_dummy_values(self, data_col):
684
+ self.data_cols_with_dummy_values.add(data_col)
685
+ # For items gained during inference, we append a dummy value here so
686
+ # that view requirements viewing these is not shifted by 1
687
+ for b in self.buffers[data_col]:
688
+ b.append(b[-1])
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/sample_collector.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from abc import ABCMeta, abstractmethod
3
+ from typing import TYPE_CHECKING, Dict, List, Optional, Union
4
+
5
+ from ray.rllib.policy.policy_map import PolicyMap
6
+ from ray.rllib.policy.sample_batch import MultiAgentBatch, SampleBatch
7
+ from ray.rllib.utils.annotations import OldAPIStack
8
+ from ray.rllib.utils.typing import AgentID, EnvID, EpisodeID, PolicyID, TensorType
9
+
10
+ if TYPE_CHECKING:
11
+ from ray.rllib.callbacks.callbacks import RLlibCallback
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ # fmt: off
17
+ # __sphinx_doc_begin__
18
+ @OldAPIStack
19
+ class SampleCollector(metaclass=ABCMeta):
20
+ """Collects samples for all policies and agents from a multi-agent env.
21
+
22
+ This API is controlled by RolloutWorker objects to store all data
23
+ generated by Environments and Policies/Models during rollout and
24
+ postprocessing. It's purposes are to a) make data collection and
25
+ SampleBatch/input_dict generation from this data faster, b) to unify
26
+ the way we collect samples from environments and model (outputs), thereby
27
+ allowing for possible user customizations, c) to allow for more complex
28
+ inputs fed into different policies (e.g. multi-agent case with inter-agent
29
+ communication channel).
30
+ """
31
+
32
+ def __init__(self,
33
+ policy_map: PolicyMap,
34
+ clip_rewards: Union[bool, float],
35
+ callbacks: "RLlibCallback",
36
+ multiple_episodes_in_batch: bool = True,
37
+ rollout_fragment_length: int = 200,
38
+ count_steps_by: str = "env_steps"):
39
+ """Initializes a SampleCollector instance.
40
+
41
+ Args:
42
+ policy_map: Maps policy ids to policy instances.
43
+ clip_rewards (Union[bool, float]): Whether to clip rewards before
44
+ postprocessing (at +/-1.0) or the actual value to +/- clip.
45
+ callbacks: RLlib callbacks.
46
+ multiple_episodes_in_batch: Whether it's allowed to pack
47
+ multiple episodes into the same built batch.
48
+ rollout_fragment_length: The
49
+
50
+ """
51
+
52
+ self.policy_map = policy_map
53
+ self.clip_rewards = clip_rewards
54
+ self.callbacks = callbacks
55
+ self.multiple_episodes_in_batch = multiple_episodes_in_batch
56
+ self.rollout_fragment_length = rollout_fragment_length
57
+ self.count_steps_by = count_steps_by
58
+
59
+ @abstractmethod
60
+ def add_init_obs(
61
+ self,
62
+ *,
63
+ episode,
64
+ agent_id: AgentID,
65
+ policy_id: PolicyID,
66
+ init_obs: TensorType,
67
+ init_infos: Optional[Dict[str, TensorType]] = None,
68
+ t: int = -1,
69
+ ) -> None:
70
+ """Adds an initial obs (after reset) to this collector.
71
+
72
+ Since the very first observation in an environment is collected w/o
73
+ additional data (w/o actions, w/o reward) after env.reset() is called,
74
+ this method initializes a new trajectory for a given agent.
75
+ `add_init_obs()` has to be called first for each agent/episode-ID
76
+ combination. After this, only `add_action_reward_next_obs()` must be
77
+ called for that same agent/episode-pair.
78
+
79
+ Args:
80
+ episode: The Episode, for which we
81
+ are adding an Agent's initial observation.
82
+ agent_id: Unique id for the agent we are adding
83
+ values for.
84
+ env_id: The environment index (in a vectorized setup).
85
+ policy_id: Unique id for policy controlling the agent.
86
+ init_obs: Initial observation (after env.reset()).
87
+ init_obs: Initial observation (after env.reset()).
88
+ init_infos: Initial infos dict (after env.reset()).
89
+ t: The time step (episode length - 1). The initial obs has
90
+ ts=-1(!), then an action/reward/next-obs at t=0, etc..
91
+
92
+ .. testcode::
93
+ :skipif: True
94
+
95
+ obs, infos = env.reset()
96
+ collector.add_init_obs(
97
+ episode=my_episode,
98
+ agent_id=0,
99
+ policy_id="pol0",
100
+ t=-1,
101
+ init_obs=obs,
102
+ init_infos=infos,
103
+ )
104
+ obs, r, terminated, truncated, info = env.step(action)
105
+ collector.add_action_reward_next_obs(12345, 0, "pol0", False, {
106
+ "action": action, "obs": obs, "reward": r, "terminated": terminated,
107
+ "truncated": truncated, "info": info
108
+ })
109
+ """
110
+ raise NotImplementedError
111
+
112
+ @abstractmethod
113
+ def add_action_reward_next_obs(
114
+ self,
115
+ episode_id: EpisodeID,
116
+ agent_id: AgentID,
117
+ env_id: EnvID,
118
+ policy_id: PolicyID,
119
+ agent_done: bool,
120
+ values: Dict[str, TensorType],
121
+ ) -> None:
122
+ """Add the given dictionary (row) of values to this collector.
123
+
124
+ The incoming data (`values`) must include action, reward, terminated, truncated,
125
+ and next_obs information and may include any other information.
126
+ For the initial observation (after Env.reset()) of the given agent/
127
+ episode-ID combination, `add_initial_obs()` must be called instead.
128
+
129
+ Args:
130
+ episode_id: Unique id for the episode we are adding
131
+ values for.
132
+ agent_id: Unique id for the agent we are adding
133
+ values for.
134
+ env_id: The environment index (in a vectorized setup).
135
+ policy_id: Unique id for policy controlling the agent.
136
+ agent_done: Whether the given agent is done (terminated or truncated) with
137
+ its trajectory (the multi-agent episode may still be ongoing).
138
+ values (Dict[str, TensorType]): Row of values to add for this
139
+ agent. This row must contain the keys SampleBatch.ACTION,
140
+ REWARD, NEW_OBS, TERMINATED, and TRUNCATED.
141
+
142
+ .. testcode::
143
+ :skipif: True
144
+
145
+ obs, info = env.reset()
146
+ collector.add_init_obs(12345, 0, "pol0", obs)
147
+ obs, r, terminated, truncated, info = env.step(action)
148
+ collector.add_action_reward_next_obs(
149
+ 12345,
150
+ 0,
151
+ "pol0",
152
+ agent_done=False,
153
+ values={
154
+ "action": action, "obs": obs, "reward": r,
155
+ "terminated": terminated, "truncated": truncated
156
+ },
157
+ )
158
+ """
159
+ raise NotImplementedError
160
+
161
+ @abstractmethod
162
+ def episode_step(self, episode) -> None:
163
+ """Increases the episode step counter (across all agents) by one.
164
+
165
+ Args:
166
+ episode: Episode we are stepping through.
167
+ Useful for handling counting b/c it is called once across
168
+ all agents that are inside this episode.
169
+ """
170
+ raise NotImplementedError
171
+
172
+ @abstractmethod
173
+ def total_env_steps(self) -> int:
174
+ """Returns total number of env-steps taken so far.
175
+
176
+ Thereby, a step in an N-agent multi-agent environment counts as only 1
177
+ for this metric. The returned count contains everything that has not
178
+ been built yet (and returned as MultiAgentBatches by the
179
+ `try_build_truncated_episode_multi_agent_batch` or
180
+ `postprocess_episode(build=True)` methods). After such build, this
181
+ counter is reset to 0.
182
+
183
+ Returns:
184
+ int: The number of env-steps taken in total in the environment(s)
185
+ so far.
186
+ """
187
+ raise NotImplementedError
188
+
189
+ @abstractmethod
190
+ def total_agent_steps(self) -> int:
191
+ """Returns total number of (individual) agent-steps taken so far.
192
+
193
+ Thereby, a step in an N-agent multi-agent environment counts as N.
194
+ If less than N agents have stepped (because some agents were not
195
+ required to send actions), the count will be increased by less than N.
196
+ The returned count contains everything that has not been built yet
197
+ (and returned as MultiAgentBatches by the
198
+ `try_build_truncated_episode_multi_agent_batch` or
199
+ `postprocess_episode(build=True)` methods). After such build, this
200
+ counter is reset to 0.
201
+
202
+ Returns:
203
+ int: The number of (individual) agent-steps taken in total in the
204
+ environment(s) so far.
205
+ """
206
+ raise NotImplementedError
207
+
208
+ # TODO(jungong) : Remove this API call once we completely move to
209
+ # connector based sample collection.
210
+ @abstractmethod
211
+ def get_inference_input_dict(self, policy_id: PolicyID) -> \
212
+ Dict[str, TensorType]:
213
+ """Returns an input_dict for an (inference) forward pass from our data.
214
+
215
+ The input_dict can then be used for action computations inside a
216
+ Policy via `Policy.compute_actions_from_input_dict()`.
217
+
218
+ Args:
219
+ policy_id: The Policy ID to get the input dict for.
220
+
221
+ Returns:
222
+ Dict[str, TensorType]: The input_dict to be passed into the ModelV2
223
+ for inference/training.
224
+
225
+ .. testcode::
226
+ :skipif: True
227
+
228
+ obs, r, terminated, truncated, info = env.step(action)
229
+ collector.add_action_reward_next_obs(12345, 0, "pol0", False, {
230
+ "action": action, "obs": obs, "reward": r,
231
+ "terminated": terminated, "truncated", truncated
232
+ })
233
+ input_dict = collector.get_inference_input_dict(policy.model)
234
+ action = policy.compute_actions_from_input_dict(input_dict)
235
+ # repeat
236
+ """
237
+ raise NotImplementedError
238
+
239
+ @abstractmethod
240
+ def postprocess_episode(
241
+ self,
242
+ episode,
243
+ is_done: bool = False,
244
+ check_dones: bool = False,
245
+ build: bool = False,
246
+ ) -> Optional[MultiAgentBatch]:
247
+ """Postprocesses all agents' trajectories in a given episode.
248
+
249
+ Generates (single-trajectory) SampleBatches for all Policies/Agents and
250
+ calls Policy.postprocess_trajectory on each of these. Postprocessing
251
+ may happens in-place, meaning any changes to the viewed data columns
252
+ are directly reflected inside this collector's buffers.
253
+ Also makes sure that additional (newly created) data columns are
254
+ correctly added to the buffers.
255
+
256
+ Args:
257
+ episode: The Episode object for which
258
+ to post-process data.
259
+ is_done: Whether the given episode is actually terminated
260
+ (all agents are terminated OR truncated). If True, the
261
+ episode will no longer be used/continued and we may need to
262
+ recycle/erase it internally. If a soft-horizon is hit, the
263
+ episode will continue to be used and `is_done` should be set
264
+ to False here.
265
+ check_dones: Whether we need to check that all agents'
266
+ trajectories have dones=True at the end.
267
+ build: Whether to build a MultiAgentBatch from the given
268
+ episode (and only that episode!) and return that
269
+ MultiAgentBatch. Used for batch_mode=`complete_episodes`.
270
+
271
+ Returns:
272
+ Optional[MultiAgentBatch]: If `build` is True, the
273
+ SampleBatch or MultiAgentBatch built from `episode` (either
274
+ just from that episde or from the `_PolicyCollectorGroup`
275
+ in the `episode.batch_builder` property).
276
+ """
277
+ raise NotImplementedError
278
+
279
+ @abstractmethod
280
+ def try_build_truncated_episode_multi_agent_batch(self) -> \
281
+ List[Union[MultiAgentBatch, SampleBatch]]:
282
+ """Tries to build an MA-batch, if `rollout_fragment_length` is reached.
283
+
284
+ Any unprocessed data will be first postprocessed with a policy
285
+ postprocessor.
286
+ This is usually called to collect samples for policy training.
287
+ If not enough data has been collected yet (`rollout_fragment_length`),
288
+ returns an empty list.
289
+
290
+ Returns:
291
+ List[Union[MultiAgentBatch, SampleBatch]]: Returns a (possibly
292
+ empty) list of MultiAgentBatches (containing the accumulated
293
+ SampleBatches for each policy or a simple SampleBatch if only
294
+ one policy). The list will be empty if
295
+ `self.rollout_fragment_length` has not been reached yet.
296
+ """
297
+ raise NotImplementedError
298
+ # __sphinx_doc_end__
lib/python3.12/site-packages/ray/rllib/evaluation/collectors/simple_list_collector.py ADDED
@@ -0,0 +1,699 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import logging
3
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
4
+
5
+ import numpy as np
6
+ import tree # pip install dm_tree
7
+ from gymnasium.spaces import Space
8
+
9
+ from ray.rllib.env.base_env import _DUMMY_AGENT_ID
10
+ from ray.rllib.evaluation.collectors.agent_collector import AgentCollector
11
+ from ray.rllib.evaluation.collectors.sample_collector import SampleCollector
12
+ from ray.rllib.policy.policy import Policy
13
+ from ray.rllib.policy.policy_map import PolicyMap
14
+ from ray.rllib.policy.sample_batch import MultiAgentBatch, SampleBatch, concat_samples
15
+ from ray.rllib.utils.annotations import OldAPIStack, override
16
+ from ray.rllib.utils.debug import summarize
17
+ from ray.rllib.utils.framework import try_import_tf, try_import_torch
18
+ from ray.rllib.utils.spaces.space_utils import get_dummy_batch_for_space
19
+ from ray.rllib.utils.typing import (
20
+ AgentID,
21
+ EnvID,
22
+ EpisodeID,
23
+ PolicyID,
24
+ TensorType,
25
+ ViewRequirementsDict,
26
+ )
27
+ from ray.util.debug import log_once
28
+
29
+ _, tf, _ = try_import_tf()
30
+ torch, _ = try_import_torch()
31
+
32
+ if TYPE_CHECKING:
33
+ from ray.rllib.callbacks.callbacks import RLlibCallback
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ @OldAPIStack
39
+ class _PolicyCollector:
40
+ """Collects already postprocessed (single agent) samples for one policy.
41
+
42
+ Samples come in through already postprocessed SampleBatches, which
43
+ contain single episode/trajectory data for a single agent and are then
44
+ appended to this policy's buffers.
45
+ """
46
+
47
+ def __init__(self, policy: Policy):
48
+ """Initializes a _PolicyCollector instance.
49
+
50
+ Args:
51
+ policy: The policy object.
52
+ """
53
+
54
+ self.batches = []
55
+ self.policy = policy
56
+ # The total timestep count for all agents that use this policy.
57
+ # NOTE: This is not an env-step count (across n agents). AgentA and
58
+ # agentB, both using this policy, acting in the same episode and both
59
+ # doing n steps would increase the count by 2*n.
60
+ self.agent_steps = 0
61
+
62
+ def add_postprocessed_batch_for_training(
63
+ self, batch: SampleBatch, view_requirements: ViewRequirementsDict
64
+ ) -> None:
65
+ """Adds a postprocessed SampleBatch (single agent) to our buffers.
66
+
67
+ Args:
68
+ batch: An individual agent's (one trajectory)
69
+ SampleBatch to be added to the Policy's buffers.
70
+ view_requirements: The view
71
+ requirements for the policy. This is so we know, whether a
72
+ view-column needs to be copied at all (not needed for
73
+ training).
74
+ """
75
+ # Add the agent's trajectory length to our count.
76
+ self.agent_steps += batch.count
77
+ # And remove columns not needed for training.
78
+ for view_col, view_req in view_requirements.items():
79
+ if view_col in batch and not view_req.used_for_training:
80
+ del batch[view_col]
81
+ self.batches.append(batch)
82
+
83
+ def build(self):
84
+ """Builds a SampleBatch for this policy from the collected data.
85
+
86
+ Also resets all buffers for further sample collection for this policy.
87
+
88
+ Returns:
89
+ SampleBatch: The SampleBatch with all thus-far collected data for
90
+ this policy.
91
+ """
92
+ # Create batch from our buffers.
93
+ batch = concat_samples(self.batches)
94
+ # Clear batches for future samples.
95
+ self.batches = []
96
+ # Reset agent steps to 0.
97
+ self.agent_steps = 0
98
+ # Add num_grad_updates counter to the policy's batch.
99
+ batch.num_grad_updates = self.policy.num_grad_updates
100
+
101
+ return batch
102
+
103
+
104
+ class _PolicyCollectorGroup:
105
+ def __init__(self, policy_map):
106
+ self.policy_collectors = {}
107
+ # Total env-steps (1 env-step=up to N agents stepped).
108
+ self.env_steps = 0
109
+ # Total agent steps (1 agent-step=1 individual agent (out of N)
110
+ # stepped).
111
+ self.agent_steps = 0
112
+
113
+
114
+ @OldAPIStack
115
+ class SimpleListCollector(SampleCollector):
116
+ """Util to build SampleBatches for each policy in a multi-agent env.
117
+
118
+ Input data is per-agent, while output data is per-policy. There is an M:N
119
+ mapping between agents and policies. We retain one local batch builder
120
+ per agent. When an agent is done, then its local batch is appended into the
121
+ corresponding policy batch for the agent's policy.
122
+ """
123
+
124
+ def __init__(
125
+ self,
126
+ policy_map: PolicyMap,
127
+ clip_rewards: Union[bool, float],
128
+ callbacks: "RLlibCallback",
129
+ multiple_episodes_in_batch: bool = True,
130
+ rollout_fragment_length: int = 200,
131
+ count_steps_by: str = "env_steps",
132
+ ):
133
+ """Initializes a SimpleListCollector instance."""
134
+
135
+ super().__init__(
136
+ policy_map,
137
+ clip_rewards,
138
+ callbacks,
139
+ multiple_episodes_in_batch,
140
+ rollout_fragment_length,
141
+ count_steps_by,
142
+ )
143
+
144
+ self.large_batch_threshold: int = (
145
+ max(1000, self.rollout_fragment_length * 10)
146
+ if self.rollout_fragment_length != float("inf")
147
+ else 5000
148
+ )
149
+
150
+ # Whenever we observe a new episode+agent, add a new
151
+ # _SingleTrajectoryCollector.
152
+ self.agent_collectors: Dict[Tuple[EpisodeID, AgentID], AgentCollector] = {}
153
+ # Internal agent-key-to-policy-id map.
154
+ self.agent_key_to_policy_id = {}
155
+ # Pool of used/unused PolicyCollectorGroups (attached to episodes for
156
+ # across-episode multi-agent sample collection).
157
+ self.policy_collector_groups = []
158
+
159
+ # Agents to collect data from for the next forward pass (per policy).
160
+ self.forward_pass_agent_keys = {pid: [] for pid in self.policy_map.keys()}
161
+ self.forward_pass_size = {pid: 0 for pid in self.policy_map.keys()}
162
+
163
+ # Maps episode ID to the (non-built) env steps taken in this episode.
164
+ self.episode_steps: Dict[EpisodeID, int] = collections.defaultdict(int)
165
+ # Maps episode ID to the (non-built) individual agent steps in this
166
+ # episode.
167
+ self.agent_steps: Dict[EpisodeID, int] = collections.defaultdict(int)
168
+ # Maps episode ID to Episode.
169
+ self.episodes = {}
170
+
171
+ @override(SampleCollector)
172
+ def episode_step(self, episode) -> None:
173
+ episode_id = episode.episode_id
174
+ # In the rase case that an "empty" step is taken at the beginning of
175
+ # the episode (none of the agents has an observation in the obs-dict
176
+ # and thus does not take an action), we have seen the episode before
177
+ # and have to add it here to our registry.
178
+ if episode_id not in self.episodes:
179
+ self.episodes[episode_id] = episode
180
+ else:
181
+ assert episode is self.episodes[episode_id]
182
+ self.episode_steps[episode_id] += 1
183
+ episode.length += 1
184
+
185
+ # In case of "empty" env steps (no agent is stepping), the builder
186
+ # object may still be None.
187
+ if episode.batch_builder:
188
+ env_steps = episode.batch_builder.env_steps
189
+ num_individual_observations = sum(
190
+ c.agent_steps for c in episode.batch_builder.policy_collectors.values()
191
+ )
192
+
193
+ if num_individual_observations > self.large_batch_threshold and log_once(
194
+ "large_batch_warning"
195
+ ):
196
+ logger.warning(
197
+ "More than {} observations in {} env steps for "
198
+ "episode {} ".format(
199
+ num_individual_observations, env_steps, episode_id
200
+ )
201
+ + "are buffered in the sampler. If this is more than you "
202
+ "expected, check that that you set a horizon on your "
203
+ "environment correctly and that it terminates at some "
204
+ "point. Note: In multi-agent environments, "
205
+ "`rollout_fragment_length` sets the batch size based on "
206
+ "(across-agents) environment steps, not the steps of "
207
+ "individual agents, which can result in unexpectedly "
208
+ "large batches."
209
+ + (
210
+ "Also, you may be waiting for your Env to "
211
+ "terminate (batch_mode=`complete_episodes`). Make sure "
212
+ "it does at some point."
213
+ if not self.multiple_episodes_in_batch
214
+ else ""
215
+ )
216
+ )
217
+
218
+ @override(SampleCollector)
219
+ def add_init_obs(
220
+ self,
221
+ *,
222
+ episode,
223
+ agent_id: AgentID,
224
+ env_id: EnvID,
225
+ policy_id: PolicyID,
226
+ init_obs: TensorType,
227
+ init_infos: Optional[Dict[str, TensorType]] = None,
228
+ t: int = -1,
229
+ ) -> None:
230
+ # Make sure our mappings are up to date.
231
+ agent_key = (episode.episode_id, agent_id)
232
+ self.agent_key_to_policy_id[agent_key] = policy_id
233
+ policy = self.policy_map[policy_id]
234
+
235
+ # Add initial obs to Trajectory.
236
+ assert agent_key not in self.agent_collectors
237
+ # TODO: determine exact shift-before based on the view-req shifts.
238
+
239
+ # get max_seq_len value (Default is 1)
240
+ try:
241
+ max_seq_len = policy.config["model"]["max_seq_len"]
242
+ except KeyError:
243
+ max_seq_len = 1
244
+
245
+ self.agent_collectors[agent_key] = AgentCollector(
246
+ policy.view_requirements,
247
+ max_seq_len=max_seq_len,
248
+ disable_action_flattening=policy.config.get(
249
+ "_disable_action_flattening", False
250
+ ),
251
+ intial_states=policy.get_initial_state(),
252
+ is_policy_recurrent=policy.is_recurrent(),
253
+ )
254
+ self.agent_collectors[agent_key].add_init_obs(
255
+ episode_id=episode.episode_id,
256
+ agent_index=episode._agent_index(agent_id),
257
+ env_id=env_id,
258
+ init_obs=init_obs,
259
+ init_infos=init_infos or {},
260
+ t=t,
261
+ )
262
+
263
+ self.episodes[episode.episode_id] = episode
264
+ if episode.batch_builder is None:
265
+ episode.batch_builder = (
266
+ self.policy_collector_groups.pop()
267
+ if self.policy_collector_groups
268
+ else _PolicyCollectorGroup(self.policy_map)
269
+ )
270
+
271
+ self._add_to_next_inference_call(agent_key)
272
+
273
+ @override(SampleCollector)
274
+ def add_action_reward_next_obs(
275
+ self,
276
+ episode_id: EpisodeID,
277
+ agent_id: AgentID,
278
+ env_id: EnvID,
279
+ policy_id: PolicyID,
280
+ agent_done: bool,
281
+ values: Dict[str, TensorType],
282
+ ) -> None:
283
+ # Make sure, episode/agent already has some (at least init) data.
284
+ agent_key = (episode_id, agent_id)
285
+ assert self.agent_key_to_policy_id[agent_key] == policy_id
286
+ assert agent_key in self.agent_collectors
287
+
288
+ self.agent_steps[episode_id] += 1
289
+
290
+ # Include the current agent id for multi-agent algorithms.
291
+ if agent_id != _DUMMY_AGENT_ID:
292
+ values["agent_id"] = agent_id
293
+
294
+ # Add action/reward/next-obs (and other data) to Trajectory.
295
+ self.agent_collectors[agent_key].add_action_reward_next_obs(values)
296
+
297
+ if not agent_done:
298
+ self._add_to_next_inference_call(agent_key)
299
+
300
+ @override(SampleCollector)
301
+ def total_env_steps(self) -> int:
302
+ # Add the non-built ongoing-episode env steps + the already built
303
+ # env-steps.
304
+ return sum(self.episode_steps.values()) + sum(
305
+ pg.env_steps for pg in self.policy_collector_groups.values()
306
+ )
307
+
308
+ @override(SampleCollector)
309
+ def total_agent_steps(self) -> int:
310
+ # Add the non-built ongoing-episode agent steps (still in the agent
311
+ # collectors) + the already built agent steps.
312
+ return sum(a.agent_steps for a in self.agent_collectors.values()) + sum(
313
+ pg.agent_steps for pg in self.policy_collector_groups.values()
314
+ )
315
+
316
+ @override(SampleCollector)
317
+ def get_inference_input_dict(self, policy_id: PolicyID) -> Dict[str, TensorType]:
318
+ policy = self.policy_map[policy_id]
319
+ keys = self.forward_pass_agent_keys[policy_id]
320
+ batch_size = len(keys)
321
+
322
+ # Return empty batch, if no forward pass to do.
323
+ if batch_size == 0:
324
+ return SampleBatch()
325
+
326
+ buffers = {}
327
+ for k in keys:
328
+ collector = self.agent_collectors[k]
329
+ buffers[k] = collector.buffers
330
+ # Use one agent's buffer_structs (they should all be the same).
331
+ buffer_structs = self.agent_collectors[keys[0]].buffer_structs
332
+
333
+ input_dict = {}
334
+ for view_col, view_req in policy.view_requirements.items():
335
+ # Not used for action computations.
336
+ if not view_req.used_for_compute_actions:
337
+ continue
338
+
339
+ # Create the batch of data from the different buffers.
340
+ data_col = view_req.data_col or view_col
341
+ delta = (
342
+ -1
343
+ if data_col
344
+ in [
345
+ SampleBatch.OBS,
346
+ SampleBatch.INFOS,
347
+ SampleBatch.ENV_ID,
348
+ SampleBatch.EPS_ID,
349
+ SampleBatch.AGENT_INDEX,
350
+ SampleBatch.T,
351
+ ]
352
+ else 0
353
+ )
354
+ # Range of shifts, e.g. "-100:0". Note: This includes index 0!
355
+ if view_req.shift_from is not None:
356
+ time_indices = (view_req.shift_from + delta, view_req.shift_to + delta)
357
+ # Single shift (e.g. -1) or list of shifts, e.g. [-4, -1, 0].
358
+ else:
359
+ time_indices = view_req.shift + delta
360
+
361
+ # Loop through agents and add up their data (batch).
362
+ data = None
363
+ for k in keys:
364
+ # Buffer for the data does not exist yet: Create dummy
365
+ # (zero) data.
366
+ if data_col not in buffers[k]:
367
+ if view_req.data_col is not None:
368
+ space = policy.view_requirements[view_req.data_col].space
369
+ else:
370
+ space = view_req.space
371
+
372
+ if isinstance(space, Space):
373
+ fill_value = get_dummy_batch_for_space(
374
+ space,
375
+ batch_size=0,
376
+ )
377
+ else:
378
+ fill_value = space
379
+
380
+ self.agent_collectors[k]._build_buffers({data_col: fill_value})
381
+
382
+ if data is None:
383
+ data = [[] for _ in range(len(buffers[keys[0]][data_col]))]
384
+
385
+ # `shift_from` and `shift_to` are defined: User wants a
386
+ # view with some time-range.
387
+ if isinstance(time_indices, tuple):
388
+ # `shift_to` == -1: Until the end (including(!) the
389
+ # last item).
390
+ if time_indices[1] == -1:
391
+ for d, b in zip(data, buffers[k][data_col]):
392
+ d.append(b[time_indices[0] :])
393
+ # `shift_to` != -1: "Normal" range.
394
+ else:
395
+ for d, b in zip(data, buffers[k][data_col]):
396
+ d.append(b[time_indices[0] : time_indices[1] + 1])
397
+ # Single index.
398
+ else:
399
+ for d, b in zip(data, buffers[k][data_col]):
400
+ d.append(b[time_indices])
401
+
402
+ np_data = [np.array(d) for d in data]
403
+ if data_col in buffer_structs:
404
+ input_dict[view_col] = tree.unflatten_as(
405
+ buffer_structs[data_col], np_data
406
+ )
407
+ else:
408
+ input_dict[view_col] = np_data[0]
409
+
410
+ self._reset_inference_calls(policy_id)
411
+
412
+ return SampleBatch(
413
+ input_dict,
414
+ seq_lens=np.ones(batch_size, dtype=np.int32)
415
+ if "state_in_0" in input_dict
416
+ else None,
417
+ )
418
+
419
+ @override(SampleCollector)
420
+ def postprocess_episode(
421
+ self,
422
+ episode,
423
+ is_done: bool = False,
424
+ check_dones: bool = False,
425
+ build: bool = False,
426
+ ) -> Union[None, SampleBatch, MultiAgentBatch]:
427
+ episode_id = episode.episode_id
428
+ policy_collector_group = episode.batch_builder
429
+
430
+ # Build SampleBatches for the given episode.
431
+ pre_batches = {}
432
+ for (eps_id, agent_id), collector in self.agent_collectors.items():
433
+ # Build only if there is data and agent is part of given episode.
434
+ if collector.agent_steps == 0 or eps_id != episode_id:
435
+ continue
436
+ pid = self.agent_key_to_policy_id[(eps_id, agent_id)]
437
+ policy = self.policy_map[pid]
438
+ pre_batch = collector.build_for_training(policy.view_requirements)
439
+ pre_batches[agent_id] = (policy, pre_batch)
440
+
441
+ # Apply reward clipping before calling postprocessing functions.
442
+ if self.clip_rewards is True:
443
+ for _, (_, pre_batch) in pre_batches.items():
444
+ pre_batch["rewards"] = np.sign(pre_batch["rewards"])
445
+ elif self.clip_rewards:
446
+ for _, (_, pre_batch) in pre_batches.items():
447
+ pre_batch["rewards"] = np.clip(
448
+ pre_batch["rewards"],
449
+ a_min=-self.clip_rewards,
450
+ a_max=self.clip_rewards,
451
+ )
452
+
453
+ post_batches = {}
454
+ for agent_id, (_, pre_batch) in pre_batches.items():
455
+ # Entire episode is said to be done.
456
+ # Error if no DONE at end of this agent's trajectory.
457
+ if is_done and check_dones and not pre_batch.is_terminated_or_truncated():
458
+ raise ValueError(
459
+ "Episode {} terminated for all agents, but we still "
460
+ "don't have a last observation for agent {} (policy "
461
+ "{}). ".format(
462
+ episode_id,
463
+ agent_id,
464
+ self.agent_key_to_policy_id[(episode_id, agent_id)],
465
+ )
466
+ + "Please ensure that you include the last observations "
467
+ "of all live agents when setting truncated[__all__] or "
468
+ "terminated[__all__] to True."
469
+ )
470
+
471
+ # Skip a trajectory's postprocessing (and thus using it for training),
472
+ # if its agent's info exists and contains the training_enabled=False
473
+ # setting (used by our PolicyClients).
474
+ last_info = episode.last_info_for(agent_id)
475
+ if last_info and not last_info.get("training_enabled", True):
476
+ if is_done:
477
+ agent_key = (episode_id, agent_id)
478
+ del self.agent_key_to_policy_id[agent_key]
479
+ del self.agent_collectors[agent_key]
480
+ continue
481
+
482
+ if len(pre_batches) > 1:
483
+ other_batches = pre_batches.copy()
484
+ del other_batches[agent_id]
485
+ else:
486
+ other_batches = {}
487
+ pid = self.agent_key_to_policy_id[(episode_id, agent_id)]
488
+ policy = self.policy_map[pid]
489
+ if not pre_batch.is_single_trajectory():
490
+ raise ValueError(
491
+ "Batches sent to postprocessing must be from a single trajectory! "
492
+ "TERMINATED & TRUNCATED need to be False everywhere, except the "
493
+ "last timestep, which can be either True or False for those keys)!",
494
+ pre_batch,
495
+ )
496
+ elif len(set(pre_batch[SampleBatch.EPS_ID])) > 1:
497
+ episode_ids = set(pre_batch[SampleBatch.EPS_ID])
498
+ raise ValueError(
499
+ "Batches sent to postprocessing must only contain steps "
500
+ "from a single episode! Your trajectory contains data from "
501
+ f"{len(episode_ids)} episodes ({list(episode_ids)}).",
502
+ pre_batch,
503
+ )
504
+ # Call the Policy's Exploration's postprocess method.
505
+ post_batches[agent_id] = pre_batch
506
+ if getattr(policy, "exploration", None) is not None:
507
+ policy.exploration.postprocess_trajectory(
508
+ policy, post_batches[agent_id], policy.get_session()
509
+ )
510
+ post_batches[agent_id].set_get_interceptor(None)
511
+ post_batches[agent_id] = policy.postprocess_trajectory(
512
+ post_batches[agent_id], other_batches, episode
513
+ )
514
+
515
+ if log_once("after_post"):
516
+ logger.info(
517
+ "Trajectory fragment after postprocess_trajectory():\n\n{}\n".format(
518
+ summarize(post_batches)
519
+ )
520
+ )
521
+
522
+ # Append into policy batches and reset.
523
+ from ray.rllib.evaluation.rollout_worker import get_global_worker
524
+
525
+ for agent_id, post_batch in sorted(post_batches.items()):
526
+ agent_key = (episode_id, agent_id)
527
+ pid = self.agent_key_to_policy_id[agent_key]
528
+ policy = self.policy_map[pid]
529
+ self.callbacks.on_postprocess_trajectory(
530
+ worker=get_global_worker(),
531
+ episode=episode,
532
+ agent_id=agent_id,
533
+ policy_id=pid,
534
+ policies=self.policy_map,
535
+ postprocessed_batch=post_batch,
536
+ original_batches=pre_batches,
537
+ )
538
+
539
+ # Add the postprocessed SampleBatch to the policy collectors for
540
+ # training.
541
+ # PID may be a newly added policy. Just confirm we have it in our
542
+ # policy map before proceeding with adding a new _PolicyCollector()
543
+ # to the group.
544
+ if pid not in policy_collector_group.policy_collectors:
545
+ assert pid in self.policy_map
546
+ policy_collector_group.policy_collectors[pid] = _PolicyCollector(policy)
547
+ policy_collector_group.policy_collectors[
548
+ pid
549
+ ].add_postprocessed_batch_for_training(post_batch, policy.view_requirements)
550
+
551
+ if is_done:
552
+ del self.agent_key_to_policy_id[agent_key]
553
+ del self.agent_collectors[agent_key]
554
+
555
+ if policy_collector_group:
556
+ env_steps = self.episode_steps[episode_id]
557
+ policy_collector_group.env_steps += env_steps
558
+ agent_steps = self.agent_steps[episode_id]
559
+ policy_collector_group.agent_steps += agent_steps
560
+
561
+ if is_done:
562
+ del self.episode_steps[episode_id]
563
+ del self.episodes[episode_id]
564
+
565
+ if episode_id in self.agent_steps:
566
+ del self.agent_steps[episode_id]
567
+ else:
568
+ assert (
569
+ len(pre_batches) == 0
570
+ ), "Expected the batch to be empty since the episode_id is missing."
571
+ # if the key does not exist it means that throughout the episode all
572
+ # observations were empty (i.e. there was no agent in the env)
573
+ msg = (
574
+ f"Data from episode {episode_id} does not show any agent "
575
+ f"interactions. Hint: Make sure for at least one timestep in the "
576
+ f"episode, env.step() returns non-empty values."
577
+ )
578
+ raise ValueError(msg)
579
+
580
+ # Make PolicyCollectorGroup available for more agent batches in
581
+ # other episodes. Do not reset count to 0.
582
+ if policy_collector_group:
583
+ self.policy_collector_groups.append(policy_collector_group)
584
+ else:
585
+ self.episode_steps[episode_id] = self.agent_steps[episode_id] = 0
586
+
587
+ # Build a MultiAgentBatch from the episode and return.
588
+ if build:
589
+ return self._build_multi_agent_batch(episode)
590
+
591
+ def _build_multi_agent_batch(self, episode) -> Union[MultiAgentBatch, SampleBatch]:
592
+
593
+ ma_batch = {}
594
+ for pid, collector in episode.batch_builder.policy_collectors.items():
595
+ if collector.agent_steps > 0:
596
+ ma_batch[pid] = collector.build()
597
+
598
+ # TODO(sven): We should always return the same type here (MultiAgentBatch),
599
+ # no matter what. Just have to unify our `training_step` methods, then. This
600
+ # will reduce a lot of confusion about what comes out of the sampling process.
601
+ # Create the batch.
602
+ ma_batch = MultiAgentBatch.wrap_as_needed(
603
+ ma_batch, env_steps=episode.batch_builder.env_steps
604
+ )
605
+
606
+ # PolicyCollectorGroup is empty.
607
+ episode.batch_builder.env_steps = 0
608
+ episode.batch_builder.agent_steps = 0
609
+
610
+ return ma_batch
611
+
612
+ @override(SampleCollector)
613
+ def try_build_truncated_episode_multi_agent_batch(
614
+ self,
615
+ ) -> List[Union[MultiAgentBatch, SampleBatch]]:
616
+ batches = []
617
+ # Loop through ongoing episodes and see whether their length plus
618
+ # what's already in the policy collectors reaches the fragment-len
619
+ # (abiding to the unit used: env-steps or agent-steps).
620
+ for episode_id, episode in self.episodes.items():
621
+ # Measure batch size in env-steps.
622
+ if self.count_steps_by == "env_steps":
623
+ built_steps = (
624
+ episode.batch_builder.env_steps if episode.batch_builder else 0
625
+ )
626
+ ongoing_steps = self.episode_steps[episode_id]
627
+ # Measure batch-size in agent-steps.
628
+ else:
629
+ built_steps = (
630
+ episode.batch_builder.agent_steps if episode.batch_builder else 0
631
+ )
632
+ ongoing_steps = self.agent_steps[episode_id]
633
+
634
+ # Reached the fragment-len -> We should build an MA-Batch.
635
+ if built_steps + ongoing_steps >= self.rollout_fragment_length:
636
+ if self.count_steps_by == "env_steps":
637
+ assert built_steps + ongoing_steps == self.rollout_fragment_length
638
+ # If we reached the fragment-len only because of `episode_id`
639
+ # (still ongoing) -> postprocess `episode_id` first.
640
+ if built_steps < self.rollout_fragment_length:
641
+ self.postprocess_episode(episode, is_done=False)
642
+ # If there is a builder for this episode,
643
+ # build the MA-batch and add to return values.
644
+ if episode.batch_builder:
645
+ batch = self._build_multi_agent_batch(episode=episode)
646
+ batches.append(batch)
647
+ # No batch-builder:
648
+ # We have reached the rollout-fragment length w/o any agent
649
+ # steps! Warn that the environment may never request any
650
+ # actions from any agents.
651
+ elif log_once("no_agent_steps"):
652
+ logger.warning(
653
+ "Your environment seems to be stepping w/o ever "
654
+ "emitting agent observations (agents are never "
655
+ "requested to act)!"
656
+ )
657
+
658
+ return batches
659
+
660
+ def _add_to_next_inference_call(self, agent_key: Tuple[EpisodeID, AgentID]) -> None:
661
+ """Adds an Agent key (episode+agent IDs) to the next inference call.
662
+
663
+ This makes sure that the agent's current data (in the trajectory) is
664
+ used for generating the next input_dict for a
665
+ `Policy.compute_actions()` call.
666
+
667
+ Args:
668
+ agent_key (Tuple[EpisodeID, AgentID]: A unique agent key (across
669
+ vectorized environments).
670
+ """
671
+ pid = self.agent_key_to_policy_id[agent_key]
672
+
673
+ # PID may be a newly added policy (added on the fly during training).
674
+ # Just confirm we have it in our policy map before proceeding with
675
+ # forward_pass_size=0.
676
+ if pid not in self.forward_pass_size:
677
+ assert pid in self.policy_map
678
+ self.forward_pass_size[pid] = 0
679
+ self.forward_pass_agent_keys[pid] = []
680
+
681
+ idx = self.forward_pass_size[pid]
682
+ assert idx >= 0
683
+ if idx == 0:
684
+ self.forward_pass_agent_keys[pid].clear()
685
+
686
+ self.forward_pass_agent_keys[pid].append(agent_key)
687
+ self.forward_pass_size[pid] += 1
688
+
689
+ def _reset_inference_calls(self, policy_id: PolicyID) -> None:
690
+ """Resets internal inference input-dict registries.
691
+
692
+ Calling `self.get_inference_input_dict()` after this method is called
693
+ would return an empty input-dict.
694
+
695
+ Args:
696
+ policy_id: The policy ID for which to reset the
697
+ inference pointers.
698
+ """
699
+ self.forward_pass_size[policy_id] = 0
lib/python3.12/site-packages/ray/rllib/evaluation/env_runner_v2.py ADDED
@@ -0,0 +1,1233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import time
3
+ from collections import defaultdict
4
+ from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Set, Tuple, Union
5
+
6
+ import numpy as np
7
+ import tree # pip install dm_tree
8
+
9
+ from ray.rllib.env.base_env import ASYNC_RESET_RETURN, BaseEnv
10
+ from ray.rllib.env.external_env import ExternalEnvWrapper
11
+ from ray.rllib.env.wrappers.atari_wrappers import MonitorEnv, get_wrapper_by_cls
12
+ from ray.rllib.evaluation.collectors.simple_list_collector import _PolicyCollectorGroup
13
+ from ray.rllib.evaluation.episode_v2 import EpisodeV2
14
+ from ray.rllib.evaluation.metrics import RolloutMetrics
15
+ from ray.rllib.models.preprocessors import Preprocessor
16
+ from ray.rllib.policy.policy import Policy
17
+ from ray.rllib.policy.sample_batch import MultiAgentBatch, SampleBatch, concat_samples
18
+ from ray.rllib.utils.annotations import OldAPIStack
19
+ from ray.rllib.utils.filter import Filter
20
+ from ray.rllib.utils.numpy import convert_to_numpy
21
+ from ray.rllib.utils.spaces.space_utils import get_original_space, unbatch
22
+ from ray.rllib.utils.typing import (
23
+ ActionConnectorDataType,
24
+ AgentConnectorDataType,
25
+ AgentID,
26
+ EnvActionType,
27
+ EnvID,
28
+ EnvInfoDict,
29
+ EnvObsType,
30
+ MultiAgentDict,
31
+ MultiEnvDict,
32
+ PolicyID,
33
+ PolicyOutputType,
34
+ SampleBatchType,
35
+ StateBatches,
36
+ TensorStructType,
37
+ )
38
+ from ray.util.debug import log_once
39
+
40
+ if TYPE_CHECKING:
41
+ from gymnasium.envs.classic_control.rendering import SimpleImageViewer
42
+
43
+ from ray.rllib.callbacks.callbacks import RLlibCallback
44
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
45
+
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+
50
+ MIN_LARGE_BATCH_THRESHOLD = 1000
51
+ DEFAULT_LARGE_BATCH_THRESHOLD = 5000
52
+ MS_TO_SEC = 1000.0
53
+
54
+
55
+ @OldAPIStack
56
+ class _PerfStats:
57
+ """Sampler perf stats that will be included in rollout metrics."""
58
+
59
+ def __init__(self, ema_coef: Optional[float] = None):
60
+ # If not None, enable Exponential Moving Average mode.
61
+ # The way we update stats is by:
62
+ # updated = (1 - ema_coef) * old + ema_coef * new
63
+ # In general provides more responsive stats about sampler performance.
64
+ # TODO(jungong) : make ema the default (only) mode if it works well.
65
+ self.ema_coef = ema_coef
66
+
67
+ self.iters = 0
68
+ self.raw_obs_processing_time = 0.0
69
+ self.inference_time = 0.0
70
+ self.action_processing_time = 0.0
71
+ self.env_wait_time = 0.0
72
+ self.env_render_time = 0.0
73
+
74
+ def incr(self, field: str, value: Union[int, float]):
75
+ if field == "iters":
76
+ self.iters += value
77
+ return
78
+
79
+ # All the other fields support either global average or ema mode.
80
+ if self.ema_coef is None:
81
+ # Global average.
82
+ self.__dict__[field] += value
83
+ else:
84
+ self.__dict__[field] = (1.0 - self.ema_coef) * self.__dict__[
85
+ field
86
+ ] + self.ema_coef * value
87
+
88
+ def _get_avg(self):
89
+ # Mean multiplicator (1000 = sec -> ms).
90
+ factor = MS_TO_SEC / self.iters
91
+ return {
92
+ # Raw observation preprocessing.
93
+ "mean_raw_obs_processing_ms": self.raw_obs_processing_time * factor,
94
+ # Computing actions through policy.
95
+ "mean_inference_ms": self.inference_time * factor,
96
+ # Processing actions (to be sent to env, e.g. clipping).
97
+ "mean_action_processing_ms": self.action_processing_time * factor,
98
+ # Waiting for environment (during poll).
99
+ "mean_env_wait_ms": self.env_wait_time * factor,
100
+ # Environment rendering (False by default).
101
+ "mean_env_render_ms": self.env_render_time * factor,
102
+ }
103
+
104
+ def _get_ema(self):
105
+ # In EMA mode, stats are already (exponentially) averaged,
106
+ # hence we only need to do the sec -> ms conversion here.
107
+ return {
108
+ # Raw observation preprocessing.
109
+ "mean_raw_obs_processing_ms": self.raw_obs_processing_time * MS_TO_SEC,
110
+ # Computing actions through policy.
111
+ "mean_inference_ms": self.inference_time * MS_TO_SEC,
112
+ # Processing actions (to be sent to env, e.g. clipping).
113
+ "mean_action_processing_ms": self.action_processing_time * MS_TO_SEC,
114
+ # Waiting for environment (during poll).
115
+ "mean_env_wait_ms": self.env_wait_time * MS_TO_SEC,
116
+ # Environment rendering (False by default).
117
+ "mean_env_render_ms": self.env_render_time * MS_TO_SEC,
118
+ }
119
+
120
+ def get(self):
121
+ if self.ema_coef is None:
122
+ return self._get_avg()
123
+ else:
124
+ return self._get_ema()
125
+
126
+
127
+ @OldAPIStack
128
+ class _NewDefaultDict(defaultdict):
129
+ def __missing__(self, env_id):
130
+ ret = self[env_id] = self.default_factory(env_id)
131
+ return ret
132
+
133
+
134
+ @OldAPIStack
135
+ def _build_multi_agent_batch(
136
+ episode_id: int,
137
+ batch_builder: _PolicyCollectorGroup,
138
+ large_batch_threshold: int,
139
+ multiple_episodes_in_batch: bool,
140
+ ) -> MultiAgentBatch:
141
+ """Build MultiAgentBatch from a dict of _PolicyCollectors.
142
+
143
+ Args:
144
+ env_steps: total env steps.
145
+ policy_collectors: collected training SampleBatchs by policy.
146
+
147
+ Returns:
148
+ Always returns a sample batch in MultiAgentBatch format.
149
+ """
150
+ ma_batch = {}
151
+ for pid, collector in batch_builder.policy_collectors.items():
152
+ if collector.agent_steps <= 0:
153
+ continue
154
+
155
+ if batch_builder.agent_steps > large_batch_threshold and log_once(
156
+ "large_batch_warning"
157
+ ):
158
+ logger.warning(
159
+ "More than {} observations in {} env steps for "
160
+ "episode {} ".format(
161
+ batch_builder.agent_steps, batch_builder.env_steps, episode_id
162
+ )
163
+ + "are buffered in the sampler. If this is more than you "
164
+ "expected, check that that you set a horizon on your "
165
+ "environment correctly and that it terminates at some "
166
+ "point. Note: In multi-agent environments, "
167
+ "`rollout_fragment_length` sets the batch size based on "
168
+ "(across-agents) environment steps, not the steps of "
169
+ "individual agents, which can result in unexpectedly "
170
+ "large batches."
171
+ + (
172
+ "Also, you may be waiting for your Env to "
173
+ "terminate (batch_mode=`complete_episodes`). Make sure "
174
+ "it does at some point."
175
+ if not multiple_episodes_in_batch
176
+ else ""
177
+ )
178
+ )
179
+
180
+ batch = collector.build()
181
+
182
+ ma_batch[pid] = batch
183
+
184
+ # Create the multi agent batch.
185
+ return MultiAgentBatch(policy_batches=ma_batch, env_steps=batch_builder.env_steps)
186
+
187
+
188
+ @OldAPIStack
189
+ def _batch_inference_sample_batches(eval_data: List[SampleBatch]) -> SampleBatch:
190
+ """Batch a list of input SampleBatches into a single SampleBatch.
191
+
192
+ Args:
193
+ eval_data: list of SampleBatches.
194
+
195
+ Returns:
196
+ single batched SampleBatch.
197
+ """
198
+ inference_batch = concat_samples(eval_data)
199
+ if "state_in_0" in inference_batch:
200
+ batch_size = len(eval_data)
201
+ inference_batch[SampleBatch.SEQ_LENS] = np.ones(batch_size, dtype=np.int32)
202
+ return inference_batch
203
+
204
+
205
+ @OldAPIStack
206
+ class EnvRunnerV2:
207
+ """Collect experiences from user environment using Connectors."""
208
+
209
+ def __init__(
210
+ self,
211
+ worker: "RolloutWorker",
212
+ base_env: BaseEnv,
213
+ multiple_episodes_in_batch: bool,
214
+ callbacks: "RLlibCallback",
215
+ perf_stats: _PerfStats,
216
+ rollout_fragment_length: int = 200,
217
+ count_steps_by: str = "env_steps",
218
+ render: bool = None,
219
+ ):
220
+ """
221
+ Args:
222
+ worker: Reference to the current rollout worker.
223
+ base_env: Env implementing BaseEnv.
224
+ multiple_episodes_in_batch: Whether to pack multiple
225
+ episodes into each batch. This guarantees batches will be exactly
226
+ `rollout_fragment_length` in size.
227
+ callbacks: User callbacks to run on episode events.
228
+ perf_stats: Record perf stats into this object.
229
+ rollout_fragment_length: The length of a fragment to collect
230
+ before building a SampleBatch from the data and resetting
231
+ the SampleBatchBuilder object.
232
+ count_steps_by: One of "env_steps" (default) or "agent_steps".
233
+ Use "agent_steps", if you want rollout lengths to be counted
234
+ by individual agent steps. In a multi-agent env,
235
+ a single env_step contains one or more agent_steps, depending
236
+ on how many agents are present at any given time in the
237
+ ongoing episode.
238
+ render: Whether to try to render the environment after each
239
+ step.
240
+ """
241
+ self._worker = worker
242
+ if isinstance(base_env, ExternalEnvWrapper):
243
+ raise ValueError(
244
+ "Policies using the new Connector API do not support ExternalEnv."
245
+ )
246
+ self._base_env = base_env
247
+ self._multiple_episodes_in_batch = multiple_episodes_in_batch
248
+ self._callbacks = callbacks
249
+ self._perf_stats = perf_stats
250
+ self._rollout_fragment_length = rollout_fragment_length
251
+ self._count_steps_by = count_steps_by
252
+ self._render = render
253
+
254
+ # May be populated for image rendering.
255
+ self._simple_image_viewer: Optional[
256
+ "SimpleImageViewer"
257
+ ] = self._get_simple_image_viewer()
258
+
259
+ # Keeps track of active episodes.
260
+ self._active_episodes: Dict[EnvID, EpisodeV2] = {}
261
+ self._batch_builders: Dict[EnvID, _PolicyCollectorGroup] = _NewDefaultDict(
262
+ self._new_batch_builder
263
+ )
264
+
265
+ self._large_batch_threshold: int = (
266
+ max(MIN_LARGE_BATCH_THRESHOLD, self._rollout_fragment_length * 10)
267
+ if self._rollout_fragment_length != float("inf")
268
+ else DEFAULT_LARGE_BATCH_THRESHOLD
269
+ )
270
+
271
+ def _get_simple_image_viewer(self):
272
+ """Maybe construct a SimpleImageViewer instance for episode rendering."""
273
+ # Try to render the env, if required.
274
+ if not self._render:
275
+ return None
276
+
277
+ try:
278
+ from gymnasium.envs.classic_control.rendering import SimpleImageViewer
279
+
280
+ return SimpleImageViewer()
281
+ except (ImportError, ModuleNotFoundError):
282
+ self._render = False # disable rendering
283
+ logger.warning(
284
+ "Could not import gymnasium.envs.classic_control."
285
+ "rendering! Try `pip install gymnasium[all]`."
286
+ )
287
+
288
+ return None
289
+
290
+ def _call_on_episode_start(self, episode, env_id):
291
+ # Call each policy's Exploration.on_episode_start method.
292
+ # Note: This may break the exploration (e.g. ParameterNoise) of
293
+ # policies in the `policy_map` that have not been recently used
294
+ # (and are therefore stashed to disk). However, we certainly do not
295
+ # want to loop through all (even stashed) policies here as that
296
+ # would counter the purpose of the LRU policy caching.
297
+ for p in self._worker.policy_map.cache.values():
298
+ if getattr(p, "exploration", None) is not None:
299
+ p.exploration.on_episode_start(
300
+ policy=p,
301
+ environment=self._base_env,
302
+ episode=episode,
303
+ tf_sess=p.get_session(),
304
+ )
305
+ # Call `on_episode_start()` callback.
306
+ self._callbacks.on_episode_start(
307
+ worker=self._worker,
308
+ base_env=self._base_env,
309
+ policies=self._worker.policy_map,
310
+ env_index=env_id,
311
+ episode=episode,
312
+ )
313
+
314
+ def _new_batch_builder(self, _) -> _PolicyCollectorGroup:
315
+ """Create a new batch builder.
316
+
317
+ We create a _PolicyCollectorGroup based on the full policy_map
318
+ as the batch builder.
319
+ """
320
+ return _PolicyCollectorGroup(self._worker.policy_map)
321
+
322
+ def run(self) -> Iterator[SampleBatchType]:
323
+ """Samples and yields training episodes continuously.
324
+
325
+ Yields:
326
+ Object containing state, action, reward, terminal condition,
327
+ and other fields as dictated by `policy`.
328
+ """
329
+ while True:
330
+ outputs = self.step()
331
+ for o in outputs:
332
+ yield o
333
+
334
+ def step(self) -> List[SampleBatchType]:
335
+ """Samples training episodes by stepping through environments."""
336
+
337
+ self._perf_stats.incr("iters", 1)
338
+
339
+ t0 = time.time()
340
+ # Get observations from all ready agents.
341
+ # types: MultiEnvDict, MultiEnvDict, MultiEnvDict, MultiEnvDict, ...
342
+ (
343
+ unfiltered_obs,
344
+ rewards,
345
+ terminateds,
346
+ truncateds,
347
+ infos,
348
+ off_policy_actions,
349
+ ) = self._base_env.poll()
350
+ env_poll_time = time.time() - t0
351
+
352
+ # Process observations and prepare for policy evaluation.
353
+ t1 = time.time()
354
+ # types: Set[EnvID], Dict[PolicyID, List[AgentConnectorDataType]],
355
+ # List[Union[RolloutMetrics, SampleBatchType]]
356
+ active_envs, to_eval, outputs = self._process_observations(
357
+ unfiltered_obs=unfiltered_obs,
358
+ rewards=rewards,
359
+ terminateds=terminateds,
360
+ truncateds=truncateds,
361
+ infos=infos,
362
+ )
363
+ self._perf_stats.incr("raw_obs_processing_time", time.time() - t1)
364
+
365
+ # Do batched policy eval (accross vectorized envs).
366
+ t2 = time.time()
367
+ # types: Dict[PolicyID, Tuple[TensorStructType, StateBatch, dict]]
368
+ eval_results = self._do_policy_eval(to_eval=to_eval)
369
+ self._perf_stats.incr("inference_time", time.time() - t2)
370
+
371
+ # Process results and update episode state.
372
+ t3 = time.time()
373
+ actions_to_send: Dict[
374
+ EnvID, Dict[AgentID, EnvActionType]
375
+ ] = self._process_policy_eval_results(
376
+ active_envs=active_envs,
377
+ to_eval=to_eval,
378
+ eval_results=eval_results,
379
+ off_policy_actions=off_policy_actions,
380
+ )
381
+ self._perf_stats.incr("action_processing_time", time.time() - t3)
382
+
383
+ # Return computed actions to ready envs. We also send to envs that have
384
+ # taken off-policy actions; those envs are free to ignore the action.
385
+ t4 = time.time()
386
+ self._base_env.send_actions(actions_to_send)
387
+ self._perf_stats.incr("env_wait_time", env_poll_time + time.time() - t4)
388
+
389
+ self._maybe_render()
390
+
391
+ return outputs
392
+
393
+ def _get_rollout_metrics(
394
+ self, episode: EpisodeV2, policy_map: Dict[str, Policy]
395
+ ) -> List[RolloutMetrics]:
396
+ """Get rollout metrics from completed episode."""
397
+ # TODO(jungong) : why do we need to handle atari metrics differently?
398
+ # Can we unify atari and normal env metrics?
399
+ atari_metrics: List[RolloutMetrics] = _fetch_atari_metrics(self._base_env)
400
+ if atari_metrics is not None:
401
+ for m in atari_metrics:
402
+ m._replace(custom_metrics=episode.custom_metrics)
403
+ return atari_metrics
404
+ # Create connector metrics
405
+ connector_metrics = {}
406
+ active_agents = episode.get_agents()
407
+ for agent in active_agents:
408
+ policy_id = episode.policy_for(agent)
409
+ policy = episode.policy_map[policy_id]
410
+ connector_metrics[policy_id] = policy.get_connector_metrics()
411
+ # Otherwise, return RolloutMetrics for the episode.
412
+ return [
413
+ RolloutMetrics(
414
+ episode_length=episode.length,
415
+ episode_reward=episode.total_reward,
416
+ agent_rewards=dict(episode.agent_rewards),
417
+ custom_metrics=episode.custom_metrics,
418
+ perf_stats={},
419
+ hist_data=episode.hist_data,
420
+ media=episode.media,
421
+ connector_metrics=connector_metrics,
422
+ )
423
+ ]
424
+
425
+ def _process_observations(
426
+ self,
427
+ unfiltered_obs: MultiEnvDict,
428
+ rewards: MultiEnvDict,
429
+ terminateds: MultiEnvDict,
430
+ truncateds: MultiEnvDict,
431
+ infos: MultiEnvDict,
432
+ ) -> Tuple[
433
+ Set[EnvID],
434
+ Dict[PolicyID, List[AgentConnectorDataType]],
435
+ List[Union[RolloutMetrics, SampleBatchType]],
436
+ ]:
437
+ """Process raw obs from env.
438
+
439
+ Group data for active agents by policy. Reset environments that are done.
440
+
441
+ Args:
442
+ unfiltered_obs: The unfiltered, raw observations from the BaseEnv
443
+ (vectorized, possibly multi-agent). Dict of dict: By env index,
444
+ then agent ID, then mapped to actual obs.
445
+ rewards: The rewards MultiEnvDict of the BaseEnv.
446
+ terminateds: The `terminated` flags MultiEnvDict of the BaseEnv.
447
+ truncateds: The `truncated` flags MultiEnvDict of the BaseEnv.
448
+ infos: The MultiEnvDict of infos dicts of the BaseEnv.
449
+
450
+ Returns:
451
+ A tuple of:
452
+ A list of envs that were active during this step.
453
+ AgentConnectorDataType for active agents for policy evaluation.
454
+ SampleBatches and RolloutMetrics for completed agents for output.
455
+ """
456
+ # Output objects.
457
+ # Note that we need to track envs that are active during this round explicitly,
458
+ # just to be confident which envs require us to send at least an empty action
459
+ # dict to.
460
+ # We can not get this from the _active_episode or to_eval lists because
461
+ # 1. All envs are not required to step during every single step. And
462
+ # 2. to_eval only contains data for the agents that are still active. An env may
463
+ # be active but all agents are done during the step.
464
+ active_envs: Set[EnvID] = set()
465
+ to_eval: Dict[PolicyID, List[AgentConnectorDataType]] = defaultdict(list)
466
+ outputs: List[Union[RolloutMetrics, SampleBatchType]] = []
467
+
468
+ # For each (vectorized) sub-environment.
469
+ # types: EnvID, Dict[AgentID, EnvObsType]
470
+ for env_id, env_obs in unfiltered_obs.items():
471
+ # Check for env_id having returned an error instead of a multi-agent
472
+ # obs dict. This is how our BaseEnv can tell the caller to `poll()` that
473
+ # one of its sub-environments is faulty and should be restarted (and the
474
+ # ongoing episode should not be used for training).
475
+ if isinstance(env_obs, Exception):
476
+ assert terminateds[env_id]["__all__"] is True, (
477
+ f"ERROR: When a sub-environment (env-id {env_id}) returns an error "
478
+ "as observation, the terminateds[__all__] flag must also be set to "
479
+ "True!"
480
+ )
481
+ # all_agents_obs is an Exception here.
482
+ # Drop this episode and skip to next.
483
+ self._handle_done_episode(
484
+ env_id=env_id,
485
+ env_obs_or_exception=env_obs,
486
+ is_done=True,
487
+ active_envs=active_envs,
488
+ to_eval=to_eval,
489
+ outputs=outputs,
490
+ )
491
+ continue
492
+
493
+ if env_id not in self._active_episodes:
494
+ episode: EpisodeV2 = self.create_episode(env_id)
495
+ self._active_episodes[env_id] = episode
496
+ else:
497
+ episode: EpisodeV2 = self._active_episodes[env_id]
498
+ # If this episode is brand-new, call the episode start callback(s).
499
+ # Note: EpisodeV2s are initialized with length=-1 (before the reset).
500
+ if not episode.has_init_obs():
501
+ self._call_on_episode_start(episode, env_id)
502
+
503
+ # Check episode termination conditions.
504
+ if terminateds[env_id]["__all__"] or truncateds[env_id]["__all__"]:
505
+ all_agents_done = True
506
+ else:
507
+ all_agents_done = False
508
+ active_envs.add(env_id)
509
+
510
+ # Special handling of common info dict.
511
+ episode.set_last_info("__common__", infos[env_id].get("__common__", {}))
512
+
513
+ # Agent sample batches grouped by policy. Each set of sample batches will
514
+ # go through agent connectors together.
515
+ sample_batches_by_policy = defaultdict(list)
516
+ # Whether an agent is terminated or truncated.
517
+ agent_terminateds = {}
518
+ agent_truncateds = {}
519
+ for agent_id, obs in env_obs.items():
520
+ assert agent_id != "__all__"
521
+
522
+ policy_id: PolicyID = episode.policy_for(agent_id)
523
+
524
+ agent_terminated = bool(
525
+ terminateds[env_id]["__all__"] or terminateds[env_id].get(agent_id)
526
+ )
527
+ agent_terminateds[agent_id] = agent_terminated
528
+ agent_truncated = bool(
529
+ truncateds[env_id]["__all__"]
530
+ or truncateds[env_id].get(agent_id, False)
531
+ )
532
+ agent_truncateds[agent_id] = agent_truncated
533
+
534
+ # A completely new agent is already done -> Skip entirely.
535
+ if not episode.has_init_obs(agent_id) and (
536
+ agent_terminated or agent_truncated
537
+ ):
538
+ continue
539
+
540
+ values_dict = {
541
+ SampleBatch.T: episode.length, # Episodes start at -1 before we
542
+ # add the initial obs. After that, we infer from initial obs at
543
+ # t=0 since that will be our new episode.length.
544
+ SampleBatch.ENV_ID: env_id,
545
+ SampleBatch.AGENT_INDEX: episode.agent_index(agent_id),
546
+ # Last action (SampleBatch.ACTIONS) column will be populated by
547
+ # StateBufferConnector.
548
+ # Reward received after taking action at timestep t.
549
+ SampleBatch.REWARDS: rewards[env_id].get(agent_id, 0.0),
550
+ # After taking action=a, did we reach terminal?
551
+ SampleBatch.TERMINATEDS: agent_terminated,
552
+ # Was the episode truncated artificially
553
+ # (e.g. b/c of some time limit)?
554
+ SampleBatch.TRUNCATEDS: agent_truncated,
555
+ SampleBatch.INFOS: infos[env_id].get(agent_id, {}),
556
+ SampleBatch.NEXT_OBS: obs,
557
+ }
558
+
559
+ # Queue this obs sample for connector preprocessing.
560
+ sample_batches_by_policy[policy_id].append((agent_id, values_dict))
561
+
562
+ # The entire episode is done.
563
+ if all_agents_done:
564
+ # Let's check to see if there are any agents that haven't got the
565
+ # last obs yet. If there are, we have to create fake-last
566
+ # observations for them. (the environment is not required to do so if
567
+ # terminateds[__all__]==True or truncateds[__all__]==True).
568
+ for agent_id in episode.get_agents():
569
+ # If the latest obs we got for this agent is done, or if its
570
+ # episode state is already done, nothing to do.
571
+ if (
572
+ agent_terminateds.get(agent_id, False)
573
+ or agent_truncateds.get(agent_id, False)
574
+ or episode.is_done(agent_id)
575
+ ):
576
+ continue
577
+
578
+ policy_id: PolicyID = episode.policy_for(agent_id)
579
+ policy = self._worker.policy_map[policy_id]
580
+
581
+ # Create a fake observation by sampling the original env
582
+ # observation space.
583
+ obs_space = get_original_space(policy.observation_space)
584
+ # Although there is no obs for this agent, there may be
585
+ # good rewards and info dicts for it.
586
+ # This is the case for e.g. OpenSpiel games, where a reward
587
+ # is only earned with the last step, but the obs for that
588
+ # step is {}.
589
+ reward = rewards[env_id].get(agent_id, 0.0)
590
+ info = infos[env_id].get(agent_id, {})
591
+ values_dict = {
592
+ SampleBatch.T: episode.length,
593
+ SampleBatch.ENV_ID: env_id,
594
+ SampleBatch.AGENT_INDEX: episode.agent_index(agent_id),
595
+ # TODO(sven): These should be the summed-up(!) rewards since the
596
+ # last observation received for this agent.
597
+ SampleBatch.REWARDS: reward,
598
+ SampleBatch.TERMINATEDS: True,
599
+ SampleBatch.TRUNCATEDS: truncateds[env_id].get(agent_id, False),
600
+ SampleBatch.INFOS: info,
601
+ SampleBatch.NEXT_OBS: obs_space.sample(),
602
+ }
603
+
604
+ # Queue these fake obs for connector preprocessing too.
605
+ sample_batches_by_policy[policy_id].append((agent_id, values_dict))
606
+
607
+ # Run agent connectors.
608
+ for policy_id, batches in sample_batches_by_policy.items():
609
+ policy: Policy = self._worker.policy_map[policy_id]
610
+ # Collected full MultiAgentDicts for this environment.
611
+ # Run agent connectors.
612
+ assert (
613
+ policy.agent_connectors
614
+ ), "EnvRunnerV2 requires agent connectors to work."
615
+
616
+ acd_list: List[AgentConnectorDataType] = [
617
+ AgentConnectorDataType(env_id, agent_id, data)
618
+ for agent_id, data in batches
619
+ ]
620
+
621
+ # For all agents mapped to policy_id, run their data
622
+ # through agent_connectors.
623
+ processed = policy.agent_connectors(acd_list)
624
+
625
+ for d in processed:
626
+ # Record transition info if applicable.
627
+ if not episode.has_init_obs(d.agent_id):
628
+ episode.add_init_obs(
629
+ agent_id=d.agent_id,
630
+ init_obs=d.data.raw_dict[SampleBatch.NEXT_OBS],
631
+ init_infos=d.data.raw_dict[SampleBatch.INFOS],
632
+ t=d.data.raw_dict[SampleBatch.T],
633
+ )
634
+ else:
635
+ episode.add_action_reward_done_next_obs(
636
+ d.agent_id, d.data.raw_dict
637
+ )
638
+
639
+ # Need to evaluate next actions.
640
+ if not (
641
+ all_agents_done
642
+ or agent_terminateds.get(d.agent_id, False)
643
+ or agent_truncateds.get(d.agent_id, False)
644
+ or episode.is_done(d.agent_id)
645
+ ):
646
+ # Add to eval set if env is not done and this particular agent
647
+ # is also not done.
648
+ item = AgentConnectorDataType(d.env_id, d.agent_id, d.data)
649
+ to_eval[policy_id].append(item)
650
+
651
+ # Finished advancing episode by 1 step, mark it so.
652
+ episode.step()
653
+
654
+ # Exception: The very first env.poll() call causes the env to get reset
655
+ # (no step taken yet, just a single starting observation logged).
656
+ # We need to skip this callback in this case.
657
+ if episode.length > 0:
658
+ # Invoke the `on_episode_step` callback after the step is logged
659
+ # to the episode.
660
+ self._callbacks.on_episode_step(
661
+ worker=self._worker,
662
+ base_env=self._base_env,
663
+ policies=self._worker.policy_map,
664
+ episode=episode,
665
+ env_index=env_id,
666
+ )
667
+
668
+ # Episode is terminated/truncated for all agents
669
+ # (terminateds[__all__] == True or truncateds[__all__] == True).
670
+ if all_agents_done:
671
+ # _handle_done_episode will build a MultiAgentBatch for all
672
+ # the agents that are done during this step of rollout in
673
+ # the case of _multiple_episodes_in_batch=False.
674
+ self._handle_done_episode(
675
+ env_id,
676
+ env_obs,
677
+ terminateds[env_id]["__all__"] or truncateds[env_id]["__all__"],
678
+ active_envs,
679
+ to_eval,
680
+ outputs,
681
+ )
682
+
683
+ # Try to build something.
684
+ if self._multiple_episodes_in_batch:
685
+ sample_batch = self._try_build_truncated_episode_multi_agent_batch(
686
+ self._batch_builders[env_id], episode
687
+ )
688
+ if sample_batch:
689
+ outputs.append(sample_batch)
690
+
691
+ # SampleBatch built from data collected by batch_builder.
692
+ # Clean up and delete the batch_builder.
693
+ del self._batch_builders[env_id]
694
+
695
+ return active_envs, to_eval, outputs
696
+
697
+ def _build_done_episode(
698
+ self,
699
+ env_id: EnvID,
700
+ is_done: bool,
701
+ outputs: List[SampleBatchType],
702
+ ):
703
+ """Builds a MultiAgentSampleBatch from the episode and adds it to outputs.
704
+
705
+ Args:
706
+ env_id: The env id.
707
+ is_done: Whether the env is done.
708
+ outputs: The list of outputs to add the
709
+ """
710
+ episode: EpisodeV2 = self._active_episodes[env_id]
711
+ batch_builder = self._batch_builders[env_id]
712
+
713
+ episode.postprocess_episode(
714
+ batch_builder=batch_builder,
715
+ is_done=is_done,
716
+ check_dones=is_done,
717
+ )
718
+
719
+ # If, we are not allowed to pack the next episode into the same
720
+ # SampleBatch (batch_mode=complete_episodes) -> Build the
721
+ # MultiAgentBatch from a single episode and add it to "outputs".
722
+ # Otherwise, just postprocess and continue collecting across
723
+ # episodes.
724
+ if not self._multiple_episodes_in_batch:
725
+ ma_sample_batch = _build_multi_agent_batch(
726
+ episode.episode_id,
727
+ batch_builder,
728
+ self._large_batch_threshold,
729
+ self._multiple_episodes_in_batch,
730
+ )
731
+ if ma_sample_batch:
732
+ outputs.append(ma_sample_batch)
733
+
734
+ # SampleBatch built from data collected by batch_builder.
735
+ # Clean up and delete the batch_builder.
736
+ del self._batch_builders[env_id]
737
+
738
+ def __process_resetted_obs_for_eval(
739
+ self,
740
+ env_id: EnvID,
741
+ obs: Dict[EnvID, Dict[AgentID, EnvObsType]],
742
+ infos: Dict[EnvID, Dict[AgentID, EnvInfoDict]],
743
+ episode: EpisodeV2,
744
+ to_eval: Dict[PolicyID, List[AgentConnectorDataType]],
745
+ ):
746
+ """Process resetted obs through agent connectors for policy eval.
747
+
748
+ Args:
749
+ env_id: The env id.
750
+ obs: The Resetted obs.
751
+ episode: New episode.
752
+ to_eval: List of agent connector data for policy eval.
753
+ """
754
+ per_policy_resetted_obs: Dict[PolicyID, List] = defaultdict(list)
755
+ # types: AgentID, EnvObsType
756
+ for agent_id, raw_obs in obs[env_id].items():
757
+ policy_id: PolicyID = episode.policy_for(agent_id)
758
+ per_policy_resetted_obs[policy_id].append((agent_id, raw_obs))
759
+
760
+ for policy_id, agents_obs in per_policy_resetted_obs.items():
761
+ policy = self._worker.policy_map[policy_id]
762
+ acd_list: List[AgentConnectorDataType] = [
763
+ AgentConnectorDataType(
764
+ env_id,
765
+ agent_id,
766
+ {
767
+ SampleBatch.NEXT_OBS: obs,
768
+ SampleBatch.INFOS: infos,
769
+ SampleBatch.T: episode.length,
770
+ SampleBatch.AGENT_INDEX: episode.agent_index(agent_id),
771
+ },
772
+ )
773
+ for agent_id, obs in agents_obs
774
+ ]
775
+ # Call agent connectors on these initial obs.
776
+ processed = policy.agent_connectors(acd_list)
777
+
778
+ for d in processed:
779
+ episode.add_init_obs(
780
+ agent_id=d.agent_id,
781
+ init_obs=d.data.raw_dict[SampleBatch.NEXT_OBS],
782
+ init_infos=d.data.raw_dict[SampleBatch.INFOS],
783
+ t=d.data.raw_dict[SampleBatch.T],
784
+ )
785
+ to_eval[policy_id].append(d)
786
+
787
+ def _handle_done_episode(
788
+ self,
789
+ env_id: EnvID,
790
+ env_obs_or_exception: MultiAgentDict,
791
+ is_done: bool,
792
+ active_envs: Set[EnvID],
793
+ to_eval: Dict[PolicyID, List[AgentConnectorDataType]],
794
+ outputs: List[SampleBatchType],
795
+ ) -> None:
796
+ """Handle an all-finished episode.
797
+
798
+ Add collected SampleBatch to batch builder. Reset corresponding env, etc.
799
+
800
+ Args:
801
+ env_id: Environment ID.
802
+ env_obs_or_exception: Last per-environment observation or Exception.
803
+ env_infos: Last per-environment infos.
804
+ is_done: If all agents are done.
805
+ active_envs: Set of active env ids.
806
+ to_eval: Output container for policy eval data.
807
+ outputs: Output container for collected sample batches.
808
+ """
809
+ if isinstance(env_obs_or_exception, Exception):
810
+ episode_or_exception: Exception = env_obs_or_exception
811
+ # Tell the sampler we have got a faulty episode.
812
+ outputs.append(RolloutMetrics(episode_faulty=True))
813
+ else:
814
+ episode_or_exception: EpisodeV2 = self._active_episodes[env_id]
815
+ # Add rollout metrics.
816
+ outputs.extend(
817
+ self._get_rollout_metrics(
818
+ episode_or_exception, policy_map=self._worker.policy_map
819
+ )
820
+ )
821
+ # Output the collected episode after adding rollout metrics so that we
822
+ # always fetch metrics with RolloutWorker before we fetch samples.
823
+ # This is because we need to behave like env_runner() for now.
824
+ self._build_done_episode(env_id, is_done, outputs)
825
+
826
+ # Clean up and deleted the post-processed episode now that we have collected
827
+ # its data.
828
+ self.end_episode(env_id, episode_or_exception)
829
+ # Create a new episode instance (before we reset the sub-environment).
830
+ new_episode: EpisodeV2 = self.create_episode(env_id)
831
+
832
+ # The sub environment at index `env_id` might throw an exception
833
+ # during the following `try_reset()` attempt. If configured with
834
+ # `restart_failed_sub_environments=True`, the BaseEnv will restart
835
+ # the affected sub environment (create a new one using its c'tor) and
836
+ # must reset the recreated sub env right after that.
837
+ # Should the sub environment fail indefinitely during these
838
+ # repeated reset attempts, the entire worker will be blocked.
839
+ # This would be ok, b/c the alternative would be the worker crashing
840
+ # entirely.
841
+ while True:
842
+ resetted_obs, resetted_infos = self._base_env.try_reset(env_id)
843
+
844
+ if (
845
+ resetted_obs is None
846
+ or resetted_obs == ASYNC_RESET_RETURN
847
+ or not isinstance(resetted_obs[env_id], Exception)
848
+ ):
849
+ break
850
+ else:
851
+ # Report a faulty episode.
852
+ outputs.append(RolloutMetrics(episode_faulty=True))
853
+
854
+ # Reset connector state if this is a hard reset.
855
+ for p in self._worker.policy_map.cache.values():
856
+ p.agent_connectors.reset(env_id)
857
+
858
+ # Creates a new episode if this is not async return.
859
+ # If reset is async, we will get its result in some future poll.
860
+ if resetted_obs is not None and resetted_obs != ASYNC_RESET_RETURN:
861
+ self._active_episodes[env_id] = new_episode
862
+ self._call_on_episode_start(new_episode, env_id)
863
+
864
+ self.__process_resetted_obs_for_eval(
865
+ env_id,
866
+ resetted_obs,
867
+ resetted_infos,
868
+ new_episode,
869
+ to_eval,
870
+ )
871
+
872
+ # Step after adding initial obs. This will give us 0 env and agent step.
873
+ new_episode.step()
874
+ active_envs.add(env_id)
875
+
876
+ def create_episode(self, env_id: EnvID) -> EpisodeV2:
877
+ """Creates a new EpisodeV2 instance and returns it.
878
+
879
+ Calls `on_episode_created` callbacks, but does NOT reset the respective
880
+ sub-environment yet.
881
+
882
+ Args:
883
+ env_id: Env ID.
884
+
885
+ Returns:
886
+ The newly created EpisodeV2 instance.
887
+ """
888
+ # Make sure we currently don't have an active episode under this env ID.
889
+ assert env_id not in self._active_episodes
890
+
891
+ # Create a new episode under the same `env_id` and call the
892
+ # `on_episode_created` callbacks.
893
+ new_episode = EpisodeV2(
894
+ env_id,
895
+ self._worker.policy_map,
896
+ self._worker.policy_mapping_fn,
897
+ worker=self._worker,
898
+ callbacks=self._callbacks,
899
+ )
900
+
901
+ # Call `on_episode_created()` callback.
902
+ self._callbacks.on_episode_created(
903
+ worker=self._worker,
904
+ base_env=self._base_env,
905
+ policies=self._worker.policy_map,
906
+ env_index=env_id,
907
+ episode=new_episode,
908
+ )
909
+ return new_episode
910
+
911
+ def end_episode(
912
+ self, env_id: EnvID, episode_or_exception: Union[EpisodeV2, Exception]
913
+ ):
914
+ """Cleans up an episode that has finished.
915
+
916
+ Args:
917
+ env_id: Env ID.
918
+ episode_or_exception: Instance of an episode if it finished successfully.
919
+ Otherwise, the exception that was thrown,
920
+ """
921
+ # Signal the end of an episode, either successfully with an Episode or
922
+ # unsuccessfully with an Exception.
923
+ self._callbacks.on_episode_end(
924
+ worker=self._worker,
925
+ base_env=self._base_env,
926
+ policies=self._worker.policy_map,
927
+ episode=episode_or_exception,
928
+ env_index=env_id,
929
+ )
930
+
931
+ # Call each (in-memory) policy's Exploration.on_episode_end
932
+ # method.
933
+ # Note: This may break the exploration (e.g. ParameterNoise) of
934
+ # policies in the `policy_map` that have not been recently used
935
+ # (and are therefore stashed to disk). However, we certainly do not
936
+ # want to loop through all (even stashed) policies here as that
937
+ # would counter the purpose of the LRU policy caching.
938
+ for p in self._worker.policy_map.cache.values():
939
+ if getattr(p, "exploration", None) is not None:
940
+ p.exploration.on_episode_end(
941
+ policy=p,
942
+ environment=self._base_env,
943
+ episode=episode_or_exception,
944
+ tf_sess=p.get_session(),
945
+ )
946
+
947
+ if isinstance(episode_or_exception, EpisodeV2):
948
+ episode = episode_or_exception
949
+ if episode.total_agent_steps == 0:
950
+ # if the key does not exist it means that throughout the episode all
951
+ # observations were empty (i.e. there was no agent in the env)
952
+ msg = (
953
+ f"Data from episode {episode.episode_id} does not show any agent "
954
+ f"interactions. Hint: Make sure for at least one timestep in the "
955
+ f"episode, env.step() returns non-empty values."
956
+ )
957
+ raise ValueError(msg)
958
+
959
+ # Clean up the episode and batch_builder for this env id.
960
+ if env_id in self._active_episodes:
961
+ del self._active_episodes[env_id]
962
+
963
+ def _try_build_truncated_episode_multi_agent_batch(
964
+ self, batch_builder: _PolicyCollectorGroup, episode: EpisodeV2
965
+ ) -> Union[None, SampleBatch, MultiAgentBatch]:
966
+ # Measure batch size in env-steps.
967
+ if self._count_steps_by == "env_steps":
968
+ built_steps = batch_builder.env_steps
969
+ ongoing_steps = episode.active_env_steps
970
+ # Measure batch-size in agent-steps.
971
+ else:
972
+ built_steps = batch_builder.agent_steps
973
+ ongoing_steps = episode.active_agent_steps
974
+
975
+ # Reached the fragment-len -> We should build an MA-Batch.
976
+ if built_steps + ongoing_steps >= self._rollout_fragment_length:
977
+ if self._count_steps_by != "agent_steps":
978
+ assert built_steps + ongoing_steps == self._rollout_fragment_length, (
979
+ f"built_steps ({built_steps}) + ongoing_steps ({ongoing_steps}) != "
980
+ f"rollout_fragment_length ({self._rollout_fragment_length})."
981
+ )
982
+
983
+ # If we reached the fragment-len only because of `episode_id`
984
+ # (still ongoing) -> postprocess `episode_id` first.
985
+ if built_steps < self._rollout_fragment_length:
986
+ episode.postprocess_episode(batch_builder=batch_builder, is_done=False)
987
+
988
+ # If builder has collected some data,
989
+ # build the MA-batch and add to return values.
990
+ if batch_builder.agent_steps > 0:
991
+ return _build_multi_agent_batch(
992
+ episode.episode_id,
993
+ batch_builder,
994
+ self._large_batch_threshold,
995
+ self._multiple_episodes_in_batch,
996
+ )
997
+ # No batch-builder:
998
+ # We have reached the rollout-fragment length w/o any agent
999
+ # steps! Warn that the environment may never request any
1000
+ # actions from any agents.
1001
+ elif log_once("no_agent_steps"):
1002
+ logger.warning(
1003
+ "Your environment seems to be stepping w/o ever "
1004
+ "emitting agent observations (agents are never "
1005
+ "requested to act)!"
1006
+ )
1007
+
1008
+ return None
1009
+
1010
+ def _do_policy_eval(
1011
+ self,
1012
+ to_eval: Dict[PolicyID, List[AgentConnectorDataType]],
1013
+ ) -> Dict[PolicyID, PolicyOutputType]:
1014
+ """Call compute_actions on collected episode data to get next action.
1015
+
1016
+ Args:
1017
+ to_eval: Mapping of policy IDs to lists of AgentConnectorDataType objects
1018
+ (items in these lists will be the batch's items for the model
1019
+ forward pass).
1020
+
1021
+ Returns:
1022
+ Dict mapping PolicyIDs to compute_actions_from_input_dict() outputs.
1023
+ """
1024
+ policies = self._worker.policy_map
1025
+
1026
+ # In case policy map has changed, try to find the new policy that
1027
+ # should handle all these per-agent eval data.
1028
+ # Throws exception if these agents are mapped to multiple different
1029
+ # policies now.
1030
+ def _try_find_policy_again(eval_data: AgentConnectorDataType):
1031
+ policy_id = None
1032
+ for d in eval_data:
1033
+ episode = self._active_episodes[d.env_id]
1034
+ # Force refresh policy mapping on the episode.
1035
+ pid = episode.policy_for(d.agent_id, refresh=True)
1036
+ if policy_id is not None and pid != policy_id:
1037
+ raise ValueError(
1038
+ "Policy map changed. The list of eval data that was handled "
1039
+ f"by a same policy is now handled by policy {pid} "
1040
+ "and {policy_id}. "
1041
+ "Please don't do this in the middle of an episode."
1042
+ )
1043
+ policy_id = pid
1044
+ return _get_or_raise(self._worker.policy_map, policy_id)
1045
+
1046
+ eval_results: Dict[PolicyID, TensorStructType] = {}
1047
+ for policy_id, eval_data in to_eval.items():
1048
+ # In case the policyID has been removed from this worker, we need to
1049
+ # re-assign policy_id and re-lookup the Policy object to use.
1050
+ try:
1051
+ policy: Policy = _get_or_raise(policies, policy_id)
1052
+ except ValueError:
1053
+ # policy_mapping_fn from the worker may have already been
1054
+ # changed (mapping fn not staying constant within one episode).
1055
+ policy: Policy = _try_find_policy_again(eval_data)
1056
+
1057
+ input_dict = _batch_inference_sample_batches(
1058
+ [d.data.sample_batch for d in eval_data]
1059
+ )
1060
+
1061
+ eval_results[policy_id] = policy.compute_actions_from_input_dict(
1062
+ input_dict,
1063
+ timestep=policy.global_timestep,
1064
+ episodes=[self._active_episodes[t.env_id] for t in eval_data],
1065
+ )
1066
+
1067
+ return eval_results
1068
+
1069
+ def _process_policy_eval_results(
1070
+ self,
1071
+ active_envs: Set[EnvID],
1072
+ to_eval: Dict[PolicyID, List[AgentConnectorDataType]],
1073
+ eval_results: Dict[PolicyID, PolicyOutputType],
1074
+ off_policy_actions: MultiEnvDict,
1075
+ ):
1076
+ """Process the output of policy neural network evaluation.
1077
+
1078
+ Records policy evaluation results into agent connectors and
1079
+ returns replies to send back to agents in the env.
1080
+
1081
+ Args:
1082
+ active_envs: Set of env IDs that are still active.
1083
+ to_eval: Mapping of policy IDs to lists of AgentConnectorDataType objects.
1084
+ eval_results: Mapping of policy IDs to list of
1085
+ actions, rnn-out states, extra-action-fetches dicts.
1086
+ off_policy_actions: Doubly keyed dict of env-ids -> agent ids ->
1087
+ off-policy-action, returned by a `BaseEnv.poll()` call.
1088
+
1089
+ Returns:
1090
+ Nested dict of env id -> agent id -> actions to be sent to
1091
+ Env (np.ndarrays).
1092
+ """
1093
+ actions_to_send: Dict[EnvID, Dict[AgentID, EnvActionType]] = defaultdict(dict)
1094
+
1095
+ for env_id in active_envs:
1096
+ actions_to_send[env_id] = {} # at minimum send empty dict
1097
+
1098
+ # types: PolicyID, List[AgentConnectorDataType]
1099
+ for policy_id, eval_data in to_eval.items():
1100
+ actions: TensorStructType = eval_results[policy_id][0]
1101
+ actions = convert_to_numpy(actions)
1102
+
1103
+ rnn_out: StateBatches = eval_results[policy_id][1]
1104
+ extra_action_out: dict = eval_results[policy_id][2]
1105
+
1106
+ # In case actions is a list (representing the 0th dim of a batch of
1107
+ # primitive actions), try converting it first.
1108
+ if isinstance(actions, list):
1109
+ actions = np.array(actions)
1110
+ # Split action-component batches into single action rows.
1111
+ actions: List[EnvActionType] = unbatch(actions)
1112
+
1113
+ policy: Policy = _get_or_raise(self._worker.policy_map, policy_id)
1114
+ assert (
1115
+ policy.agent_connectors and policy.action_connectors
1116
+ ), "EnvRunnerV2 requires action connectors to work."
1117
+
1118
+ # types: int, EnvActionType
1119
+ for i, action in enumerate(actions):
1120
+ env_id: int = eval_data[i].env_id
1121
+ agent_id: AgentID = eval_data[i].agent_id
1122
+ input_dict: TensorStructType = eval_data[i].data.raw_dict
1123
+
1124
+ rnn_states: List[StateBatches] = tree.map_structure(
1125
+ lambda x, i=i: x[i], rnn_out
1126
+ )
1127
+
1128
+ # extra_action_out could be a nested dict
1129
+ fetches: Dict = tree.map_structure(
1130
+ lambda x, i=i: x[i], extra_action_out
1131
+ )
1132
+
1133
+ # Post-process policy output by running them through action connectors.
1134
+ ac_data = ActionConnectorDataType(
1135
+ env_id, agent_id, input_dict, (action, rnn_states, fetches)
1136
+ )
1137
+
1138
+ action_to_send, rnn_states, fetches = policy.action_connectors(
1139
+ ac_data
1140
+ ).output
1141
+
1142
+ # The action we want to buffer is the direct output of
1143
+ # compute_actions_from_input_dict() here. This is because we want to
1144
+ # send the unsqushed actions to the environment while learning and
1145
+ # possibly basing subsequent actions on the squashed actions.
1146
+ action_to_buffer = (
1147
+ action
1148
+ if env_id not in off_policy_actions
1149
+ or agent_id not in off_policy_actions[env_id]
1150
+ else off_policy_actions[env_id][agent_id]
1151
+ )
1152
+
1153
+ # Notify agent connectors with this new policy output.
1154
+ # Necessary for state buffering agent connectors, for example.
1155
+ ac_data: ActionConnectorDataType = ActionConnectorDataType(
1156
+ env_id,
1157
+ agent_id,
1158
+ input_dict,
1159
+ (action_to_buffer, rnn_states, fetches),
1160
+ )
1161
+ policy.agent_connectors.on_policy_output(ac_data)
1162
+
1163
+ assert agent_id not in actions_to_send[env_id]
1164
+ actions_to_send[env_id][agent_id] = action_to_send
1165
+
1166
+ return actions_to_send
1167
+
1168
+ def _maybe_render(self):
1169
+ """Visualize environment."""
1170
+ # Check if we should render.
1171
+ if not self._render or not self._simple_image_viewer:
1172
+ return
1173
+
1174
+ t5 = time.time()
1175
+
1176
+ # Render can either return an RGB image (uint8 [w x h x 3] numpy
1177
+ # array) or take care of rendering itself (returning True).
1178
+ rendered = self._base_env.try_render()
1179
+ # Rendering returned an image -> Display it in a SimpleImageViewer.
1180
+ if isinstance(rendered, np.ndarray) and len(rendered.shape) == 3:
1181
+ self._simple_image_viewer.imshow(rendered)
1182
+ elif rendered not in [True, False, None]:
1183
+ raise ValueError(
1184
+ f"The env's ({self._base_env}) `try_render()` method returned an"
1185
+ " unsupported value! Make sure you either return a "
1186
+ "uint8/w x h x 3 (RGB) image or handle rendering in a "
1187
+ "window and then return `True`."
1188
+ )
1189
+
1190
+ self._perf_stats.incr("env_render_time", time.time() - t5)
1191
+
1192
+
1193
+ def _fetch_atari_metrics(base_env: BaseEnv) -> List[RolloutMetrics]:
1194
+ """Atari games have multiple logical episodes, one per life.
1195
+
1196
+ However, for metrics reporting we count full episodes, all lives included.
1197
+ """
1198
+ sub_environments = base_env.get_sub_environments()
1199
+ if not sub_environments:
1200
+ return None
1201
+ atari_out = []
1202
+ for sub_env in sub_environments:
1203
+ monitor = get_wrapper_by_cls(sub_env, MonitorEnv)
1204
+ if not monitor:
1205
+ return None
1206
+ for eps_rew, eps_len in monitor.next_episode_results():
1207
+ atari_out.append(RolloutMetrics(eps_len, eps_rew))
1208
+ return atari_out
1209
+
1210
+
1211
+ def _get_or_raise(
1212
+ mapping: Dict[PolicyID, Union[Policy, Preprocessor, Filter]], policy_id: PolicyID
1213
+ ) -> Union[Policy, Preprocessor, Filter]:
1214
+ """Returns an object under key `policy_id` in `mapping`.
1215
+
1216
+ Args:
1217
+ mapping (Dict[PolicyID, Union[Policy, Preprocessor, Filter]]): The
1218
+ mapping dict from policy id (str) to actual object (Policy,
1219
+ Preprocessor, etc.).
1220
+ policy_id: The policy ID to lookup.
1221
+
1222
+ Returns:
1223
+ Union[Policy, Preprocessor, Filter]: The found object.
1224
+
1225
+ Raises:
1226
+ ValueError: If `policy_id` cannot be found in `mapping`.
1227
+ """
1228
+ if policy_id not in mapping:
1229
+ raise ValueError(
1230
+ "Could not find policy for agent: PolicyID `{}` not found "
1231
+ "in policy map, whose keys are `{}`.".format(policy_id, mapping.keys())
1232
+ )
1233
+ return mapping[policy_id]
lib/python3.12/site-packages/ray/rllib/evaluation/episode_v2.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from collections import defaultdict
3
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
4
+
5
+ import numpy as np
6
+
7
+ from ray.rllib.env.base_env import _DUMMY_AGENT_ID
8
+ from ray.rllib.evaluation.collectors.agent_collector import AgentCollector
9
+ from ray.rllib.evaluation.collectors.simple_list_collector import (
10
+ _PolicyCollector,
11
+ _PolicyCollectorGroup,
12
+ )
13
+ from ray.rllib.policy.policy_map import PolicyMap
14
+ from ray.rllib.policy.sample_batch import SampleBatch
15
+ from ray.rllib.utils.annotations import OldAPIStack
16
+ from ray.rllib.utils.typing import AgentID, EnvID, EnvInfoDict, PolicyID, TensorType
17
+
18
+ if TYPE_CHECKING:
19
+ from ray.rllib.callbacks.callbacks import RLlibCallback
20
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
21
+
22
+
23
+ @OldAPIStack
24
+ class EpisodeV2:
25
+ """Tracks the current state of a (possibly multi-agent) episode."""
26
+
27
+ def __init__(
28
+ self,
29
+ env_id: EnvID,
30
+ policies: PolicyMap,
31
+ policy_mapping_fn: Callable[[AgentID, "EpisodeV2", "RolloutWorker"], PolicyID],
32
+ *,
33
+ worker: Optional["RolloutWorker"] = None,
34
+ callbacks: Optional["RLlibCallback"] = None,
35
+ ):
36
+ """Initializes an Episode instance.
37
+
38
+ Args:
39
+ env_id: The environment's ID in which this episode runs.
40
+ policies: The PolicyMap object (mapping PolicyIDs to Policy
41
+ objects) to use for determining, which policy is used for
42
+ which agent.
43
+ policy_mapping_fn: The mapping function mapping AgentIDs to
44
+ PolicyIDs.
45
+ worker: The RolloutWorker instance, in which this episode runs.
46
+ """
47
+ # Unique id identifying this trajectory.
48
+ self.episode_id: int = random.randrange(int(1e18))
49
+ # ID of the environment this episode is tracking.
50
+ self.env_id = env_id
51
+ # Summed reward across all agents in this episode.
52
+ self.total_reward: float = 0.0
53
+ # Active (uncollected) # of env steps taken by this episode.
54
+ # Start from -1. After add_init_obs(), we will be at 0 step.
55
+ self.active_env_steps: int = -1
56
+ # Total # of env steps taken by this episode.
57
+ # Start from -1, After add_init_obs(), we will be at 0 step.
58
+ self.total_env_steps: int = -1
59
+ # Active (uncollected) agent steps.
60
+ self.active_agent_steps: int = 0
61
+ # Total # of steps take by all agents in this env.
62
+ self.total_agent_steps: int = 0
63
+ # Dict for user to add custom metrics.
64
+ # TODO (sven): We should probably unify custom_metrics, user_data,
65
+ # and hist_data into a single data container for user to track per-step.
66
+ # metrics and states.
67
+ self.custom_metrics: Dict[str, float] = {}
68
+ # Temporary storage. E.g. storing data in between two custom
69
+ # callbacks referring to the same episode.
70
+ self.user_data: Dict[str, Any] = {}
71
+ # Dict mapping str keys to List[float] for storage of
72
+ # per-timestep float data throughout the episode.
73
+ self.hist_data: Dict[str, List[float]] = {}
74
+ self.media: Dict[str, Any] = {}
75
+
76
+ self.worker = worker
77
+ self.callbacks = callbacks
78
+
79
+ self.policy_map: PolicyMap = policies
80
+ self.policy_mapping_fn: Callable[
81
+ [AgentID, "EpisodeV2", "RolloutWorker"], PolicyID
82
+ ] = policy_mapping_fn
83
+ # Per-agent data collectors.
84
+ self._agent_to_policy: Dict[AgentID, PolicyID] = {}
85
+ self._agent_collectors: Dict[AgentID, AgentCollector] = {}
86
+
87
+ self._next_agent_index: int = 0
88
+ self._agent_to_index: Dict[AgentID, int] = {}
89
+
90
+ # Summed rewards broken down by agent.
91
+ self.agent_rewards: Dict[Tuple[AgentID, PolicyID], float] = defaultdict(float)
92
+ self._agent_reward_history: Dict[AgentID, List[int]] = defaultdict(list)
93
+
94
+ self._has_init_obs: Dict[AgentID, bool] = {}
95
+ self._last_terminateds: Dict[AgentID, bool] = {}
96
+ self._last_truncateds: Dict[AgentID, bool] = {}
97
+ # Keep last info dict around, in case an environment tries to signal
98
+ # us something.
99
+ self._last_infos: Dict[AgentID, Dict] = {}
100
+
101
+ def policy_for(
102
+ self, agent_id: AgentID = _DUMMY_AGENT_ID, refresh: bool = False
103
+ ) -> PolicyID:
104
+ """Returns and stores the policy ID for the specified agent.
105
+
106
+ If the agent is new, the policy mapping fn will be called to bind the
107
+ agent to a policy for the duration of the entire episode (even if the
108
+ policy_mapping_fn is changed in the meantime!).
109
+
110
+ Args:
111
+ agent_id: The agent ID to lookup the policy ID for.
112
+
113
+ Returns:
114
+ The policy ID for the specified agent.
115
+ """
116
+
117
+ # Perform a new policy_mapping_fn lookup and bind AgentID for the
118
+ # duration of this episode to the returned PolicyID.
119
+ if agent_id not in self._agent_to_policy or refresh:
120
+ policy_id = self._agent_to_policy[agent_id] = self.policy_mapping_fn(
121
+ agent_id, # agent_id
122
+ self, # episode
123
+ worker=self.worker,
124
+ )
125
+ # Use already determined PolicyID.
126
+ else:
127
+ policy_id = self._agent_to_policy[agent_id]
128
+
129
+ # PolicyID not found in policy map -> Error.
130
+ if policy_id not in self.policy_map:
131
+ raise KeyError(
132
+ "policy_mapping_fn returned invalid policy id " f"'{policy_id}'!"
133
+ )
134
+ return policy_id
135
+
136
+ def get_agents(self) -> List[AgentID]:
137
+ """Returns list of agent IDs that have appeared in this episode.
138
+
139
+ Returns:
140
+ The list of all agent IDs that have appeared so far in this
141
+ episode.
142
+ """
143
+ return list(self._agent_to_index.keys())
144
+
145
+ def agent_index(self, agent_id: AgentID) -> int:
146
+ """Get the index of an agent among its environment.
147
+
148
+ A new index will be created if an agent is seen for the first time.
149
+
150
+ Args:
151
+ agent_id: ID of an agent.
152
+
153
+ Returns:
154
+ The index of this agent.
155
+ """
156
+ if agent_id not in self._agent_to_index:
157
+ self._agent_to_index[agent_id] = self._next_agent_index
158
+ self._next_agent_index += 1
159
+ return self._agent_to_index[agent_id]
160
+
161
+ def step(self) -> None:
162
+ """Advance the episode forward by one step."""
163
+ self.active_env_steps += 1
164
+ self.total_env_steps += 1
165
+
166
+ def add_init_obs(
167
+ self,
168
+ *,
169
+ agent_id: AgentID,
170
+ init_obs: TensorType,
171
+ init_infos: Dict[str, TensorType],
172
+ t: int = -1,
173
+ ) -> None:
174
+ """Add initial env obs at the start of a new episode
175
+
176
+ Args:
177
+ agent_id: Agent ID.
178
+ init_obs: Initial observations.
179
+ init_infos: Initial infos dicts.
180
+ t: timestamp.
181
+ """
182
+ policy = self.policy_map[self.policy_for(agent_id)]
183
+
184
+ # Add initial obs to Trajectory.
185
+ assert agent_id not in self._agent_collectors
186
+
187
+ self._agent_collectors[agent_id] = AgentCollector(
188
+ policy.view_requirements,
189
+ max_seq_len=policy.config["model"]["max_seq_len"],
190
+ disable_action_flattening=policy.config.get(
191
+ "_disable_action_flattening", False
192
+ ),
193
+ is_policy_recurrent=policy.is_recurrent(),
194
+ intial_states=policy.get_initial_state(),
195
+ _enable_new_api_stack=False,
196
+ )
197
+ self._agent_collectors[agent_id].add_init_obs(
198
+ episode_id=self.episode_id,
199
+ agent_index=self.agent_index(agent_id),
200
+ env_id=self.env_id,
201
+ init_obs=init_obs,
202
+ init_infos=init_infos,
203
+ t=t,
204
+ )
205
+
206
+ self._has_init_obs[agent_id] = True
207
+
208
+ def add_action_reward_done_next_obs(
209
+ self,
210
+ agent_id: AgentID,
211
+ values: Dict[str, TensorType],
212
+ ) -> None:
213
+ """Add action, reward, info, and next_obs as a new step.
214
+
215
+ Args:
216
+ agent_id: Agent ID.
217
+ values: Dict of action, reward, info, and next_obs.
218
+ """
219
+ # Make sure, agent already has some (at least init) data.
220
+ assert agent_id in self._agent_collectors
221
+
222
+ self.active_agent_steps += 1
223
+ self.total_agent_steps += 1
224
+
225
+ # Include the current agent id for multi-agent algorithms.
226
+ if agent_id != _DUMMY_AGENT_ID:
227
+ values["agent_id"] = agent_id
228
+
229
+ # Add action/reward/next-obs (and other data) to Trajectory.
230
+ self._agent_collectors[agent_id].add_action_reward_next_obs(values)
231
+
232
+ # Keep track of agent reward history.
233
+ reward = values[SampleBatch.REWARDS]
234
+ self.total_reward += reward
235
+ self.agent_rewards[(agent_id, self.policy_for(agent_id))] += reward
236
+ self._agent_reward_history[agent_id].append(reward)
237
+
238
+ # Keep track of last terminated info for agent.
239
+ if SampleBatch.TERMINATEDS in values:
240
+ self._last_terminateds[agent_id] = values[SampleBatch.TERMINATEDS]
241
+ # Keep track of last truncated info for agent.
242
+ if SampleBatch.TRUNCATEDS in values:
243
+ self._last_truncateds[agent_id] = values[SampleBatch.TRUNCATEDS]
244
+
245
+ # Keep track of last info dict if available.
246
+ if SampleBatch.INFOS in values:
247
+ self.set_last_info(agent_id, values[SampleBatch.INFOS])
248
+
249
+ def postprocess_episode(
250
+ self,
251
+ batch_builder: _PolicyCollectorGroup,
252
+ is_done: bool = False,
253
+ check_dones: bool = False,
254
+ ) -> None:
255
+ """Build and return currently collected training samples by policies.
256
+
257
+ Clear agent collector states if this episode is done.
258
+
259
+ Args:
260
+ batch_builder: _PolicyCollectorGroup for saving the collected per-agent
261
+ sample batches.
262
+ is_done: If this episode is done (terminated or truncated).
263
+ check_dones: Whether to make sure per-agent trajectories are actually done.
264
+ """
265
+ # TODO: (sven) Once we implement multi-agent communication channels,
266
+ # we have to resolve the restriction of only sending other agent
267
+ # batches from the same policy to the postprocess methods.
268
+ # Build SampleBatches for the given episode.
269
+ pre_batches = {}
270
+ for agent_id, collector in self._agent_collectors.items():
271
+ # Build only if there is data and agent is part of given episode.
272
+ if collector.agent_steps == 0:
273
+ continue
274
+ pid = self.policy_for(agent_id)
275
+ policy = self.policy_map[pid]
276
+ pre_batch = collector.build_for_training(policy.view_requirements)
277
+ pre_batches[agent_id] = (pid, policy, pre_batch)
278
+
279
+ for agent_id, (pid, policy, pre_batch) in pre_batches.items():
280
+ # Entire episode is said to be done.
281
+ # Error if no DONE at end of this agent's trajectory.
282
+ if is_done and check_dones and not pre_batch.is_terminated_or_truncated():
283
+ raise ValueError(
284
+ "Episode {} terminated for all agents, but we still "
285
+ "don't have a last observation for agent {} (policy "
286
+ "{}). ".format(self.episode_id, agent_id, self.policy_for(agent_id))
287
+ + "Please ensure that you include the last observations "
288
+ "of all live agents when setting done[__all__] to "
289
+ "True."
290
+ )
291
+
292
+ # Skip a trajectory's postprocessing (and thus using it for training),
293
+ # if its agent's info exists and contains the training_enabled=False
294
+ # setting (used by our PolicyClients).
295
+ if not self._last_infos.get(agent_id, {}).get("training_enabled", True):
296
+ continue
297
+
298
+ if (
299
+ not pre_batch.is_single_trajectory()
300
+ or len(np.unique(pre_batch[SampleBatch.EPS_ID])) > 1
301
+ ):
302
+ raise ValueError(
303
+ "Batches sent to postprocessing must only contain steps "
304
+ "from a single trajectory.",
305
+ pre_batch,
306
+ )
307
+
308
+ if len(pre_batches) > 1:
309
+ other_batches = pre_batches.copy()
310
+ del other_batches[agent_id]
311
+ else:
312
+ other_batches = {}
313
+
314
+ # Call the Policy's Exploration's postprocess method.
315
+ post_batch = pre_batch
316
+ if getattr(policy, "exploration", None) is not None:
317
+ policy.exploration.postprocess_trajectory(
318
+ policy, post_batch, policy.get_session()
319
+ )
320
+ post_batch.set_get_interceptor(None)
321
+ post_batch = policy.postprocess_trajectory(post_batch, other_batches, self)
322
+
323
+ from ray.rllib.evaluation.rollout_worker import get_global_worker
324
+
325
+ self.callbacks.on_postprocess_trajectory(
326
+ worker=get_global_worker(),
327
+ episode=self,
328
+ agent_id=agent_id,
329
+ policy_id=pid,
330
+ policies=self.policy_map,
331
+ postprocessed_batch=post_batch,
332
+ original_batches=pre_batches,
333
+ )
334
+
335
+ # Append post_batch for return.
336
+ if pid not in batch_builder.policy_collectors:
337
+ batch_builder.policy_collectors[pid] = _PolicyCollector(policy)
338
+ batch_builder.policy_collectors[pid].add_postprocessed_batch_for_training(
339
+ post_batch, policy.view_requirements
340
+ )
341
+
342
+ batch_builder.agent_steps += self.active_agent_steps
343
+ batch_builder.env_steps += self.active_env_steps
344
+
345
+ # AgentCollector cleared.
346
+ self.active_agent_steps = 0
347
+ self.active_env_steps = 0
348
+
349
+ def has_init_obs(self, agent_id: AgentID = None) -> bool:
350
+ """Returns whether this episode has initial obs for an agent.
351
+
352
+ If agent_id is None, return whether we have received any initial obs,
353
+ in other words, whether this episode is completely fresh.
354
+ """
355
+ if agent_id is not None:
356
+ return agent_id in self._has_init_obs and self._has_init_obs[agent_id]
357
+ else:
358
+ return any(list(self._has_init_obs.values()))
359
+
360
+ def is_done(self, agent_id: AgentID) -> bool:
361
+ return self.is_terminated(agent_id) or self.is_truncated(agent_id)
362
+
363
+ def is_terminated(self, agent_id: AgentID) -> bool:
364
+ return self._last_terminateds.get(agent_id, False)
365
+
366
+ def is_truncated(self, agent_id: AgentID) -> bool:
367
+ return self._last_truncateds.get(agent_id, False)
368
+
369
+ def set_last_info(self, agent_id: AgentID, info: Dict):
370
+ self._last_infos[agent_id] = info
371
+
372
+ def last_info_for(
373
+ self, agent_id: AgentID = _DUMMY_AGENT_ID
374
+ ) -> Optional[EnvInfoDict]:
375
+ return self._last_infos.get(agent_id)
376
+
377
+ @property
378
+ def length(self):
379
+ return self.total_env_steps
lib/python3.12/site-packages/ray/rllib/evaluation/metrics.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import logging
3
+ from typing import TYPE_CHECKING, List, Optional
4
+
5
+ import numpy as np
6
+
7
+ from ray.rllib.policy.sample_batch import DEFAULT_POLICY_ID
8
+ from ray.rllib.utils.annotations import OldAPIStack
9
+ from ray.rllib.utils.metrics.learner_info import LEARNER_STATS_KEY
10
+ from ray.rllib.utils.typing import GradInfoDict, LearnerStatsDict, ResultDict
11
+
12
+ if TYPE_CHECKING:
13
+ from ray.rllib.env.env_runner_group import EnvRunnerGroup
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ RolloutMetrics = OldAPIStack(
18
+ collections.namedtuple(
19
+ "RolloutMetrics",
20
+ [
21
+ "episode_length",
22
+ "episode_reward",
23
+ "agent_rewards",
24
+ "custom_metrics",
25
+ "perf_stats",
26
+ "hist_data",
27
+ "media",
28
+ "episode_faulty",
29
+ "connector_metrics",
30
+ ],
31
+ )
32
+ )
33
+ RolloutMetrics.__new__.__defaults__ = (0, 0, {}, {}, {}, {}, {}, False, {})
34
+
35
+
36
+ @OldAPIStack
37
+ def get_learner_stats(grad_info: GradInfoDict) -> LearnerStatsDict:
38
+ """Return optimization stats reported from the policy.
39
+
40
+ .. testcode::
41
+ :skipif: True
42
+
43
+ grad_info = worker.learn_on_batch(samples)
44
+
45
+ # {"td_error": [...], "learner_stats": {"vf_loss": ..., ...}}
46
+
47
+ print(get_stats(grad_info))
48
+
49
+ .. testoutput::
50
+
51
+ {"vf_loss": ..., "policy_loss": ...}
52
+ """
53
+ if LEARNER_STATS_KEY in grad_info:
54
+ return grad_info[LEARNER_STATS_KEY]
55
+
56
+ multiagent_stats = {}
57
+ for k, v in grad_info.items():
58
+ if type(v) is dict:
59
+ if LEARNER_STATS_KEY in v:
60
+ multiagent_stats[k] = v[LEARNER_STATS_KEY]
61
+
62
+ return multiagent_stats
63
+
64
+
65
+ @OldAPIStack
66
+ def collect_metrics(
67
+ workers: "EnvRunnerGroup",
68
+ remote_worker_ids: Optional[List[int]] = None,
69
+ timeout_seconds: int = 180,
70
+ keep_custom_metrics: bool = False,
71
+ ) -> ResultDict:
72
+ """Gathers episode metrics from rollout worker set.
73
+
74
+ Args:
75
+ workers: EnvRunnerGroup.
76
+ remote_worker_ids: Optional list of IDs of remote workers to collect
77
+ metrics from.
78
+ timeout_seconds: Timeout in seconds for collecting metrics from remote workers.
79
+ keep_custom_metrics: Whether to keep custom metrics in the result dict as
80
+ they are (True) or to aggregate them (False).
81
+
82
+ Returns:
83
+ A result dict of metrics.
84
+ """
85
+ episodes = collect_episodes(
86
+ workers, remote_worker_ids, timeout_seconds=timeout_seconds
87
+ )
88
+ metrics = summarize_episodes(
89
+ episodes, episodes, keep_custom_metrics=keep_custom_metrics
90
+ )
91
+ return metrics
92
+
93
+
94
+ @OldAPIStack
95
+ def collect_episodes(
96
+ workers: "EnvRunnerGroup",
97
+ remote_worker_ids: Optional[List[int]] = None,
98
+ timeout_seconds: int = 180,
99
+ ) -> List[RolloutMetrics]:
100
+ """Gathers new episodes metrics tuples from the given RolloutWorkers.
101
+
102
+ Args:
103
+ workers: EnvRunnerGroup.
104
+ remote_worker_ids: Optional list of IDs of remote workers to collect
105
+ metrics from.
106
+ timeout_seconds: Timeout in seconds for collecting metrics from remote workers.
107
+
108
+ Returns:
109
+ List of RolloutMetrics.
110
+ """
111
+ # This will drop get_metrics() calls that are too slow.
112
+ # We can potentially make this an asynchronous call if this turns
113
+ # out to be a problem.
114
+ metric_lists = workers.foreach_env_runner(
115
+ lambda w: w.get_metrics(),
116
+ local_env_runner=True,
117
+ remote_worker_ids=remote_worker_ids,
118
+ timeout_seconds=timeout_seconds,
119
+ )
120
+ if len(metric_lists) == 0:
121
+ logger.warning("WARNING: collected no metrics.")
122
+
123
+ episodes = []
124
+ for metrics in metric_lists:
125
+ episodes.extend(metrics)
126
+
127
+ return episodes
128
+
129
+
130
+ @OldAPIStack
131
+ def summarize_episodes(
132
+ episodes: List[RolloutMetrics],
133
+ new_episodes: List[RolloutMetrics] = None,
134
+ keep_custom_metrics: bool = False,
135
+ ) -> ResultDict:
136
+ """Summarizes a set of episode metrics tuples.
137
+
138
+ Args:
139
+ episodes: List of most recent n episodes. This may include historical ones
140
+ (not newly collected in this iteration) in order to achieve the size of
141
+ the smoothing window.
142
+ new_episodes: All the episodes that were completed in this iteration.
143
+ keep_custom_metrics: Whether to keep custom metrics in the result dict as
144
+ they are (True) or to aggregate them (False).
145
+
146
+ Returns:
147
+ A result dict of metrics.
148
+ """
149
+
150
+ if new_episodes is None:
151
+ new_episodes = episodes
152
+
153
+ episode_rewards = []
154
+ episode_lengths = []
155
+ policy_rewards = collections.defaultdict(list)
156
+ custom_metrics = collections.defaultdict(list)
157
+ perf_stats = collections.defaultdict(list)
158
+ hist_stats = collections.defaultdict(list)
159
+ episode_media = collections.defaultdict(list)
160
+ connector_metrics = collections.defaultdict(list)
161
+ num_faulty_episodes = 0
162
+
163
+ for episode in episodes:
164
+ # Faulty episodes may still carry perf_stats data.
165
+ for k, v in episode.perf_stats.items():
166
+ perf_stats[k].append(v)
167
+ # Continue if this is a faulty episode.
168
+ # There should be other meaningful stats to be collected.
169
+ if episode.episode_faulty:
170
+ num_faulty_episodes += 1
171
+ continue
172
+
173
+ episode_lengths.append(episode.episode_length)
174
+ episode_rewards.append(episode.episode_reward)
175
+ for k, v in episode.custom_metrics.items():
176
+ custom_metrics[k].append(v)
177
+ is_multi_agent = (
178
+ len(episode.agent_rewards) > 1
179
+ or DEFAULT_POLICY_ID not in episode.agent_rewards
180
+ )
181
+ if is_multi_agent:
182
+ for (_, policy_id), reward in episode.agent_rewards.items():
183
+ policy_rewards[policy_id].append(reward)
184
+ for k, v in episode.hist_data.items():
185
+ hist_stats[k] += v
186
+ for k, v in episode.media.items():
187
+ episode_media[k].append(v)
188
+ if hasattr(episode, "connector_metrics"):
189
+ # Group connector metrics by connector_metric name for all policies
190
+ for per_pipeline_metrics in episode.connector_metrics.values():
191
+ for per_connector_metrics in per_pipeline_metrics.values():
192
+ for connector_metric_name, val in per_connector_metrics.items():
193
+ connector_metrics[connector_metric_name].append(val)
194
+
195
+ if episode_rewards:
196
+ min_reward = min(episode_rewards)
197
+ max_reward = max(episode_rewards)
198
+ avg_reward = np.mean(episode_rewards)
199
+ else:
200
+ min_reward = float("nan")
201
+ max_reward = float("nan")
202
+ avg_reward = float("nan")
203
+ if episode_lengths:
204
+ avg_length = np.mean(episode_lengths)
205
+ else:
206
+ avg_length = float("nan")
207
+
208
+ # Show as histogram distributions.
209
+ hist_stats["episode_reward"] = episode_rewards
210
+ hist_stats["episode_lengths"] = episode_lengths
211
+
212
+ policy_reward_min = {}
213
+ policy_reward_mean = {}
214
+ policy_reward_max = {}
215
+ for policy_id, rewards in policy_rewards.copy().items():
216
+ policy_reward_min[policy_id] = np.min(rewards)
217
+ policy_reward_mean[policy_id] = np.mean(rewards)
218
+ policy_reward_max[policy_id] = np.max(rewards)
219
+
220
+ # Show as histogram distributions.
221
+ hist_stats["policy_{}_reward".format(policy_id)] = rewards
222
+
223
+ for k, v_list in custom_metrics.copy().items():
224
+ filt = [v for v in v_list if not np.any(np.isnan(v))]
225
+ if keep_custom_metrics:
226
+ custom_metrics[k] = filt
227
+ else:
228
+ custom_metrics[k + "_mean"] = np.mean(filt)
229
+ if filt:
230
+ custom_metrics[k + "_min"] = np.min(filt)
231
+ custom_metrics[k + "_max"] = np.max(filt)
232
+ else:
233
+ custom_metrics[k + "_min"] = float("nan")
234
+ custom_metrics[k + "_max"] = float("nan")
235
+ del custom_metrics[k]
236
+
237
+ for k, v_list in perf_stats.copy().items():
238
+ perf_stats[k] = np.mean(v_list)
239
+
240
+ mean_connector_metrics = dict()
241
+ for k, v_list in connector_metrics.items():
242
+ mean_connector_metrics[k] = np.mean(v_list)
243
+
244
+ return dict(
245
+ episode_reward_max=max_reward,
246
+ episode_reward_min=min_reward,
247
+ episode_reward_mean=avg_reward,
248
+ episode_len_mean=avg_length,
249
+ episode_media=dict(episode_media),
250
+ episodes_timesteps_total=sum(episode_lengths),
251
+ policy_reward_min=policy_reward_min,
252
+ policy_reward_max=policy_reward_max,
253
+ policy_reward_mean=policy_reward_mean,
254
+ custom_metrics=dict(custom_metrics),
255
+ hist_stats=dict(hist_stats),
256
+ sampler_perf=dict(perf_stats),
257
+ num_faulty_episodes=num_faulty_episodes,
258
+ connector_metrics=mean_connector_metrics,
259
+ # Added these (duplicate) values here for forward compatibility with the new API
260
+ # stack's metrics structure. This allows us to unify our test cases and keeping
261
+ # the new API stack clean of backward-compatible keys.
262
+ num_episodes=len(new_episodes),
263
+ episode_return_max=max_reward,
264
+ episode_return_min=min_reward,
265
+ episode_return_mean=avg_reward,
266
+ episodes_this_iter=len(new_episodes), # deprecate in favor of `num_epsodes_...`
267
+ )
lib/python3.12/site-packages/ray/rllib/evaluation/observation_function.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ from ray.rllib.env import BaseEnv
4
+ from ray.rllib.evaluation import RolloutWorker
5
+ from ray.rllib.policy import Policy
6
+ from ray.rllib.utils.annotations import OldAPIStack
7
+ from ray.rllib.utils.framework import TensorType
8
+ from ray.rllib.utils.typing import AgentID, PolicyID
9
+
10
+
11
+ @OldAPIStack
12
+ class ObservationFunction:
13
+ """Interceptor function for rewriting observations from the environment.
14
+
15
+ These callbacks can be used for preprocessing of observations, especially
16
+ in multi-agent scenarios.
17
+
18
+ Observation functions can be specified in the multi-agent config by
19
+ specifying ``{"observation_fn": your_obs_func}``. Note that
20
+ ``your_obs_func`` can be a plain Python function.
21
+
22
+ This API is **experimental**.
23
+ """
24
+
25
+ def __call__(
26
+ self,
27
+ agent_obs: Dict[AgentID, TensorType],
28
+ worker: RolloutWorker,
29
+ base_env: BaseEnv,
30
+ policies: Dict[PolicyID, Policy],
31
+ episode,
32
+ **kw
33
+ ) -> Dict[AgentID, TensorType]:
34
+ """Callback run on each environment step to observe the environment.
35
+
36
+ This method takes in the original agent observation dict returned by
37
+ a MultiAgentEnv, and returns a possibly modified one. It can be
38
+ thought of as a "wrapper" around the environment.
39
+
40
+ TODO(ekl): allow end-to-end differentiation through the observation
41
+ function and policy losses.
42
+
43
+ TODO(ekl): enable batch processing.
44
+
45
+ Args:
46
+ agent_obs: Dictionary of default observations from the
47
+ environment. The default implementation of observe() simply
48
+ returns this dict.
49
+ worker: Reference to the current rollout worker.
50
+ base_env: BaseEnv running the episode. The underlying
51
+ sub environment objects (BaseEnvs are vectorized) can be
52
+ retrieved by calling `base_env.get_sub_environments()`.
53
+ policies: Mapping of policy id to policy objects. In single
54
+ agent mode there will only be a single "default" policy.
55
+ episode: Episode state object.
56
+ kwargs: Forward compatibility placeholder.
57
+
58
+ Returns:
59
+ new_agent_obs: copy of agent obs with updates. You can
60
+ rewrite or drop data from the dict if needed (e.g., the env
61
+ can have a dummy "global" observation, and the observer can
62
+ merge the global state into individual observations.
63
+
64
+ .. testcode::
65
+ :skipif: True
66
+
67
+ # Observer that merges global state into individual obs. It is
68
+ # rewriting the discrete obs into a tuple with global state.
69
+ example_obs_fn1({"a": 1, "b": 2, "global_state": 101}, ...)
70
+
71
+ .. testoutput::
72
+
73
+ {"a": [1, 101], "b": [2, 101]}
74
+
75
+ .. testcode::
76
+ :skipif: True
77
+
78
+ # Observer for e.g., custom centralized critic model. It is
79
+ # rewriting the discrete obs into a dict with more data.
80
+ example_obs_fn2({"a": 1, "b": 2}, ...)
81
+
82
+ .. testoutput::
83
+
84
+ {"a": {"self": 1, "other": 2}, "b": {"self": 2, "other": 1}}
85
+ """
86
+
87
+ return agent_obs
lib/python3.12/site-packages/ray/rllib/evaluation/postprocessing.py ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Optional
2
+
3
+ import numpy as np
4
+ import scipy.signal
5
+
6
+ from ray.rllib.policy.policy import Policy
7
+ from ray.rllib.policy.sample_batch import SampleBatch
8
+ from ray.rllib.utils.annotations import DeveloperAPI, OldAPIStack
9
+ from ray.rllib.utils.numpy import convert_to_numpy
10
+ from ray.rllib.utils.typing import AgentID, TensorType
11
+
12
+
13
+ @DeveloperAPI
14
+ class Postprocessing:
15
+ """Constant definitions for postprocessing."""
16
+
17
+ ADVANTAGES = "advantages"
18
+ VALUE_TARGETS = "value_targets"
19
+
20
+
21
+ @OldAPIStack
22
+ def adjust_nstep(n_step: int, gamma: float, batch: SampleBatch) -> None:
23
+ """Rewrites `batch` to encode n-step rewards, terminateds, truncateds, and next-obs.
24
+
25
+ Observations and actions remain unaffected. At the end of the trajectory,
26
+ n is truncated to fit in the traj length.
27
+
28
+ Args:
29
+ n_step: The number of steps to look ahead and adjust.
30
+ gamma: The discount factor.
31
+ batch: The SampleBatch to adjust (in place).
32
+
33
+ Examples:
34
+ n-step=3
35
+ Trajectory=o0 r0 d0, o1 r1 d1, o2 r2 d2, o3 r3 d3, o4 r4 d4=True o5
36
+ gamma=0.9
37
+ Returned trajectory:
38
+ 0: o0 [r0 + 0.9*r1 + 0.9^2*r2 + 0.9^3*r3] d3 o0'=o3
39
+ 1: o1 [r1 + 0.9*r2 + 0.9^2*r3 + 0.9^3*r4] d4 o1'=o4
40
+ 2: o2 [r2 + 0.9*r3 + 0.9^2*r4] d4 o1'=o5
41
+ 3: o3 [r3 + 0.9*r4] d4 o3'=o5
42
+ 4: o4 r4 d4 o4'=o5
43
+ """
44
+
45
+ assert (
46
+ batch.is_single_trajectory()
47
+ ), "Unexpected terminated|truncated in middle of trajectory!"
48
+
49
+ len_ = len(batch)
50
+
51
+ # Shift NEXT_OBS, TERMINATEDS, and TRUNCATEDS.
52
+ batch[SampleBatch.NEXT_OBS] = np.concatenate(
53
+ [
54
+ batch[SampleBatch.OBS][n_step:],
55
+ np.stack([batch[SampleBatch.NEXT_OBS][-1]] * min(n_step, len_)),
56
+ ],
57
+ axis=0,
58
+ )
59
+ batch[SampleBatch.TERMINATEDS] = np.concatenate(
60
+ [
61
+ batch[SampleBatch.TERMINATEDS][n_step - 1 :],
62
+ np.tile(batch[SampleBatch.TERMINATEDS][-1], min(n_step - 1, len_)),
63
+ ],
64
+ axis=0,
65
+ )
66
+ # Only fix `truncateds`, if present in the batch.
67
+ if SampleBatch.TRUNCATEDS in batch:
68
+ batch[SampleBatch.TRUNCATEDS] = np.concatenate(
69
+ [
70
+ batch[SampleBatch.TRUNCATEDS][n_step - 1 :],
71
+ np.tile(batch[SampleBatch.TRUNCATEDS][-1], min(n_step - 1, len_)),
72
+ ],
73
+ axis=0,
74
+ )
75
+
76
+ # Change rewards in place.
77
+ for i in range(len_):
78
+ for j in range(1, n_step):
79
+ if i + j < len_:
80
+ batch[SampleBatch.REWARDS][i] += (
81
+ gamma**j * batch[SampleBatch.REWARDS][i + j]
82
+ )
83
+
84
+
85
+ @OldAPIStack
86
+ def compute_advantages(
87
+ rollout: SampleBatch,
88
+ last_r: float,
89
+ gamma: float = 0.9,
90
+ lambda_: float = 1.0,
91
+ use_gae: bool = True,
92
+ use_critic: bool = True,
93
+ rewards: TensorType = None,
94
+ vf_preds: TensorType = None,
95
+ ):
96
+ """Given a rollout, compute its value targets and the advantages.
97
+
98
+ Args:
99
+ rollout: SampleBatch of a single trajectory.
100
+ last_r: Value estimation for last observation.
101
+ gamma: Discount factor.
102
+ lambda_: Parameter for GAE.
103
+ use_gae: Using Generalized Advantage Estimation.
104
+ use_critic: Whether to use critic (value estimates). Setting
105
+ this to False will use 0 as baseline.
106
+ rewards: Override the reward values in rollout.
107
+ vf_preds: Override the value function predictions in rollout.
108
+
109
+ Returns:
110
+ SampleBatch with experience from rollout and processed rewards.
111
+ """
112
+ assert (
113
+ SampleBatch.VF_PREDS in rollout or not use_critic
114
+ ), "use_critic=True but values not found"
115
+ assert use_critic or not use_gae, "Can't use gae without using a value function"
116
+ last_r = convert_to_numpy(last_r)
117
+
118
+ if rewards is None:
119
+ rewards = rollout[SampleBatch.REWARDS]
120
+ if vf_preds is None and use_critic:
121
+ vf_preds = rollout[SampleBatch.VF_PREDS]
122
+
123
+ if use_gae:
124
+ vpred_t = np.concatenate([vf_preds, np.array([last_r])])
125
+ delta_t = rewards + gamma * vpred_t[1:] - vpred_t[:-1]
126
+ # This formula for the advantage comes from:
127
+ # "Generalized Advantage Estimation": https://arxiv.org/abs/1506.02438
128
+ rollout[Postprocessing.ADVANTAGES] = discount_cumsum(delta_t, gamma * lambda_)
129
+ rollout[Postprocessing.VALUE_TARGETS] = (
130
+ rollout[Postprocessing.ADVANTAGES] + vf_preds
131
+ ).astype(np.float32)
132
+ else:
133
+ rewards_plus_v = np.concatenate([rewards, np.array([last_r])])
134
+ discounted_returns = discount_cumsum(rewards_plus_v, gamma)[:-1].astype(
135
+ np.float32
136
+ )
137
+
138
+ if use_critic:
139
+ rollout[Postprocessing.ADVANTAGES] = discounted_returns - vf_preds
140
+ rollout[Postprocessing.VALUE_TARGETS] = discounted_returns
141
+ else:
142
+ rollout[Postprocessing.ADVANTAGES] = discounted_returns
143
+ rollout[Postprocessing.VALUE_TARGETS] = np.zeros_like(
144
+ rollout[Postprocessing.ADVANTAGES]
145
+ )
146
+
147
+ rollout[Postprocessing.ADVANTAGES] = rollout[Postprocessing.ADVANTAGES].astype(
148
+ np.float32
149
+ )
150
+
151
+ return rollout
152
+
153
+
154
+ @OldAPIStack
155
+ def compute_gae_for_sample_batch(
156
+ policy: Policy,
157
+ sample_batch: SampleBatch,
158
+ other_agent_batches: Optional[Dict[AgentID, SampleBatch]] = None,
159
+ episode=None,
160
+ ) -> SampleBatch:
161
+ """Adds GAE (generalized advantage estimations) to a trajectory.
162
+
163
+ The trajectory contains only data from one episode and from one agent.
164
+ - If `config.batch_mode=truncate_episodes` (default), sample_batch may
165
+ contain a truncated (at-the-end) episode, in case the
166
+ `config.rollout_fragment_length` was reached by the sampler.
167
+ - If `config.batch_mode=complete_episodes`, sample_batch will contain
168
+ exactly one episode (no matter how long).
169
+ New columns can be added to sample_batch and existing ones may be altered.
170
+
171
+ Args:
172
+ policy: The Policy used to generate the trajectory (`sample_batch`)
173
+ sample_batch: The SampleBatch to postprocess.
174
+ other_agent_batches: Optional dict of AgentIDs mapping to other
175
+ agents' trajectory data (from the same episode).
176
+ NOTE: The other agents use the same policy.
177
+ episode: Optional multi-agent episode object in which the agents
178
+ operated.
179
+
180
+ Returns:
181
+ The postprocessed, modified SampleBatch (or a new one).
182
+ """
183
+ # Compute the SampleBatch.VALUES_BOOTSTRAPPED column, which we'll need for the
184
+ # following `last_r` arg in `compute_advantages()`.
185
+ sample_batch = compute_bootstrap_value(sample_batch, policy)
186
+
187
+ vf_preds = np.array(sample_batch[SampleBatch.VF_PREDS])
188
+ rewards = np.array(sample_batch[SampleBatch.REWARDS])
189
+ # We need to squeeze out the time dimension if there is one
190
+ # Sanity check that both have the same shape
191
+ if len(vf_preds.shape) == 2:
192
+ assert vf_preds.shape == rewards.shape
193
+ vf_preds = np.squeeze(vf_preds, axis=1)
194
+ rewards = np.squeeze(rewards, axis=1)
195
+ squeezed = True
196
+ else:
197
+ squeezed = False
198
+
199
+ # Adds the policy logits, VF preds, and advantages to the batch,
200
+ # using GAE ("generalized advantage estimation") or not.
201
+ batch = compute_advantages(
202
+ rollout=sample_batch,
203
+ last_r=sample_batch[SampleBatch.VALUES_BOOTSTRAPPED][-1],
204
+ gamma=policy.config["gamma"],
205
+ lambda_=policy.config["lambda"],
206
+ use_gae=policy.config["use_gae"],
207
+ use_critic=policy.config.get("use_critic", True),
208
+ vf_preds=vf_preds,
209
+ rewards=rewards,
210
+ )
211
+
212
+ if squeezed:
213
+ # If we needed to squeeze rewards and vf_preds, we need to unsqueeze
214
+ # advantages again for it to have the same shape
215
+ batch[Postprocessing.ADVANTAGES] = np.expand_dims(
216
+ batch[Postprocessing.ADVANTAGES], axis=1
217
+ )
218
+
219
+ return batch
220
+
221
+
222
+ @OldAPIStack
223
+ def compute_bootstrap_value(sample_batch: SampleBatch, policy: Policy) -> SampleBatch:
224
+ """Performs a value function computation at the end of a trajectory.
225
+
226
+ If the trajectory is terminated (not truncated), will not use the value function,
227
+ but assume that the value of the last timestep is 0.0.
228
+ In all other cases, will use the given policy's value function to compute the
229
+ "bootstrapped" value estimate at the end of the given trajectory. To do so, the
230
+ very last observation (sample_batch[NEXT_OBS][-1]) and - if applicable -
231
+ the very last state output (sample_batch[STATE_OUT][-1]) wil be used as inputs to
232
+ the value function.
233
+
234
+ The thus computed value estimate will be stored in a new column of the
235
+ `sample_batch`: SampleBatch.VALUES_BOOTSTRAPPED. Thereby, values at all timesteps
236
+ in this column are set to 0.0, except or the last timestep, which receives the
237
+ computed bootstrapped value.
238
+ This is done, such that in any loss function (which processes raw, intact
239
+ trajectories, such as those of IMPALA and APPO) can use this new column as follows:
240
+
241
+ Example: numbers=ts in episode, '|'=episode boundary (terminal),
242
+ X=bootstrapped value (!= 0.0 b/c ts=12 is not a terminal).
243
+ ts=5 is NOT a terminal.
244
+ T: 8 9 10 11 12 <- no terminal
245
+ VF_PREDS: . . . . .
246
+ VALUES_BOOTSTRAPPED: 0 0 0 0 X
247
+
248
+ Args:
249
+ sample_batch: The SampleBatch (single trajectory) for which to compute the
250
+ bootstrap value at the end. This SampleBatch will be altered in place
251
+ (by adding a new column: SampleBatch.VALUES_BOOTSTRAPPED).
252
+ policy: The Policy object, whose value function to use.
253
+
254
+ Returns:
255
+ The altered SampleBatch (with the extra SampleBatch.VALUES_BOOTSTRAPPED
256
+ column).
257
+ """
258
+ # Trajectory is actually complete -> last r=0.0.
259
+ if sample_batch[SampleBatch.TERMINATEDS][-1]:
260
+ last_r = 0.0
261
+ # Trajectory has been truncated -> last r=VF estimate of last obs.
262
+ else:
263
+ # Input dict is provided to us automatically via the Model's
264
+ # requirements. It's a single-timestep (last one in trajectory)
265
+ # input_dict.
266
+ # Create an input dict according to the Policy's requirements.
267
+ input_dict = sample_batch.get_single_step_input_dict(
268
+ policy.view_requirements, index="last"
269
+ )
270
+ last_r = policy._value(**input_dict)
271
+
272
+ vf_preds = np.array(sample_batch[SampleBatch.VF_PREDS])
273
+ # We need to squeeze out the time dimension if there is one
274
+ if len(vf_preds.shape) == 2:
275
+ vf_preds = np.squeeze(vf_preds, axis=1)
276
+ squeezed = True
277
+ else:
278
+ squeezed = False
279
+
280
+ # Set the SampleBatch.VALUES_BOOTSTRAPPED field to VF_PREDS[1:] + the
281
+ # very last timestep (where this bootstrapping value is actually needed), which
282
+ # we set to the computed `last_r`.
283
+ sample_batch[SampleBatch.VALUES_BOOTSTRAPPED] = np.concatenate(
284
+ [
285
+ convert_to_numpy(vf_preds[1:]),
286
+ np.array([convert_to_numpy(last_r)], dtype=np.float32),
287
+ ],
288
+ axis=0,
289
+ )
290
+
291
+ if squeezed:
292
+ sample_batch[SampleBatch.VF_PREDS] = np.expand_dims(vf_preds, axis=1)
293
+ sample_batch[SampleBatch.VALUES_BOOTSTRAPPED] = np.expand_dims(
294
+ sample_batch[SampleBatch.VALUES_BOOTSTRAPPED], axis=1
295
+ )
296
+
297
+ return sample_batch
298
+
299
+
300
+ @OldAPIStack
301
+ def discount_cumsum(x: np.ndarray, gamma: float) -> np.ndarray:
302
+ """Calculates the discounted cumulative sum over a reward sequence `x`.
303
+
304
+ y[t] - discount*y[t+1] = x[t]
305
+ reversed(y)[t] - discount*reversed(y)[t-1] = reversed(x)[t]
306
+
307
+ Args:
308
+ gamma: The discount factor gamma.
309
+
310
+ Returns:
311
+ The sequence containing the discounted cumulative sums
312
+ for each individual reward in `x` till the end of the trajectory.
313
+
314
+ .. testcode::
315
+ :skipif: True
316
+
317
+ x = np.array([0.0, 1.0, 2.0, 3.0])
318
+ gamma = 0.9
319
+ discount_cumsum(x, gamma)
320
+
321
+ .. testoutput::
322
+
323
+ array([0.0 + 0.9*1.0 + 0.9^2*2.0 + 0.9^3*3.0,
324
+ 1.0 + 0.9*2.0 + 0.9^2*3.0,
325
+ 2.0 + 0.9*3.0,
326
+ 3.0])
327
+ """
328
+ return scipy.signal.lfilter([1], [1, float(-gamma)], x[::-1], axis=0)[::-1]
lib/python3.12/site-packages/ray/rllib/evaluation/rollout_worker.py ADDED
@@ -0,0 +1,2006 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import importlib.util
3
+ import logging
4
+ import os
5
+ import platform
6
+ import socket
7
+ import threading
8
+ from collections import defaultdict
9
+ from types import FunctionType
10
+ from typing import (
11
+ TYPE_CHECKING,
12
+ Any,
13
+ Callable,
14
+ Collection,
15
+ Dict,
16
+ List,
17
+ Optional,
18
+ Set,
19
+ Tuple,
20
+ Type,
21
+ Union,
22
+ )
23
+
24
+ from gymnasium.spaces import Space
25
+
26
+ import ray
27
+ from ray import ObjectRef, cloudpickle as pickle
28
+ from ray.rllib.connectors.util import (
29
+ create_connectors_for_policy,
30
+ maybe_get_filters_for_syncing,
31
+ )
32
+ from ray.rllib.core.rl_module import validate_module_id
33
+ from ray.rllib.core.rl_module.rl_module import RLModuleSpec
34
+ from ray.rllib.env.base_env import BaseEnv, convert_to_base_env
35
+ from ray.rllib.env.env_context import EnvContext
36
+ from ray.rllib.env.env_runner import EnvRunner
37
+ from ray.rllib.env.external_multi_agent_env import ExternalMultiAgentEnv
38
+ from ray.rllib.env.multi_agent_env import MultiAgentEnv
39
+ from ray.rllib.env.wrappers.atari_wrappers import is_atari, wrap_deepmind
40
+ from ray.rllib.evaluation.metrics import RolloutMetrics
41
+ from ray.rllib.evaluation.sampler import SyncSampler
42
+ from ray.rllib.models import ModelCatalog
43
+ from ray.rllib.models.preprocessors import Preprocessor
44
+ from ray.rllib.offline import (
45
+ D4RLReader,
46
+ DatasetReader,
47
+ DatasetWriter,
48
+ InputReader,
49
+ IOContext,
50
+ JsonReader,
51
+ JsonWriter,
52
+ MixedInput,
53
+ NoopOutput,
54
+ OutputWriter,
55
+ ShuffledInput,
56
+ )
57
+ from ray.rllib.policy.policy import Policy, PolicySpec
58
+ from ray.rllib.policy.policy_map import PolicyMap
59
+ from ray.rllib.policy.sample_batch import (
60
+ DEFAULT_POLICY_ID,
61
+ MultiAgentBatch,
62
+ concat_samples,
63
+ convert_ma_batch_to_sample_batch,
64
+ )
65
+ from ray.rllib.policy.torch_policy import TorchPolicy
66
+ from ray.rllib.policy.torch_policy_v2 import TorchPolicyV2
67
+ from ray.rllib.utils import force_list
68
+ from ray.rllib.utils.annotations import OldAPIStack, override
69
+ from ray.rllib.utils.debug import summarize, update_global_seed_if_necessary
70
+ from ray.rllib.utils.error import ERR_MSG_NO_GPUS, HOWTO_CHANGE_CONFIG
71
+ from ray.rllib.utils.filter import Filter, NoFilter
72
+ from ray.rllib.utils.framework import try_import_tf, try_import_torch
73
+ from ray.rllib.utils.from_config import from_config
74
+ from ray.rllib.utils.policy import create_policy_for_framework
75
+ from ray.rllib.utils.sgd import do_minibatch_sgd
76
+ from ray.rllib.utils.tf_run_builder import _TFRunBuilder
77
+ from ray.rllib.utils.tf_utils import (
78
+ get_gpu_devices as get_tf_gpu_devices,
79
+ get_tf_eager_cls_if_necessary,
80
+ )
81
+ from ray.rllib.utils.typing import (
82
+ AgentID,
83
+ EnvCreator,
84
+ EnvType,
85
+ ModelGradients,
86
+ ModelWeights,
87
+ MultiAgentPolicyConfigDict,
88
+ PartialAlgorithmConfigDict,
89
+ PolicyID,
90
+ PolicyState,
91
+ SampleBatchType,
92
+ T,
93
+ )
94
+ from ray.tune.registry import registry_contains_input, registry_get_input
95
+ from ray.util.annotations import PublicAPI
96
+ from ray.util.debug import disable_log_once_globally, enable_periodic_logging, log_once
97
+ from ray.util.iter import ParallelIteratorWorker
98
+
99
+ if TYPE_CHECKING:
100
+ from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
101
+ from ray.rllib.callbacks.callbacks import RLlibCallback
102
+
103
+ tf1, tf, tfv = try_import_tf()
104
+ torch, _ = try_import_torch()
105
+
106
+ logger = logging.getLogger(__name__)
107
+
108
+ # Handle to the current rollout worker, which will be set to the most recently
109
+ # created RolloutWorker in this process. This can be helpful to access in
110
+ # custom env or policy classes for debugging or advanced use cases.
111
+ _global_worker: Optional["RolloutWorker"] = None
112
+
113
+
114
+ @OldAPIStack
115
+ def get_global_worker() -> "RolloutWorker":
116
+ """Returns a handle to the active rollout worker in this process."""
117
+
118
+ global _global_worker
119
+ return _global_worker
120
+
121
+
122
+ def _update_env_seed_if_necessary(
123
+ env: EnvType, seed: int, worker_idx: int, vector_idx: int
124
+ ):
125
+ """Set a deterministic random seed on environment.
126
+
127
+ NOTE: this may not work with remote environments (issue #18154).
128
+ """
129
+ if seed is None:
130
+ return
131
+
132
+ # A single RL job is unlikely to have more than 10K
133
+ # rollout workers.
134
+ max_num_envs_per_env_runner: int = 1000
135
+ assert (
136
+ worker_idx < max_num_envs_per_env_runner
137
+ ), "Too many envs per worker. Random seeds may collide."
138
+ computed_seed: int = worker_idx * max_num_envs_per_env_runner + vector_idx + seed
139
+
140
+ # Gymnasium.env.
141
+ # This will silently fail for most Farama-foundation gymnasium environments.
142
+ # (they do nothing and return None per default)
143
+ if not hasattr(env, "reset"):
144
+ if log_once("env_has_no_reset_method"):
145
+ logger.info(f"Env {env} doesn't have a `reset()` method. Cannot seed.")
146
+ else:
147
+ try:
148
+ env.reset(seed=computed_seed)
149
+ except Exception:
150
+ logger.info(
151
+ f"Env {env} doesn't support setting a seed via its `reset()` "
152
+ "method! Implement this method as `reset(self, *, seed=None, "
153
+ "options=None)` for it to abide to the correct API. Cannot seed."
154
+ )
155
+
156
+
157
+ @OldAPIStack
158
+ class RolloutWorker(ParallelIteratorWorker, EnvRunner):
159
+ """Common experience collection class.
160
+
161
+ This class wraps a policy instance and an environment class to
162
+ collect experiences from the environment. You can create many replicas of
163
+ this class as Ray actors to scale RL training.
164
+
165
+ This class supports vectorized and multi-agent policy evaluation (e.g.,
166
+ VectorEnv, MultiAgentEnv, etc.)
167
+
168
+ .. testcode::
169
+ :skipif: True
170
+
171
+ # Create a rollout worker and using it to collect experiences.
172
+ import gymnasium as gym
173
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
174
+ from ray.rllib.algorithms.ppo.ppo_tf_policy import PPOTF1Policy
175
+ worker = RolloutWorker(
176
+ env_creator=lambda _: gym.make("CartPole-v1"),
177
+ default_policy_class=PPOTF1Policy)
178
+ print(worker.sample())
179
+
180
+ # Creating a multi-agent rollout worker
181
+ from gymnasium.spaces import Discrete, Box
182
+ import random
183
+ MultiAgentTrafficGrid = ...
184
+ worker = RolloutWorker(
185
+ env_creator=lambda _: MultiAgentTrafficGrid(num_cars=25),
186
+ config=AlgorithmConfig().multi_agent(
187
+ policies={
188
+ # Use an ensemble of two policies for car agents
189
+ "car_policy1":
190
+ (PGTFPolicy, Box(...), Discrete(...),
191
+ AlgorithmConfig.overrides(gamma=0.99)),
192
+ "car_policy2":
193
+ (PGTFPolicy, Box(...), Discrete(...),
194
+ AlgorithmConfig.overrides(gamma=0.95)),
195
+ # Use a single shared policy for all traffic lights
196
+ "traffic_light_policy":
197
+ (PGTFPolicy, Box(...), Discrete(...), {}),
198
+ },
199
+ policy_mapping_fn=(
200
+ lambda agent_id, episode, **kwargs:
201
+ random.choice(["car_policy1", "car_policy2"])
202
+ if agent_id.startswith("car_") else "traffic_light_policy"),
203
+ ),
204
+ )
205
+ print(worker.sample())
206
+
207
+ .. testoutput::
208
+
209
+ SampleBatch({
210
+ "obs": [[...]], "actions": [[...]], "rewards": [[...]],
211
+ "terminateds": [[...]], "truncateds": [[...]], "new_obs": [[...]]}
212
+ )
213
+
214
+ MultiAgentBatch({
215
+ "car_policy1": SampleBatch(...),
216
+ "car_policy2": SampleBatch(...),
217
+ "traffic_light_policy": SampleBatch(...)}
218
+ )
219
+
220
+ """
221
+
222
+ def __init__(
223
+ self,
224
+ *,
225
+ env_creator: EnvCreator,
226
+ validate_env: Optional[Callable[[EnvType, EnvContext], None]] = None,
227
+ config: Optional["AlgorithmConfig"] = None,
228
+ worker_index: int = 0,
229
+ num_workers: Optional[int] = None,
230
+ recreated_worker: bool = False,
231
+ log_dir: Optional[str] = None,
232
+ spaces: Optional[Dict[PolicyID, Tuple[Space, Space]]] = None,
233
+ default_policy_class: Optional[Type[Policy]] = None,
234
+ dataset_shards: Optional[List[ray.data.Dataset]] = None,
235
+ **kwargs,
236
+ ):
237
+ """Initializes a RolloutWorker instance.
238
+
239
+ Args:
240
+ env_creator: Function that returns a gym.Env given an EnvContext
241
+ wrapped configuration.
242
+ validate_env: Optional callable to validate the generated
243
+ environment (only on worker=0).
244
+ worker_index: For remote workers, this should be set to a
245
+ non-zero and unique value. This index is passed to created envs
246
+ through EnvContext so that envs can be configured per worker.
247
+ recreated_worker: Whether this worker is a recreated one. Workers are
248
+ recreated by an Algorithm (via EnvRunnerGroup) in case
249
+ `restart_failed_env_runners=True` and one of the original workers (or
250
+ an already recreated one) has failed. They don't differ from original
251
+ workers other than the value of this flag (`self.recreated_worker`).
252
+ log_dir: Directory where logs can be placed.
253
+ spaces: An optional space dict mapping policy IDs
254
+ to (obs_space, action_space)-tuples. This is used in case no
255
+ Env is created on this RolloutWorker.
256
+ """
257
+ self._original_kwargs: dict = locals().copy()
258
+ del self._original_kwargs["self"]
259
+
260
+ global _global_worker
261
+ _global_worker = self
262
+
263
+ from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
264
+
265
+ # Default config needed?
266
+ if config is None or isinstance(config, dict):
267
+ config = AlgorithmConfig().update_from_dict(config or {})
268
+ # Freeze config, so no one else can alter it from here on.
269
+ config.freeze()
270
+
271
+ # Set extra python env variables before calling super constructor.
272
+ if config.extra_python_environs_for_driver and worker_index == 0:
273
+ for key, value in config.extra_python_environs_for_driver.items():
274
+ os.environ[key] = str(value)
275
+ elif config.extra_python_environs_for_worker and worker_index > 0:
276
+ for key, value in config.extra_python_environs_for_worker.items():
277
+ os.environ[key] = str(value)
278
+
279
+ def gen_rollouts():
280
+ while True:
281
+ yield self.sample()
282
+
283
+ ParallelIteratorWorker.__init__(self, gen_rollouts, False)
284
+ EnvRunner.__init__(self, config=config)
285
+
286
+ self.num_workers = (
287
+ num_workers if num_workers is not None else self.config.num_env_runners
288
+ )
289
+ # In case we are reading from distributed datasets, store the shards here
290
+ # and pick our shard by our worker-index.
291
+ self._ds_shards = dataset_shards
292
+ self.worker_index: int = worker_index
293
+
294
+ # Lock to be able to lock this entire worker
295
+ # (via `self.lock()` and `self.unlock()`).
296
+ # This might be crucial to prevent a race condition in case
297
+ # `config.policy_states_are_swappable=True` and you are using an Algorithm
298
+ # with a learner thread. In this case, the thread might update a policy
299
+ # that is being swapped (during the update) by the Algorithm's
300
+ # training_step's `RolloutWorker.get_weights()` call (to sync back the
301
+ # new weights to all remote workers).
302
+ self._lock = threading.Lock()
303
+
304
+ if (
305
+ tf1
306
+ and (config.framework_str == "tf2" or config.enable_tf1_exec_eagerly)
307
+ # This eager check is necessary for certain all-framework tests
308
+ # that use tf's eager_mode() context generator.
309
+ and not tf1.executing_eagerly()
310
+ ):
311
+ tf1.enable_eager_execution()
312
+
313
+ if self.config.log_level:
314
+ logging.getLogger("ray.rllib").setLevel(self.config.log_level)
315
+
316
+ if self.worker_index > 1:
317
+ disable_log_once_globally() # only need 1 worker to log
318
+ elif self.config.log_level == "DEBUG":
319
+ enable_periodic_logging()
320
+
321
+ env_context = EnvContext(
322
+ self.config.env_config,
323
+ worker_index=self.worker_index,
324
+ vector_index=0,
325
+ num_workers=self.num_workers,
326
+ remote=self.config.remote_worker_envs,
327
+ recreated_worker=recreated_worker,
328
+ )
329
+ self.env_context = env_context
330
+ self.config: AlgorithmConfig = config
331
+ self.callbacks: RLlibCallback = self.config.callbacks_class()
332
+ self.recreated_worker: bool = recreated_worker
333
+
334
+ # Setup current policy_mapping_fn. Start with the one from the config, which
335
+ # might be None in older checkpoints (nowadays AlgorithmConfig has a proper
336
+ # default for this); Need to cover this situation via the backup lambda here.
337
+ self.policy_mapping_fn = (
338
+ lambda agent_id, episode, worker, **kw: DEFAULT_POLICY_ID
339
+ )
340
+ self.set_policy_mapping_fn(self.config.policy_mapping_fn)
341
+
342
+ self.env_creator: EnvCreator = env_creator
343
+ # Resolve possible auto-fragment length.
344
+ configured_rollout_fragment_length = self.config.get_rollout_fragment_length(
345
+ worker_index=self.worker_index
346
+ )
347
+ self.total_rollout_fragment_length: int = (
348
+ configured_rollout_fragment_length * self.config.num_envs_per_env_runner
349
+ )
350
+ self.preprocessing_enabled: bool = not config._disable_preprocessor_api
351
+ self.last_batch: Optional[SampleBatchType] = None
352
+ self.global_vars: dict = {
353
+ # TODO(sven): Make this per-policy!
354
+ "timestep": 0,
355
+ # Counter for performed gradient updates per policy in `self.policy_map`.
356
+ # Allows for compiling metrics on the off-policy'ness of an update given
357
+ # that the number of gradient updates of the sampling policies are known
358
+ # to the learner (and can be compared to the learner version of the same
359
+ # policy).
360
+ "num_grad_updates_per_policy": defaultdict(int),
361
+ }
362
+
363
+ # If seed is provided, add worker index to it and 10k iff evaluation worker.
364
+ self.seed = (
365
+ None
366
+ if self.config.seed is None
367
+ else self.config.seed
368
+ + self.worker_index
369
+ + self.config.in_evaluation * 10000
370
+ )
371
+
372
+ # Update the global seed for numpy/random/tf-eager/torch if we are not
373
+ # the local worker, otherwise, this was already done in the Algorithm
374
+ # object itself.
375
+ if self.worker_index > 0:
376
+ update_global_seed_if_necessary(self.config.framework_str, self.seed)
377
+
378
+ # A single environment provided by the user (via config.env). This may
379
+ # also remain None.
380
+ # 1) Create the env using the user provided env_creator. This may
381
+ # return a gym.Env (incl. MultiAgentEnv), an already vectorized
382
+ # VectorEnv, BaseEnv, ExternalEnv, or an ActorHandle (remote env).
383
+ # 2) Wrap - if applicable - with Atari/rendering wrappers.
384
+ # 3) Seed the env, if necessary.
385
+ # 4) Vectorize the existing single env by creating more clones of
386
+ # this env and wrapping it with the RLlib BaseEnv class.
387
+ self.env = self.make_sub_env_fn = None
388
+
389
+ # Create a (single) env for this worker.
390
+ if not (
391
+ self.worker_index == 0
392
+ and self.num_workers > 0
393
+ and not self.config.create_local_env_runner
394
+ ):
395
+ # Run the `env_creator` function passing the EnvContext.
396
+ self.env = env_creator(copy.deepcopy(self.env_context))
397
+
398
+ clip_rewards = self.config.clip_rewards
399
+
400
+ if self.env is not None:
401
+ # Custom validation function given, typically a function attribute of the
402
+ # Algorithm.
403
+ if validate_env is not None:
404
+ validate_env(self.env, self.env_context)
405
+
406
+ # We can't auto-wrap a BaseEnv.
407
+ if isinstance(self.env, (BaseEnv, ray.actor.ActorHandle)):
408
+
409
+ def wrap(env):
410
+ return env
411
+
412
+ # Atari type env and "deepmind" preprocessor pref.
413
+ elif is_atari(self.env) and self.config.preprocessor_pref == "deepmind":
414
+ # Deepmind wrappers already handle all preprocessing.
415
+ self.preprocessing_enabled = False
416
+
417
+ # If clip_rewards not explicitly set to False, switch it
418
+ # on here (clip between -1.0 and 1.0).
419
+ if self.config.clip_rewards is None:
420
+ clip_rewards = True
421
+
422
+ # Framestacking is used.
423
+ use_framestack = self.config.model.get("framestack") is True
424
+
425
+ def wrap(env):
426
+ env = wrap_deepmind(
427
+ env,
428
+ dim=self.config.model.get("dim"),
429
+ framestack=use_framestack,
430
+ noframeskip=self.config.env_config.get("frameskip", 0) == 1,
431
+ )
432
+ return env
433
+
434
+ elif self.config.preprocessor_pref is None:
435
+ # Only turn off preprocessing
436
+ self.preprocessing_enabled = False
437
+
438
+ def wrap(env):
439
+ return env
440
+
441
+ else:
442
+
443
+ def wrap(env):
444
+ return env
445
+
446
+ # Wrap env through the correct wrapper.
447
+ self.env: EnvType = wrap(self.env)
448
+ # Ideally, we would use the same make_sub_env() function below
449
+ # to create self.env, but wrap(env) and self.env has a cyclic
450
+ # dependency on each other right now, so we would settle on
451
+ # duplicating the random seed setting logic for now.
452
+ _update_env_seed_if_necessary(self.env, self.seed, self.worker_index, 0)
453
+ # Call custom callback function `on_sub_environment_created`.
454
+ self.callbacks.on_sub_environment_created(
455
+ worker=self,
456
+ sub_environment=self.env,
457
+ env_context=self.env_context,
458
+ )
459
+
460
+ self.make_sub_env_fn = self._get_make_sub_env_fn(
461
+ env_creator, env_context, validate_env, wrap, self.seed
462
+ )
463
+
464
+ self.spaces = spaces
465
+ self.default_policy_class = default_policy_class
466
+ self.policy_dict, self.is_policy_to_train = self.config.get_multi_agent_setup(
467
+ env=self.env,
468
+ spaces=self.spaces,
469
+ default_policy_class=self.default_policy_class,
470
+ )
471
+
472
+ self.policy_map: Optional[PolicyMap] = None
473
+ # TODO(jungong) : clean up after non-connector env_runner is fully deprecated.
474
+ self.preprocessors: Dict[PolicyID, Preprocessor] = None
475
+
476
+ # Check available number of GPUs.
477
+ num_gpus = (
478
+ self.config.num_gpus
479
+ if self.worker_index == 0
480
+ else self.config.num_gpus_per_env_runner
481
+ )
482
+
483
+ # Error if we don't find enough GPUs.
484
+ if (
485
+ ray.is_initialized()
486
+ and ray._private.worker._mode() != ray._private.worker.LOCAL_MODE
487
+ and not config._fake_gpus
488
+ ):
489
+ devices = []
490
+ if self.config.framework_str in ["tf2", "tf"]:
491
+ devices = get_tf_gpu_devices()
492
+ elif self.config.framework_str == "torch":
493
+ devices = list(range(torch.cuda.device_count()))
494
+
495
+ if len(devices) < num_gpus:
496
+ raise RuntimeError(
497
+ ERR_MSG_NO_GPUS.format(len(devices), devices) + HOWTO_CHANGE_CONFIG
498
+ )
499
+ # Warn, if running in local-mode and actual GPUs (not faked) are
500
+ # requested.
501
+ elif (
502
+ ray.is_initialized()
503
+ and ray._private.worker._mode() == ray._private.worker.LOCAL_MODE
504
+ and num_gpus > 0
505
+ and not self.config._fake_gpus
506
+ ):
507
+ logger.warning(
508
+ "You are running ray with `local_mode=True`, but have "
509
+ f"configured {num_gpus} GPUs to be used! In local mode, "
510
+ f"Policies are placed on the CPU and the `num_gpus` setting "
511
+ f"is ignored."
512
+ )
513
+
514
+ self.filters: Dict[PolicyID, Filter] = defaultdict(NoFilter)
515
+
516
+ # If RLModule API is enabled, multi_rl_module_spec holds the specs of the
517
+ # RLModules.
518
+ self.multi_rl_module_spec = None
519
+ self._update_policy_map(policy_dict=self.policy_dict)
520
+
521
+ # Update Policy's view requirements from Model, only if Policy directly
522
+ # inherited from base `Policy` class. At this point here, the Policy
523
+ # must have it's Model (if any) defined and ready to output an initial
524
+ # state.
525
+ for pol in self.policy_map.values():
526
+ if not pol._model_init_state_automatically_added:
527
+ pol._update_model_view_requirements_from_init_state()
528
+
529
+ if (
530
+ self.config.is_multi_agent
531
+ and self.env is not None
532
+ and not isinstance(
533
+ self.env,
534
+ (BaseEnv, ExternalMultiAgentEnv, MultiAgentEnv, ray.actor.ActorHandle),
535
+ )
536
+ ):
537
+ raise ValueError(
538
+ f"You are running a multi-agent setup, but the env {self.env} is not a "
539
+ f"subclass of BaseEnv, MultiAgentEnv, ActorHandle, or "
540
+ f"ExternalMultiAgentEnv!"
541
+ )
542
+
543
+ if self.worker_index == 0:
544
+ logger.info("Built filter map: {}".format(self.filters))
545
+
546
+ # This RolloutWorker has no env.
547
+ if self.env is None:
548
+ self.async_env = None
549
+ # Use a custom env-vectorizer and call it providing self.env.
550
+ elif "custom_vector_env" in self.config:
551
+ self.async_env = self.config.custom_vector_env(self.env)
552
+ # Default: Vectorize self.env via the make_sub_env function. This adds
553
+ # further clones of self.env and creates a RLlib BaseEnv (which is
554
+ # vectorized under the hood).
555
+ else:
556
+ # Always use vector env for consistency even if num_envs_per_env_runner=1.
557
+ self.async_env: BaseEnv = convert_to_base_env(
558
+ self.env,
559
+ make_env=self.make_sub_env_fn,
560
+ num_envs=self.config.num_envs_per_env_runner,
561
+ remote_envs=self.config.remote_worker_envs,
562
+ remote_env_batch_wait_ms=self.config.remote_env_batch_wait_ms,
563
+ worker=self,
564
+ restart_failed_sub_environments=(
565
+ self.config.restart_failed_sub_environments
566
+ ),
567
+ )
568
+
569
+ # `truncate_episodes`: Allow a batch to contain more than one episode
570
+ # (fragments) and always make the batch `rollout_fragment_length`
571
+ # long.
572
+ rollout_fragment_length_for_sampler = configured_rollout_fragment_length
573
+ if self.config.batch_mode == "truncate_episodes":
574
+ pack = True
575
+ # `complete_episodes`: Never cut episodes and sampler will return
576
+ # exactly one (complete) episode per poll.
577
+ else:
578
+ assert self.config.batch_mode == "complete_episodes"
579
+ rollout_fragment_length_for_sampler = float("inf")
580
+ pack = False
581
+
582
+ # Create the IOContext for this worker.
583
+ self.io_context: IOContext = IOContext(
584
+ log_dir, self.config, self.worker_index, self
585
+ )
586
+
587
+ render = False
588
+ if self.config.render_env is True and (
589
+ self.num_workers == 0 or self.worker_index == 1
590
+ ):
591
+ render = True
592
+
593
+ if self.env is None:
594
+ self.sampler = None
595
+ else:
596
+ self.sampler = SyncSampler(
597
+ worker=self,
598
+ env=self.async_env,
599
+ clip_rewards=clip_rewards,
600
+ rollout_fragment_length=rollout_fragment_length_for_sampler,
601
+ count_steps_by=self.config.count_steps_by,
602
+ callbacks=self.callbacks,
603
+ multiple_episodes_in_batch=pack,
604
+ normalize_actions=self.config.normalize_actions,
605
+ clip_actions=self.config.clip_actions,
606
+ observation_fn=self.config.observation_fn,
607
+ sample_collector_class=self.config.sample_collector,
608
+ render=render,
609
+ )
610
+
611
+ self.input_reader: InputReader = self._get_input_creator_from_config()(
612
+ self.io_context
613
+ )
614
+ self.output_writer: OutputWriter = self._get_output_creator_from_config()(
615
+ self.io_context
616
+ )
617
+
618
+ # The current weights sequence number (version). May remain None for when
619
+ # not tracking weights versions.
620
+ self.weights_seq_no: Optional[int] = None
621
+
622
+ @override(EnvRunner)
623
+ def make_env(self):
624
+ # Override this method, b/c it's abstract and must be overridden.
625
+ # However, we see no point in implementing it for the old API stack any longer
626
+ # (the RolloutWorker class will be deprecated soon).
627
+ raise NotImplementedError
628
+
629
+ @override(EnvRunner)
630
+ def assert_healthy(self):
631
+ is_healthy = self.policy_map and self.input_reader and self.output_writer
632
+ assert is_healthy, (
633
+ f"RolloutWorker {self} (idx={self.worker_index}; "
634
+ f"num_workers={self.num_workers}) not healthy!"
635
+ )
636
+
637
+ @override(EnvRunner)
638
+ def sample(self, **kwargs) -> SampleBatchType:
639
+ """Returns a batch of experience sampled from this worker.
640
+
641
+ This method must be implemented by subclasses.
642
+
643
+ Returns:
644
+ A columnar batch of experiences (e.g., tensors) or a MultiAgentBatch.
645
+
646
+ .. testcode::
647
+ :skipif: True
648
+
649
+ import gymnasium as gym
650
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
651
+ from ray.rllib.algorithms.ppo.ppo_tf_policy import PPOTF1Policy
652
+ worker = RolloutWorker(
653
+ env_creator=lambda _: gym.make("CartPole-v1"),
654
+ default_policy_class=PPOTF1Policy,
655
+ config=AlgorithmConfig(),
656
+ )
657
+ print(worker.sample())
658
+
659
+ .. testoutput::
660
+
661
+ SampleBatch({"obs": [...], "action": [...], ...})
662
+ """
663
+ if self.config.fake_sampler and self.last_batch is not None:
664
+ return self.last_batch
665
+ elif self.input_reader is None:
666
+ raise ValueError(
667
+ "RolloutWorker has no `input_reader` object! "
668
+ "Cannot call `sample()`. You can try setting "
669
+ "`create_local_env_runner` to True."
670
+ )
671
+
672
+ if log_once("sample_start"):
673
+ logger.info(
674
+ "Generating sample batch of size {}".format(
675
+ self.total_rollout_fragment_length
676
+ )
677
+ )
678
+
679
+ batches = [self.input_reader.next()]
680
+ steps_so_far = (
681
+ batches[0].count
682
+ if self.config.count_steps_by == "env_steps"
683
+ else batches[0].agent_steps()
684
+ )
685
+
686
+ # In truncate_episodes mode, never pull more than 1 batch per env.
687
+ # This avoids over-running the target batch size.
688
+ if (
689
+ self.config.batch_mode == "truncate_episodes"
690
+ and not self.config.offline_sampling
691
+ ):
692
+ max_batches = self.config.num_envs_per_env_runner
693
+ else:
694
+ max_batches = float("inf")
695
+ while steps_so_far < self.total_rollout_fragment_length and (
696
+ len(batches) < max_batches
697
+ ):
698
+ batch = self.input_reader.next()
699
+ steps_so_far += (
700
+ batch.count
701
+ if self.config.count_steps_by == "env_steps"
702
+ else batch.agent_steps()
703
+ )
704
+ batches.append(batch)
705
+
706
+ batch = concat_samples(batches)
707
+
708
+ self.callbacks.on_sample_end(worker=self, samples=batch)
709
+
710
+ # Always do writes prior to compression for consistency and to allow
711
+ # for better compression inside the writer.
712
+ self.output_writer.write(batch)
713
+
714
+ if log_once("sample_end"):
715
+ logger.info("Completed sample batch:\n\n{}\n".format(summarize(batch)))
716
+
717
+ if self.config.compress_observations:
718
+ batch.compress(bulk=self.config.compress_observations == "bulk")
719
+
720
+ if self.config.fake_sampler:
721
+ self.last_batch = batch
722
+
723
+ return batch
724
+
725
+ @override(EnvRunner)
726
+ def get_spaces(self) -> Dict[str, Tuple[Space, Space]]:
727
+ spaces = self.foreach_policy(
728
+ lambda p, pid: (pid, p.observation_space, p.action_space)
729
+ )
730
+ spaces = {e[0]: (getattr(e[1], "original_space", e[1]), e[2]) for e in spaces}
731
+ # Try to add the actual env's obs/action spaces.
732
+ env_spaces = self.foreach_env(
733
+ lambda env: (env.observation_space, env.action_space)
734
+ )
735
+ if env_spaces:
736
+ from ray.rllib.env import INPUT_ENV_SPACES
737
+
738
+ spaces[INPUT_ENV_SPACES] = env_spaces[0]
739
+ return spaces
740
+
741
+ @ray.method(num_returns=2)
742
+ def sample_with_count(self) -> Tuple[SampleBatchType, int]:
743
+ """Same as sample() but returns the count as a separate value.
744
+
745
+ Returns:
746
+ A columnar batch of experiences (e.g., tensors) and the
747
+ size of the collected batch.
748
+
749
+ .. testcode::
750
+ :skipif: True
751
+
752
+ import gymnasium as gym
753
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
754
+ from ray.rllib.algorithms.ppo.ppo_tf_policy import PPOTF1Policy
755
+ worker = RolloutWorker(
756
+ env_creator=lambda _: gym.make("CartPole-v1"),
757
+ default_policy_class=PPOTFPolicy)
758
+ print(worker.sample_with_count())
759
+
760
+ .. testoutput::
761
+
762
+ (SampleBatch({"obs": [...], "action": [...], ...}), 3)
763
+ """
764
+ batch = self.sample()
765
+ return batch, batch.count
766
+
767
+ def learn_on_batch(self, samples: SampleBatchType) -> Dict:
768
+ """Update policies based on the given batch.
769
+
770
+ This is the equivalent to apply_gradients(compute_gradients(samples)),
771
+ but can be optimized to avoid pulling gradients into CPU memory.
772
+
773
+ Args:
774
+ samples: The SampleBatch or MultiAgentBatch to learn on.
775
+
776
+ Returns:
777
+ Dictionary of extra metadata from compute_gradients().
778
+
779
+ .. testcode::
780
+ :skipif: True
781
+
782
+ import gymnasium as gym
783
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
784
+ from ray.rllib.algorithms.ppo.ppo_tf_policy import PPOTF1Policy
785
+ worker = RolloutWorker(
786
+ env_creator=lambda _: gym.make("CartPole-v1"),
787
+ default_policy_class=PPOTF1Policy)
788
+ batch = worker.sample()
789
+ info = worker.learn_on_batch(samples)
790
+ """
791
+ if log_once("learn_on_batch"):
792
+ logger.info(
793
+ "Training on concatenated sample batches:\n\n{}\n".format(
794
+ summarize(samples)
795
+ )
796
+ )
797
+
798
+ info_out = {}
799
+ if isinstance(samples, MultiAgentBatch):
800
+ builders = {}
801
+ to_fetch = {}
802
+ for pid, batch in samples.policy_batches.items():
803
+ if self.is_policy_to_train is not None and not self.is_policy_to_train(
804
+ pid, samples
805
+ ):
806
+ continue
807
+ # Decompress SampleBatch, in case some columns are compressed.
808
+ batch.decompress_if_needed()
809
+
810
+ policy = self.policy_map[pid]
811
+ tf_session = policy.get_session()
812
+ if tf_session and hasattr(policy, "_build_learn_on_batch"):
813
+ builders[pid] = _TFRunBuilder(tf_session, "learn_on_batch")
814
+ to_fetch[pid] = policy._build_learn_on_batch(builders[pid], batch)
815
+ else:
816
+ info_out[pid] = policy.learn_on_batch(batch)
817
+
818
+ info_out.update({pid: builders[pid].get(v) for pid, v in to_fetch.items()})
819
+ else:
820
+ if self.is_policy_to_train is None or self.is_policy_to_train(
821
+ DEFAULT_POLICY_ID, samples
822
+ ):
823
+ info_out.update(
824
+ {
825
+ DEFAULT_POLICY_ID: self.policy_map[
826
+ DEFAULT_POLICY_ID
827
+ ].learn_on_batch(samples)
828
+ }
829
+ )
830
+ if log_once("learn_out"):
831
+ logger.debug("Training out:\n\n{}\n".format(summarize(info_out)))
832
+ return info_out
833
+
834
+ def sample_and_learn(
835
+ self,
836
+ expected_batch_size: int,
837
+ num_sgd_iter: int,
838
+ sgd_minibatch_size: str,
839
+ standardize_fields: List[str],
840
+ ) -> Tuple[dict, int]:
841
+ """Sample and batch and learn on it.
842
+
843
+ This is typically used in combination with distributed allreduce.
844
+
845
+ Args:
846
+ expected_batch_size: Expected number of samples to learn on.
847
+ num_sgd_iter: Number of SGD iterations.
848
+ sgd_minibatch_size: SGD minibatch size.
849
+ standardize_fields: List of sample fields to normalize.
850
+
851
+ Returns:
852
+ A tuple consisting of a dictionary of extra metadata returned from
853
+ the policies' `learn_on_batch()` and the number of samples
854
+ learned on.
855
+ """
856
+ batch = self.sample()
857
+ assert batch.count == expected_batch_size, (
858
+ "Batch size possibly out of sync between workers, expected:",
859
+ expected_batch_size,
860
+ "got:",
861
+ batch.count,
862
+ )
863
+ logger.info(
864
+ "Executing distributed minibatch SGD "
865
+ "with epoch size {}, minibatch size {}".format(
866
+ batch.count, sgd_minibatch_size
867
+ )
868
+ )
869
+ info = do_minibatch_sgd(
870
+ batch,
871
+ self.policy_map,
872
+ self,
873
+ num_sgd_iter,
874
+ sgd_minibatch_size,
875
+ standardize_fields,
876
+ )
877
+ return info, batch.count
878
+
879
+ def compute_gradients(
880
+ self,
881
+ samples: SampleBatchType,
882
+ single_agent: bool = None,
883
+ ) -> Tuple[ModelGradients, dict]:
884
+ """Returns a gradient computed w.r.t the specified samples.
885
+
886
+ Uses the Policy's/ies' compute_gradients method(s) to perform the
887
+ calculations. Skips policies that are not trainable as per
888
+ `self.is_policy_to_train()`.
889
+
890
+ Args:
891
+ samples: The SampleBatch or MultiAgentBatch to compute gradients
892
+ for using this worker's trainable policies.
893
+
894
+ Returns:
895
+ In the single-agent case, a tuple consisting of ModelGradients and
896
+ info dict of the worker's policy.
897
+ In the multi-agent case, a tuple consisting of a dict mapping
898
+ PolicyID to ModelGradients and a dict mapping PolicyID to extra
899
+ metadata info.
900
+ Note that the first return value (grads) can be applied as is to a
901
+ compatible worker using the worker's `apply_gradients()` method.
902
+
903
+ .. testcode::
904
+ :skipif: True
905
+
906
+ import gymnasium as gym
907
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
908
+ from ray.rllib.algorithms.ppo.ppo_tf_policy import PPOTF1Policy
909
+ worker = RolloutWorker(
910
+ env_creator=lambda _: gym.make("CartPole-v1"),
911
+ default_policy_class=PPOTF1Policy)
912
+ batch = worker.sample()
913
+ grads, info = worker.compute_gradients(samples)
914
+ """
915
+ if log_once("compute_gradients"):
916
+ logger.info("Compute gradients on:\n\n{}\n".format(summarize(samples)))
917
+
918
+ if single_agent is True:
919
+ samples = convert_ma_batch_to_sample_batch(samples)
920
+ grad_out, info_out = self.policy_map[DEFAULT_POLICY_ID].compute_gradients(
921
+ samples
922
+ )
923
+ info_out["batch_count"] = samples.count
924
+ return grad_out, info_out
925
+
926
+ # Treat everything as is multi-agent.
927
+ samples = samples.as_multi_agent()
928
+
929
+ # Calculate gradients for all policies.
930
+ grad_out, info_out = {}, {}
931
+ if self.config.framework_str == "tf":
932
+ for pid, batch in samples.policy_batches.items():
933
+ if self.is_policy_to_train is not None and not self.is_policy_to_train(
934
+ pid, samples
935
+ ):
936
+ continue
937
+ policy = self.policy_map[pid]
938
+ builder = _TFRunBuilder(policy.get_session(), "compute_gradients")
939
+ grad_out[pid], info_out[pid] = policy._build_compute_gradients(
940
+ builder, batch
941
+ )
942
+ grad_out = {k: builder.get(v) for k, v in grad_out.items()}
943
+ info_out = {k: builder.get(v) for k, v in info_out.items()}
944
+ else:
945
+ for pid, batch in samples.policy_batches.items():
946
+ if self.is_policy_to_train is not None and not self.is_policy_to_train(
947
+ pid, samples
948
+ ):
949
+ continue
950
+ grad_out[pid], info_out[pid] = self.policy_map[pid].compute_gradients(
951
+ batch
952
+ )
953
+
954
+ info_out["batch_count"] = samples.count
955
+ if log_once("grad_out"):
956
+ logger.info("Compute grad info:\n\n{}\n".format(summarize(info_out)))
957
+
958
+ return grad_out, info_out
959
+
960
+ def apply_gradients(
961
+ self,
962
+ grads: Union[ModelGradients, Dict[PolicyID, ModelGradients]],
963
+ ) -> None:
964
+ """Applies the given gradients to this worker's models.
965
+
966
+ Uses the Policy's/ies' apply_gradients method(s) to perform the
967
+ operations.
968
+
969
+ Args:
970
+ grads: Single ModelGradients (single-agent case) or a dict
971
+ mapping PolicyIDs to the respective model gradients
972
+ structs.
973
+
974
+ .. testcode::
975
+ :skipif: True
976
+
977
+ import gymnasium as gym
978
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
979
+ from ray.rllib.algorithms.ppo.ppo_tf_policy import PPOTF1Policy
980
+ worker = RolloutWorker(
981
+ env_creator=lambda _: gym.make("CartPole-v1"),
982
+ default_policy_class=PPOTF1Policy)
983
+ samples = worker.sample()
984
+ grads, info = worker.compute_gradients(samples)
985
+ worker.apply_gradients(grads)
986
+ """
987
+ if log_once("apply_gradients"):
988
+ logger.info("Apply gradients:\n\n{}\n".format(summarize(grads)))
989
+ # Grads is a dict (mapping PolicyIDs to ModelGradients).
990
+ # Multi-agent case.
991
+ if isinstance(grads, dict):
992
+ for pid, g in grads.items():
993
+ if self.is_policy_to_train is None or self.is_policy_to_train(
994
+ pid, None
995
+ ):
996
+ self.policy_map[pid].apply_gradients(g)
997
+ # Grads is a ModelGradients type. Single-agent case.
998
+ elif self.is_policy_to_train is None or self.is_policy_to_train(
999
+ DEFAULT_POLICY_ID, None
1000
+ ):
1001
+ self.policy_map[DEFAULT_POLICY_ID].apply_gradients(grads)
1002
+
1003
+ @override(EnvRunner)
1004
+ def get_metrics(self) -> List[RolloutMetrics]:
1005
+ """Returns the thus-far collected metrics from this worker's rollouts.
1006
+
1007
+ Returns:
1008
+ List of RolloutMetrics collected thus-far.
1009
+ """
1010
+ # Get metrics from sampler (if any).
1011
+ if self.sampler is not None:
1012
+ out = self.sampler.get_metrics()
1013
+ else:
1014
+ out = []
1015
+
1016
+ return out
1017
+
1018
+ def foreach_env(self, func: Callable[[EnvType], T]) -> List[T]:
1019
+ """Calls the given function with each sub-environment as arg.
1020
+
1021
+ Args:
1022
+ func: The function to call for each underlying
1023
+ sub-environment (as only arg).
1024
+
1025
+ Returns:
1026
+ The list of return values of all calls to `func([env])`.
1027
+ """
1028
+
1029
+ if self.async_env is None:
1030
+ return []
1031
+
1032
+ envs = self.async_env.get_sub_environments()
1033
+ # Empty list (not implemented): Call function directly on the
1034
+ # BaseEnv.
1035
+ if not envs:
1036
+ return [func(self.async_env)]
1037
+ # Call function on all underlying (vectorized) sub environments.
1038
+ else:
1039
+ return [func(e) for e in envs]
1040
+
1041
+ def foreach_env_with_context(
1042
+ self, func: Callable[[EnvType, EnvContext], T]
1043
+ ) -> List[T]:
1044
+ """Calls given function with each sub-env plus env_ctx as args.
1045
+
1046
+ Args:
1047
+ func: The function to call for each underlying
1048
+ sub-environment and its EnvContext (as the args).
1049
+
1050
+ Returns:
1051
+ The list of return values of all calls to `func([env, ctx])`.
1052
+ """
1053
+
1054
+ if self.async_env is None:
1055
+ return []
1056
+
1057
+ envs = self.async_env.get_sub_environments()
1058
+ # Empty list (not implemented): Call function directly on the
1059
+ # BaseEnv.
1060
+ if not envs:
1061
+ return [func(self.async_env, self.env_context)]
1062
+ # Call function on all underlying (vectorized) sub environments.
1063
+ else:
1064
+ ret = []
1065
+ for i, e in enumerate(envs):
1066
+ ctx = self.env_context.copy_with_overrides(vector_index=i)
1067
+ ret.append(func(e, ctx))
1068
+ return ret
1069
+
1070
+ def get_policy(self, policy_id: PolicyID = DEFAULT_POLICY_ID) -> Optional[Policy]:
1071
+ """Return policy for the specified id, or None.
1072
+
1073
+ Args:
1074
+ policy_id: ID of the policy to return. None for DEFAULT_POLICY_ID
1075
+ (in the single agent case).
1076
+
1077
+ Returns:
1078
+ The policy under the given ID (or None if not found).
1079
+ """
1080
+ return self.policy_map.get(policy_id)
1081
+
1082
+ def add_policy(
1083
+ self,
1084
+ policy_id: PolicyID,
1085
+ policy_cls: Optional[Type[Policy]] = None,
1086
+ policy: Optional[Policy] = None,
1087
+ *,
1088
+ observation_space: Optional[Space] = None,
1089
+ action_space: Optional[Space] = None,
1090
+ config: Optional[PartialAlgorithmConfigDict] = None,
1091
+ policy_state: Optional[PolicyState] = None,
1092
+ policy_mapping_fn=None,
1093
+ policies_to_train: Optional[
1094
+ Union[Collection[PolicyID], Callable[[PolicyID, SampleBatchType], bool]]
1095
+ ] = None,
1096
+ module_spec: Optional[RLModuleSpec] = None,
1097
+ ) -> Policy:
1098
+ """Adds a new policy to this RolloutWorker.
1099
+
1100
+ Args:
1101
+ policy_id: ID of the policy to add.
1102
+ policy_cls: The Policy class to use for constructing the new Policy.
1103
+ Note: Only one of `policy_cls` or `policy` must be provided.
1104
+ policy: The Policy instance to add to this algorithm.
1105
+ Note: Only one of `policy_cls` or `policy` must be provided.
1106
+ observation_space: The observation space of the policy to add.
1107
+ action_space: The action space of the policy to add.
1108
+ config: The config overrides for the policy to add.
1109
+ policy_state: Optional state dict to apply to the new
1110
+ policy instance, right after its construction.
1111
+ policy_mapping_fn: An optional (updated) policy mapping function
1112
+ to use from here on. Note that already ongoing episodes will
1113
+ not change their mapping but will use the old mapping till
1114
+ the end of the episode.
1115
+ policies_to_train: An optional collection of policy IDs to be
1116
+ trained or a callable taking PolicyID and - optionally -
1117
+ SampleBatchType and returning a bool (trainable or not?).
1118
+ If None, will keep the existing setup in place.
1119
+ Policies, whose IDs are not in the list (or for which the
1120
+ callable returns False) will not be updated.
1121
+ module_spec: In the new RLModule API we need to pass in the module_spec for
1122
+ the new module that is supposed to be added. Knowing the policy spec is
1123
+ not sufficient.
1124
+
1125
+ Returns:
1126
+ The newly added policy.
1127
+
1128
+ Raises:
1129
+ ValueError: If both `policy_cls` AND `policy` are provided.
1130
+ KeyError: If the given `policy_id` already exists in this worker's
1131
+ PolicyMap.
1132
+ """
1133
+ validate_module_id(policy_id, error=False)
1134
+
1135
+ if module_spec is not None:
1136
+ raise ValueError(
1137
+ "If you pass in module_spec to the policy, the RLModule API needs "
1138
+ "to be enabled."
1139
+ )
1140
+
1141
+ if policy_id in self.policy_map:
1142
+ raise KeyError(
1143
+ f"Policy ID '{policy_id}' already exists in policy map! "
1144
+ "Make sure you use a Policy ID that has not been taken yet."
1145
+ " Policy IDs that are already in your policy map: "
1146
+ f"{list(self.policy_map.keys())}"
1147
+ )
1148
+ if (policy_cls is None) == (policy is None):
1149
+ raise ValueError(
1150
+ "Only one of `policy_cls` or `policy` must be provided to "
1151
+ "RolloutWorker.add_policy()!"
1152
+ )
1153
+
1154
+ if policy is None:
1155
+ policy_dict_to_add, _ = self.config.get_multi_agent_setup(
1156
+ policies={
1157
+ policy_id: PolicySpec(
1158
+ policy_cls, observation_space, action_space, config
1159
+ )
1160
+ },
1161
+ env=self.env,
1162
+ spaces=self.spaces,
1163
+ default_policy_class=self.default_policy_class,
1164
+ )
1165
+ else:
1166
+ policy_dict_to_add = {
1167
+ policy_id: PolicySpec(
1168
+ type(policy),
1169
+ policy.observation_space,
1170
+ policy.action_space,
1171
+ policy.config,
1172
+ )
1173
+ }
1174
+
1175
+ self.policy_dict.update(policy_dict_to_add)
1176
+ self._update_policy_map(
1177
+ policy_dict=policy_dict_to_add,
1178
+ policy=policy,
1179
+ policy_states={policy_id: policy_state},
1180
+ single_agent_rl_module_spec=module_spec,
1181
+ )
1182
+
1183
+ self.set_policy_mapping_fn(policy_mapping_fn)
1184
+ if policies_to_train is not None:
1185
+ self.set_is_policy_to_train(policies_to_train)
1186
+
1187
+ return self.policy_map[policy_id]
1188
+
1189
+ def remove_policy(
1190
+ self,
1191
+ *,
1192
+ policy_id: PolicyID = DEFAULT_POLICY_ID,
1193
+ policy_mapping_fn: Optional[Callable[[AgentID], PolicyID]] = None,
1194
+ policies_to_train: Optional[
1195
+ Union[Collection[PolicyID], Callable[[PolicyID, SampleBatchType], bool]]
1196
+ ] = None,
1197
+ ) -> None:
1198
+ """Removes a policy from this RolloutWorker.
1199
+
1200
+ Args:
1201
+ policy_id: ID of the policy to be removed. None for
1202
+ DEFAULT_POLICY_ID.
1203
+ policy_mapping_fn: An optional (updated) policy mapping function
1204
+ to use from here on. Note that already ongoing episodes will
1205
+ not change their mapping but will use the old mapping till
1206
+ the end of the episode.
1207
+ policies_to_train: An optional collection of policy IDs to be
1208
+ trained or a callable taking PolicyID and - optionally -
1209
+ SampleBatchType and returning a bool (trainable or not?).
1210
+ If None, will keep the existing setup in place.
1211
+ Policies, whose IDs are not in the list (or for which the
1212
+ callable returns False) will not be updated.
1213
+ """
1214
+ if policy_id not in self.policy_map:
1215
+ raise ValueError(f"Policy ID '{policy_id}' not in policy map!")
1216
+ del self.policy_map[policy_id]
1217
+ del self.preprocessors[policy_id]
1218
+ self.set_policy_mapping_fn(policy_mapping_fn)
1219
+ if policies_to_train is not None:
1220
+ self.set_is_policy_to_train(policies_to_train)
1221
+
1222
+ def set_policy_mapping_fn(
1223
+ self,
1224
+ policy_mapping_fn: Optional[Callable[[AgentID, Any], PolicyID]] = None,
1225
+ ) -> None:
1226
+ """Sets `self.policy_mapping_fn` to a new callable (if provided).
1227
+
1228
+ Args:
1229
+ policy_mapping_fn: The new mapping function to use. If None,
1230
+ will keep the existing mapping function in place.
1231
+ """
1232
+ if policy_mapping_fn is not None:
1233
+ self.policy_mapping_fn = policy_mapping_fn
1234
+ if not callable(self.policy_mapping_fn):
1235
+ raise ValueError("`policy_mapping_fn` must be a callable!")
1236
+
1237
+ def set_is_policy_to_train(
1238
+ self,
1239
+ is_policy_to_train: Union[
1240
+ Collection[PolicyID], Callable[[PolicyID, Optional[SampleBatchType]], bool]
1241
+ ],
1242
+ ) -> None:
1243
+ """Sets `self.is_policy_to_train()` to a new callable.
1244
+
1245
+ Args:
1246
+ is_policy_to_train: A collection of policy IDs to be
1247
+ trained or a callable taking PolicyID and - optionally -
1248
+ SampleBatchType and returning a bool (trainable or not?).
1249
+ If None, will keep the existing setup in place.
1250
+ Policies, whose IDs are not in the list (or for which the
1251
+ callable returns False) will not be updated.
1252
+ """
1253
+ # If collection given, construct a simple default callable returning True
1254
+ # if the PolicyID is found in the list/set of IDs.
1255
+ if not callable(is_policy_to_train):
1256
+ assert isinstance(is_policy_to_train, (list, set, tuple)), (
1257
+ "ERROR: `is_policy_to_train`must be a [list|set|tuple] or a "
1258
+ "callable taking PolicyID and SampleBatch and returning "
1259
+ "True|False (trainable or not?)."
1260
+ )
1261
+ pols = set(is_policy_to_train)
1262
+
1263
+ def is_policy_to_train(pid, batch=None):
1264
+ return pid in pols
1265
+
1266
+ self.is_policy_to_train = is_policy_to_train
1267
+
1268
+ @PublicAPI(stability="alpha")
1269
+ def get_policies_to_train(
1270
+ self, batch: Optional[SampleBatchType] = None
1271
+ ) -> Set[PolicyID]:
1272
+ """Returns all policies-to-train, given an optional batch.
1273
+
1274
+ Loops through all policies currently in `self.policy_map` and checks
1275
+ the return value of `self.is_policy_to_train(pid, batch)`.
1276
+
1277
+ Args:
1278
+ batch: An optional SampleBatchType for the
1279
+ `self.is_policy_to_train(pid, [batch]?)` check.
1280
+
1281
+ Returns:
1282
+ The set of currently trainable policy IDs, given the optional
1283
+ `batch`.
1284
+ """
1285
+ return {
1286
+ pid
1287
+ for pid in self.policy_map.keys()
1288
+ if self.is_policy_to_train is None or self.is_policy_to_train(pid, batch)
1289
+ }
1290
+
1291
+ def for_policy(
1292
+ self,
1293
+ func: Callable[[Policy, Optional[Any]], T],
1294
+ policy_id: Optional[PolicyID] = DEFAULT_POLICY_ID,
1295
+ **kwargs,
1296
+ ) -> T:
1297
+ """Calls the given function with the specified policy as first arg.
1298
+
1299
+ Args:
1300
+ func: The function to call with the policy as first arg.
1301
+ policy_id: The PolicyID of the policy to call the function with.
1302
+
1303
+ Keyword Args:
1304
+ kwargs: Additional kwargs to be passed to the call.
1305
+
1306
+ Returns:
1307
+ The return value of the function call.
1308
+ """
1309
+
1310
+ return func(self.policy_map[policy_id], **kwargs)
1311
+
1312
+ def foreach_policy(
1313
+ self, func: Callable[[Policy, PolicyID, Optional[Any]], T], **kwargs
1314
+ ) -> List[T]:
1315
+ """Calls the given function with each (policy, policy_id) tuple.
1316
+
1317
+ Args:
1318
+ func: The function to call with each (policy, policy ID) tuple.
1319
+
1320
+ Keyword Args:
1321
+ kwargs: Additional kwargs to be passed to the call.
1322
+
1323
+ Returns:
1324
+ The list of return values of all calls to
1325
+ `func([policy, pid, **kwargs])`.
1326
+ """
1327
+ return [func(policy, pid, **kwargs) for pid, policy in self.policy_map.items()]
1328
+
1329
+ def foreach_policy_to_train(
1330
+ self, func: Callable[[Policy, PolicyID, Optional[Any]], T], **kwargs
1331
+ ) -> List[T]:
1332
+ """
1333
+ Calls the given function with each (policy, policy_id) tuple.
1334
+
1335
+ Only those policies/IDs will be called on, for which
1336
+ `self.is_policy_to_train()` returns True.
1337
+
1338
+ Args:
1339
+ func: The function to call with each (policy, policy ID) tuple,
1340
+ for only those policies that `self.is_policy_to_train`
1341
+ returns True.
1342
+
1343
+ Keyword Args:
1344
+ kwargs: Additional kwargs to be passed to the call.
1345
+
1346
+ Returns:
1347
+ The list of return values of all calls to
1348
+ `func([policy, pid, **kwargs])`.
1349
+ """
1350
+ return [
1351
+ # Make sure to only iterate over keys() and not items(). Iterating over
1352
+ # items will access policy_map elements even for pids that we do not need,
1353
+ # i.e. those that are not in policy_to_train. Access to policy_map elements
1354
+ # can cause disk access for policies that were offloaded to disk. Since
1355
+ # these policies will be skipped in the for-loop accessing them is
1356
+ # unnecessary, making subsequent disk access unnecessary.
1357
+ func(self.policy_map[pid], pid, **kwargs)
1358
+ for pid in self.policy_map.keys()
1359
+ if self.is_policy_to_train is None or self.is_policy_to_train(pid, None)
1360
+ ]
1361
+
1362
+ def sync_filters(self, new_filters: dict) -> None:
1363
+ """Changes self's filter to given and rebases any accumulated delta.
1364
+
1365
+ Args:
1366
+ new_filters: Filters with new state to update local copy.
1367
+ """
1368
+ assert all(k in new_filters for k in self.filters)
1369
+ for k in self.filters:
1370
+ self.filters[k].sync(new_filters[k])
1371
+
1372
+ def get_filters(self, flush_after: bool = False) -> Dict:
1373
+ """Returns a snapshot of filters.
1374
+
1375
+ Args:
1376
+ flush_after: Clears the filter buffer state.
1377
+
1378
+ Returns:
1379
+ Dict for serializable filters
1380
+ """
1381
+ return_filters = {}
1382
+ for k, f in self.filters.items():
1383
+ return_filters[k] = f.as_serializable()
1384
+ if flush_after:
1385
+ f.reset_buffer()
1386
+ return return_filters
1387
+
1388
+ def get_state(self) -> dict:
1389
+ filters = self.get_filters(flush_after=True)
1390
+ policy_states = {}
1391
+ for pid in self.policy_map.keys():
1392
+ # If required by the user, only capture policies that are actually
1393
+ # trainable. Otherwise, capture all policies (for saving to disk).
1394
+ if (
1395
+ not self.config.checkpoint_trainable_policies_only
1396
+ or self.is_policy_to_train is None
1397
+ or self.is_policy_to_train(pid)
1398
+ ):
1399
+ policy_states[pid] = self.policy_map[pid].get_state()
1400
+
1401
+ return {
1402
+ # List all known policy IDs here for convenience. When an Algorithm gets
1403
+ # restored from a checkpoint, it will not have access to the list of
1404
+ # possible IDs as each policy is stored in its own sub-dir
1405
+ # (see "policy_states").
1406
+ "policy_ids": list(self.policy_map.keys()),
1407
+ # Note that this field will not be stored in the algorithm checkpoint's
1408
+ # state file, but each policy will get its own state file generated in
1409
+ # a sub-dir within the algo's checkpoint dir.
1410
+ "policy_states": policy_states,
1411
+ # Also store current mapping fn and which policies to train.
1412
+ "policy_mapping_fn": self.policy_mapping_fn,
1413
+ "is_policy_to_train": self.is_policy_to_train,
1414
+ # TODO: Filters will be replaced by connectors.
1415
+ "filters": filters,
1416
+ }
1417
+
1418
+ def set_state(self, state: dict) -> None:
1419
+ # Backward compatibility (old checkpoints' states would have the local
1420
+ # worker state as a bytes object, not a dict).
1421
+ if isinstance(state, bytes):
1422
+ state = pickle.loads(state)
1423
+
1424
+ # TODO: Once filters are handled by connectors, get rid of the "filters"
1425
+ # key in `state` entirely (will be part of the policies then).
1426
+ self.sync_filters(state["filters"])
1427
+
1428
+ # Support older checkpoint versions (< 1.0), in which the policy_map
1429
+ # was stored under the "state" key, not "policy_states".
1430
+ policy_states = (
1431
+ state["policy_states"] if "policy_states" in state else state["state"]
1432
+ )
1433
+ for pid, policy_state in policy_states.items():
1434
+ # If - for some reason - we have an invalid PolicyID in the state,
1435
+ # this might be from an older checkpoint (pre v1.0). Just warn here.
1436
+ validate_module_id(pid, error=False)
1437
+
1438
+ if pid not in self.policy_map:
1439
+ spec = policy_state.get("policy_spec", None)
1440
+ if spec is None:
1441
+ logger.warning(
1442
+ f"PolicyID '{pid}' was probably added on-the-fly (not"
1443
+ " part of the static `multagent.policies` config) and"
1444
+ " no PolicySpec objects found in the pickled policy "
1445
+ f"state. Will not add `{pid}`, but ignore it for now."
1446
+ )
1447
+ else:
1448
+ policy_spec = (
1449
+ PolicySpec.deserialize(spec) if isinstance(spec, dict) else spec
1450
+ )
1451
+ self.add_policy(
1452
+ policy_id=pid,
1453
+ policy_cls=policy_spec.policy_class,
1454
+ observation_space=policy_spec.observation_space,
1455
+ action_space=policy_spec.action_space,
1456
+ config=policy_spec.config,
1457
+ )
1458
+ if pid in self.policy_map:
1459
+ self.policy_map[pid].set_state(policy_state)
1460
+
1461
+ # Also restore mapping fn and which policies to train.
1462
+ if "policy_mapping_fn" in state:
1463
+ self.set_policy_mapping_fn(state["policy_mapping_fn"])
1464
+ if state.get("is_policy_to_train") is not None:
1465
+ self.set_is_policy_to_train(state["is_policy_to_train"])
1466
+
1467
+ def get_weights(
1468
+ self,
1469
+ policies: Optional[Collection[PolicyID]] = None,
1470
+ inference_only: bool = False,
1471
+ ) -> Dict[PolicyID, ModelWeights]:
1472
+ """Returns each policies' model weights of this worker.
1473
+
1474
+ Args:
1475
+ policies: List of PolicyIDs to get the weights from.
1476
+ Use None for all policies.
1477
+ inference_only: This argument is only added for interface
1478
+ consistency with the new api stack.
1479
+
1480
+ Returns:
1481
+ Dict mapping PolicyIDs to ModelWeights.
1482
+
1483
+ .. testcode::
1484
+ :skipif: True
1485
+
1486
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
1487
+ # Create a RolloutWorker.
1488
+ worker = ...
1489
+ weights = worker.get_weights()
1490
+ print(weights)
1491
+
1492
+ .. testoutput::
1493
+
1494
+ {"default_policy": {"layer1": array(...), "layer2": ...}}
1495
+ """
1496
+ if policies is None:
1497
+ policies = list(self.policy_map.keys())
1498
+ policies = force_list(policies)
1499
+
1500
+ return {
1501
+ # Make sure to only iterate over keys() and not items(). Iterating over
1502
+ # items will access policy_map elements even for pids that we do not need,
1503
+ # i.e. those that are not in policies. Access to policy_map elements can
1504
+ # cause disk access for policies that were offloaded to disk. Since these
1505
+ # policies will be skipped in the for-loop accessing them is unnecessary,
1506
+ # making subsequent disk access unnecessary.
1507
+ pid: self.policy_map[pid].get_weights()
1508
+ for pid in self.policy_map.keys()
1509
+ if pid in policies
1510
+ }
1511
+
1512
+ def set_weights(
1513
+ self,
1514
+ weights: Dict[PolicyID, ModelWeights],
1515
+ global_vars: Optional[Dict] = None,
1516
+ weights_seq_no: Optional[int] = None,
1517
+ ) -> None:
1518
+ """Sets each policies' model weights of this worker.
1519
+
1520
+ Args:
1521
+ weights: Dict mapping PolicyIDs to the new weights to be used.
1522
+ global_vars: An optional global vars dict to set this
1523
+ worker to. If None, do not update the global_vars.
1524
+ weights_seq_no: If needed, a sequence number for the weights version
1525
+ can be passed into this method. If not None, will store this seq no
1526
+ (in self.weights_seq_no) and in future calls - if the seq no did not
1527
+ change wrt. the last call - will ignore the call to save on performance.
1528
+
1529
+ .. testcode::
1530
+ :skipif: True
1531
+
1532
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
1533
+ # Create a RolloutWorker.
1534
+ worker = ...
1535
+ weights = worker.get_weights()
1536
+ # Set `global_vars` (timestep) as well.
1537
+ worker.set_weights(weights, {"timestep": 42})
1538
+ """
1539
+ # Only update our weights, if no seq no given OR given seq no is different
1540
+ # from ours.
1541
+ if weights_seq_no is None or weights_seq_no != self.weights_seq_no:
1542
+ # If per-policy weights are object refs, `ray.get()` them first.
1543
+ if weights and isinstance(next(iter(weights.values())), ObjectRef):
1544
+ actual_weights = ray.get(list(weights.values()))
1545
+ weights = {
1546
+ pid: actual_weights[i] for i, pid in enumerate(weights.keys())
1547
+ }
1548
+
1549
+ for pid, w in weights.items():
1550
+ if pid in self.policy_map:
1551
+ self.policy_map[pid].set_weights(w)
1552
+ elif log_once("set_weights_on_non_existent_policy"):
1553
+ logger.warning(
1554
+ "`RolloutWorker.set_weights()` used with weights from "
1555
+ f"policyID={pid}, but this policy cannot be found on this "
1556
+ f"worker! Skipping ..."
1557
+ )
1558
+
1559
+ self.weights_seq_no = weights_seq_no
1560
+
1561
+ if global_vars:
1562
+ self.set_global_vars(global_vars)
1563
+
1564
+ def get_global_vars(self) -> dict:
1565
+ """Returns the current `self.global_vars` dict of this RolloutWorker.
1566
+
1567
+ Returns:
1568
+ The current `self.global_vars` dict of this RolloutWorker.
1569
+
1570
+ .. testcode::
1571
+ :skipif: True
1572
+
1573
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
1574
+ # Create a RolloutWorker.
1575
+ worker = ...
1576
+ global_vars = worker.get_global_vars()
1577
+ print(global_vars)
1578
+
1579
+ .. testoutput::
1580
+
1581
+ {"timestep": 424242}
1582
+ """
1583
+ return self.global_vars
1584
+
1585
+ def set_global_vars(
1586
+ self,
1587
+ global_vars: dict,
1588
+ policy_ids: Optional[List[PolicyID]] = None,
1589
+ ) -> None:
1590
+ """Updates this worker's and all its policies' global vars.
1591
+
1592
+ Updates are done using the dict's update method.
1593
+
1594
+ Args:
1595
+ global_vars: The global_vars dict to update the `self.global_vars` dict
1596
+ from.
1597
+ policy_ids: Optional list of Policy IDs to update. If None, will update all
1598
+ policies on the to-be-updated workers.
1599
+
1600
+ .. testcode::
1601
+ :skipif: True
1602
+
1603
+ worker = ...
1604
+ global_vars = worker.set_global_vars(
1605
+ ... {"timestep": 4242})
1606
+ """
1607
+ # Handle per-policy values.
1608
+ global_vars_copy = global_vars.copy()
1609
+ gradient_updates_per_policy = global_vars_copy.pop(
1610
+ "num_grad_updates_per_policy", {}
1611
+ )
1612
+ self.global_vars["num_grad_updates_per_policy"].update(
1613
+ gradient_updates_per_policy
1614
+ )
1615
+ # Only update explicitly provided policies or those that that are being
1616
+ # trained, in order to avoid superfluous access of policies, which might have
1617
+ # been offloaded to the object store.
1618
+ # Important b/c global vars are constantly being updated.
1619
+ for pid in policy_ids if policy_ids is not None else self.policy_map.keys():
1620
+ if self.is_policy_to_train is None or self.is_policy_to_train(pid, None):
1621
+ self.policy_map[pid].on_global_var_update(
1622
+ dict(
1623
+ global_vars_copy,
1624
+ # If count is None, Policy won't update the counter.
1625
+ **{"num_grad_updates": gradient_updates_per_policy.get(pid)},
1626
+ )
1627
+ )
1628
+
1629
+ # Update all other global vars.
1630
+ self.global_vars.update(global_vars_copy)
1631
+
1632
+ @override(EnvRunner)
1633
+ def stop(self) -> None:
1634
+ """Releases all resources used by this RolloutWorker."""
1635
+
1636
+ # If we have an env -> Release its resources.
1637
+ if self.env is not None:
1638
+ self.async_env.stop()
1639
+
1640
+ # Close all policies' sessions (if tf static graph).
1641
+ for policy in self.policy_map.cache.values():
1642
+ sess = policy.get_session()
1643
+ # Closes the tf session, if any.
1644
+ if sess is not None:
1645
+ sess.close()
1646
+
1647
+ def lock(self) -> None:
1648
+ """Locks this RolloutWorker via its own threading.Lock."""
1649
+ self._lock.acquire()
1650
+
1651
+ def unlock(self) -> None:
1652
+ """Unlocks this RolloutWorker via its own threading.Lock."""
1653
+ self._lock.release()
1654
+
1655
+ def setup_torch_data_parallel(
1656
+ self, url: str, world_rank: int, world_size: int, backend: str
1657
+ ) -> None:
1658
+ """Join a torch process group for distributed SGD."""
1659
+
1660
+ logger.info(
1661
+ "Joining process group, url={}, world_rank={}, "
1662
+ "world_size={}, backend={}".format(url, world_rank, world_size, backend)
1663
+ )
1664
+ torch.distributed.init_process_group(
1665
+ backend=backend, init_method=url, rank=world_rank, world_size=world_size
1666
+ )
1667
+
1668
+ for pid, policy in self.policy_map.items():
1669
+ if not isinstance(policy, (TorchPolicy, TorchPolicyV2)):
1670
+ raise ValueError(
1671
+ "This policy does not support torch distributed", policy
1672
+ )
1673
+ policy.distributed_world_size = world_size
1674
+
1675
+ def creation_args(self) -> dict:
1676
+ """Returns the kwargs dict used to create this worker."""
1677
+ return self._original_kwargs
1678
+
1679
+ def get_host(self) -> str:
1680
+ """Returns the hostname of the process running this evaluator."""
1681
+ return platform.node()
1682
+
1683
+ def get_node_ip(self) -> str:
1684
+ """Returns the IP address of the node that this worker runs on."""
1685
+ return ray.util.get_node_ip_address()
1686
+
1687
+ def find_free_port(self) -> int:
1688
+ """Finds a free port on the node that this worker runs on."""
1689
+ from ray._common.network_utils import find_free_port
1690
+
1691
+ return find_free_port(socket.AF_INET)
1692
+
1693
+ def _update_policy_map(
1694
+ self,
1695
+ *,
1696
+ policy_dict: MultiAgentPolicyConfigDict,
1697
+ policy: Optional[Policy] = None,
1698
+ policy_states: Optional[Dict[PolicyID, PolicyState]] = None,
1699
+ single_agent_rl_module_spec: Optional[RLModuleSpec] = None,
1700
+ ) -> None:
1701
+ """Updates the policy map (and other stuff) on this worker.
1702
+
1703
+ It performs the following:
1704
+ 1. It updates the observation preprocessors and updates the policy_specs
1705
+ with the postprocessed observation_spaces.
1706
+ 2. It updates the policy_specs with the complete algorithm_config (merged
1707
+ with the policy_spec's config).
1708
+ 3. If needed it will update the self.multi_rl_module_spec on this worker
1709
+ 3. It updates the policy map with the new policies
1710
+ 4. It updates the filter dict
1711
+ 5. It calls the on_create_policy() hook of the callbacks on the newly added
1712
+ policies.
1713
+
1714
+ Args:
1715
+ policy_dict: The policy dict to update the policy map with.
1716
+ policy: The policy to update the policy map with.
1717
+ policy_states: The policy states to update the policy map with.
1718
+ single_agent_rl_module_spec: The RLModuleSpec to add to the
1719
+ MultiRLModuleSpec. If None, the config's
1720
+ `get_default_rl_module_spec` method's output will be used to create
1721
+ the policy with.
1722
+ """
1723
+
1724
+ # Update the input policy dict with the postprocessed observation spaces and
1725
+ # merge configs. Also updates the preprocessor dict.
1726
+ updated_policy_dict = self._get_complete_policy_specs_dict(policy_dict)
1727
+
1728
+ # Builds the self.policy_map dict
1729
+ self._build_policy_map(
1730
+ policy_dict=updated_policy_dict,
1731
+ policy=policy,
1732
+ policy_states=policy_states,
1733
+ )
1734
+
1735
+ # Initialize the filter dict
1736
+ self._update_filter_dict(updated_policy_dict)
1737
+
1738
+ # Call callback policy init hooks (only if the added policy did not exist
1739
+ # before).
1740
+ if policy is None:
1741
+ self._call_callbacks_on_create_policy()
1742
+
1743
+ if self.worker_index == 0:
1744
+ logger.info(f"Built policy map: {self.policy_map}")
1745
+ logger.info(f"Built preprocessor map: {self.preprocessors}")
1746
+
1747
+ def _get_complete_policy_specs_dict(
1748
+ self, policy_dict: MultiAgentPolicyConfigDict
1749
+ ) -> MultiAgentPolicyConfigDict:
1750
+ """Processes the policy dict and creates a new copy with the processed attrs.
1751
+
1752
+ This processes the observation_space and prepares them for passing to rl module
1753
+ construction. It also merges the policy configs with the algorithm config.
1754
+ During this processing, we will also construct the preprocessors dict.
1755
+ """
1756
+ from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
1757
+
1758
+ updated_policy_dict = copy.deepcopy(policy_dict)
1759
+ # If our preprocessors dict does not exist yet, create it here.
1760
+ self.preprocessors = self.preprocessors or {}
1761
+ # Loop through given policy-dict and add each entry to our map.
1762
+ for name, policy_spec in sorted(updated_policy_dict.items()):
1763
+ logger.debug("Creating policy for {}".format(name))
1764
+
1765
+ # Policy brings its own complete AlgorithmConfig -> Use it for this policy.
1766
+ if isinstance(policy_spec.config, AlgorithmConfig):
1767
+ merged_conf = policy_spec.config
1768
+ else:
1769
+ # Update the general config with the specific config
1770
+ # for this particular policy.
1771
+ merged_conf: "AlgorithmConfig" = self.config.copy(copy_frozen=False)
1772
+ merged_conf.update_from_dict(policy_spec.config or {})
1773
+
1774
+ # Update num_workers and worker_index.
1775
+ merged_conf.worker_index = self.worker_index
1776
+
1777
+ # Preprocessors.
1778
+ obs_space = policy_spec.observation_space
1779
+ # Initialize preprocessor for this policy to None.
1780
+ self.preprocessors[name] = None
1781
+ if self.preprocessing_enabled:
1782
+ # Policies should deal with preprocessed (automatically flattened)
1783
+ # observations if preprocessing is enabled.
1784
+ preprocessor = ModelCatalog.get_preprocessor_for_space(
1785
+ obs_space,
1786
+ merged_conf.model,
1787
+ include_multi_binary=False,
1788
+ )
1789
+ # Original observation space should be accessible at
1790
+ # obs_space.original_space after this step.
1791
+ if preprocessor is not None:
1792
+ obs_space = preprocessor.observation_space
1793
+
1794
+ policy_spec.config = merged_conf
1795
+ policy_spec.observation_space = obs_space
1796
+
1797
+ return updated_policy_dict
1798
+
1799
+ def _update_policy_dict_with_multi_rl_module(
1800
+ self, policy_dict: MultiAgentPolicyConfigDict
1801
+ ) -> MultiAgentPolicyConfigDict:
1802
+ for name, policy_spec in policy_dict.items():
1803
+ policy_spec.config["__multi_rl_module_spec"] = self.multi_rl_module_spec
1804
+ return policy_dict
1805
+
1806
+ def _build_policy_map(
1807
+ self,
1808
+ *,
1809
+ policy_dict: MultiAgentPolicyConfigDict,
1810
+ policy: Optional[Policy] = None,
1811
+ policy_states: Optional[Dict[PolicyID, PolicyState]] = None,
1812
+ ) -> None:
1813
+ """Adds the given policy_dict to `self.policy_map`.
1814
+
1815
+ Args:
1816
+ policy_dict: The MultiAgentPolicyConfigDict to be added to this
1817
+ worker's PolicyMap.
1818
+ policy: If the policy to add already exists, user can provide it here.
1819
+ policy_states: Optional dict from PolicyIDs to PolicyStates to
1820
+ restore the states of the policies being built.
1821
+ """
1822
+
1823
+ # If our policy_map does not exist yet, create it here.
1824
+ self.policy_map = self.policy_map or PolicyMap(
1825
+ capacity=self.config.policy_map_capacity,
1826
+ policy_states_are_swappable=self.config.policy_states_are_swappable,
1827
+ )
1828
+
1829
+ # Loop through given policy-dict and add each entry to our map.
1830
+ for name, policy_spec in sorted(policy_dict.items()):
1831
+ # Create the actual policy object.
1832
+ if policy is None:
1833
+ new_policy = create_policy_for_framework(
1834
+ policy_id=name,
1835
+ policy_class=get_tf_eager_cls_if_necessary(
1836
+ policy_spec.policy_class, policy_spec.config
1837
+ ),
1838
+ merged_config=policy_spec.config,
1839
+ observation_space=policy_spec.observation_space,
1840
+ action_space=policy_spec.action_space,
1841
+ worker_index=self.worker_index,
1842
+ seed=self.seed,
1843
+ )
1844
+ else:
1845
+ new_policy = policy
1846
+
1847
+ self.policy_map[name] = new_policy
1848
+
1849
+ restore_states = (policy_states or {}).get(name, None)
1850
+ # Set the state of the newly created policy before syncing filters, etc.
1851
+ if restore_states:
1852
+ new_policy.set_state(restore_states)
1853
+
1854
+ def _update_filter_dict(self, policy_dict: MultiAgentPolicyConfigDict) -> None:
1855
+ """Updates the filter dict for the given policy_dict."""
1856
+
1857
+ for name, policy_spec in sorted(policy_dict.items()):
1858
+ new_policy = self.policy_map[name]
1859
+ # Note(jungong) : We should only create new connectors for the
1860
+ # policy iff we are creating a new policy from scratch. i.e,
1861
+ # we should NOT create new connectors when we already have the
1862
+ # policy object created before this function call or have the
1863
+ # restoring states from the caller.
1864
+ # Also note that we cannot just check the existence of connectors
1865
+ # to decide whether we should create connectors because we may be
1866
+ # restoring a policy that has 0 connectors configured.
1867
+ if (
1868
+ new_policy.agent_connectors is None
1869
+ or new_policy.action_connectors is None
1870
+ ):
1871
+ # TODO(jungong) : revisit this. It will be nicer to create
1872
+ # connectors as the last step of Policy.__init__().
1873
+ create_connectors_for_policy(new_policy, policy_spec.config)
1874
+ maybe_get_filters_for_syncing(self, name)
1875
+
1876
+ def _call_callbacks_on_create_policy(self):
1877
+ """Calls the on_create_policy callback for each policy in the policy map."""
1878
+ for name, policy in self.policy_map.items():
1879
+ self.callbacks.on_create_policy(policy_id=name, policy=policy)
1880
+
1881
+ def _get_input_creator_from_config(self):
1882
+ def valid_module(class_path):
1883
+ if (
1884
+ isinstance(class_path, str)
1885
+ and not os.path.isfile(class_path)
1886
+ and "." in class_path
1887
+ ):
1888
+ module_path, class_name = class_path.rsplit(".", 1)
1889
+ try:
1890
+ spec = importlib.util.find_spec(module_path)
1891
+ if spec is not None:
1892
+ return True
1893
+ except (ModuleNotFoundError, ValueError):
1894
+ print(
1895
+ f"module {module_path} not found while trying to get "
1896
+ f"input {class_path}"
1897
+ )
1898
+ return False
1899
+
1900
+ # A callable returning an InputReader object to use.
1901
+ if isinstance(self.config.input_, FunctionType):
1902
+ return self.config.input_
1903
+ # Use RLlib's Sampler classes (SyncSampler).
1904
+ elif self.config.input_ == "sampler":
1905
+ return lambda ioctx: ioctx.default_sampler_input()
1906
+ # Ray Dataset input -> Use `config.input_config` to construct DatasetReader.
1907
+ elif self.config.input_ == "dataset":
1908
+ assert self._ds_shards is not None
1909
+ # Input dataset shards should have already been prepared.
1910
+ # We just need to take the proper shard here.
1911
+ return lambda ioctx: DatasetReader(
1912
+ self._ds_shards[self.worker_index], ioctx
1913
+ )
1914
+ # Dict: Mix of different input methods with different ratios.
1915
+ elif isinstance(self.config.input_, dict):
1916
+ return lambda ioctx: ShuffledInput(
1917
+ MixedInput(self.config.input_, ioctx), self.config.shuffle_buffer_size
1918
+ )
1919
+ # A pre-registered input descriptor (str).
1920
+ elif isinstance(self.config.input_, str) and registry_contains_input(
1921
+ self.config.input_
1922
+ ):
1923
+ return registry_get_input(self.config.input_)
1924
+ # D4RL input.
1925
+ elif "d4rl" in self.config.input_:
1926
+ env_name = self.config.input_.split(".")[-1]
1927
+ return lambda ioctx: D4RLReader(env_name, ioctx)
1928
+ # Valid python module (class path) -> Create using `from_config`.
1929
+ elif valid_module(self.config.input_):
1930
+ return lambda ioctx: ShuffledInput(
1931
+ from_config(self.config.input_, ioctx=ioctx)
1932
+ )
1933
+ # JSON file or list of JSON files -> Use JsonReader (shuffled).
1934
+ else:
1935
+ return lambda ioctx: ShuffledInput(
1936
+ JsonReader(self.config.input_, ioctx), self.config.shuffle_buffer_size
1937
+ )
1938
+
1939
+ def _get_output_creator_from_config(self):
1940
+ if isinstance(self.config.output, FunctionType):
1941
+ return self.config.output
1942
+ elif self.config.output is None:
1943
+ return lambda ioctx: NoopOutput()
1944
+ elif self.config.output == "dataset":
1945
+ return lambda ioctx: DatasetWriter(
1946
+ ioctx, compress_columns=self.config.output_compress_columns
1947
+ )
1948
+ elif self.config.output == "logdir":
1949
+ return lambda ioctx: JsonWriter(
1950
+ ioctx.log_dir,
1951
+ ioctx,
1952
+ max_file_size=self.config.output_max_file_size,
1953
+ compress_columns=self.config.output_compress_columns,
1954
+ )
1955
+ else:
1956
+ return lambda ioctx: JsonWriter(
1957
+ self.config.output,
1958
+ ioctx,
1959
+ max_file_size=self.config.output_max_file_size,
1960
+ compress_columns=self.config.output_compress_columns,
1961
+ )
1962
+
1963
+ def _get_make_sub_env_fn(
1964
+ self, env_creator, env_context, validate_env, env_wrapper, seed
1965
+ ):
1966
+ def _make_sub_env_local(vector_index):
1967
+ # Used to created additional environments during environment
1968
+ # vectorization.
1969
+
1970
+ # Create the env context (config dict + meta-data) for
1971
+ # this particular sub-env within the vectorized one.
1972
+ env_ctx = env_context.copy_with_overrides(vector_index=vector_index)
1973
+ # Create the sub-env.
1974
+ env = env_creator(env_ctx)
1975
+ # Custom validation function given by user.
1976
+ if validate_env is not None:
1977
+ validate_env(env, env_ctx)
1978
+ # Use our wrapper, defined above.
1979
+ env = env_wrapper(env)
1980
+
1981
+ # Make sure a deterministic random seed is set on
1982
+ # all the sub-environments if specified.
1983
+ _update_env_seed_if_necessary(
1984
+ env, seed, env_context.worker_index, vector_index
1985
+ )
1986
+ return env
1987
+
1988
+ if not env_context.remote:
1989
+
1990
+ def _make_sub_env_remote(vector_index):
1991
+ sub_env = _make_sub_env_local(vector_index)
1992
+ self.callbacks.on_sub_environment_created(
1993
+ worker=self,
1994
+ sub_environment=sub_env,
1995
+ env_context=env_context.copy_with_overrides(
1996
+ worker_index=env_context.worker_index,
1997
+ vector_index=vector_index,
1998
+ remote=False,
1999
+ ),
2000
+ )
2001
+ return sub_env
2002
+
2003
+ return _make_sub_env_remote
2004
+
2005
+ else:
2006
+ return _make_sub_env_local
lib/python3.12/site-packages/ray/rllib/evaluation/sample_batch_builder.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import logging
3
+ from typing import TYPE_CHECKING, Any, Dict, List
4
+
5
+ import numpy as np
6
+
7
+ from ray._common.deprecation import deprecation_warning
8
+ from ray.rllib.env.base_env import _DUMMY_AGENT_ID
9
+ from ray.rllib.policy.policy import Policy
10
+ from ray.rllib.policy.sample_batch import MultiAgentBatch, SampleBatch
11
+ from ray.rllib.utils.annotations import OldAPIStack
12
+ from ray.rllib.utils.debug import summarize
13
+ from ray.rllib.utils.typing import AgentID, PolicyID
14
+ from ray.util.debug import log_once
15
+
16
+ if TYPE_CHECKING:
17
+ from ray.rllib.callbacks.callbacks import RLlibCallback
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def _to_float_array(v: List[Any]) -> np.ndarray:
23
+ arr = np.array(v)
24
+ if arr.dtype == np.float64:
25
+ return arr.astype(np.float32) # save some memory
26
+ return arr
27
+
28
+
29
+ @OldAPIStack
30
+ class SampleBatchBuilder:
31
+ """Util to build a SampleBatch incrementally.
32
+
33
+ For efficiency, SampleBatches hold values in column form (as arrays).
34
+ However, it is useful to add data one row (dict) at a time.
35
+ """
36
+
37
+ _next_unroll_id = 0 # disambiguates unrolls within a single episode
38
+
39
+ def __init__(self):
40
+ self.buffers: Dict[str, List] = collections.defaultdict(list)
41
+ self.count = 0
42
+
43
+ def add_values(self, **values: Any) -> None:
44
+ """Add the given dictionary (row) of values to this batch."""
45
+
46
+ for k, v in values.items():
47
+ self.buffers[k].append(v)
48
+ self.count += 1
49
+
50
+ def add_batch(self, batch: SampleBatch) -> None:
51
+ """Add the given batch of values to this batch."""
52
+
53
+ for k, column in batch.items():
54
+ self.buffers[k].extend(column)
55
+ self.count += batch.count
56
+
57
+ def build_and_reset(self) -> SampleBatch:
58
+ """Returns a sample batch including all previously added values."""
59
+
60
+ batch = SampleBatch({k: _to_float_array(v) for k, v in self.buffers.items()})
61
+ if SampleBatch.UNROLL_ID not in batch:
62
+ batch[SampleBatch.UNROLL_ID] = np.repeat(
63
+ SampleBatchBuilder._next_unroll_id, batch.count
64
+ )
65
+ SampleBatchBuilder._next_unroll_id += 1
66
+ self.buffers.clear()
67
+ self.count = 0
68
+ return batch
69
+
70
+
71
+ @OldAPIStack
72
+ class MultiAgentSampleBatchBuilder:
73
+ """Util to build SampleBatches for each policy in a multi-agent env.
74
+
75
+ Input data is per-agent, while output data is per-policy. There is an M:N
76
+ mapping between agents and policies. We retain one local batch builder
77
+ per agent. When an agent is done, then its local batch is appended into the
78
+ corresponding policy batch for the agent's policy.
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ policy_map: Dict[PolicyID, Policy],
84
+ clip_rewards: bool,
85
+ callbacks: "RLlibCallback",
86
+ ):
87
+ """Initialize a MultiAgentSampleBatchBuilder.
88
+
89
+ Args:
90
+ policy_map (Dict[str,Policy]): Maps policy ids to policy instances.
91
+ clip_rewards (Union[bool,float]): Whether to clip rewards before
92
+ postprocessing (at +/-1.0) or the actual value to +/- clip.
93
+ callbacks: RLlib callbacks.
94
+ """
95
+ if log_once("MultiAgentSampleBatchBuilder"):
96
+ deprecation_warning(old="MultiAgentSampleBatchBuilder", error=False)
97
+ self.policy_map = policy_map
98
+ self.clip_rewards = clip_rewards
99
+ # Build the Policies' SampleBatchBuilders.
100
+ self.policy_builders = {k: SampleBatchBuilder() for k in policy_map.keys()}
101
+ # Whenever we observe a new agent, add a new SampleBatchBuilder for
102
+ # this agent.
103
+ self.agent_builders = {}
104
+ # Internal agent-to-policy map.
105
+ self.agent_to_policy = {}
106
+ self.callbacks = callbacks
107
+ # Number of "inference" steps taken in the environment.
108
+ # Regardless of the number of agents involved in each of these steps.
109
+ self.count = 0
110
+
111
+ def total(self) -> int:
112
+ """Returns the total number of steps taken in the env (all agents).
113
+
114
+ Returns:
115
+ int: The number of steps taken in total in the environment over all
116
+ agents.
117
+ """
118
+
119
+ return sum(a.count for a in self.agent_builders.values())
120
+
121
+ def has_pending_agent_data(self) -> bool:
122
+ """Returns whether there is pending unprocessed data.
123
+
124
+ Returns:
125
+ bool: True if there is at least one per-agent builder (with data
126
+ in it).
127
+ """
128
+
129
+ return len(self.agent_builders) > 0
130
+
131
+ def add_values(self, agent_id: AgentID, policy_id: AgentID, **values: Any) -> None:
132
+ """Add the given dictionary (row) of values to this batch.
133
+
134
+ Args:
135
+ agent_id: Unique id for the agent we are adding values for.
136
+ policy_id: Unique id for policy controlling the agent.
137
+ values: Row of values to add for this agent.
138
+ """
139
+
140
+ if agent_id not in self.agent_builders:
141
+ self.agent_builders[agent_id] = SampleBatchBuilder()
142
+ self.agent_to_policy[agent_id] = policy_id
143
+
144
+ # Include the current agent id for multi-agent algorithms.
145
+ if agent_id != _DUMMY_AGENT_ID:
146
+ values["agent_id"] = agent_id
147
+
148
+ self.agent_builders[agent_id].add_values(**values)
149
+
150
+ def postprocess_batch_so_far(self, episode=None) -> None:
151
+ """Apply policy postprocessors to any unprocessed rows.
152
+
153
+ This pushes the postprocessed per-agent batches onto the per-policy
154
+ builders, clearing per-agent state.
155
+
156
+ Args:
157
+ episode (Optional[Episode]): The Episode object that
158
+ holds this MultiAgentBatchBuilder object.
159
+ """
160
+
161
+ # Materialize the batches so far.
162
+ pre_batches = {}
163
+ for agent_id, builder in self.agent_builders.items():
164
+ pre_batches[agent_id] = (
165
+ self.policy_map[self.agent_to_policy[agent_id]],
166
+ builder.build_and_reset(),
167
+ )
168
+
169
+ # Apply postprocessor.
170
+ post_batches = {}
171
+ if self.clip_rewards is True:
172
+ for _, (_, pre_batch) in pre_batches.items():
173
+ pre_batch["rewards"] = np.sign(pre_batch["rewards"])
174
+ elif self.clip_rewards:
175
+ for _, (_, pre_batch) in pre_batches.items():
176
+ pre_batch["rewards"] = np.clip(
177
+ pre_batch["rewards"],
178
+ a_min=-self.clip_rewards,
179
+ a_max=self.clip_rewards,
180
+ )
181
+ for agent_id, (_, pre_batch) in pre_batches.items():
182
+ other_batches = pre_batches.copy()
183
+ del other_batches[agent_id]
184
+ policy = self.policy_map[self.agent_to_policy[agent_id]]
185
+ if (
186
+ not pre_batch.is_single_trajectory()
187
+ or len(set(pre_batch[SampleBatch.EPS_ID])) > 1
188
+ ):
189
+ raise ValueError(
190
+ "Batches sent to postprocessing must only contain steps "
191
+ "from a single trajectory.",
192
+ pre_batch,
193
+ )
194
+ # Call the Policy's Exploration's postprocess method.
195
+ post_batches[agent_id] = pre_batch
196
+ if getattr(policy, "exploration", None) is not None:
197
+ policy.exploration.postprocess_trajectory(
198
+ policy, post_batches[agent_id], policy.get_session()
199
+ )
200
+ post_batches[agent_id] = policy.postprocess_trajectory(
201
+ post_batches[agent_id], other_batches, episode
202
+ )
203
+
204
+ if log_once("after_post"):
205
+ logger.info(
206
+ "Trajectory fragment after postprocess_trajectory():\n\n{}\n".format(
207
+ summarize(post_batches)
208
+ )
209
+ )
210
+
211
+ # Append into policy batches and reset
212
+ from ray.rllib.evaluation.rollout_worker import get_global_worker
213
+
214
+ for agent_id, post_batch in sorted(post_batches.items()):
215
+ self.callbacks.on_postprocess_trajectory(
216
+ worker=get_global_worker(),
217
+ episode=episode,
218
+ agent_id=agent_id,
219
+ policy_id=self.agent_to_policy[agent_id],
220
+ policies=self.policy_map,
221
+ postprocessed_batch=post_batch,
222
+ original_batches=pre_batches,
223
+ )
224
+ self.policy_builders[self.agent_to_policy[agent_id]].add_batch(post_batch)
225
+
226
+ self.agent_builders.clear()
227
+ self.agent_to_policy.clear()
228
+
229
+ def check_missing_dones(self) -> None:
230
+ for agent_id, builder in self.agent_builders.items():
231
+ if not builder.buffers.is_terminated_or_truncated():
232
+ raise ValueError(
233
+ "The environment terminated for all agents, but we still "
234
+ "don't have a last observation for "
235
+ "agent {} (policy {}). ".format(
236
+ agent_id, self.agent_to_policy[agent_id]
237
+ )
238
+ + "Please ensure that you include the last observations "
239
+ "of all live agents when setting '__all__' terminated|truncated "
240
+ "to True. "
241
+ )
242
+
243
+ def build_and_reset(self, episode=None) -> MultiAgentBatch:
244
+ """Returns the accumulated sample batches for each policy.
245
+
246
+ Any unprocessed rows will be first postprocessed with a policy
247
+ postprocessor. The internal state of this builder will be reset.
248
+
249
+ Args:
250
+ episode (Optional[Episode]): The Episode object that
251
+ holds this MultiAgentBatchBuilder object or None.
252
+
253
+ Returns:
254
+ MultiAgentBatch: Returns the accumulated sample batches for each
255
+ policy.
256
+ """
257
+
258
+ self.postprocess_batch_so_far(episode)
259
+ policy_batches = {}
260
+ for policy_id, builder in self.policy_builders.items():
261
+ if builder.count > 0:
262
+ policy_batches[policy_id] = builder.build_and_reset()
263
+ old_count = self.count
264
+ self.count = 0
265
+ return MultiAgentBatch.wrap_as_needed(policy_batches, old_count)
lib/python3.12/site-packages/ray/rllib/evaluation/sampler.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import queue
3
+ from abc import ABCMeta, abstractmethod
4
+ from collections import defaultdict, namedtuple
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ List,
9
+ Optional,
10
+ Type,
11
+ Union,
12
+ )
13
+
14
+ from ray._common.deprecation import DEPRECATED_VALUE, deprecation_warning
15
+ from ray.rllib.env.base_env import BaseEnv, convert_to_base_env
16
+ from ray.rllib.evaluation.collectors.sample_collector import SampleCollector
17
+ from ray.rllib.evaluation.collectors.simple_list_collector import SimpleListCollector
18
+ from ray.rllib.evaluation.env_runner_v2 import EnvRunnerV2, _PerfStats
19
+ from ray.rllib.evaluation.metrics import RolloutMetrics
20
+ from ray.rllib.offline import InputReader
21
+ from ray.rllib.policy.sample_batch import concat_samples
22
+ from ray.rllib.utils.annotations import OldAPIStack, override
23
+ from ray.rllib.utils.framework import try_import_tf
24
+ from ray.rllib.utils.typing import SampleBatchType
25
+ from ray.util.debug import log_once
26
+
27
+ if TYPE_CHECKING:
28
+ from ray.rllib.callbacks.callbacks import RLlibCallback
29
+ from ray.rllib.evaluation.observation_function import ObservationFunction
30
+ from ray.rllib.evaluation.rollout_worker import RolloutWorker
31
+
32
+ tf1, tf, _ = try_import_tf()
33
+ logger = logging.getLogger(__name__)
34
+
35
+ _PolicyEvalData = namedtuple(
36
+ "_PolicyEvalData",
37
+ ["env_id", "agent_id", "obs", "info", "rnn_state", "prev_action", "prev_reward"],
38
+ )
39
+
40
+ # A batch of RNN states with dimensions [state_index, batch, state_object].
41
+ StateBatch = List[List[Any]]
42
+
43
+
44
+ class _NewEpisodeDefaultDict(defaultdict):
45
+ def __missing__(self, env_id):
46
+ if self.default_factory is None:
47
+ raise KeyError(env_id)
48
+ else:
49
+ ret = self[env_id] = self.default_factory(env_id)
50
+ return ret
51
+
52
+
53
+ @OldAPIStack
54
+ class SamplerInput(InputReader, metaclass=ABCMeta):
55
+ """Reads input experiences from an existing sampler."""
56
+
57
+ @override(InputReader)
58
+ def next(self) -> SampleBatchType:
59
+ batches = [self.get_data()]
60
+ batches.extend(self.get_extra_batches())
61
+ if len(batches) == 0:
62
+ raise RuntimeError("No data available from sampler.")
63
+ return concat_samples(batches)
64
+
65
+ @abstractmethod
66
+ def get_data(self) -> SampleBatchType:
67
+ """Called by `self.next()` to return the next batch of data.
68
+
69
+ Override this in child classes.
70
+
71
+ Returns:
72
+ The next batch of data.
73
+ """
74
+ raise NotImplementedError
75
+
76
+ @abstractmethod
77
+ def get_metrics(self) -> List[RolloutMetrics]:
78
+ """Returns list of episode metrics since the last call to this method.
79
+
80
+ The list will contain one RolloutMetrics object per completed episode.
81
+
82
+ Returns:
83
+ List of RolloutMetrics objects, one per completed episode since
84
+ the last call to this method.
85
+ """
86
+ raise NotImplementedError
87
+
88
+ @abstractmethod
89
+ def get_extra_batches(self) -> List[SampleBatchType]:
90
+ """Returns list of extra batches since the last call to this method.
91
+
92
+ The list will contain all SampleBatches or
93
+ MultiAgentBatches that the user has provided thus-far. Users can
94
+ add these "extra batches" to an episode by calling the episode's
95
+ `add_extra_batch([SampleBatchType])` method. This can be done from
96
+ inside an overridden `Policy.compute_actions_from_input_dict(...,
97
+ episodes)` or from a custom callback's `on_episode_[start|step|end]()`
98
+ methods.
99
+
100
+ Returns:
101
+ List of SamplesBatches or MultiAgentBatches provided thus-far by
102
+ the user since the last call to this method.
103
+ """
104
+ raise NotImplementedError
105
+
106
+
107
+ @OldAPIStack
108
+ class SyncSampler(SamplerInput):
109
+ """Sync SamplerInput that collects experiences when `get_data()` is called."""
110
+
111
+ def __init__(
112
+ self,
113
+ *,
114
+ worker: "RolloutWorker",
115
+ env: BaseEnv,
116
+ clip_rewards: Union[bool, float],
117
+ rollout_fragment_length: int,
118
+ count_steps_by: str = "env_steps",
119
+ callbacks: "RLlibCallback",
120
+ multiple_episodes_in_batch: bool = False,
121
+ normalize_actions: bool = True,
122
+ clip_actions: bool = False,
123
+ observation_fn: Optional["ObservationFunction"] = None,
124
+ sample_collector_class: Optional[Type[SampleCollector]] = None,
125
+ render: bool = False,
126
+ # Obsolete.
127
+ policies=None,
128
+ policy_mapping_fn=None,
129
+ preprocessors=None,
130
+ obs_filters=None,
131
+ tf_sess=None,
132
+ horizon=DEPRECATED_VALUE,
133
+ soft_horizon=DEPRECATED_VALUE,
134
+ no_done_at_end=DEPRECATED_VALUE,
135
+ ):
136
+ """Initializes a SyncSampler instance.
137
+
138
+ Args:
139
+ worker: The RolloutWorker that will use this Sampler for sampling.
140
+ env: Any Env object. Will be converted into an RLlib BaseEnv.
141
+ clip_rewards: True for +/-1.0 clipping,
142
+ actual float value for +/- value clipping. False for no
143
+ clipping.
144
+ rollout_fragment_length: The length of a fragment to collect
145
+ before building a SampleBatch from the data and resetting
146
+ the SampleBatchBuilder object.
147
+ count_steps_by: One of "env_steps" (default) or "agent_steps".
148
+ Use "agent_steps", if you want rollout lengths to be counted
149
+ by individual agent steps. In a multi-agent env,
150
+ a single env_step contains one or more agent_steps, depending
151
+ on how many agents are present at any given time in the
152
+ ongoing episode.
153
+ callbacks: The RLlibCallback object to use when episode
154
+ events happen during rollout.
155
+ multiple_episodes_in_batch: Whether to pack multiple
156
+ episodes into each batch. This guarantees batches will be
157
+ exactly `rollout_fragment_length` in size.
158
+ normalize_actions: Whether to normalize actions to the
159
+ action space's bounds.
160
+ clip_actions: Whether to clip actions according to the
161
+ given action_space's bounds.
162
+ observation_fn: Optional multi-agent observation func to use for
163
+ preprocessing observations.
164
+ sample_collector_class: An optional SampleCollector sub-class to
165
+ use to collect, store, and retrieve environment-, model-,
166
+ and sampler data.
167
+ render: Whether to try to render the environment after each step.
168
+ """
169
+ # All of the following arguments are deprecated. They will instead be
170
+ # provided via the passed in `worker` arg, e.g. `worker.policy_map`.
171
+ if log_once("deprecated_sync_sampler_args"):
172
+ if policies is not None:
173
+ deprecation_warning(old="policies")
174
+ if policy_mapping_fn is not None:
175
+ deprecation_warning(old="policy_mapping_fn")
176
+ if preprocessors is not None:
177
+ deprecation_warning(old="preprocessors")
178
+ if obs_filters is not None:
179
+ deprecation_warning(old="obs_filters")
180
+ if tf_sess is not None:
181
+ deprecation_warning(old="tf_sess")
182
+ if horizon != DEPRECATED_VALUE:
183
+ deprecation_warning(old="horizon", error=True)
184
+ if soft_horizon != DEPRECATED_VALUE:
185
+ deprecation_warning(old="soft_horizon", error=True)
186
+ if no_done_at_end != DEPRECATED_VALUE:
187
+ deprecation_warning(old="no_done_at_end", error=True)
188
+
189
+ self.base_env = convert_to_base_env(env)
190
+ self.rollout_fragment_length = rollout_fragment_length
191
+ self.extra_batches = queue.Queue()
192
+ self.perf_stats = _PerfStats(
193
+ ema_coef=worker.config.sampler_perf_stats_ema_coef,
194
+ )
195
+ if not sample_collector_class:
196
+ sample_collector_class = SimpleListCollector
197
+ self.sample_collector = sample_collector_class(
198
+ worker.policy_map,
199
+ clip_rewards,
200
+ callbacks,
201
+ multiple_episodes_in_batch,
202
+ rollout_fragment_length,
203
+ count_steps_by=count_steps_by,
204
+ )
205
+ self.render = render
206
+
207
+ # Keep a reference to the underlying EnvRunnerV2 instance for
208
+ # unit testing purpose.
209
+ self._env_runner_obj = EnvRunnerV2(
210
+ worker=worker,
211
+ base_env=self.base_env,
212
+ multiple_episodes_in_batch=multiple_episodes_in_batch,
213
+ callbacks=callbacks,
214
+ perf_stats=self.perf_stats,
215
+ rollout_fragment_length=rollout_fragment_length,
216
+ count_steps_by=count_steps_by,
217
+ render=self.render,
218
+ )
219
+ self._env_runner = self._env_runner_obj.run()
220
+ self.metrics_queue = queue.Queue()
221
+
222
+ @override(SamplerInput)
223
+ def get_data(self) -> SampleBatchType:
224
+ while True:
225
+ item = next(self._env_runner)
226
+ if isinstance(item, RolloutMetrics):
227
+ self.metrics_queue.put(item)
228
+ else:
229
+ return item
230
+
231
+ @override(SamplerInput)
232
+ def get_metrics(self) -> List[RolloutMetrics]:
233
+ completed = []
234
+ while True:
235
+ try:
236
+ completed.append(
237
+ self.metrics_queue.get_nowait()._replace(
238
+ perf_stats=self.perf_stats.get()
239
+ )
240
+ )
241
+ except queue.Empty:
242
+ break
243
+ return completed
244
+
245
+ @override(SamplerInput)
246
+ def get_extra_batches(self) -> List[SampleBatchType]:
247
+ extra = []
248
+ while True:
249
+ try:
250
+ extra.append(self.extra_batches.get_nowait())
251
+ except queue.Empty:
252
+ break
253
+ return extra
lib/python3.12/site-packages/ray/rllib/evaluation/worker_set.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from ray._common.deprecation import Deprecated
2
+
3
+
4
+ @Deprecated(
5
+ new="ray.rllib.env.env_runner_group.EnvRunnerGroup",
6
+ help="The class has only be renamed w/o any changes in functionality.",
7
+ error=True,
8
+ )
9
+ class WorkerSet:
10
+ pass
lib/python3.12/site-packages/ray/rllib/examples/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (212 Bytes). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/attention_net_supervised.cpython-312.pyc ADDED
Binary file (4.34 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/parametric_actions_cartpole.cpython-312.pyc ADDED
Binary file (4.26 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/__pycache__/parametric_actions_cartpole_embeddings_learnt_by_model.cpython-312.pyc ADDED
Binary file (3.99 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/attention_net_supervised.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @OldAPIStack
2
+ import numpy as np
3
+ from gymnasium.spaces import Box, Discrete
4
+
5
+ from rllib.models.tf.attention_net import TrXLNet
6
+
7
+ from ray.rllib.utils.framework import try_import_tf
8
+
9
+ tf1, tf, tfv = try_import_tf()
10
+
11
+
12
+ def bit_shift_generator(seq_length, shift, batch_size):
13
+ while True:
14
+ values = np.array([0.0, 1.0], dtype=np.float32)
15
+ seq = np.random.choice(values, (batch_size, seq_length, 1))
16
+ targets = np.squeeze(np.roll(seq, shift, axis=1).astype(np.int32))
17
+ targets[:, :shift] = 0
18
+ yield seq, targets
19
+
20
+
21
+ def train_loss(targets, outputs):
22
+ loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
23
+ labels=targets, logits=outputs
24
+ )
25
+ return tf.reduce_mean(loss)
26
+
27
+
28
+ def train_bit_shift(seq_length, num_iterations, print_every_n):
29
+
30
+ optimizer = tf.keras.optimizers.Adam(1e-3)
31
+
32
+ model = TrXLNet(
33
+ observation_space=Box(low=0, high=1, shape=(1,), dtype=np.int32),
34
+ action_space=Discrete(2),
35
+ num_outputs=2,
36
+ model_config={"max_seq_len": seq_length},
37
+ name="trxl",
38
+ num_transformer_units=1,
39
+ attention_dim=10,
40
+ num_heads=5,
41
+ head_dim=20,
42
+ position_wise_mlp_dim=20,
43
+ )
44
+
45
+ shift = 10
46
+ train_batch = 10
47
+ test_batch = 100
48
+ data_gen = bit_shift_generator(seq_length, shift=shift, batch_size=train_batch)
49
+ test_gen = bit_shift_generator(seq_length, shift=shift, batch_size=test_batch)
50
+
51
+ @tf.function
52
+ def update_step(inputs, targets):
53
+ model_out = model(
54
+ {"obs": inputs},
55
+ state=[tf.reshape(inputs, [-1, seq_length, 1])],
56
+ seq_lens=np.full(shape=(train_batch,), fill_value=seq_length),
57
+ )
58
+ optimizer.minimize(
59
+ lambda: train_loss(targets, model_out), lambda: model.trainable_variables
60
+ )
61
+
62
+ for i, (inputs, targets) in zip(range(num_iterations), data_gen):
63
+ inputs_in = np.reshape(inputs, [-1, 1])
64
+ targets_in = np.reshape(targets, [-1])
65
+ update_step(tf.convert_to_tensor(inputs_in), tf.convert_to_tensor(targets_in))
66
+
67
+ if i % print_every_n == 0:
68
+ test_inputs, test_targets = next(test_gen)
69
+ print(i, train_loss(test_targets, model(test_inputs)))
70
+
71
+
72
+ if __name__ == "__main__":
73
+ tf.enable_eager_execution()
74
+ train_bit_shift(
75
+ seq_length=20,
76
+ num_iterations=2000,
77
+ print_every_n=200,
78
+ )
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (219 Bytes). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/action_mask_model.cpython-312.pyc ADDED
Binary file (4.77 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/autoregressive_action_dist.cpython-312.pyc ADDED
Binary file (8.62 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/autoregressive_action_model.cpython-312.pyc ADDED
Binary file (7.25 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/centralized_critic_models.cpython-312.pyc ADDED
Binary file (9.46 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/custom_loss_model.cpython-312.pyc ADDED
Binary file (7.2 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/fast_model.cpython-312.pyc ADDED
Binary file (5.01 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/mobilenet_v2_encoder.cpython-312.pyc ADDED
Binary file (2.69 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/mobilenet_v2_with_lstm_models.cpython-312.pyc ADDED
Binary file (9.11 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/neural_computer.cpython-312.pyc ADDED
Binary file (11 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/parametric_actions_model.cpython-312.pyc ADDED
Binary file (7.64 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/shared_weights_model.cpython-312.pyc ADDED
Binary file (10.3 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/__pycache__/simple_rpg_model.cpython-312.pyc ADDED
Binary file (3.5 kB). View file
 
lib/python3.12/site-packages/ray/rllib/examples/_old_api_stack/models/action_mask_model.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @OldAPIStack
2
+ from gymnasium.spaces import Dict
3
+
4
+ from ray.rllib.models.tf.fcnet import FullyConnectedNetwork
5
+ from ray.rllib.models.tf.tf_modelv2 import TFModelV2
6
+ from ray.rllib.models.torch.fcnet import FullyConnectedNetwork as TorchFC
7
+ from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
8
+ from ray.rllib.utils.framework import try_import_tf, try_import_torch
9
+ from ray.rllib.utils.torch_utils import FLOAT_MIN
10
+
11
+ tf1, tf, tfv = try_import_tf()
12
+ torch, nn = try_import_torch()
13
+
14
+
15
+ class ActionMaskModel(TFModelV2):
16
+ """Model that handles simple discrete action masking.
17
+
18
+ This assumes the outputs are logits for a single Categorical action dist.
19
+ Getting this to work with a more complex output (e.g., if the action space
20
+ is a tuple of several distributions) is also possible but left as an
21
+ exercise to the reader.
22
+ """
23
+
24
+ def __init__(
25
+ self, obs_space, action_space, num_outputs, model_config, name, **kwargs
26
+ ):
27
+
28
+ orig_space = getattr(obs_space, "original_space", obs_space)
29
+ assert (
30
+ isinstance(orig_space, Dict)
31
+ and "action_mask" in orig_space.spaces
32
+ and "observations" in orig_space.spaces
33
+ )
34
+
35
+ super().__init__(obs_space, action_space, num_outputs, model_config, name)
36
+
37
+ self.internal_model = FullyConnectedNetwork(
38
+ orig_space["observations"],
39
+ action_space,
40
+ num_outputs,
41
+ model_config,
42
+ name + "_internal",
43
+ )
44
+
45
+ # disable action masking --> will likely lead to invalid actions
46
+ self.no_masking = model_config["custom_model_config"].get("no_masking", False)
47
+
48
+ def forward(self, input_dict, state, seq_lens):
49
+ # Extract the available actions tensor from the observation.
50
+ action_mask = input_dict["obs"]["action_mask"]
51
+
52
+ # Compute the unmasked logits.
53
+ logits, _ = self.internal_model({"obs": input_dict["obs"]["observations"]})
54
+
55
+ # If action masking is disabled, directly return unmasked logits
56
+ if self.no_masking:
57
+ return logits, state
58
+
59
+ # Convert action_mask into a [0.0 || -inf]-type mask.
60
+ inf_mask = tf.maximum(tf.math.log(action_mask), tf.float32.min)
61
+ masked_logits = logits + inf_mask
62
+
63
+ # Return masked logits.
64
+ return masked_logits, state
65
+
66
+ def value_function(self):
67
+ return self.internal_model.value_function()
68
+
69
+
70
+ class TorchActionMaskModel(TorchModelV2, nn.Module):
71
+ """PyTorch version of above ActionMaskingModel."""
72
+
73
+ def __init__(
74
+ self,
75
+ obs_space,
76
+ action_space,
77
+ num_outputs,
78
+ model_config,
79
+ name,
80
+ **kwargs,
81
+ ):
82
+ orig_space = getattr(obs_space, "original_space", obs_space)
83
+ assert (
84
+ isinstance(orig_space, Dict)
85
+ and "action_mask" in orig_space.spaces
86
+ and "observations" in orig_space.spaces
87
+ )
88
+
89
+ TorchModelV2.__init__(
90
+ self, obs_space, action_space, num_outputs, model_config, name, **kwargs
91
+ )
92
+ nn.Module.__init__(self)
93
+
94
+ self.internal_model = TorchFC(
95
+ orig_space["observations"],
96
+ action_space,
97
+ num_outputs,
98
+ model_config,
99
+ name + "_internal",
100
+ )
101
+
102
+ # disable action masking --> will likely lead to invalid actions
103
+ self.no_masking = False
104
+ if "no_masking" in model_config["custom_model_config"]:
105
+ self.no_masking = model_config["custom_model_config"]["no_masking"]
106
+
107
+ def forward(self, input_dict, state, seq_lens):
108
+ # Extract the available actions tensor from the observation.
109
+ action_mask = input_dict["obs"]["action_mask"]
110
+
111
+ # Compute the unmasked logits.
112
+ logits, _ = self.internal_model({"obs": input_dict["obs"]["observations"]})
113
+
114
+ # If action masking is disabled, directly return unmasked logits
115
+ if self.no_masking:
116
+ return logits, state
117
+
118
+ # Convert action_mask into a [0.0 || -inf]-type mask.
119
+ inf_mask = torch.clamp(torch.log(action_mask), min=FLOAT_MIN)
120
+ masked_logits = logits + inf_mask
121
+
122
+ # Return masked logits.
123
+ return masked_logits, state
124
+
125
+ def value_function(self):
126
+ return self.internal_model.value_function()