PettingZoo Wrappers

PettingZoo includes the following types of wrappers:

  • Conversion Wrappers: wrappers for converting environments between the AEC and Parallel APIs

  • Utility Wrappers: a set of wrappers which provide convenient reusable logic, such as enforcing turn order or clipping out-of-bounds actions.

Conversion wrappers

AEC to Parallel

pettingzoo.utils.conversions.aec_to_parallel(aec_env: AECEnv[AgentID, ObsType, ActionType]) ParallelEnv[AgentID, ObsType, ActionType][source]

Converts an AEC environment to a Parallel environment.

In the case of an existing Parallel environment wrapped using a parallel_to_aec_wrapper, this function will return the original Parallel environment. Otherwise, it will apply the aec_to_parallel_wrapper to convert the environment.

An environment can be converted from an AEC environment to a parallel environment with the aec_to_parallel wrapper shown below. Note that this wrapper makes the following assumptions about the underlying environment:

  1. The environment steps in a cycle, i.e. it steps through every live agent in order.

  2. The environment does not update the observations of the agents except at the end of a cycle.

Most parallel environments in PettingZoo only allocate rewards at the end of a cycle. In these environments, the reward scheme of the AEC API an the parallel API is equivalent. If an AEC environment does allocate rewards within a cycle, then the rewards will be allocated at different timesteps in the AEC environment an the Parallel environment. In particular, the AEC environment will allocate all rewards from one time the agent steps to the next time, while the Parallel environment will allocate all rewards from when the first agent stepped to the last agent stepped.

To convert an AEC environment into a parallel environment:

from pettingzoo import make
from pettingzoo.utils.conversions import aec_to_parallel

env = make("aec", "butterfly/pistonball-v6")
env = aec_to_parallel(env)

Parallel to AEC

pettingzoo.utils.conversions.parallel_to_aec(par_env: ParallelEnv[AgentID, ObsType, ActionType | None]) AECEnv[AgentID, ObsType, ActionType | None][source]

Converts a Parallel environment to an AEC environment.

In the case of an existing AEC environment wrapped using a aec_to_parallel_wrapper, this function will return the original AEC environment. Otherwise, it will apply the parallel_to_aec_wrapper to convert the environment.

Any parallel environment can be efficiently converted to an AEC environment with the parallel_to_aec wrapper.

To convert a parallel environment into an AEC environment:

from pettingzoo import make
from pettingzoo.utils import parallel_to_aec

env = make("parallel", "butterfly/pistonball-v6")
env = parallel_to_aec(env)

Utility Wrappers

We wanted our pettingzoo environments to be both easy to use and easy to implement. To combine these, we have a set of simple wrappers which provide input validation and other convenient reusable logic.

You can apply these wrappers to your environment in a similar manner to the below examples:

To wrap an AEC environment:

from pettingzoo import make
from pettingzoo.utils import TerminateIllegalWrapper

env = make("aec", "classic/tictactoe-v3")
env = TerminateIllegalWrapper(env, illegal_reward=-1)

env.reset()
for agent in env.agent_iter():
    observation, reward, termination, truncation, info = env.last()
    if termination or truncation:
        action = None
    else:
        action = env.action_space(agent).sample()  # this is where you would insert your policy
    env.step(action)
env.close()

Note: Most AEC environments include TerminateIllegalWrapper in their initialization, so this code does not change the environment’s behavior.

To wrap a Parallel environment.

from pettingzoo import make
from pettingzoo.utils import BaseParallelWrapper

parallel_env = make("parallel", "butterfly/pistonball-v6", render_mode="human")
parallel_env = BaseParallelWrapper(parallel_env)

observations, infos = parallel_env.reset()

while parallel_env.agents:
    actions = {agent: parallel_env.action_space(agent).sample() for agent in parallel_env.agents}  # this is where you would insert your policy
    observations, rewards, terminations, truncations, infos = parallel_env.step(actions)

Note

Wrappers are specific to either the AEC or Parallel API unless documented otherwise. Parallel variants include Parallel in their name, such as AgentIndicatorParallelV1. To apply an AEC-only wrapper to a Parallel environment, convert it to AEC, apply the wrapper, and convert it back.

from pettingzoo import make
from pettingzoo.utils import ClipOutOfBoundsWrapper
from pettingzoo.utils import aec_to_parallel

parallel_env = make("aec", "sisl/multiwalker-v9", render_mode="human")
parallel_env = ClipOutOfBoundsWrapper(parallel_env)
parallel_env = aec_to_parallel(parallel_env)

observations, infos = parallel_env.reset()

while parallel_env.agents:
    actions = {agent: parallel_env.action_space(agent).sample() for agent in parallel_env.agents}  # this is where you would insert your policy
    observations, rewards, terminations, truncations, infos = parallel_env.step(actions)
class pettingzoo.utils.wrappers.BaseWrapper(env: AECEnv[AgentID, ObsType, ActionType])[source]

Creates a wrapper around env parameter.

All AECEnv wrappers should inherit from this base class

class pettingzoo.utils.wrappers.TerminateIllegalWrapper(env: AECEnv[AgentID, ObsType, ActionType], illegal_reward: float)[source]

This wrapper terminates the game with the current player losing in case of illegal values.

Parameters:

illegal_reward – number that is the value of the player making an illegal move.

class pettingzoo.utils.wrappers.CaptureStdoutWrapper(env: AECEnv[AgentID, ObsType, ActionType])[source]

Takes an environment which prints to terminal, and gives it an ansi render mode where it captures the terminal output and returns it as a string instead.

class pettingzoo.utils.wrappers.AssertOutOfBoundsWrapper(env: AECEnv[AgentID, ObsType, ActionType])[source]

Asserts if the action given to step is outside of the action space.

class pettingzoo.utils.wrappers.ClipOutOfBoundsWrapper(env: AECEnv[Any, Any, Any])[source]

Clips the input action to fit in the continuous action space (emitting a warning if it does so).

Applied to continuous environments in pettingzoo.

class pettingzoo.utils.wrappers.ClipRewardV1(env: AECEnv[AgentID, ObsType, ActionType], min_reward: float = -1, max_reward: float = 1)[source]

Clips each agent’s per-step reward into [min_reward, max_reward].

After every reset and step the inner rewards dict is clipped and the running total is rebuilt from that, so last() reports the sum of clipped steps. That is SuperSuit’s clip_reward_v0.

Parameters:
  • env – The AEC environment to wrap.

  • min_reward – Lower bound, inclusive. Default -1.

  • max_reward – Upper bound, inclusive. Default 1.

class pettingzoo.utils.wrappers.ClipRewardParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], min_reward: float = -1, max_reward: float = 1)[source]

Clips each agent’s per-step reward into [min_reward, max_reward].

SuperSuit’s clip_reward_v0 has no Parallel implementation of its own. This one clips the reward dict that step() returns.

Parameters:
  • env – The parallel environment to wrap.

  • min_reward – Lower bound, inclusive. Default -1.

  • max_reward – Upper bound, inclusive. Default 1.

class pettingzoo.utils.wrappers.OrderEnforcingWrapper(env: AECEnv[AgentID, ObsType, ActionType])[source]

Checks if function calls or attribute access are in a disallowed order.

The following are raised: * AttributeError if any of the following are accessed before reset():

rewards, terminations, truncations, infos, agent_selection, num_agents, agents.

  • An error if any of the following are called before reset: render(), step(), observe(), state(), agent_iter()

  • A warning if step() is called when there are no agents remaining.

class pettingzoo.utils.wrappers.AgentIndicatorV1(env: AECEnv[AgentID, ObsType, ActionType], type_only: bool = False)[source]

Adds an agent indicator to each observation.

With type_only=True, agents named <type>_<n> share an indicator for their type.

Parameters:
  • env – The AEC environment to wrap.

  • type_only – Whether to indicate agent types instead of individual agents.

class pettingzoo.utils.wrappers.AgentIndicatorParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], type_only: bool = False)[source]

Adds an agent indicator to each observation.

With type_only=True, agents named <type>_<n> share an indicator for their type.

Parameters:
  • env – The parallel environment to wrap.

  • type_only – Whether to indicate agent types instead of individual agents.

class pettingzoo.utils.wrappers.ColorReductionObservationV1(env: AECEnv[AgentID, ObsType, ActionType], mode: str = 'full')[source]

Reduces an image observation to a single channel.

"full" converts to grayscale with the luminance weights [0.299, 0.587, 0.114] and returns uint8 whatever the input dtype was. "R", "G" and "B" take the named channel and keep the input dtype. Either way the trailing channel axis is dropped, so an (H, W, 3) observation becomes (H, W).

Parameters:
  • env – The AEC environment to wrap.

  • mode – One of “full”, “R”, “G”, “B”.

class pettingzoo.utils.wrappers.ColorReductionObservationParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], mode: str = 'full')[source]

Reduces an image observation to a single channel.

"full" converts to grayscale with the luminance weights [0.299, 0.587, 0.114] and returns uint8 whatever the input dtype was. "R", "G" and "B" take the named channel and keep the input dtype. Either way the trailing channel axis is dropped, so an (H, W, 3) observation becomes (H, W).

Parameters:
  • env – The parallel environment to wrap.

  • mode – One of “full”, “R”, “G”, “B”.

class pettingzoo.utils.wrappers.DtypeObservationV1(env: AECEnv[AgentID, ObsType, ActionType], dtype: Any)[source]

Recasts each agent’s observation to dtype.

The observation space becomes a Box with the same shape and the new dtype, with the old bounds put through the same cast. Every space in possible_agents is built and checked when the wrapper is created.

Bounds that do not fit the new dtype wrap around, and casting an infinite bound to an integer dtype is undefined in numpy, so an unbounded Box cast to an integer dtype collapses to a single point that will not contain the observations. Both match SuperSuit’s dtype_v0.

Parameters:
  • env – The AEC environment to wrap.

  • dtype – Anything numpy.dtype accepts, e.g. np.float32.

class pettingzoo.utils.wrappers.DtypeObservationParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], dtype: Any)[source]

Recasts each agent’s observation to dtype.

The observation space becomes a Box with the same shape and the new dtype, with the old bounds put through the same cast. Every space in possible_agents is built and checked when the wrapper is created.

Bounds that do not fit the new dtype wrap around, and casting an infinite bound to an integer dtype is undefined in numpy, so an unbounded Box cast to an integer dtype collapses to a single point that will not contain the observations. Both match SuperSuit’s dtype_v0.

Parameters:
  • env – The parallel environment to wrap.

  • dtype – Anything numpy.dtype accepts, e.g. np.float32.

class pettingzoo.utils.wrappers.MaxObservationV1(env: AECEnv[AgentID, ObsType, ActionType], memory: int)[source]

Replaces each agent’s observation with the elementwise max over its last memory observations.

This removes the flicker in environments that draw sprites on alternate frames. Histories are per agent and cleared on reset, and the observation space is unchanged. Only array observation spaces work: Box, MultiBinary, MultiDiscrete. Ported from SuperSuit’s max_observation_v0; the version suffix continues that numbering.

Parameters:
  • env – The AEC environment to wrap.

  • memory – How many observations to take the max over. Must be a positive int.

class pettingzoo.utils.wrappers.MaxObservationParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], memory: int)[source]

Replaces each agent’s observation with the elementwise max over its last memory observations.

This removes the flicker in environments that draw sprites on alternate frames. Histories are per agent and cleared on reset, and the observation space is unchanged. Only array observation spaces work: Box, MultiBinary, MultiDiscrete. Ported from SuperSuit’s max_observation_v0; the version suffix continues that numbering.

Parameters:
  • env – The parallel environment to wrap.

  • memory – How many observations to take the max over. Must be a positive int.

class pettingzoo.utils.wrappers.PadObservationsV1(env: AECEnv[AgentID, ObsType, ActionType])[source]

Pads each agent’s observation up to one shared observation space.

The shared space covers the observation spaces of possible_agents. For Box spaces it takes the largest size along each axis, and each observation is padded with zeros at the end of every axis, so the real values stay at the front. For Discrete spaces it takes the range covering all of them and observations pass through unchanged. Every agent then reports that same space.

Box spaces have to agree on dtype and number of dimensions. Anything other than Box and Discrete is rejected.

An agent whose observation is already the full shape gets the array back without a copy, so writing into it writes into the environment.

Ported from SuperSuit’s pad_observations_v0; the version suffix continues that numbering.

Parameters:

env – The AEC environment to wrap.

class pettingzoo.utils.wrappers.PadObservationsParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType])[source]

Pads each agent’s observation up to one shared observation space.

The shared space covers the observation spaces of possible_agents. For Box spaces it takes the largest size along each axis, and each observation is padded with zeros at the end of every axis, so the real values stay at the front. For Discrete spaces it takes the range covering all of them and observations pass through unchanged. Every agent then reports that same space.

Box spaces have to agree on dtype and number of dimensions. Anything other than Box and Discrete is rejected.

An agent whose observation is already the full shape gets the array back without a copy, so writing into it writes into the environment.

Ported from SuperSuit’s pad_observations_v0; the version suffix continues that numbering.

Parameters:

env – The parallel environment to wrap.

class pettingzoo.utils.wrappers.RescaleObservationV1(env: AECEnv[AgentID, ObsType, ActionType], min_obs: float = 0.0, max_obs: float = 1.0)[source]

Linearly rescales each agent’s Box observation into a fixed range.

Every element is mapped from its own [low, high] in the wrapped space onto [min_obs, max_obs], and the result is clipped so it always sits inside the advertised space. The wrapped space has to be a float Box whose elements all have finite low and high with high > low, since anything else gives nothing to scale from. Every space in possible_agents is checked when the wrapper is built.

Ported from SuperSuit’s normalize_obs_v0; the version suffix continues that numbering.

Parameters:
  • env – The AEC environment to wrap.

  • min_obs – Lower bound of the rescaled observations.

  • max_obs – Upper bound of the rescaled observations.

class pettingzoo.utils.wrappers.RescaleObservationParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], min_obs: float = 0.0, max_obs: float = 1.0)[source]

Linearly rescales each agent’s Box observation into a fixed range.

Every element is mapped from its own [low, high] in the wrapped space onto [min_obs, max_obs], and the result is clipped so it always sits inside the advertised space. The wrapped space has to be a float Box whose elements all have finite low and high with high > low, since anything else gives nothing to scale from. Every space in possible_agents is checked when the wrapper is built.

Ported from SuperSuit’s normalize_obs_v0; the version suffix continues that numbering.

Parameters:
  • env – The parallel environment to wrap.

  • min_obs – Lower bound of the rescaled observations.

  • max_obs – Upper bound of the rescaled observations.

class pettingzoo.utils.wrappers.ReshapeObservationV1(env: AECEnv[AgentID, ObsType, ActionType], shape: tuple[int, ...])[source]

Reshapes each agent’s observation to the given shape.

The observation space is reshaped the same way, so per-element bounds are kept. Only works on Box observation spaces.

Parameters:
  • env – The AEC environment to wrap.

  • shape – The new observation shape. Must have as many elements as the old one.

class pettingzoo.utils.wrappers.ReshapeObservationParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], shape: tuple[int, ...])[source]

Reshapes each agent’s observation to the given shape.

The observation space is reshaped the same way, so per-element bounds are kept. Only works on Box observation spaces.

Parameters:
  • env – The parallel environment to wrap.

  • shape – The new observation shape. Must have as many elements as the old one.

class pettingzoo.utils.wrappers.ScaleActionV1(env: AECEnv[AgentID, ObsType, ActionType], scale: float)[source]

Scales the bounds of each agent’s Box action space by a constant factor.

The action space this wrapper advertises has low and high multiplied by scale. Actions are divided by scale before they reach the wrapped environment, so an action drawn from the advertised space is a valid action for the wrapped environment. Actions on a bound of the advertised space are clipped to the wrapped bound, since the round trip through scale is not exact in floating point. An action outside the advertised space is passed through as is.

Scaling an integer Box narrows what can be reached: at scale 0.5, a Box(0, 10, dtype=int64) advertises Box(0, 5) and only even actions are reachable. A bound large enough that bound * scale overflows the dtype becomes infinite, and actions there stay infinite.

Parameters:
  • env – The AEC environment to wrap.

  • scale – Non-zero factor applied to the action space bounds. A negative scale flips the bounds, so they are swapped to keep the Box valid.

class pettingzoo.utils.wrappers.ScaleActionParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], scale: float)[source]

Scales the bounds of each agent’s Box action space by a constant factor.

The action space this wrapper advertises has low and high multiplied by scale. Actions are divided by scale before they reach the wrapped environment, so an action drawn from the advertised space is a valid action for the wrapped environment. Actions on a bound of the advertised space are clipped to the wrapped bound, since the round trip through scale is not exact in floating point. An action outside the advertised space is passed through as is.

Scaling an integer Box narrows what can be reached: at scale 0.5, a Box(0, 10, dtype=int64) advertises Box(0, 5) and only even actions are reachable. A bound large enough that bound * scale overflows the dtype becomes infinite, and actions there stay infinite.

Parameters:
  • env – The parallel environment to wrap.

  • scale – Non-zero factor applied to the action space bounds. A negative scale flips the bounds, so they are swapped to keep the Box valid.

class pettingzoo.utils.wrappers.StickyActionV1(env: AECEnv[AgentID, ObsType, ActionType], repeat_action_probability: float)[source]

Repeats an agent’s previous action with probability repeat_action_probability.

Each agent keeps its own previous action, and an agent has nothing to repeat until it has acted once since the last reset(), so the first action after a reset is always passed through. Only this wrapper’s own reset clears the memory: an environment that auto-resets underneath it, such as MultiEpisodeParallelEnv on the inside, can still have the first action of a later episode repeated. The remembered action is a deep copy, so reusing an action buffer between steps does not defeat the wrapper.

Seeding follows the usual PettingZoo pattern: reset() reseeds from the seed it is given, which means reset(seed=None) starts a fresh unseeded stream. Pass a seed on every reset if you want a whole multi-episode run to be reproducible.

Parameters:
  • env – The AEC environment to wrap.

  • repeat_action_probability – Probability of using the previous action, in the interval [0, 1).

class pettingzoo.utils.wrappers.StickyActionParallelV1(env: ParallelEnv[AgentID, ObsType, ActionType], repeat_action_probability: float)[source]

Repeats an agent’s previous action with probability repeat_action_probability.

Each agent keeps its own previous action, and an agent has nothing to repeat until it has acted once since the last reset(), so the first action after a reset is always passed through. Only this wrapper’s own reset clears the memory: an environment that auto-resets underneath it, such as MultiEpisodeParallelEnv on the inside, can still have the first action of a later episode repeated. The remembered action is a deep copy, so reusing an action buffer between steps does not defeat the wrapper.

Seeding follows the usual PettingZoo pattern: reset() reseeds from the seed it is given, which means reset(seed=None) starts a fresh unseeded stream. Pass a seed on every reset if you want a whole multi-episode run to be reproducible.

Parameters:
  • env – The parallel environment to wrap.

  • repeat_action_probability – Probability of using the previous action, in the interval [0, 1).