Source code for objectrl.utils.environment.noisy_wrappers
# -----------------------------------------------------------------------------------
# ObjectRL: An Object-Oriented Reinforcement Learning Codebase
# Copyright (C) 2025 ADIN Lab
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------------
import gymnasium as gym
import numpy as np
[docs]
class NoisyActionWrapper(gym.ActionWrapper):
"""
A Gymnasium wrapper that injects noise into the agent's actions.
For discrete action spaces, the action is randomly replaced with another action
with a given probability. For continuous action spaces, Gaussian noise is added.
Attributes:
env (gym.Env): The environment to wrap.
noise_act (float): Noise level for the action.
- For discrete spaces: probability of replacing the action.
- For continuous spaces: standard deviation of Gaussian noise.
"""
[docs]
def __init__(self, env: gym.Env, noise_act: float = 0.1) -> None:
"""
Initialize the NoisyActionWrapper.
Args:
env (gym.Env): The environment to wrap.
noise_act (float): Noise level for the action.
- For discrete spaces: probability of replacing the action.
- For continuous spaces: standard deviation of Gaussian noise.
Returns:
None
"""
super().__init__(env)
self.noise_act = noise_act
[docs]
def step(self, action: np.ndarray) -> tuple:
"""
Modify the action by injecting noise, then step the environment.
Args:
action: The original action chosen by the agent.
Returns:
Tuple: (obs, reward, terminated, truncated, info) after stepping the env.
"""
if isinstance(self.action_space, gym.spaces.Discrete):
if np.random.random() < self.noise_act:
action = self.action_space.sample()
else:
eps = self.noise_act * np.random.randn(*action.shape)
action = np.clip(
action + eps, self.action_space.low, self.action_space.high
)
return self.env.step(action)
[docs]
class NoisyObservationWrapper(gym.ObservationWrapper):
"""
A Gymnasium wrapper that injects noise into observations.
Adds Gaussian noise to array-based observations or to values in dictionary observations.
Attributes:
env (gym.Env): The environment to wrap.
noise_obs (float): Standard deviation of Gaussian noise added to observations.
"""
[docs]
def __init__(self, env: gym.Env, noise_obs: float = 0.1) -> None:
"""
Initialize the NoisyObservationWrapper.
Args:
env (gym.Env): The environment to wrap.
noise_obs (float): Standard deviation of Gaussian noise added to observations.
Returns:
None
"""
super().__init__(env)
self.noise_obs = noise_obs
[docs]
def observation(self, obs: np.ndarray | dict) -> np.ndarray | dict:
"""
Apply Gaussian noise to the observation.
Args:
obs (np.ndarray or dict): The observation to be noised.
Returns:
np.ndarray or dict: The noisy observation.
"""
if isinstance(obs, np.ndarray):
eps = self.noise_obs * np.random.randn(*obs.shape)
return obs + eps
elif isinstance(obs, dict):
noisy_obs = {}
for key, value in obs.items():
if isinstance(value, np.ndarray):
eps = self.noise_obs * np.random.randn(*value.shape)
noisy_obs[key] = value + eps
else:
noisy_obs[key] = value
return noisy_obs
else:
raise ValueError("Unsupported observation type")