829 lines
30 KiB
Python
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__]))
|