Source code for pettingzoo.utils.wrappers.pad_action_space

"""Wrappers that pad every agent's action space up to a single shared space."""

from __future__ import annotations

from typing import Any

import numpy as np
from gymnasium.spaces import Box, Discrete, Space
from typing_extensions import override

from pettingzoo.utils.env import ActionType, AECEnv, AgentID, ObsType, ParallelEnv
from pettingzoo.utils.wrappers.base import BaseWrapper
from pettingzoo.utils.wrappers.base_parallel import BaseParallelWrapper
from pettingzoo.utils.wrappers.pad_observations import _check_paddable, _padded_space


def _unpad_action(space: Space[Any], action: Any) -> Any:
    """Maps an action from the shared space back into the agent's own ``space``."""
    if action is None:
        return None
    if isinstance(space, Discrete):
        # an action only another agent has maps to this agent's first action
        start = int(space.start)
        return action if start <= int(action) < start + int(space.n) else space.start
    assert isinstance(space, Box)
    return np.asarray(action)[tuple(slice(0, dim) for dim in space.shape)]


[docs] class PadActionSpaceV1(BaseWrapper[AgentID, ObsType, Any]): """Pads each agent's action space up to one shared action space. The shared space covers the action spaces of ``possible_agents``, built the same way as :class:`PadObservationsV1` builds its observation space: for Box spaces the largest size along each axis, for Discrete spaces the range covering all of them. Every agent then reports that same space. Before an action reaches the wrapped environment it is mapped back into the acting agent's own space: a Box action is sliced to the agent's original shape, and a Discrete action outside the agent's own range becomes its first action (``0`` for a space starting at zero). ``None`` actions of dead agents pass through. Box spaces have to agree on dtype and number of dimensions. Anything other than Box and Discrete, or a mix of both, is rejected. Ported from SuperSuit's pad_action_space_v0; the version suffix continues that numbering. :param env: The AEC environment to wrap. """ def __init__(self, env: AECEnv[AgentID, ObsType, ActionType]): assert isinstance(env, AECEnv), ( "PadActionSpaceV1 is only compatible with AEC environments, " "use PadActionSpaceParallelV1 instead." ) assert hasattr(env, "possible_agents"), ( "environment passed to PadActionSpaceV1 must have a possible_agents list." ) super().__init__(env) spaces = [env.action_space(agent) for agent in env.possible_agents] _check_paddable(spaces, kind="action") self._action_space = _padded_space(spaces) @override def action_space(self, agent: AgentID) -> Space[Any]: return self._action_space @override def step(self, action: Any) -> None: space = self.env.action_space(self.agent_selection) self.env.step(_unpad_action(space, action)) @override def __str__(self) -> str: return f"PadActionSpaceV1<{self.env!s}>"
[docs] class PadActionSpaceParallelV1(BaseParallelWrapper[AgentID, ObsType, Any]): """Pads each agent's action space up to one shared action space. The shared space covers the action spaces of ``possible_agents``, built the same way as :class:`PadObservationsParallelV1` builds its observation space: for Box spaces the largest size along each axis, for Discrete spaces the range covering all of them. Every agent then reports that same space. Before the actions reach the wrapped environment, each one is mapped back into its agent's own space: a Box action is sliced to the agent's original shape, and a Discrete action outside the agent's own range becomes its first action (``0`` for a space starting at zero). Box spaces have to agree on dtype and number of dimensions. Anything other than Box and Discrete, or a mix of both, is rejected. Ported from SuperSuit's pad_action_space_v0; the version suffix continues that numbering. :param env: The parallel environment to wrap. """ def __init__(self, env: ParallelEnv[AgentID, ObsType, ActionType]): assert isinstance(env, ParallelEnv), ( "PadActionSpaceParallelV1 is only compatible with parallel environments, " "use PadActionSpaceV1 instead." ) assert hasattr(env, "possible_agents"), ( "environment passed to PadActionSpaceParallelV1 must have a " "possible_agents list." ) super().__init__(env) spaces = [env.action_space(agent) for agent in env.possible_agents] _check_paddable(spaces, kind="action") self._action_space = _padded_space(spaces) @override def action_space(self, agent: AgentID) -> Space[Any]: return self._action_space @override def step( self, actions: dict[AgentID, Any] ) -> tuple[ dict[AgentID, ObsType], dict[AgentID, float], dict[AgentID, bool], dict[AgentID, bool], dict[AgentID, dict[str, Any]], ]: return self.env.step( { agent: _unpad_action(self.env.action_space(agent), action) for agent, action in actions.items() } ) @override def __str__(self) -> str: return f"PadActionSpaceParallelV1<{self.env!s}>"