Files
2026-07-13 13:17:40 +08:00

829 lines
30 KiB
Python

import random
import unittest
import gymnasium as gym
import numpy as np
import tree # pip install dm-tree
import ray
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.env.multi_agent_env import (
MultiAgentEnv,
MultiAgentEnvWrapper,
)
from ray.rllib.evaluation.rollout_worker import RolloutWorker
from ray.rllib.evaluation.tests.test_rollout_worker import MockPolicy
from ray.rllib.examples._old_api_stack.policy.random_policy import RandomPolicy
from ray.rllib.examples.envs.classes.mock_env import MockEnv, MockEnv2
from ray.rllib.policy.sample_batch import (
convert_ma_batch_to_sample_batch,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.rllib.utils.numpy import one_hot
from ray.rllib.utils.test_utils import check
from ray.tune.registry import register_env
class BasicMultiAgent(MultiAgentEnv):
"""Env of N independent agents, each of which exits after 25 steps."""
metadata = {
"render.modes": ["rgb_array"],
}
render_mode = "rgb_array"
def __init__(self, num):
super().__init__()
self.envs = [MockEnv(25) for _ in range(num)]
self.agents = list(range(num))
self.terminateds = set()
self.truncateds = set()
self.observation_space = gym.spaces.Discrete(2)
self.action_space = gym.spaces.Discrete(2)
self.resetted = False
def reset(self, *, seed=None, options=None):
# Call super's `reset()` method to set the np_random with the value of `seed`.
# Note: This call to super does NOT return anything.
super().reset(seed=seed)
self.resetted = True
self.terminateds = set()
self.truncateds = set()
reset_results = [a.reset() for a in self.envs]
return (
{i: oi[0] for i, oi in enumerate(reset_results)},
{i: oi[1] for i, oi in enumerate(reset_results)},
)
def step(self, action_dict):
obs, rew, terminated, truncated, info = {}, {}, {}, {}, {}
for i, action in action_dict.items():
obs[i], rew[i], terminated[i], truncated[i], info[i] = self.envs[i].step(
action
)
if terminated[i]:
self.terminateds.add(i)
if truncated[i]:
self.truncateds.add(i)
terminated["__all__"] = len(self.terminateds) == len(self.envs)
truncated["__all__"] = len(self.truncateds) == len(self.envs)
return obs, rew, terminated, truncated, info
def render(self):
# Just generate a random image here for demonstration purposes.
# Also see `gym/envs/classic_control/cartpole.py` for
# an example on how to use a Viewer object.
return np.random.randint(0, 256, size=(200, 300, 3), dtype=np.uint8)
class EarlyDoneMultiAgent(MultiAgentEnv):
"""Env for testing when the env terminates (after agent 0 does)."""
def __init__(self):
super().__init__()
self.envs = [MockEnv(3), MockEnv(5)]
self.agents = list(range(len(self.envs)))
self.terminateds = set()
self.truncateds = set()
self.last_obs = {}
self.last_rew = {}
self.last_terminated = {}
self.last_truncated = {}
self.last_info = {}
self.i = 0
self.observation_space = gym.spaces.Discrete(10)
self.action_space = gym.spaces.Discrete(2)
def reset(self, *, seed=None, options=None):
self.terminateds = set()
self.truncateds = set()
self.last_obs = {}
self.last_rew = {}
self.last_terminated = {}
self.last_truncated = {}
self.last_info = {}
self.i = 0
for i, a in enumerate(self.envs):
self.last_obs[i], self.last_info[i] = a.reset()
self.last_rew[i] = 0
self.last_terminated[i] = False
self.last_truncated[i] = False
obs_dict = {self.i: self.last_obs[self.i]}
info_dict = {self.i: self.last_info[self.i]}
self.i = (self.i + 1) % len(self.envs)
return obs_dict, info_dict
def step(self, action_dict):
assert len(self.terminateds) != len(self.envs)
for i, action in action_dict.items():
(
self.last_obs[i],
self.last_rew[i],
self.last_terminated[i],
self.last_truncated[i],
self.last_info[i],
) = self.envs[i].step(action)
obs = {self.i: self.last_obs[self.i]}
rew = {self.i: self.last_rew[self.i]}
terminated = {self.i: self.last_terminated[self.i]}
truncated = {self.i: self.last_truncated[self.i]}
info = {self.i: self.last_info[self.i]}
if terminated[self.i]:
rew[self.i] = 0
self.terminateds.add(self.i)
if truncated[self.i]:
rew[self.i] = 0
self.truncateds.add(self.i)
self.i = (self.i + 1) % len(self.envs)
terminated["__all__"] = len(self.terminateds) == len(self.envs) - 1
truncated["__all__"] = len(self.truncateds) == len(self.envs) - 1
return obs, rew, terminated, truncated, info
class FlexAgentsMultiAgent(MultiAgentEnv):
"""Env of independent agents, each of which exits after n steps."""
def __init__(self):
super().__init__()
self.envs = {}
self.agents = []
self.possible_agents = list(range(10000)) # Absolute max. number of agents.
self.agentID = 0
self.terminateds = set()
self.truncateds = set()
# All agents have the exact same spaces.
self.observation_space = gym.spaces.Discrete(2)
self.action_space = gym.spaces.Discrete(2)
self.resetted = False
def spawn(self):
# Spawn a new agent into the current episode.
agentID = self.agentID
self.envs[agentID] = MockEnv(25)
self.agents.append(agentID)
self.agentID += 1
return agentID
def kill(self, agent_id):
del self.envs[agent_id]
self.agents.remove(agent_id)
def reset(self, *, seed=None, options=None):
self.envs = {}
self.agents.clear()
self.spawn()
self.resetted = True
self.terminateds = set()
self.truncateds = set()
obs = {}
infos = {}
for i, a in self.envs.items():
obs[i], infos[i] = a.reset()
return obs, infos
def step(self, action_dict):
obs, rew, terminated, truncated, info = {}, {}, {}, {}, {}
# Apply the actions.
for i, action in action_dict.items():
obs[i], rew[i], terminated[i], truncated[i], info[i] = self.envs[i].step(
action
)
if terminated[i]:
self.terminateds.add(i)
if truncated[i]:
self.truncateds.add(i)
# Sometimes, add a new agent to the episode.
if random.random() > 0.75 and len(action_dict) > 0:
aid = self.spawn()
obs[aid], rew[aid], terminated[aid], truncated[aid], info[aid] = self.envs[
aid
].step(action)
if terminated[aid]:
self.terminateds.add(aid)
if truncated[aid]:
self.truncateds.add(aid)
# Sometimes, kill an existing agent.
if len(self.envs) > 1 and random.random() > 0.25:
keys = list(self.envs.keys())
aid = random.choice(keys)
self.kill(aid)
terminated[aid] = True
self.terminateds.add(aid)
terminated["__all__"] = len(self.terminateds) == len(self.envs)
truncated["__all__"] = len(self.truncateds) == len(self.envs)
return obs, rew, terminated, truncated, info
class SometimesZeroAgentsMultiAgent(MultiAgentEnv):
"""Multi-agent env in which sometimes, no agent acts.
At each timestep, we determine, which agents emit observations (and thereby request
actions). This set of observing (and action-requesting) agents could be anything
from the empty set to the full set of all agents.
For simplicity, all agents terminate after n timesteps.
"""
def __init__(self, num=3):
super().__init__()
self.agents = list(range(num))
self.envs = [MockEnv(25) for _ in range(self.num_agents)]
self._observations = {}
self._infos = {}
self.terminateds = set()
self.truncateds = set()
self.observation_space = gym.spaces.Discrete(2)
self.action_space = gym.spaces.Discrete(2)
def reset(self, *, seed=None, options=None):
self.terminateds = set()
self.truncateds = set()
self._observations = {}
self._infos = {}
for aid in self._get_random_agents():
self._observations[aid], self._infos[aid] = self.envs[aid].reset()
return self._observations, self._infos
def step(self, action_dict):
rew, terminated, truncated = {}, {}, {}
# Step those agents, for which we have actions from RLlib.
for aid, action in action_dict.items():
(
self._observations[aid],
rew[aid],
terminated[aid],
truncated[aid],
self._infos[aid],
) = self.envs[aid].step(action)
if terminated[aid]:
self.terminateds.add(aid)
if truncated[aid]:
self.truncateds.add(aid)
# Must add the __all__ flag.
terminated["__all__"] = len(self.terminateds) == self.num_agents
truncated["__all__"] = len(self.truncateds) == self.num_agents
# Select some of our observations to be published next (randomly).
obs = {}
infos = {}
for aid in self._get_random_agents():
if aid not in self._observations:
self._observations[aid] = self.observation_space.sample()
self._infos[aid] = {"fourty-two": 42}
obs[aid] = self._observations.pop(aid)
infos[aid] = self._infos.pop(aid)
# Override some of the rewards. Rewards and dones should be always publishable,
# even if no observation/action for an agent was sent/received.
# An agent might get a reward because of the action of another agent. In this
# case, the rewards for that agent are accumulated over the in-between timesteps
# (in which the other agents step, but not this agent).
for aid in self._get_random_agents():
rew[aid] = np.random.rand()
return obs, rew, terminated, truncated, infos
def _get_random_agents(self):
num_observing_agents = np.random.randint(self.num_agents)
aids = np.random.permutation(self.num_agents)[:num_observing_agents]
return {
aid
for aid in aids
if aid not in self.terminateds and aid not in self.truncateds
}
class RoundRobinMultiAgent(MultiAgentEnv):
"""Env of N independent agents, each of which exits after 5 steps.
On each step() of the env, only one agent takes an action."""
def __init__(self, num, increment_obs=False):
super().__init__()
if increment_obs:
# Observations are 0, 1, 2, 3... etc. as time advances
self.envs = [MockEnv2(5) for _ in range(num)]
else:
# Observations are all zeros
self.envs = [MockEnv(5) for _ in range(num)]
self._agent_ids = set(range(num))
self.terminateds = set()
self.truncateds = set()
self.last_obs = {}
self.last_rew = {}
self.last_terminated = {}
self.last_truncated = {}
self.last_info = {}
self.i = 0
self.num = num
self.observation_space = gym.spaces.Discrete(10)
self.action_space = gym.spaces.Discrete(2)
def reset(self, *, seed=None, options=None):
self.terminateds = set()
self.truncateds = set()
self.last_obs = {}
self.last_rew = {}
self.last_terminated = {}
self.last_truncated = {}
self.last_info = {}
self.i = 0
for i, a in enumerate(self.envs):
self.last_obs[i], self.last_info[i] = a.reset()
self.last_rew[i] = 0
self.last_terminated[i] = False
self.last_truncated[i] = False
obs_dict = {self.i: self.last_obs[self.i]}
info_dict = {self.i: self.last_info[self.i]}
self.i = (self.i + 1) % self.num
return obs_dict, info_dict
def step(self, action_dict):
assert len(self.terminateds) != len(self.envs)
for i, action in action_dict.items():
(
self.last_obs[i],
self.last_rew[i],
self.last_terminated[i],
self.last_truncated[i],
self.last_info[i],
) = self.envs[i].step(action)
obs = {self.i: self.last_obs[self.i]}
rew = {self.i: self.last_rew[self.i]}
terminated = {self.i: self.last_terminated[self.i]}
truncated = {self.i: self.last_truncated[self.i]}
info = {self.i: self.last_info[self.i]}
if terminated[self.i]:
rew[self.i] = 0
self.terminateds.add(self.i)
if truncated[self.i]:
self.truncateds.add(self.i)
self.i = (self.i + 1) % self.num
terminated["__all__"] = len(self.terminateds) == len(self.envs)
truncated["__all__"] = len(self.truncateds) == len(self.envs)
return obs, rew, terminated, truncated, info
class NestedMultiAgentEnv(MultiAgentEnv):
DICT_SPACE = gym.spaces.Dict(
{
"sensors": gym.spaces.Dict(
{
"position": gym.spaces.Box(low=-100, high=100, shape=(3,)),
"velocity": gym.spaces.Box(low=-1, high=1, shape=(3,)),
"front_cam": gym.spaces.Tuple(
(
gym.spaces.Box(low=0, high=1, shape=(10, 10, 3)),
gym.spaces.Box(low=0, high=1, shape=(10, 10, 3)),
)
),
"rear_cam": gym.spaces.Box(low=0, high=1, shape=(10, 10, 3)),
}
),
"inner_state": gym.spaces.Dict(
{
"charge": gym.spaces.Discrete(100),
"job_status": gym.spaces.Dict(
{
"task": gym.spaces.Discrete(5),
"progress": gym.spaces.Box(low=0, high=100, shape=()),
}
),
}
),
}
)
TUPLE_SPACE = gym.spaces.Tuple(
[
gym.spaces.Box(low=-100, high=100, shape=(3,)),
gym.spaces.Tuple(
(
gym.spaces.Box(low=0, high=1, shape=(10, 10, 3)),
gym.spaces.Box(low=0, high=1, shape=(10, 10, 3)),
)
),
gym.spaces.Discrete(5),
]
)
def __init__(self):
super().__init__()
self.observation_space = gym.spaces.Dict(
{"dict_agent": self.DICT_SPACE, "tuple_agent": self.TUPLE_SPACE}
)
self.action_space = gym.spaces.Dict(
{
"dict_agent": gym.spaces.Discrete(1),
"tuple_agent": gym.spaces.Discrete(1),
}
)
self._agent_ids = {"dict_agent", "tuple_agent"}
self.steps = 0
self.DICT_SAMPLES = [self.DICT_SPACE.sample() for _ in range(10)]
self.TUPLE_SAMPLES = [self.TUPLE_SPACE.sample() for _ in range(10)]
def reset(self, *, seed=None, options=None):
self.steps = 0
return {
"dict_agent": self.DICT_SAMPLES[0],
"tuple_agent": self.TUPLE_SAMPLES[0],
}, {}
def step(self, actions):
self.steps += 1
obs = {
"dict_agent": self.DICT_SAMPLES[self.steps],
"tuple_agent": self.TUPLE_SAMPLES[self.steps],
}
rew = {
"dict_agent": 0,
"tuple_agent": 0,
}
terminateds = {"__all__": self.steps >= 5}
truncateds = {"__all__": self.steps >= 5}
infos = {
"dict_agent": {},
"tuple_agent": {},
}
return obs, rew, terminateds, truncateds, infos
class TestMultiAgentEnv(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
ray.init()
@classmethod
def tearDownClass(cls) -> None:
ray.shutdown()
def test_basic_mock(self):
env = BasicMultiAgent(4)
obs, info = env.reset()
check(obs, {0: 0, 1: 0, 2: 0, 3: 0})
for _ in range(24):
obs, rew, done, truncated, info = env.step({0: 0, 1: 0, 2: 0, 3: 0})
check(obs, {0: 0, 1: 0, 2: 0, 3: 0})
check(rew, {0: 1, 1: 1, 2: 1, 3: 1})
check(done, {0: False, 1: False, 2: False, 3: False, "__all__": False})
obs, rew, done, truncated, info = env.step({0: 0, 1: 0, 2: 0, 3: 0})
check(done, {0: True, 1: True, 2: True, 3: True, "__all__": True})
def test_round_robin_mock(self):
env = RoundRobinMultiAgent(2)
obs, info = env.reset()
check(obs, {0: 0})
for _ in range(5):
obs, rew, done, truncated, info = env.step({0: 0})
check(obs, {1: 0})
check(done["__all__"], False)
obs, rew, done, truncated, info = env.step({1: 0})
check(obs, {0: 0})
check(done["__all__"], False)
obs, rew, done, truncated, info = env.step({0: 0})
check(done["__all__"], True)
def test_no_reset_until_poll(self):
env = MultiAgentEnvWrapper(lambda v: BasicMultiAgent(2), [], 1)
self.assertFalse(env.get_sub_environments()[0].resetted)
env.poll()
self.assertTrue(env.get_sub_environments()[0].resetted)
def test_vectorize_basic(self):
env = MultiAgentEnvWrapper(lambda v: BasicMultiAgent(2), [], 2)
obs, rew, terminateds, truncateds, _, _ = env.poll()
check(obs, {0: {0: 0, 1: 0}, 1: {0: 0, 1: 0}})
check(rew, {0: {}, 1: {}})
check(terminateds, {0: {"__all__": False}, 1: {"__all__": False}})
check(truncateds, terminateds)
for _ in range(24):
env.send_actions({0: {0: 0, 1: 0}, 1: {0: 0, 1: 0}})
obs, rew, terminateds, truncateds, _, _ = env.poll()
check(obs, {0: {0: 0, 1: 0}, 1: {0: 0, 1: 0}})
check(rew, {0: {0: 1, 1: 1}, 1: {0: 1, 1: 1}})
check(
terminateds,
{
0: {0: False, 1: False, "__all__": False},
1: {0: False, 1: False, "__all__": False},
},
)
check(truncateds, terminateds)
env.send_actions({0: {0: 0, 1: 0}, 1: {0: 0, 1: 0}})
obs, rew, terminateds, truncateds, _, _ = env.poll()
check(
terminateds,
{
0: {0: True, 1: True, "__all__": True},
1: {0: True, 1: True, "__all__": True},
},
)
check(truncateds, terminateds)
# Reset processing
self.assertRaises(
ValueError, lambda: env.send_actions({0: {0: 0, 1: 0}, 1: {0: 0, 1: 0}})
)
init_obs, init_infos = env.try_reset(0)
check(init_obs, {0: {0: 0, 1: 0}})
check(init_infos, {0: {0: {}, 1: {}}})
init_obs, init_infos = env.try_reset(1)
check(init_obs, {1: {0: 0, 1: 0}})
check(init_infos, {1: {0: {}, 1: {}}})
env.send_actions({0: {0: 0, 1: 0}, 1: {0: 0, 1: 0}})
obs, rew, terminateds, truncateds, _, _ = env.poll()
check(obs, {0: {0: 0, 1: 0}, 1: {0: 0, 1: 0}})
check(rew, {0: {0: 1, 1: 1}, 1: {0: 1, 1: 1}})
check(
terminateds,
{
0: {0: False, 1: False, "__all__": False},
1: {0: False, 1: False, "__all__": False},
},
)
check(truncateds, terminateds)
def test_vectorize_round_robin(self):
env = MultiAgentEnvWrapper(lambda v: RoundRobinMultiAgent(2), [], 2)
obs, rew, terminateds, truncateds, _, _ = env.poll()
check(obs, {0: {0: 0}, 1: {0: 0}})
check(rew, {0: {}, 1: {}})
check(truncateds, {0: {"__all__": False}, 1: {"__all__": False}})
env.send_actions({0: {0: 0}, 1: {0: 0}})
obs, rew, terminateds, truncateds, _, _ = env.poll()
check(obs, {0: {1: 0}, 1: {1: 0}})
check(
truncateds,
{0: {"__all__": False, 1: False}, 1: {"__all__": False, 1: False}},
)
env.send_actions({0: {1: 0}, 1: {1: 0}})
obs, rew, terminateds, truncateds, _, _ = env.poll()
check(obs, {0: {0: 0}, 1: {0: 0}})
check(
truncateds,
{0: {"__all__": False, 0: False}, 1: {"__all__": False, 0: False}},
)
def test_multi_agent_sample(self):
def policy_mapping_fn(agent_id, episode, worker, **kwargs):
return "p{}".format(agent_id % 2)
ev = RolloutWorker(
env_creator=lambda _: BasicMultiAgent(5),
default_policy_class=MockPolicy,
config=AlgorithmConfig()
.env_runners(rollout_fragment_length=50, num_env_runners=0)
.multi_agent(
policies={"p0", "p1"},
policy_mapping_fn=policy_mapping_fn,
),
)
batch = ev.sample()
check(batch.count, 50)
check(batch.policy_batches["p0"].count, 150)
check(batch.policy_batches["p1"].count, 100)
check(batch.policy_batches["p0"]["t"].tolist(), list(range(25)) * 6)
def test_multi_agent_sample_sync_remote(self):
ev = RolloutWorker(
env_creator=lambda _: BasicMultiAgent(5),
default_policy_class=MockPolicy,
# This signature will raise a soft-deprecation warning due
# to the new signature we are using (agent_id, episode, **kwargs),
# but should not break this test.
config=AlgorithmConfig()
.env_runners(
rollout_fragment_length=50,
num_env_runners=0,
num_envs_per_env_runner=4,
remote_worker_envs=True,
remote_env_batch_wait_ms=99999999,
)
.multi_agent(
policies={"p0", "p1"},
policy_mapping_fn=lambda agent_id, episode, worker, **kwargs: (
"p{}".format(agent_id % 2)
),
),
)
batch = ev.sample()
check(batch.count, 200)
def test_multi_agent_sample_async_remote(self):
ev = RolloutWorker(
env_creator=lambda _: BasicMultiAgent(5),
default_policy_class=MockPolicy,
config=AlgorithmConfig()
.env_runners(
rollout_fragment_length=50,
num_env_runners=0,
num_envs_per_env_runner=4,
remote_worker_envs=True,
)
.multi_agent(
policies={"p0", "p1"},
policy_mapping_fn=lambda agent_id, episode, worker, **kwargs: (
"p{}".format(agent_id % 2)
),
),
)
batch = ev.sample()
check(batch.count, 200)
def test_sample_from_early_done_env(self):
ev = RolloutWorker(
env_creator=lambda _: EarlyDoneMultiAgent(),
default_policy_class=MockPolicy,
config=AlgorithmConfig()
.env_runners(
rollout_fragment_length=1,
num_env_runners=0,
batch_mode="complete_episodes",
)
.multi_agent(
policies={"p0", "p1"},
policy_mapping_fn=lambda agent_id, episode, worker, **kwargs: (
"p{}".format(agent_id % 2)
),
),
)
# This used to raise an Error due to the EarlyDoneMultiAgent
# terminating at e.g. agent0 w/o publishing the observation for
# agent1 anymore. This limitation is fixed and an env may
# terminate at any time (as well as return rewards for any agent
# at any time, even when that agent doesn't have an obs returned
# in the same call to `step()`).
ma_batch = ev.sample()
# Make sure that agents took the correct (alternating timesteps)
# path. Except for the last timestep, where both agents got
# terminated.
ag0_ts = ma_batch.policy_batches["p0"]["t"]
ag1_ts = ma_batch.policy_batches["p1"]["t"]
self.assertTrue(np.all(np.abs(ag0_ts[:-1] - ag1_ts[:-1]) == 1.0))
self.assertTrue(ag0_ts[-1] == ag1_ts[-1])
def test_multi_agent_with_flex_agents(self):
register_env("flex_agents_multi_agent", lambda _: FlexAgentsMultiAgent())
config = (
PPOConfig()
.api_stack(
enable_env_runner_and_connector_v2=False,
enable_rl_module_and_learner=False,
)
.environment("flex_agents_multi_agent")
.env_runners(num_env_runners=0)
.training(train_batch_size=50, minibatch_size=50, num_epochs=1)
)
algo = config.build()
for i in range(10):
result = algo.train()
print(
"Iteration {}, reward {}, timesteps {}".format(
i,
result[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN],
result[NUM_ENV_STEPS_SAMPLED_LIFETIME],
)
)
algo.stop()
def test_multi_agent_with_sometimes_zero_agents_observing(self):
register_env(
"sometimes_zero_agents", lambda _: SometimesZeroAgentsMultiAgent(num=4)
)
config = (
PPOConfig()
.api_stack(
enable_rl_module_and_learner=False,
enable_env_runner_and_connector_v2=False,
)
.environment("sometimes_zero_agents")
.env_runners(num_env_runners=0)
)
algo = config.build()
for i in range(4):
result = algo.train()
print(
"Iteration {}, reward {}, timesteps {}".format(
i,
result[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN],
result[NUM_ENV_STEPS_SAMPLED_LIFETIME],
)
)
algo.stop()
def test_multi_agent_sample_round_robin(self):
ev = RolloutWorker(
env_creator=lambda _: RoundRobinMultiAgent(5, increment_obs=True),
default_policy_class=MockPolicy,
config=AlgorithmConfig()
.env_runners(
rollout_fragment_length=50,
num_env_runners=0,
)
.multi_agent(
policies={"p0"},
policy_mapping_fn=lambda agent_id, episode, worker, **kwargs: "p0",
),
)
batch = ev.sample()
check(batch.count, 50)
# since we round robin introduce agents into the env, some of the env
# steps don't count as proper transitions
check(batch.policy_batches["p0"].count, 42)
check(
batch.policy_batches["p0"]["obs"][:10],
one_hot(np.array([0, 1, 2, 3, 4] * 2), 10),
)
check(
batch.policy_batches["p0"]["new_obs"][:10],
one_hot(np.array([1, 2, 3, 4, 5] * 2), 10),
)
check(
batch.policy_batches["p0"]["rewards"].tolist()[:10],
[100, 100, 100, 100, 0] * 2,
)
check(
batch.policy_batches["p0"]["terminateds"].tolist()[:10],
[False, False, False, False, True] * 2,
)
check(
batch.policy_batches["p0"]["truncateds"].tolist()[:10],
[False, False, False, False, True] * 2,
)
check(
batch.policy_batches["p0"]["t"].tolist()[:10],
[4, 9, 14, 19, 24, 5, 10, 15, 20, 25],
)
def test_custom_rnn_state_values(self):
h = {"some": {"here": np.array([1.0, 2.0, 3.0])}}
class StatefulPolicy(RandomPolicy):
def compute_actions(
self,
obs_batch,
state_batches=None,
prev_action_batch=None,
prev_reward_batch=None,
episodes=None,
explore=True,
timestep=None,
**kwargs,
):
obs_shape = (len(obs_batch),)
actions = np.zeros(obs_shape, dtype=np.int32)
states = tree.map_structure(
lambda x: np.ones(obs_shape + x.shape) * x, h
)
return actions, [states], {}
def get_initial_state(self):
return [{}] # empty dict
def is_recurrent(self):
return True
ev = RolloutWorker(
env_creator=lambda _: gym.make("CartPole-v1"),
default_policy_class=StatefulPolicy,
config=(
AlgorithmConfig().env_runners(
rollout_fragment_length=5,
num_env_runners=0,
)
# Force `state_in_0` to be repeated every ts in the collected batch
# (even though we don't even have a model that would care about this).
.training(model={"max_seq_len": 1})
),
)
batch = ev.sample()
batch = convert_ma_batch_to_sample_batch(batch)
check(batch.count, 5)
check(batch["state_in_0"][0], {})
check(batch["state_out_0"][0], h)
for i in range(1, 5):
check(batch["state_in_0"][i], h)
check(batch["state_out_0"][i], h)
if __name__ == "__main__":
import sys
import pytest
sys.exit(pytest.main(["-v", __file__]))