docstr: Tests if concatenation of two `SingleAgentEpisode`s works.
  
  This test ensures that concatenation of two episodes work. Note that
  concatenation should only work for two chunks of the same episode, i.e.
  they have the same `id_` and one should be the successor of the other.
  It is also tested that concatenation fails, if timesteps do not match or
  the episode to which we want to concatenate is already terminated.
docstr_tok: ["Tests","if","concatenation","of","two","`","SingleAgentEpisode","`","s","works",".","This","test","ensures","that","concatenation","of","two","episodes","work",".","Note","that","concatenation","should","only","work","for","two","chunks","of","the","same","episode",",","i.e",".","they","have","the","same","`","id_","`","and","one","should","be","the","successor","of","the","other",".","It","is","also","tested","that","concatenation","fails",",","if","timesteps","do","not","match","or","the","episode","to","which","we","want","to","concatenate","is","already","terminated","."]
code: def test_concat_episode(self):
      """Tests if concatenation of two `SingleAgentEpisode`s works.
  
      This test ensures that concatenation of two episodes work. Note that
      concatenation should only work for two chunks of the same episode, i.e.
      they have the same `id_` and one should be the successor of the other.
      It is also tested that concatenation fails, if timesteps do not match or
      the episode to which we want to concatenate is already terminated.
      """
      # Create two episodes and fill them with 100 timesteps each.
      env = TestEnv()
      init_obs, init_info = env.reset()
      episode_1 = SingleAgentEpisode()
      episode_1.add_env_reset(observation=init_obs, infos=init_info)
      # Sample 100 timesteps.
      for i in range(100):
          action = i
          obs, reward, terminated, truncated, info = env.step(action)
          episode_1.add_env_step(
              observation=obs,
              action=action,
              reward=reward,
              infos=info,
              terminated=terminated,
              truncated=truncated,
              extra_model_outputs={"extra": np.random.random(1)},
          )
  
      # Create a successor.
      episode_2 = episode_1.cut()
  
      # Now, sample 100 more timesteps.
      for i in range(100, 200):
          action = i
          obs, reward, terminated, truncated, info = env.step(action)
          episode_2.add_env_step(
              observation=obs,
              action=action,
              reward=reward,
              infos=info,
              terminated=terminated,
              truncated=truncated,
              extra_model_outputs={"extra": np.random.random(1)},
          )
  
      # Assert that the second episode's `t_started` is at the first episode's
      # `t`.
      self.assertTrue(episode_1.t == episode_2.t_started)
      # Assert that the second episode's `t` is at 200.
      self.assertTrue(episode_2.t == 200)
  
      # Manipulate the id of the second episode and make sure an error is
      # thrown during concatenation.
      episode_2.id_ = "wrong"
      with self.assertRaises(AssertionError):
          episode_1.concat_episode(episode_2)
      # Reset the id.
      episode_2.id_ = episode_1.id_
      # Assert that when timesteps do not match an error is thrown.
      episode_2.t_started += 1
      with self.assertRaises(AssertionError):
          episode_1.concat_episode(episode_2)
      # Reset the timestep.
      episode_2.t_started -= 1
      # Assert that when the first episode is already done no concatenation can take
      # place.
      episode_1.is_terminated = True
      with self.assertRaises(AssertionError):
          episode_1.concat_episode(episode_2)
      # Reset `is_terminated`.
      episode_1.is_terminated = False
  
      # Concatenate the episodes.
  
      episode_1.concat_episode(episode_2)
      # Assert that the concatenated episode start at `t_started=0`
      # and has 200 sampled steps, i.e. `t=200`.
      self.assertTrue(episode_1.t_started == 0)
      self.assertTrue(episode_1.t == 200)
      # Assert that all lists have appropriate length.
      self.assertTrue(
          len(episode_1.actions)
          == len(episode_1.rewards)
          == len(episode_1.observations) - 1
          == len(episode_1.infos) - 1
          == 200
      )
      # Assert that specific observations in the two episodes match.
      self.assertEqual(
          episode_2.observations[5], episode_1.observations[105]
      )
  
imports: from collections import defaultdict
  from typing import Any, Dict, Optional, SupportsFloat, Tuple
  import unittest
  import gymnasium
  from gymnasium.core import ActType, ObsType
  import numpy
  from ray.rllib.env.single_agent_episode import SingleAgentEpisode
  from ray.rllib.utils.test_utils import check
label_desc: Use image node_id 6 for calling the TestSingelAgentEpisode obj's underlying member method code with example usage: obj.test_concat_episode() without return types