class_docstr: Stores multi-agent episode data.
  
  The central attribute of the class is the timestep mapping
  `global_t_to_local_t` that maps the global (environment)
  timestep to the local (agent) timesteps.
  
  The `MultiAgentEpisode` is based on the `SingleAgentEpisode`s
  for each agent, stored in `MultiAgentEpisode.agent_episodes`.
class_docstr_tok: ["Stores","multi-agent","episode","data",".","The","central","attribute","of","the","class","is","the","timestep","mapping","`","global_t_to_local_t","`","that","maps","the","global","(","environment",")","timestep","to","the","local","(","agent",")","timesteps",".","The","`","MultiAgentEpisode","`","is","based","on","the","`","SingleAgentEpisode","`","s","for","each","agent",",","stored","in","`","MultiAgentEpisode.agent_episodes","`","."]
docstr: Adds the given `episode_chunk` to the right side of self.
  
  For concatenating episodes the following rules hold:
      - IDs are identical.
      - timesteps match (`t` of `self` matches `t_started` of `episode_chunk`).
  
  Args:
      episode_chunk: `MultiAgentEpsiode` instance that should be concatenated
          to `self`.
code: def concat_episode(self, episode_chunk: "MultiAgentEpisode") -> None:
      """Adds the given `episode_chunk` to the right side of self.
  
      For concatenating episodes the following rules hold:
          - IDs are identical.
          - timesteps match (`t` of `self` matches `t_started` of `episode_chunk`).
  
      Args:
          episode_chunk: `MultiAgentEpsiode` instance that should be concatenated
              to `self`.
      """
      assert episode_chunk.id_ == self.id_
      assert not self.is_done
      # Make sure the timesteps match.
      assert self.t == episode_chunk.t_started
  
      # TODO (simon): Write `validate()` method.
  
      # Make sure, end matches `episode_chunk`'s beginning for all agents.
      # Note, we have to assert for the last local observations as for
      # each agent alive, we need in the successor an initial observation.
      observations: MultiAgentDict = {
          agent_id: agent_obs
          for agent_id, agent_obs in self.get_observations(
              global_ts=False
          ).items()
          if not self.agent_episodes[agent_id].is_done
      }
      for agent_id, agent_obs in episode_chunk.get_observations(
          indices=0
      ).items():
          # Make sure that the same agents stepped at both timesteps.
          assert agent_id in observations
          assert observations[agent_id] == agent_obs
  
      # Call the `SingleAgentEpisode`'s `concat_episode()` method for all agents.
      for agent_id, agent_eps in self.agent_episodes.items():
          if not agent_eps.is_done:
              agent_eps.concat_episode(
                  episode_chunk.agent_episodes[agent_id]
              )
              # Update our timestep mapping.
              # As with observations we need the global timestep mappings to overlap.
              assert (
                  self.global_t_to_local_t[agent_id][-1]
                  == episode_chunk.global_t_to_local_t[agent_id][0]
              )
              self.global_t_to_local_t[
                  agent_id
              ] += episode_chunk.global_t_to_local_t[agent_id][1:]
              # TODO (simon): Check, if this works always.
              # We have to take care of cases where a successor took over full action
              # buffers, b/c it then also took over the last timestep of the global
              # action timestep mapping.
              if (
                  self.global_actions_t[agent_id][-1]
                  == episode_chunk.global_actions_t[agent_id][0]
              ):
                  self.global_actions_t[
                      agent_id
                  ] += episode_chunk.global_actions_t[agent_id][1:]
              # If the action buffer was empty before the successor was created, we
              # can concatenate all values.
              else:
                  self.global_actions_t[
                      agent_id
                  ] += episode_chunk.global_actions_t[agent_id]
  
              indices_for_partial_rewards = (
                  episode_chunk.partial_rewards_t[
                      agent_id
                  ].find_indices_right(self.t)
              )
              # We use the `map()` here with `__getitem__` for the case of empty
              # lists.
              self.partial_rewards_t[agent_id] += list(
                  map(
                      episode_chunk.partial_rewards_t[
                          agent_id
                      ].__getitem__,
                      indices_for_partial_rewards,
                  )
              )
              self.partial_rewards[agent_id] += list(
                  map(
                      episode_chunk.partial_rewards[
                          agent_id
                      ].__getitem__,
                      indices_for_partial_rewards,
                  )
              )
  
      # Copy the agent buffers over.
      self._copy_buffer(episode_chunk)
  
      self.t = episode_chunk.t
      if episode_chunk.is_terminated:
          self.is_terminated = True
      if episode_chunk.is_truncated:
          self.is_truncated = True
  
imports: from collections import defaultdict
  from queue import Queue
  from typing import Any, Dict, List, Optional, Set, Union
  import uuid
  import numpy
  from ray.rllib.env.single_agent_episode import SingleAgentEpisode
  from ray.rllib.policy.sample_batch import MultiAgentBatch
  from ray.rllib.utils.typing import MultiAgentDict
label_desc: Use image node_id 2 for calling the MultiAgentEpisode obj's underlying member method code with example usage: obj.concat_episode(episode_chunk) without return types