Merge branch 'master' into feat/mps-support

This commit is contained in:
Antonin RAFFIN 2023-08-17 16:55:21 +02:00 committed by GitHub
commit ef39571f86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 254 additions and 103 deletions

View file

@ -67,7 +67,7 @@ body:
required: true required: true
- label: I have read the [documentation](https://stable-baselines3.readthedocs.io/en/master/) - label: I have read the [documentation](https://stable-baselines3.readthedocs.io/en/master/)
required: true required: true
- label: I have provided a minimal working example to reproduce the bug - label: I have provided a [minimal and working](https://github.com/DLR-RM/stable-baselines3/issues/982#issuecomment-1197044014) example to reproduce the bug
required: true required: true
- label: I've used the [markdown code blocks](https://help.github.com/en/articles/creating-and-highlighting-code-blocks) for both code and stack traces. - label: I've used the [markdown code blocks](https://help.github.com/en/articles/creating-and-highlighting-code-blocks) for both code and stack traces.
required: true required: true

View file

@ -28,7 +28,7 @@ body:
attributes: attributes:
label: Code example label: Code example
description: | description: |
Please try to provide a minimal example to reproduce the bug. Please try to provide a [minimal example](https://github.com/DLR-RM/stable-baselines3/issues/982#issuecomment-1197044014) to reproduce the bug.
For a custom environment, you need to give at least the observation space, action space, `reset()` and `step()` methods (see working example below). For a custom environment, you need to give at least the observation space, action space, `reset()` and `step()` methods (see working example below).
Error messages and stack traces are also helpful. Error messages and stack traces are also helpful.
Please use the [markdown code blocks](https://help.github.com/en/articles/creating-and-highlighting-code-blocks) for both code and stack traces. Please use the [markdown code blocks](https://help.github.com/en/articles/creating-and-highlighting-code-blocks) for both code and stack traces.
@ -101,7 +101,7 @@ body:
required: true required: true
- label: I have read the [documentation](https://stable-baselines3.readthedocs.io/en/master/) - label: I have read the [documentation](https://stable-baselines3.readthedocs.io/en/master/)
required: true required: true
- label: I have provided a minimal working example to reproduce the bug - label: I have provided a [minimal and working](https://github.com/DLR-RM/stable-baselines3/issues/982#issuecomment-1197044014) example to reproduce the bug
required: true required: true
- label: I have checked my env using the env checker - label: I have checked my env using the env checker
required: true required: true

View file

@ -42,3 +42,5 @@ body:
options: options:
- label: I have checked that there is no similar [issue](https://github.com/DLR-RM/stable-baselines3/issues) in the repo - label: I have checked that there is no similar [issue](https://github.com/DLR-RM/stable-baselines3/issues) in the repo
required: true required: true
- label: If I'm requesting a new feature, I have proposed alternatives
required: true

View file

@ -26,7 +26,7 @@ body:
required: true required: true
- label: I have read the [documentation](https://stable-baselines3.readthedocs.io/en/master/) - label: I have read the [documentation](https://stable-baselines3.readthedocs.io/en/master/)
required: true required: true
- label: If code there is, it is minimal and working - label: If code there is, it is [minimal and working](https://github.com/DLR-RM/stable-baselines3/issues/982#issuecomment-1197044014)
required: true required: true
- label: If code there is, it is formatted using the [markdown code blocks](https://help.github.com/en/articles/creating-and-highlighting-code-blocks) for both code and stack traces. - label: If code there is, it is formatted using the [markdown code blocks](https://help.github.com/en/articles/creating-and-highlighting-code-blocks) for both code and stack traces.
required: true required: true

View file

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"] python-version: ["3.8", "3.9", "3.10", "3.11"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -32,7 +32,7 @@ jobs:
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
# cpu version of pytorch # cpu version of pytorch
pip install torch==1.11+cpu -f https://download.pytorch.org/whl/torch_stable.html pip install torch==1.13+cpu -f https://download.pytorch.org/whl/torch_stable.html
# Install Atari Roms # Install Atari Roms
pip install autorom pip install autorom
@ -55,8 +55,8 @@ jobs:
- name: Type check - name: Type check
run: | run: |
make type make type
# skip mypy type check for python3.7 (result is different to all other versions) # skip PyType, doesn't support 3.11 yet
if: "!(matrix.python-version == '3.7')" if: "!(matrix.python-version == '3.11')"
- name: Test with pytest - name: Test with pytest
run: | run: |
make pytest make pytest

View file

@ -92,10 +92,10 @@ It provides a minimal number of features compared to SB3 but can be much faster
## Installation ## Installation
**Note:** Stable-Baselines3 supports PyTorch >= 1.11 **Note:** Stable-Baselines3 supports PyTorch >= 1.13
### Prerequisites ### Prerequisites
Stable Baselines3 requires Python 3.7+. Stable Baselines3 requires Python 3.8+.
#### Windows 10 #### Windows 10

View file

@ -8,7 +8,7 @@ This folder contains documentation for the RL baselines.
#### Install Sphinx and Theme #### Install Sphinx and Theme
Execute this command in the project root: Execute this command in the project root:
``` ```
pip install -e .[docs] pip install -e ".[docs]"
``` ```
#### Building the Docs #### Building the Docs

View file

@ -5,8 +5,8 @@ channels:
dependencies: dependencies:
- cpuonly=1.0=0 - cpuonly=1.0=0
- pip=22.3.1 - pip=22.3.1
- python=3.7 - python=3.8
- pytorch=1.11.0=py3.7_cpu_0 - pytorch=1.13.0=py3.8_cpu_0
- pip: - pip:
- gymnasium - gymnasium
- cloudpickle - cloudpickle
@ -15,6 +15,6 @@ dependencies:
- numpy - numpy
- matplotlib - matplotlib
- sphinx_autodoc_typehints - sphinx_autodoc_typehints
- sphinx>=4.2 - sphinx>=5.3,<7.0
- sphinx_rtd_theme>=1.0 - sphinx_rtd_theme>=1.0
- sphinx_copybutton - sphinx_copybutton

View file

@ -215,7 +215,7 @@ downsampling and "vector" with a single linear layer.
from stable_baselines3.common.torch_layers import BaseFeaturesExtractor from stable_baselines3.common.torch_layers import BaseFeaturesExtractor
class CustomCombinedExtractor(BaseFeaturesExtractor): class CustomCombinedExtractor(BaseFeaturesExtractor):
def __init__(self, observation_space: spaces.Dict): def __init__(self, observation_space: gym.spaces.Dict):
# We do not know features-dim here before going over all the items, # We do not know features-dim here before going over all the items,
# so put something dummy for now. PyTorch requires calling # so put something dummy for now. PyTorch requires calling
# nn.Module.__init__ before adding modules # nn.Module.__init__ before adding modules

View file

@ -319,7 +319,7 @@ You can control the evaluation frequency with ``eval_freq`` to monitor your agen
from stable_baselines3 import SAC from stable_baselines3 import SAC
from stable_baselines3.common.callbacks import EvalCallback from stable_baselines3.common.callbacks import EvalCallback
from stable-baselines3.common.env_util import make_vec_env from stable_baselines3.common.env_util import make_vec_env
env_id = "Pendulum-v1" env_id = "Pendulum-v1"
n_training_envs = 1 n_training_envs = 1
@ -330,7 +330,7 @@ You can control the evaluation frequency with ``eval_freq`` to monitor your agen
os.makedirs(eval_log_dir, exist_ok=True) os.makedirs(eval_log_dir, exist_ok=True)
# Initialize a vectorized training environment with default parameters # Initialize a vectorized training environment with default parameters
train_env = make_vec_env(env_id, n_env=n_training_envs, seed=0) train_env = make_vec_env(env_id, n_envs=n_training_envs, seed=0)
# Separate evaluation env, with different parameters passed via env_kwargs # Separate evaluation env, with different parameters passed via env_kwargs
# Eval environments can be vectorized to speed up evaluation. # Eval environments can be vectorized to speed up evaluation.

View file

@ -7,7 +7,7 @@ Installation
Prerequisites Prerequisites
------------- -------------
Stable-Baselines3 requires python 3.7+ and PyTorch >= 1.11 Stable-Baselines3 requires python 3.8+ and PyTorch >= 1.13
Windows 10 Windows 10
~~~~~~~~~~ ~~~~~~~~~~

View file

@ -3,6 +3,49 @@
Changelog Changelog
========== ==========
Release 2.1.0a4 (WIP)
--------------------------
Breaking Changes:
^^^^^^^^^^^^^^^^^
- Removed Python 3.7 support
- SB3 now requires PyTorch >= 1.13
New Features:
^^^^^^^^^^^^^
- Added Python 3.11 support
- Added Gymnasium 0.29 support (@pseudo-rnd-thoughts)
`SB3-Contrib`_
^^^^^^^^^^^^^^
`RL Zoo`_
^^^^^^^^^
Bug Fixes:
^^^^^^^^^^
- Relaxed check in logger, that was causing issue on Windows with colorama
- Fixed off-policy algorithms with continuous float64 actions (see #1145) (@tobirohrer)
- Fixed env_checker.py warning messages for out of bounds in complex observation spaces (@Gabo-Tor)
Deprecations:
^^^^^^^^^^^^^
Others:
^^^^^^^
- Updated GitHub issue templates
- Fix typo in gym patch error message (@lukashass)
- Refactor ``test_spaces.py`` tests
Documentation:
^^^^^^^^^^^^^^
- Fixed callback example (@BertrandDecoster)
- Fixed policy network example (@kyle-he)
- Added mobile-env as new community project (@stefanbschneider)
- Added [DeepNetSlice](https://github.com/AlexPasqua/DeepNetSlice) to community projects (@AlexPasqua)
Release 2.0.0 (2023-06-22) Release 2.0.0 (2023-06-22)
-------------------------- --------------------------
@ -1359,8 +1402,8 @@ And all the contributors:
@eleurent @ac-93 @cove9988 @theDebugger811 @hsuehch @Demetrio92 @thomasgubler @IperGiove @ScheiklP @eleurent @ac-93 @cove9988 @theDebugger811 @hsuehch @Demetrio92 @thomasgubler @IperGiove @ScheiklP
@simoninithomas @armandpl @manuel-delverme @Gautam-J @gianlucadecola @buoyancy99 @caburu @xy9485 @simoninithomas @armandpl @manuel-delverme @Gautam-J @gianlucadecola @buoyancy99 @caburu @xy9485
@Gregwar @ycheng517 @quantitative-technologies @bcollazo @git-thor @TibiGG @cool-RR @MWeltevrede @Gregwar @ycheng517 @quantitative-technologies @bcollazo @git-thor @TibiGG @cool-RR @MWeltevrede
@carlosluis @arjun-kg @tlpss @JonathanKuelz @carlosluis @arjun-kg @tlpss @JonathanKuelz @Gabo-Tor
@Melanol @qgallouedec @francescoluciano @jlp-ue @burakdmb @timothe-chaumont @honglu2875 @Melanol @qgallouedec @francescoluciano @jlp-ue @burakdmb @timothe-chaumont @honglu2875
@anand-bala @hughperkins @sidney-tio @AlexPasqua @dominicgkerr @Akhilez @Rocamonde @tobirohrer @ZikangXiong @anand-bala @hughperkins @sidney-tio @AlexPasqua @dominicgkerr @Akhilez @Rocamonde @tobirohrer @ZikangXiong
@DavyMorgan @luizapozzobon @Bonifatius94 @theSquaredError @harveybellini @DavyMorgan @FieteO @jonasreiher @npit @WeberSamuel @troiganto @DavyMorgan @luizapozzobon @Bonifatius94 @theSquaredError @harveybellini @DavyMorgan @FieteO @jonasreiher @npit @WeberSamuel @troiganto
@lutogniew @lbergmann1 @lutogniew @lbergmann1 @lukashass @BertrandDecoster @pseudo-rnd-thoughts @stefanbschneider @kyle-he

View file

@ -197,3 +197,35 @@ A simple library for pink noise exploration with deterministic (DDPG / TD3) and
| Authors: Onno Eberhard, Jakob Hollenstein, Cristina Pinneri, Georg Martius | Authors: Onno Eberhard, Jakob Hollenstein, Cristina Pinneri, Georg Martius
| Github: https://github.com/martius-lab/pink-noise-rl | Github: https://github.com/martius-lab/pink-noise-rl
| Paper: https://openreview.net/forum?id=hQ9V5QN27eS (Oral at ICLR 2023) | Paper: https://openreview.net/forum?id=hQ9V5QN27eS (Oral at ICLR 2023)
mobile-env
----------
An open, minimalist Gymnasium environment for autonomous coordination in wireless mobile networks.
It allows simulating various scenarios with moving users in a cellular network with multiple base stations.
- Written in pure Python, easy to modify and extend, and can be installed directly via PyPI.
- Implements the standard Gymnasium interface such that it can be used with all common frameworks for reinforcement learning.
- There are examples for both single-agent and multi-agent RL using either `stable-baselines3` or Ray RLlib.
| Authors: Stefan Schneider, Stefan Werner
| Github: https://github.com/stefanbschneider/mobile-env
| Paper: https://ris.uni-paderborn.de/download/30236/30237 (2022 IEEE/IFIP Network Operations and Management Symposium (NOMS))
DeepNetSlice
------------
A Deep Reinforcement Learning Open-Source Toolkit for Network Slice Placement (NSP).
NSP is the problem of deciding which physical servers in a network should host the virtual network functions (VNFs) that make up a network slice, as well as managing the mapping of the virtual links between the VNFs onto the physical infrastructure.
It is a complex optimization problem, as it involves considering the requirements of the network slice and the available resources on the physical network.
The goal is generally to maximize the utilization of the physical resources while ensuring that the network slices meet their performance requirements.
The toolkit includes a customizable simulation environments, as well as some ready-to-use demos for training
intelligent agents to perform network slice placement.
| Author: Alex Pasquali
| Github: https://github.com/AlexPasqua/DeepNetSlice
| Paper: **under review** (citation instructions on the project's README.md) -> see this Master's Thesis for the moment: https://etd.adm.unipi.it/theses/available/etd-01182023-110038/unrestricted/Tesi_magistrale_Pasquali_Alex.pdf

View file

@ -1,8 +1,8 @@
[tool.ruff] [tool.ruff]
# Same as Black. # Same as Black.
line-length = 127 line-length = 127
# Assume Python 3.7 # Assume Python 3.8
target-version = "py37" target-version = "py38"
# See https://beta.ruff.rs/docs/rules/ # See https://beta.ruff.rs/docs/rules/
select = ["E", "F", "B", "UP", "C90", "RUF"] select = ["E", "F", "B", "UP", "C90", "RUF"]
# B028: Ignore explicit stacklevel` # B028: Ignore explicit stacklevel`

View file

@ -76,9 +76,7 @@ model = PPO("MlpPolicy", "CartPole-v1").learn(10_000)
extra_no_roms = [ extra_no_roms = [
# For render # For render
"opencv-python", "opencv-python",
'pygame; python_version >= "3.8.0"', "pygame",
# See https://github.com/pygame/pygame/issues/3572
'pygame>=2.0,<2.1.3; python_version < "3.8.0"',
# Tensorboard support # Tensorboard support
"tensorboard>=2.9.1", "tensorboard>=2.9.1",
# Checking memory taken by replay buffer # Checking memory taken by replay buffer
@ -87,13 +85,13 @@ extra_no_roms = [
"tqdm", "tqdm",
"rich", "rich",
# For atari games, # For atari games,
"shimmy[atari]~=0.2.1", "shimmy[atari]~=1.1.0",
"pillow", "pillow",
] ]
extra_packages = extra_no_roms + [ # noqa: RUF005 extra_packages = extra_no_roms + [ # noqa: RUF005
# For atari roms, # For atari roms,
"autorom[accept-rom-license]~=0.6.0", "autorom[accept-rom-license]~=0.6.1",
] ]
@ -102,10 +100,9 @@ setup(
packages=[package for package in find_packages() if package.startswith("stable_baselines3")], packages=[package for package in find_packages() if package.startswith("stable_baselines3")],
package_data={"stable_baselines3": ["py.typed", "version.txt"]}, package_data={"stable_baselines3": ["py.typed", "version.txt"]},
install_requires=[ install_requires=[
"gymnasium==0.28.1", "gymnasium>=0.28.1,<0.30",
"numpy>=1.20", "numpy>=1.20",
"torch>=1.11", "torch>=1.13",
'typing_extensions>=4.0,<5; python_version < "3.8.0"',
# For saving models # For saving models
"cloudpickle", "cloudpickle",
# For reading logs # For reading logs
@ -154,7 +151,7 @@ setup(
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
version=__version__, version=__version__,
python_requires=">=3.7", python_requires=">=3.8",
# PyPI package information. # PyPI package information.
project_urls={ project_urls={
"Code": "https://github.com/DLR-RM/stable-baselines3", "Code": "https://github.com/DLR-RM/stable-baselines3",
@ -166,10 +163,10 @@ setup(
}, },
classifiers=[ classifiers=[
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
], ],
) )

View file

@ -11,7 +11,7 @@ try:
cv2.ocl.setUseOpenCL(False) cv2.ocl.setUseOpenCL(False)
except ImportError: except ImportError:
cv2 = None cv2 = None # type: ignore[assignment]
class StickyActionEnv(gym.Wrapper[np.ndarray, int, np.ndarray, int]): class StickyActionEnv(gym.Wrapper[np.ndarray, int, np.ndarray, int]):
@ -241,6 +241,7 @@ class WarpFrame(gym.ObservationWrapper[np.ndarray, int, np.ndarray]):
:param frame: environment frame :param frame: environment frame
:return: the observation :return: the observation
""" """
assert cv2 is not None, "OpenCV is not installed, you can do `pip install opencv-python`"
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
frame = cv2.resize(frame, (self.width, self.height), interpolation=cv2.INTER_AREA) frame = cv2.resize(frame, (self.width, self.height), interpolation=cv2.INTER_AREA)
return frame[:, :, None] return frame[:, :, None]

View file

@ -207,7 +207,9 @@ class ReplayBuffer(BaseBuffer):
else: else:
self.next_observations = np.zeros((self.buffer_size, self.n_envs, *self.obs_shape), dtype=observation_space.dtype) self.next_observations = np.zeros((self.buffer_size, self.n_envs, *self.obs_shape), dtype=observation_space.dtype)
self.actions = np.zeros((self.buffer_size, self.n_envs, self.action_dim), dtype=action_space.dtype) self.actions = np.zeros(
(self.buffer_size, self.n_envs, self.action_dim), dtype=self._maybe_cast_dtype(action_space.dtype)
)
self.rewards = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32) self.rewards = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32)
self.dones = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32) self.dones = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32)
@ -311,6 +313,21 @@ class ReplayBuffer(BaseBuffer):
) )
return ReplayBufferSamples(*tuple(map(self.to_torch, data))) return ReplayBufferSamples(*tuple(map(self.to_torch, data)))
@staticmethod
def _maybe_cast_dtype(dtype: np.typing.DTypeLike) -> np.typing.DTypeLike:
"""
Cast `np.float64` action datatype to `np.float32`,
keep the others dtype unchanged.
See GH#1572 for more information.
:param dtype: The original action space dtype
:return: ``np.float32`` if the dtype was float64,
the original dtype otherwise.
"""
if dtype == np.float64:
return np.float32
return dtype
class RolloutBuffer(BaseBuffer): class RolloutBuffer(BaseBuffer):
""" """
@ -543,7 +560,9 @@ class DictReplayBuffer(ReplayBuffer):
for key, _obs_shape in self.obs_shape.items() for key, _obs_shape in self.obs_shape.items()
} }
self.actions = np.zeros((self.buffer_size, self.n_envs, self.action_dim), dtype=action_space.dtype) self.actions = np.zeros(
(self.buffer_size, self.n_envs, self.action_dim), dtype=self._maybe_cast_dtype(action_space.dtype)
)
self.rewards = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32) self.rewards = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32)
self.dones = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32) self.dones = np.zeros((self.buffer_size, self.n_envs), dtype=np.float32)

View file

@ -203,18 +203,24 @@ def _check_obs(obs: Union[tuple, dict, np.ndarray, int], observation_space: spac
f"Expected: {observation_space.dtype}, actual dtype: {obs.dtype}" f"Expected: {observation_space.dtype}, actual dtype: {obs.dtype}"
) )
if isinstance(observation_space, spaces.Box): if isinstance(observation_space, spaces.Box):
assert np.all(obs >= observation_space.low), ( lower_bounds, upper_bounds = observation_space.low, observation_space.high
f"The observation returned by the `{method_name}()` method does not match the lower bound " # Expose all invalid indices at once
f"of the given observation space {observation_space}." invalid_indices = np.where(np.logical_or(obs < lower_bounds, obs > upper_bounds))
f"Expected: obs >= {np.min(observation_space.low)}, " if (obs > upper_bounds).any() or (obs < lower_bounds).any():
f"actual min value: {np.min(obs)} at index {np.argmin(obs)}" message = (
) f"The observation returned by the `{method_name}()` method does not match the bounds "
assert np.all(obs <= observation_space.high), ( f"of the given observation space {observation_space}. \n"
f"The observation returned by the `{method_name}()` method does not match the upper bound " )
f"of the given observation space {observation_space}. " message += f"{len(invalid_indices[0])} invalid indices: \n"
f"Expected: obs <= {np.max(observation_space.high)}, "
f"actual max value: {np.max(obs)} at index {np.argmax(obs)}" for index in zip(*invalid_indices):
) index_str = ",".join(map(str, index))
message += (
f"Expected: {lower_bounds[index]} <= obs[{index_str}] <= {upper_bounds[index]}, "
f"actual value: {obs[index]} \n"
)
raise AssertionError(message)
assert observation_space.contains(obs), ( assert observation_space.contains(obs), (
f"The observation returned by the `{method_name}()` method " f"The observation returned by the `{method_name}()` method "

View file

@ -164,8 +164,10 @@ class HumanOutputFormat(KVWriter, SeqWriter):
if isinstance(filename_or_file, str): if isinstance(filename_or_file, str):
self.file = open(filename_or_file, "w") self.file = open(filename_or_file, "w")
self.own_file = True self.own_file = True
elif isinstance(filename_or_file, TextIOBase): elif isinstance(filename_or_file, TextIOBase) or hasattr(filename_or_file, "write"):
self.file = filename_or_file # Note: in theory `TextIOBase` check should be sufficient,
# in practice, libraries don't always inherit from it, see GH#1598
self.file = filename_or_file # type: ignore[assignment]
self.own_file = False self.own_file = False
else: else:
raise ValueError(f"Expected file or str, got {filename_or_file}") raise ValueError(f"Expected file or str, got {filename_or_file}")

View file

@ -193,7 +193,9 @@ class ResultsWriter:
mode = "w" if override_existing else "a" mode = "w" if override_existing else "a"
# Prevent newline issue on Windows, see GH issue #692 # Prevent newline issue on Windows, see GH issue #692
self.file_handler = open(filename, f"{mode}t", newline="\n") self.file_handler = open(filename, f"{mode}t", newline="\n")
self.logger = csv.DictWriter(self.file_handler, fieldnames=("r", "l", "t", *extra_keys)) self.logger = csv.DictWriter(
self.file_handler, fieldnames=("r", "l", "t", *extra_keys)
) # pytype: disable=wrong-arg-types
if override_existing: if override_existing:
self.file_handler.write(f"#{json.dumps(header)}\n") self.file_handler.write(f"#{json.dumps(header)}\n")
self.logger.writeheader() self.logger.writeheader()

View file

@ -1,18 +1,12 @@
"""Common aliases for type hints""" """Common aliases for type hints"""
import sys
from enum import Enum from enum import Enum
from typing import Any, Callable, Dict, List, NamedTuple, Optional, SupportsFloat, Tuple, Union from typing import Any, Callable, Dict, List, NamedTuple, Optional, Protocol, SupportsFloat, Tuple, Union
import gymnasium as gym import gymnasium as gym
import numpy as np import numpy as np
import torch as th import torch as th
if sys.version_info >= (3, 8):
from typing import Protocol
else:
from typing_extensions import Protocol
from stable_baselines3.common import callbacks, vec_env from stable_baselines3.common import callbacks, vec_env
GymEnv = Union[gym.Env, vec_env.VecEnv] GymEnv = Union[gym.Env, vec_env.VecEnv]

View file

@ -40,7 +40,7 @@ def _patch_env(env: Union["gym.Env", gymnasium.Env]) -> gymnasium.Env: # pragma
import shimmy # pytype: disable=import-error import shimmy # pytype: disable=import-error
except ImportError as e: except ImportError as e:
raise ImportError( raise ImportError(
"Missing shimmy installation. You an OpenAI Gym environment. " "Missing shimmy installation. You provided an OpenAI Gym environment. "
"Stable-Baselines3 (SB3) has transitioned to using Gymnasium internally. " "Stable-Baselines3 (SB3) has transitioned to using Gymnasium internally. "
"In order to use OpenAI Gym environments with SB3, you need to " "In order to use OpenAI Gym environments with SB3, you need to "
"install shimmy (`pip install 'shimmy>=0.2.1'`)." "install shimmy (`pip install 'shimmy>=0.2.1'`)."

View file

@ -245,7 +245,7 @@ class DQN(OffPolicyAlgorithm):
if not deterministic and np.random.rand() < self.exploration_rate: if not deterministic and np.random.rand() < self.exploration_rate:
if self.policy.is_vectorized_observation(observation): if self.policy.is_vectorized_observation(observation):
if isinstance(observation, dict): if isinstance(observation, dict):
n_batch = observation[list(observation.keys())[0]].shape[0] n_batch = observation[next(iter(observation.keys()))].shape[0]
else: else:
n_batch = observation.shape[0] n_batch = observation.shape[0]
action = np.array([self.action_space.sample() for _ in range(n_batch)]) action = np.array([self.action_space.sample() for _ in range(n_batch)])

View file

@ -1 +1 @@
2.0.0 2.1.0a4

View file

@ -42,15 +42,28 @@ def test_check_env_dict_action():
[ [
# Above upper bound # Above upper bound
( (
spaces.Box(low=0.0, high=1.0, shape=(3,), dtype=np.float32), spaces.Box(low=np.array([0.0, 0.0, 0.0]), high=np.array([2.0, 1.0, 1.0]), shape=(3,), dtype=np.float32),
np.array([1.0, 1.5, 0.5], dtype=np.float32), np.array([1.0, 1.5, 0.5], dtype=np.float32),
r"Expected: obs <= 1\.0, actual max value: 1\.5 at index 1", r"Expected: 0\.0 <= obs\[1] <= 1\.0, actual value: 1\.5",
),
# Above upper bound (multi-dim)
(
spaces.Box(low=-1.0, high=2.0, shape=(2, 3, 3, 1), dtype=np.float32),
3.0 * np.ones((2, 3, 3, 1), dtype=np.float32),
# Note: this is one of the 18 invalid indices
r"Expected: -1\.0 <= obs\[1,2,1,0\] <= 2\.0, actual value: 3\.0",
), ),
# Below lower bound # Below lower bound
( (
spaces.Box(low=0.0, high=2.0, shape=(3,), dtype=np.float32), spaces.Box(low=np.array([0.0, -10.0, 0.0]), high=np.array([2.0, 1.0, 1.0]), shape=(3,), dtype=np.float32),
np.array([-1.0, 1.5, 0.5], dtype=np.float32), np.array([-1.0, 1.5, 0.5], dtype=np.float32),
r"Expected: obs >= 0\.0, actual min value: -1\.0 at index 0", r"Expected: 0\.0 <= obs\[0] <= 2\.0, actual value: -1\.0",
),
# Below lower bound (multi-dim)
(
spaces.Box(low=-1.0, high=2.0, shape=(2, 3, 3, 1), dtype=np.float32),
-2 * np.ones((2, 3, 3, 1), dtype=np.float32),
r"18 invalid indices:",
), ),
# Wrong dtype # Wrong dtype
( (
@ -111,7 +124,7 @@ def test_check_env_detailed_error(obs_tuple, method):
test_env = TestEnv() test_env = TestEnv()
with pytest.raises(AssertionError, match=error_message): with pytest.raises(AssertionError, match=error_message):
check_env(env=test_env) check_env(env=test_env, warn=False)
class LimitedStepsTestEnv(gym.Env): class LimitedStepsTestEnv(gym.Env):

View file

@ -156,8 +156,6 @@ def test_non_default_spaces(new_obs_space):
spaces.Box(low=-1000, high=1000, shape=(3,), dtype=np.float32), spaces.Box(low=-1000, high=1000, shape=(3,), dtype=np.float32),
# Too small range # Too small range
spaces.Box(low=-0.1, high=0.1, shape=(2,), dtype=np.float32), spaces.Box(low=-0.1, high=0.1, shape=(2,), dtype=np.float32),
# Inverted boundaries
spaces.Box(low=1, high=-1, shape=(2,), dtype=np.float32),
# Same boundaries # Same boundaries
spaces.Box(low=1, high=1, shape=(2,), dtype=np.float32), spaces.Box(low=1, high=1, shape=(2,), dtype=np.float32),
# Unbounded action space # Unbounded action space

View file

@ -437,8 +437,9 @@ def test_ep_buffers_stats_window_size(algo, stats_window_size):
assert model.ep_success_buffer.maxlen == stats_window_size assert model.ep_success_buffer.maxlen == stats_window_size
def test_human_output_format_custom_test_io(): @pytest.mark.parametrize("base_class", [object, TextIOBase])
class DummyTextIO(TextIOBase): def test_human_output_format_custom_test_io(base_class):
class DummyTextIO(base_class):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.lines = [[]] self.lines = [[]]

View file

@ -1,63 +1,67 @@
from dataclasses import dataclass
from typing import Dict, Optional from typing import Dict, Optional
import gymnasium as gym import gymnasium as gym
import numpy as np import numpy as np
import pytest import pytest
from gymnasium import spaces from gymnasium import spaces
from gymnasium.spaces.space import Space
from stable_baselines3 import A2C, DDPG, DQN, PPO, SAC, TD3 from stable_baselines3 import A2C, DDPG, DQN, PPO, SAC, TD3
from stable_baselines3.common.env_checker import check_env from stable_baselines3.common.env_checker import check_env
from stable_baselines3.common.env_util import make_vec_env from stable_baselines3.common.env_util import make_vec_env
from stable_baselines3.common.evaluation import evaluate_policy from stable_baselines3.common.evaluation import evaluate_policy
BOX_SPACE_FLOAT64 = spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float64)
BOX_SPACE_FLOAT32 = spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float32)
class DummyMultiDiscreteSpace(gym.Env):
def __init__(self, nvec): @dataclass
super().__init__() class DummyEnv(gym.Env):
self.observation_space = spaces.MultiDiscrete(nvec) observation_space: Space
self.action_space = spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float32) action_space: Space
def step(self, action):
return self.observation_space.sample(), 0.0, False, False, {}
def reset(self, *, seed: Optional[int] = None, options: Optional[Dict] = None): def reset(self, *, seed: Optional[int] = None, options: Optional[Dict] = None):
if seed is not None: if seed is not None:
super().reset(seed=seed) super().reset(seed=seed)
return self.observation_space.sample(), {} return self.observation_space.sample(), {}
def step(self, action):
return self.observation_space.sample(), 0.0, False, False, {}
class DummyMultidimensionalAction(DummyEnv):
class DummyMultiBinary(gym.Env):
def __init__(self, n):
super().__init__()
self.observation_space = spaces.MultiBinary(n)
self.action_space = spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float32)
def reset(self, *, seed: Optional[int] = None, options: Optional[Dict] = None):
if seed is not None:
super().reset(seed=seed)
return self.observation_space.sample(), {}
def step(self, action):
return self.observation_space.sample(), 0.0, False, False, {}
class DummyMultidimensionalAction(gym.Env):
def __init__(self): def __init__(self):
super().__init__() super().__init__(
self.observation_space = spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float32) BOX_SPACE_FLOAT32,
self.action_space = spaces.Box(low=-1, high=1, shape=(2, 2), dtype=np.float32) spaces.Box(low=-1, high=1, shape=(2, 2), dtype=np.float32),
)
def reset(self, *, seed: Optional[int] = None, options: Optional[Dict] = None):
if seed is not None:
super().reset(seed=seed)
return self.observation_space.sample(), {}
def step(self, action): class DummyMultiBinary(DummyEnv):
return self.observation_space.sample(), 0.0, False, False, {} def __init__(self, n):
super().__init__(
spaces.MultiBinary(n),
BOX_SPACE_FLOAT32,
)
class DummyMultiDiscreteSpace(DummyEnv):
def __init__(self, nvec):
super().__init__(
spaces.MultiDiscrete(nvec),
BOX_SPACE_FLOAT32,
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"env", [DummyMultiDiscreteSpace([4, 3]), DummyMultiBinary(8), DummyMultiBinary((3, 2)), DummyMultidimensionalAction()] "env",
[
DummyMultiDiscreteSpace([4, 3]),
DummyMultiBinary(8),
DummyMultiBinary((3, 2)),
DummyMultidimensionalAction(),
],
) )
def test_env(env): def test_env(env):
# Check the env used for testing # Check the env used for testing
@ -127,3 +131,40 @@ def test_discrete_obs_space(model_class, env):
else: else:
kwargs = dict(n_steps=256) kwargs = dict(n_steps=256)
model_class("MlpPolicy", env, **kwargs).learn(256) model_class("MlpPolicy", env, **kwargs).learn(256)
@pytest.mark.parametrize("model_class", [SAC, TD3, PPO, DDPG, A2C])
@pytest.mark.parametrize(
"obs_space",
[
BOX_SPACE_FLOAT32,
BOX_SPACE_FLOAT64,
spaces.Dict({"a": BOX_SPACE_FLOAT32, "b": BOX_SPACE_FLOAT32}),
spaces.Dict({"a": BOX_SPACE_FLOAT32, "b": BOX_SPACE_FLOAT64}),
],
)
@pytest.mark.parametrize(
"action_space",
[
BOX_SPACE_FLOAT32,
BOX_SPACE_FLOAT64,
],
)
def test_float64_action_space(model_class, obs_space, action_space):
env = DummyEnv(obs_space, action_space)
env = gym.wrappers.TimeLimit(env, max_episode_steps=200)
if isinstance(env.observation_space, spaces.Dict):
policy = "MultiInputPolicy"
else:
policy = "MlpPolicy"
if model_class in [PPO, A2C]:
kwargs = dict(n_steps=64, policy_kwargs=dict(net_arch=[12]))
else:
kwargs = dict(learning_starts=60, policy_kwargs=dict(net_arch=[12]))
model = model_class(policy, env, **kwargs)
model.learn(64)
initial_obs, _ = env.reset()
action, _ = model.predict(initial_obs, deterministic=False)
assert action.dtype == env.action_space.dtype