diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51745f86a..9a7c288a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v2 @@ -25,20 +25,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - python -m pip install --upgrade pip - sudo pip install pygame - pip install -e .[deploy] - - name: Lint with flake8 - run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + run: pip install .[testing] - name: Test with pytest - run: | - pip install pytest - pip install pytest-cov - pytest --cov=./ --cov-report=xml - + run: pytest --cov=./ --cov-report=xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc95bb980..789334631 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,6 @@ repos: hooks: - id: isort args: ["--profile", "black"] - exclude: "__init__.py" - repo: https://github.com/python/black rev: 23.3.0 hooks: diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index bfdc9877d..000000000 --- a/codecov.yml +++ /dev/null @@ -1,8 +0,0 @@ -coverage: - status: - project: - default: - informational: true - patch: - default: - informational: true diff --git a/highway_env/__init__.py b/highway_env/__init__.py index b5f91e94b..a1c1d9138 100644 --- a/highway_env/__init__.py +++ b/highway_env/__init__.py @@ -1,7 +1,9 @@ import os import sys -__version__ = "1.9.1" +from gymnasium.envs.registration import register + +__version__ = "2.0.0" try: from farama_notifications import notifications @@ -15,96 +17,101 @@ # Hide pygame support prompt os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1" -from gymnasium.envs.registration import register -from highway_env.envs.common.abstract import MultiAgentWrapper - -def register_highway_envs(): +def _register_highway_envs(): """Import the envs module so that envs register themselves.""" + from highway_env.envs.common.abstract import MultiAgentWrapper + # exit_env.py register( id="exit-v0", - entry_point="highway_env.envs:ExitEnv", + entry_point="highway_env.envs.exit_env:ExitEnv", ) # highway_env.py register( id="highway-v0", - entry_point="highway_env.envs:HighwayEnv", + entry_point="highway_env.envs.highway_env:HighwayEnv", ) register( id="highway-fast-v0", - entry_point="highway_env.envs:HighwayEnvFast", + entry_point="highway_env.envs.highway_env:HighwayEnvFast", ) # intersection_env.py register( id="intersection-v0", - entry_point="highway_env.envs:IntersectionEnv", + entry_point="highway_env.envs.intersection_env:IntersectionEnv", ) register( id="intersection-v1", - entry_point="highway_env.envs:ContinuousIntersectionEnv", + entry_point="highway_env.envs.intersection_env:ContinuousIntersectionEnv", ) register( id="intersection-multi-agent-v0", - entry_point="highway_env.envs:MultiAgentIntersectionEnv", + entry_point="highway_env.envs.intersection_env:MultiAgentIntersectionEnv", ) register( id="intersection-multi-agent-v1", - entry_point="highway_env.envs:MultiAgentIntersectionEnv", + entry_point="highway_env.envs.intersection_env:MultiAgentIntersectionEnv", additional_wrappers=(MultiAgentWrapper.wrapper_spec(),), ) # lane_keeping_env.py register( id="lane-keeping-v0", - entry_point="highway_env.envs:LaneKeepingEnv", + entry_point="highway_env.envs.lane_keeping_env:LaneKeepingEnv", max_episode_steps=200, ) # merge_env.py register( id="merge-v0", - entry_point="highway_env.envs:MergeEnv", + entry_point="highway_env.envs.merge_env:MergeEnv", ) # parking_env.py register( id="parking-v0", - entry_point="highway_env.envs:ParkingEnv", + entry_point="highway_env.envs.parking_env:ParkingEnv", ) register( id="parking-ActionRepeat-v0", - entry_point="highway_env.envs:ParkingEnvActionRepeat", + entry_point="highway_env.envs.parking_env:ParkingEnvActionRepeat", ) register( - id="parking-parked-v0", entry_point="highway_env.envs:ParkingEnvParkedVehicles" + id="parking-parked-v0", + entry_point="highway_env.envs.parking_env:ParkingEnvParkedVehicles", ) # racetrack_env.py register( id="racetrack-v0", - entry_point="highway_env.envs:RacetrackEnv", + entry_point="highway_env.envs.racetrack_env:RacetrackEnv", ) # roundabout_env.py register( id="roundabout-v0", - entry_point="highway_env.envs:RoundaboutEnv", + entry_point="highway_env.envs.roundabout_env:RoundaboutEnv", ) # two_way_env.py register( - id="two-way-v0", entry_point="highway_env.envs:TwoWayEnv", max_episode_steps=15 + id="two-way-v0", + entry_point="highway_env.envs.two_way_env:TwoWayEnv", + max_episode_steps=15, ) # u_turn_env.py - register(id="u-turn-v0", entry_point="highway_env.envs:UTurnEnv") + register(id="u-turn-v0", entry_point="highway_env.envs.u_turn_env:UTurnEnv") + + +_register_highway_envs() diff --git a/highway_env/envs/__init__.py b/highway_env/envs/__init__.py index 6d6f5c9a7..e24fabbff 100644 --- a/highway_env/envs/__init__.py +++ b/highway_env/envs/__init__.py @@ -1,10 +1,35 @@ -from highway_env.envs.highway_env import * -from highway_env.envs.merge_env import * -from highway_env.envs.parking_env import * -from highway_env.envs.roundabout_env import * -from highway_env.envs.two_way_env import * -from highway_env.envs.intersection_env import * -from highway_env.envs.lane_keeping_env import * -from highway_env.envs.u_turn_env import * -from highway_env.envs.exit_env import * -from highway_env.envs.racetrack_env import * +from highway_env.envs.exit_env import ExitEnv +from highway_env.envs.highway_env import HighwayEnv, HighwayEnvFast +from highway_env.envs.intersection_env import ( + ContinuousIntersectionEnv, + IntersectionEnv, + MultiAgentIntersectionEnv, +) +from highway_env.envs.lane_keeping_env import LaneKeepingEnv +from highway_env.envs.merge_env import MergeEnv +from highway_env.envs.parking_env import ( + ParkingEnv, + ParkingEnvActionRepeat, + ParkingEnvParkedVehicles, +) +from highway_env.envs.racetrack_env import RacetrackEnv +from highway_env.envs.roundabout_env import RoundaboutEnv +from highway_env.envs.two_way_env import TwoWayEnv +from highway_env.envs.u_turn_env import UTurnEnv + +__all__ = [ + "ExitEnv", + "HighwayEnv", + "HighwayEnvFast", + "IntersectionEnv", + "ContinuousIntersectionEnv", + "MultiAgentIntersectionEnv", + "LaneKeepingEnv", + "MergeEnv", + "ParkingEnv", + "ParkingEnvActionRepeat", + "RacetrackEnv", + "RoundaboutEnv", + "TwoWayEnv", + "UTurnEnv", +] diff --git a/highway_env/envs/common/abstract.py b/highway_env/envs/common/abstract.py index 1c8b62e7a..454cf7345 100644 --- a/highway_env/envs/common/abstract.py +++ b/highway_env/envs/common/abstract.py @@ -5,6 +5,7 @@ import gymnasium as gym import numpy as np from gymnasium import Wrapper +from gymnasium.utils import RecordConstructorArgs from gymnasium.wrappers import RecordVideo from highway_env import utils @@ -426,10 +427,13 @@ def __deepcopy__(self, memo): return result -class MultiAgentWrapper(Wrapper): +class MultiAgentWrapper(Wrapper, RecordConstructorArgs): + def __init__(self, env): + Wrapper.__init__(self, env) + RecordConstructorArgs.__init__(self) + def step(self, action): - obs, reward, terminated, truncated, info = super().step(action) + obs, _, _, truncated, info = super().step(action) reward = info["agents_rewards"] terminated = info["agents_terminated"] - truncated = info["agents_truncated"] return obs, reward, terminated, truncated, info diff --git a/highway_env/envs/common/action.py b/highway_env/envs/common/action.py index e52d445bb..a82e00f1d 100644 --- a/highway_env/envs/common/action.py +++ b/highway_env/envs/common/action.py @@ -94,7 +94,7 @@ def __init__( lateral: bool = True, dynamical: bool = False, clip: bool = True, - **kwargs + **kwargs, ) -> None: """ Create a continuous action space. @@ -172,7 +172,7 @@ def __init__( dynamical: bool = False, clip: bool = True, actions_per_axis: int = 3, - **kwargs + **kwargs, ) -> None: super().__init__( env, @@ -216,7 +216,7 @@ def __init__( longitudinal: bool = True, lateral: bool = True, target_speeds: Optional[Vector] = None, - **kwargs + **kwargs, ) -> None: """ Create a discrete action space of meta-actions. diff --git a/highway_env/envs/common/observation.py b/highway_env/envs/common/observation.py index 02f9de5a3..69f7a8547 100644 --- a/highway_env/envs/common/observation.py +++ b/highway_env/envs/common/observation.py @@ -70,7 +70,7 @@ def __init__( weights: List[float], scaling: Optional[float] = None, centering_position: Optional[List[float]] = None, - **kwargs + **kwargs, ) -> None: super().__init__(env) self.observation_shape = observation_shape @@ -168,7 +168,7 @@ def __init__( see_behind: bool = False, observe_intentions: bool = False, include_obstacles: bool = True, - **kwargs: dict + **kwargs: dict, ) -> None: """ :param env: The environment to observe @@ -293,7 +293,7 @@ def __init__( align_to_vehicle_axes: bool = False, clip: bool = True, as_image: bool = False, - **kwargs: dict + **kwargs: dict, ) -> None: """ :param env: The environment to observe @@ -674,7 +674,7 @@ def observe(self) -> np.ndarray: if self.order == "shuffled": self.env.np_random.shuffle(obs[1:]) # Flatten - return obs + return obs.astype(self.space().dtype) class LidarObservation(ObservationType): @@ -687,7 +687,7 @@ def __init__( cells: int = 16, maximum_range: float = 60, normalize: bool = True, - **kwargs + **kwargs, ): super().__init__(env, **kwargs) self.cells = cells diff --git a/highway_env/envs/exit_env.py b/highway_env/envs/exit_env.py index f42067849..4f590c8b6 100644 --- a/highway_env/envs/exit_env.py +++ b/highway_env/envs/exit_env.py @@ -1,12 +1,14 @@ -from typing import Dict, Text, Tuple +from __future__ import annotations import numpy as np from highway_env import utils -from highway_env.envs import CircularLane, HighwayEnv, Vehicle from highway_env.envs.common.action import Action +from highway_env.envs.highway_env import HighwayEnv +from highway_env.road.lane import CircularLane from highway_env.road.road import Road, RoadNetwork from highway_env.vehicle.controller import ControlledVehicle +from highway_env.vehicle.kinematics import Vehicle class ExitEnv(HighwayEnv): @@ -44,10 +46,10 @@ def _reset(self) -> None: self._create_road() self._create_vehicles() - def step(self, action) -> Tuple[np.ndarray, float, bool, dict]: - obs, reward, terminal, info = super().step(action) + def step(self, action) -> tuple[np.ndarray, float, bool, bool, dict]: + obs, reward, terminated, truncated, info = super().step(action) info.update({"is_success": self._is_success()}) - return obs, reward, terminal, info + return obs, reward, terminated, truncated, info def _create_road( self, road_length=1000, exit_position=400, exit_length=100 @@ -154,7 +156,7 @@ def _reward(self, action: Action) -> float: reward = np.clip(reward, 0, 1) return reward - def _rewards(self, action: Action) -> Dict[Text, float]: + def _rewards(self, action: Action) -> dict[str, float]: lane_index = ( self.vehicle.target_lane_index if isinstance(self.vehicle, ControlledVehicle) diff --git a/highway_env/envs/intersection_env.py b/highway_env/envs/intersection_env.py index d55e32035..c290c3398 100644 --- a/highway_env/envs/intersection_env.py +++ b/highway_env/envs/intersection_env.py @@ -123,7 +123,7 @@ def _info(self, obs: np.ndarray, action: int) -> dict: info["agents_rewards"] = tuple( self._agent_reward(action, vehicle) for vehicle in self.controlled_vehicles ) - info["agents_dones"] = tuple( + info["agents_terminated"] = tuple( self._agent_is_terminal(vehicle) for vehicle in self.controlled_vehicles ) return info diff --git a/highway_env/road/road.py b/highway_env/road/road.py index 998eb017e..b88983abd 100644 --- a/highway_env/road/road.py +++ b/highway_env/road/road.py @@ -313,7 +313,7 @@ def straight_road_network( *nodes_str, StraightLane( origin, end, line_types=line_types, speed_limit=speed_limit - ) + ), ) return net diff --git a/pyproject.toml b/pyproject.toml index b5a3c468d..3818d26d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,50 @@ -[build-system] -requires = [ - "setuptools>=42", - "wheel" +[project] +name = "highway-env" +description = "An environment for simulated highway driving tasks." +readme = "README.md" +requires-python = ">= 3.8" +authors = [{ name = "Edouard Leurent", email = "eleurent@gmail.com" }] +license = { text = "MIT License" } +keywords = ["Reinforcement Learning", "car simulation", "RL", "AI"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: MIT License", + 'Intended Audience :: Science/Research', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', ] -build-backend = "setuptools.build_meta" \ No newline at end of file +dependencies = [ + "gymnasium >=1.0.0a2", + "farama-notifications >=0.0.1", + "numpy >=1.21.0", + "pygame >=2.0.2", + "matplotlib", + "pandas", + "scipy" +] +dynamic = ["version"] + +[project.optional-dependencies] +testing = [ + "pytest", + "pytest-cov" +] + +[project.urls] +Homepage = "https://farama.org" +Repository = "https://github.com/eleurent/highway-env" +Documentation = "https://highway-env.farama.org/" +"Bug Report" = "https://github.com/eleurent/highway-env/issues" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +include = ["highway_env", "highway_env.*"] + +[tool.setuptools.package-data] diff --git a/scripts/highway_planning.ipynb b/scripts/highway_planning.ipynb index 4fd9a87c0..b1722e1bb 100644 --- a/scripts/highway_planning.ipynb +++ b/scripts/highway_planning.ipynb @@ -1,100 +1,100 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "QKWvMXWMBEJA" - }, - "source": [ - "# Behavioural Planning for Autonomous Highway Driving\n", - "\n", - "We plan a trajectory using the _Optimistic Planning for Deterministic systems_ ([OPD](https://hal.inria.fr/hal-00830182)) algorithm.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "s-ghXis6A_md" - }, - "outputs": [], - "source": [ - "#@title Imports for env, agent, and visualisation.\n", - "# Environment\n", - "!pip install highway-env\n", - "import gymnasium as gym\n", - "import highway_env\n", - "\n", - "# Agent\n", - "!pip install git+https://github.com/eleurent/rl-agents#egg=rl-agents\n", - "from rl_agents.agents.common.factory import agent_factory\n", - "\n", - "# Visualisation\n", - "import sys\n", - "from tqdm.notebook import trange\n", - "!pip install moviepy -U\n", - "!pip install imageio_ffmpeg\n", - "!pip install pyvirtualdisplay\n", - "!apt-get install -y xvfb ffmpeg\n", - "!git clone https://github.com/Farama-Foundation/HighwayEnv.git\n", - "sys.path.insert(0, './highway-env/scripts/')\n", - "from utils import record_videos, show_videos\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "bgNDDWwqCj8l" - }, - "outputs": [], - "source": [ - "#@title Run an episode\n", - "\n", - "# Make environment\n", - "env = gym.make(\"highway-fast-v0\", render_mode=\"rgb_array\")\n", - "env = record_videos(env)\n", - "(obs, info), done = env.reset(), False\n", - "\n", - "# Make agent\n", - "agent_config = {\n", - " \"__class__\": \"\",\n", - " \"env_preprocessors\": [{\"method\":\"simplify\"}],\n", - " \"budget\": 50,\n", - " \"gamma\": 0.7,\n", - "}\n", - "agent = agent_factory(env, agent_config)\n", - "\n", - "# Run episode\n", - "for step in trange(env.unwrapped.config[\"duration\"], desc=\"Running...\"):\n", - " action = agent.act(obs)\n", - " obs, reward, done, truncated, info = env.step(action)\n", - " \n", - "env.close()\n", - "show_videos()" - ] - } - ], - "metadata": { - "colab": { - "name": "highway-planning.ipynb", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "pycharm": { - "stem_cell": { - "cell_type": "raw", - "metadata": { - "collapsed": false - }, - "source": [] - } - } + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "QKWvMXWMBEJA" + }, + "source": [ + "# Behavioural Planning for Autonomous Highway Driving\n", + "\n", + "We plan a trajectory using the _Optimistic Planning for Deterministic systems_ ([OPD](https://hal.inria.fr/hal-00830182)) algorithm.\n" + ] }, - "nbformat": 4, - "nbformat_minor": 0 + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "s-ghXis6A_md" + }, + "source": [ + "#@title Imports for env, agent, and visualisation.\n", + "# Environment\n", + "!pip install highway-env\n", + "import gymnasium as gym\n", + "import highway_env\n", + "\n", + "# Agent\n", + "!pip install git+https://github.com/eleurent/rl-agents#egg=rl-agents\n", + "from rl_agents.agents.common.factory import agent_factory\n", + "\n", + "# Visualisation\n", + "import sys\n", + "from tqdm.notebook import trange\n", + "!pip install moviepy -U\n", + "!pip install imageio_ffmpeg\n", + "!pip install pyvirtualdisplay\n", + "!apt-get install -y xvfb ffmpeg\n", + "!git clone https://github.com/Farama-Foundation/HighwayEnv.git\n", + "sys.path.insert(0, './highway-env/scripts/')\n", + "from utils import record_videos, show_videos\n" + ], + "outputs": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bgNDDWwqCj8l" + }, + "source": [ + "#@title Run an episode\n", + "\n", + "# Make environment\n", + "env = gym.make(\"highway-fast-v0\", render_mode=\"rgb_array\")\n", + "env = record_videos(env)\n", + "(obs, info), done = env.reset(), False\n", + "\n", + "# Make agent\n", + "agent_config = {\n", + " \"__class__\": \"\",\n", + " \"env_preprocessors\": [{\"method\":\"simplify\"}],\n", + " \"budget\": 50,\n", + " \"gamma\": 0.7,\n", + "}\n", + "agent = agent_factory(env, agent_config)\n", + "\n", + "# Run episode\n", + "for step in trange(env.unwrapped.config[\"duration\"], desc=\"Running...\"):\n", + " action = agent.act(obs)\n", + " obs, reward, done, truncated, info = env.step(action)\n", + " \n", + "env.close()\n", + "show_videos()" + ], + "outputs": [] + } + ], + "metadata": { + "colab": { + "name": "highway-planning.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/scripts/intersection_social_dqn.ipynb b/scripts/intersection_social_dqn.ipynb index e29f32acb..bbfb2aaf2 100644 --- a/scripts/intersection_social_dqn.ipynb +++ b/scripts/intersection_social_dqn.ipynb @@ -1,179 +1,179 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "sepDWoBqdRMK" - }, - "source": [ - "# Training a DQN with social attention on `intersection-v0`\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Kx8X4s8krNWt" - }, - "outputs": [], - "source": [ - "#@title Import requirements\n", - "\n", - "# Environment\n", - "!pip install highway-env\n", - "import gymnasium as gym\n", - "\n", - "# Agent\n", - "!pip install git+https://github.com/eleurent/rl-agents#egg=rl-agents\n", - "\n", - "# Visualisation utils\n", - "!pip install moviepy\n", - "!pip install imageio_ffmpeg\n", - "import sys\n", - "%load_ext tensorboard\n", - "!pip install tensorboardx gym pyvirtualdisplay\n", - "!apt-get install -y xvfb ffmpeg\n", - "!git clone https://github.com/Farama-Foundation/HighwayEnv.git 2> /dev/null\n", - "sys.path.insert(0, '/content/HighwayEnv/scripts/')\n", - "from utils import show_videos" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vvOEW00pdHrG" - }, - "source": [ - "## Training\n", - "\n", - "We use a policy architecture based on social attention, see [[Leurent and Mercat, 2019]](https://arxiv.org/abs/1911.12250).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "QowKW3ix45ZW" - }, - "outputs": [], - "source": [ - "#@title Prepare environment, agent, and evaluation process.\n", - "\n", - "NUM_EPISODES = 3000 #@param {type: \"integer\"}\n", - "\n", - "from rl_agents.trainer.evaluation import Evaluation\n", - "from rl_agents.agents.common.factory import load_agent, load_environment\n", - "\n", - "# Get the environment and agent configurations from the rl-agents repository\n", - "!git clone https://github.com/eleurent/rl-agents.git 2> /dev/null\n", - "%cd /content/rl-agents/scripts/\n", - "env_config = 'configs/IntersectionEnv/env.json'\n", - "agent_config = 'configs/IntersectionEnv/agents/DQNAgent/ego_attention_2h.json'\n", - "\n", - "env = load_environment(env_config)\n", - "agent = load_agent(agent_config, env)\n", - "evaluation = Evaluation(env, agent, num_episodes=NUM_EPISODES, display_env=False, display_agent=False)\n", - "print(f\"Ready to train {agent} on {env}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nqnGqW6jd1xN" - }, - "source": [ - "Run tensorboard locally to visualize training." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "q7QJY2wc4_1N" - }, - "outputs": [], - "source": [ - "%tensorboard --logdir \"{evaluation.directory}\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BtK9dtfb0JMF" - }, - "source": [ - "Start training. This should take about an hour." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "sFVq1gFz42Eg" - }, - "outputs": [], - "source": [ - "evaluation.train()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "-lNvWg42RWiw" - }, - "source": [ - "Progress can be visualised in the tensorboard cell above, which should update every 30s (or manually). You may need to click the *Fit domain to data* buttons below each graph." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "VKfvu5uhzCIU" - }, - "source": [ - "## Testing" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "gY0rpVYUtRpN" - }, - "outputs": [], - "source": [ - "#@title Run the learned policy for a few episodes.\n", - "env = load_environment(env_config)\n", - "env.config[\"offscreen_rendering\"] = True\n", - "agent = load_agent(agent_config, env)\n", - "evaluation = Evaluation(env, agent, num_episodes=20, training = False, recover = True)\n", - "evaluation.test()\n", - "show_videos(evaluation.run_directory)" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "name": "SocialAttentionDQN", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "pycharm": { - "stem_cell": { - "cell_type": "raw", - "metadata": { - "collapsed": false - }, - "source": [] - } - } + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "sepDWoBqdRMK" + }, + "source": [ + "# Training a DQN with social attention on `intersection-v0`\n", + "\n" + ] }, - "nbformat": 4, - "nbformat_minor": 0 + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Kx8X4s8krNWt" + }, + "source": [ + "#@title Import requirements\n", + "\n", + "# Environment\n", + "!pip install highway-env\n", + "import gymnasium as gym\n", + "\n", + "# Agent\n", + "!pip install git+https://github.com/eleurent/rl-agents#egg=rl-agents\n", + "\n", + "# Visualisation utils\n", + "!pip install moviepy\n", + "!pip install imageio_ffmpeg\n", + "import sys\n", + "%load_ext tensorboard\n", + "!pip install tensorboardx gym pyvirtualdisplay\n", + "!apt-get install -y xvfb ffmpeg\n", + "!git clone https://github.com/Farama-Foundation/HighwayEnv.git 2> /dev/null\n", + "sys.path.insert(0, '/content/HighwayEnv/scripts/')\n", + "from utils import show_videos" + ], + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vvOEW00pdHrG" + }, + "source": [ + "## Training\n", + "\n", + "We use a policy architecture based on social attention, see [[Leurent and Mercat, 2019]](https://arxiv.org/abs/1911.12250).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "QowKW3ix45ZW" + }, + "source": [ + "#@title Prepare environment, agent, and evaluation process.\n", + "\n", + "NUM_EPISODES = 3000 #@param {type: \"integer\"}\n", + "\n", + "from rl_agents.trainer.evaluation import Evaluation\n", + "from rl_agents.agents.common.factory import load_agent, load_environment\n", + "\n", + "# Get the environment and agent configurations from the rl-agents repository\n", + "!git clone https://github.com/eleurent/rl-agents.git 2> /dev/null\n", + "%cd /content/rl-agents/scripts/\n", + "env_config = 'configs/IntersectionEnv/env.json'\n", + "agent_config = 'configs/IntersectionEnv/agents/DQNAgent/ego_attention_2h.json'\n", + "\n", + "env = load_environment(env_config)\n", + "agent = load_agent(agent_config, env)\n", + "evaluation = Evaluation(env, agent, num_episodes=NUM_EPISODES, display_env=False, display_agent=False)\n", + "print(f\"Ready to train {agent} on {env}\")" + ], + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nqnGqW6jd1xN" + }, + "source": [ + "Run tensorboard locally to visualize training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "q7QJY2wc4_1N" + }, + "source": [ + "%tensorboard --logdir \"{evaluation.directory}\"" + ], + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BtK9dtfb0JMF" + }, + "source": [ + "Start training. This should take about an hour." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sFVq1gFz42Eg" + }, + "source": [ + "evaluation.train()" + ], + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-lNvWg42RWiw" + }, + "source": [ + "Progress can be visualised in the tensorboard cell above, which should update every 30s (or manually). You may need to click the *Fit domain to data* buttons below each graph." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VKfvu5uhzCIU" + }, + "source": [ + "## Testing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gY0rpVYUtRpN" + }, + "source": [ + "#@title Run the learned policy for a few episodes.\n", + "env = load_environment(env_config)\n", + "env.config[\"offscreen_rendering\"] = True\n", + "agent = load_agent(agent_config, env)\n", + "evaluation = Evaluation(env, agent, num_episodes=20, training = False, recover = True)\n", + "evaluation.test()\n", + "show_videos(evaluation.run_directory)" + ], + "outputs": [] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "name": "SocialAttentionDQN", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/scripts/sb3_highway_ppo_transformer.py b/scripts/sb3_highway_ppo_transformer.py index aa234de3b..277374ad3 100644 --- a/scripts/sb3_highway_ppo_transformer.py +++ b/scripts/sb3_highway_ppo_transformer.py @@ -69,7 +69,7 @@ def __init__( out_size=None, activation="RELU", is_policy=False, - **kwargs + **kwargs, ): super().__init__(**kwargs) self.reshape = reshape @@ -170,7 +170,7 @@ def __init__( presence_feature_idx=0, embedding_layer_kwargs=None, attention_layer_kwargs=None, - **kwargs + **kwargs, ): super().__init__(**kwargs) self.out_size = out_size diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 6417bab87..000000000 --- a/setup.cfg +++ /dev/null @@ -1,46 +0,0 @@ -[metadata] -name=highway-env -version=attr: my_package.__version__ -author=Edouard Leurent -author_email=eleurent@gmail.com -description=An environment for simulated highway driving tasks. -long_description=file:README.md -long_description_content_type=text/markdown -url=https://github.com/eleurent/highway-env -license=MIT -classifiers= - Development Status :: 5 - Production/Stable - Programming Language :: Python :: 3 - License :: OSI Approved :: MIT License - - -[options] -setup_requires= - pytest-runner -install_requires= - farama-notifications - gymnasium>=0.27 - numpy - pygame>=2.0.2 - matplotlib - pandas - scipy -packages=find: -tests_require= - pytest - -[options.extras_require] -deploy = pytest-runner; sphinx<1.7.3; sphinx_rtd_theme - -[options.packages.find] -exclude = - tests - docs - scripts - -[options.entry_points] -gymnasium.envs = - __root__ = highway_env.__init__:register_highway_envs - -[aliases] -test=pytest diff --git a/setup.py b/setup.py index e5e5d6041..7d8731597 100644 --- a/setup.py +++ b/setup.py @@ -20,5 +20,4 @@ def get_version(): raise RuntimeError("bad version data in __init__.py") -if __name__ == "__main__": - setup(version=get_version()) +setup(version=get_version()) diff --git a/tests/envs/test_actions.py b/tests/envs/test_actions.py index 8cad31f2b..451b84550 100644 --- a/tests/envs/test_actions.py +++ b/tests/envs/test_actions.py @@ -3,18 +3,19 @@ import highway_env -highway_env.register_highway_envs() +gym.register_envs(highway_env) -action_configs = [ - {"type": "ContinuousAction"}, - {"type": "DiscreteAction"}, - {"type": "DiscreteMetaAction"}, -] - -@pytest.mark.parametrize("action_config", action_configs) +@pytest.mark.parametrize( + "action_config", + [ + {"type": "ContinuousAction"}, + {"type": "DiscreteAction"}, + {"type": "DiscreteMetaAction"}, + ], +) def test_action_type(action_config): - env = gym.make("highway-v0") + env = gym.make("highway-v0").unwrapped env.configure({"action": action_config}) env.reset() for _ in range(3): diff --git a/tests/envs/test_env_preprocessors.py b/tests/envs/test_env_preprocessors.py index 9db589196..4f78643fd 100644 --- a/tests/envs/test_env_preprocessors.py +++ b/tests/envs/test_env_preprocessors.py @@ -2,11 +2,11 @@ import highway_env -highway_env.register_highway_envs() +gym.register_envs(highway_env) def test_preprocessors(): - env = gym.make("highway-v0") + env = gym.make("highway-v0").unwrapped env = env.simplify() env = env.change_vehicles("highway_env.vehicle.behavior.IDMVehicle") env = env.set_preferred_lane(0) diff --git a/tests/envs/test_gym.py b/tests/envs/test_gym.py index 71cbb91e4..7514b6924 100644 --- a/tests/envs/test_gym.py +++ b/tests/envs/test_gym.py @@ -1,25 +1,68 @@ +import warnings + import gymnasium as gym import pytest +from gymnasium.utils.env_checker import check_env import highway_env +from highway_env.envs.common.abstract import MultiAgentWrapper from highway_env.envs.highway_env import HighwayEnv -highway_env.register_highway_envs() - -envs = [ - "highway-v0", - "merge-v0", - "roundabout-v0", - "intersection-v0", - "intersection-v1", - "parking-v0", - "two-way-v0", - "lane-keeping-v0", - "racetrack-v0", +gym.register_envs(highway_env) + + +highway_env_ids = [ + env_id + for env_id, env_spec in gym.registry.items() + if isinstance(env_spec.entry_point, str) and "highway_env" in env_spec.entry_point +] + + +CHECK_ENV_IGNORE_WARNINGS = [ + f"\x1b[33mWARN: {message}\x1b[0m" + for message in [ + "A Box observation space minimum value is -infinity. This is probably too low.", + "A Box observation space maximum value is infinity. This is probably too high.", + # "For Box action spaces, we recommend using a symmetric and normalized space (range=[-1, 1] or [0, 1]). See https://stable-baselines3.readthedocs.io/en/master/guide/rl_tips.html for more information.", + "The environment intersection-v0 is out of date. You should consider upgrading to version `v1`.", + "The environment intersection-multi-agent-v0 is out of date. You should consider upgrading to version `v1`.", + ] ] -@pytest.mark.parametrize("env_spec", envs) +@pytest.mark.parametrize("env_id", highway_env_ids) +def test_highway_api(env_id): + """Check that all environments pass the environment checker with no warnings other than the expected.""" + with warnings.catch_warnings(record=True) as caught_warnings: + env = gym.make(env_id) + if isinstance(env, MultiAgentWrapper): + pytest.skip(f"Multi-Agent wrapper does not match api ({env}).") + + check_env(env, skip_render_check=True) + + env.close() + + for warning in caught_warnings: + if "is different from the unwrapped version" in warning.message.args[0]: + continue + elif warning.message.args[0] not in CHECK_ENV_IGNORE_WARNINGS: + raise gym.error.Error(f"Unexpected warning: {warning.message}") + + +@pytest.mark.parametrize( + "env_spec", + [ + "highway-v0", + "merge-v0", + "roundabout-v0", + "intersection-v0", + "intersection-v1", + "parking-v0", + "two-way-v0", + "lane-keeping-v0", + "racetrack-v0", + ], +) def test_env_step(env_spec): env = gym.make(env_spec) @@ -35,7 +78,7 @@ def test_env_step(env_spec): def test_env_reset_options(env_spec: str = "highway-v0"): - env = gym.make(env_spec) + env = gym.make(env_spec).unwrapped default_duration = HighwayEnv().default_config()["duration"] assert env.config["duration"] == default_duration diff --git a/tests/envs/test_time.py b/tests/envs/test_time.py index d2981890e..4de14aa75 100644 --- a/tests/envs/test_time.py +++ b/tests/envs/test_time.py @@ -4,7 +4,7 @@ import highway_env -highway_env.register_highway_envs() +gym.register_envs(highway_env) def wrapper(func, *args, **kwargs): diff --git a/tests/graphics/test_render.py b/tests/graphics/test_render.py index 8e8848a3c..f11062493 100644 --- a/tests/graphics/test_render.py +++ b/tests/graphics/test_render.py @@ -4,13 +4,12 @@ import highway_env -highway_env.register_highway_envs() -envs = ["highway-v0", "merge-v0"] +gym.register_envs(highway_env) -@pytest.mark.parametrize("env_spec", envs) +@pytest.mark.parametrize("env_spec", ["highway-v0", "merge-v0"]) def test_render(env_spec): - env = gym.make(env_spec, render_mode="rgb_array") + env = gym.make(env_spec, render_mode="rgb_array").unwrapped env.configure({"offscreen_rendering": True}) env.reset() img = env.render() @@ -23,9 +22,9 @@ def test_render(env_spec): ) # (H,W,C) -@pytest.mark.parametrize("env_spec", envs) +@pytest.mark.parametrize("env_spec", ["highway-v0", "merge-v0"]) def test_obs_grayscale(env_spec, stack_size=4): - env = gym.make(env_spec) + env = gym.make(env_spec).unwrapped env.configure( { "offscreen_rendering": True, diff --git a/tests/vehicle/test_behavior.py b/tests/vehicle/test_behavior.py index 4ae93f94b..e34e64b15 100644 --- a/tests/vehicle/test_behavior.py +++ b/tests/vehicle/test_behavior.py @@ -5,10 +5,9 @@ from highway_env.vehicle.objects import Obstacle FPS = 15 -vehicle_types = [IDMVehicle, LinearVehicle] -@pytest.mark.parametrize("vehicle_type", vehicle_types) +@pytest.mark.parametrize("vehicle_type", [IDMVehicle, LinearVehicle]) def test_stop_before_obstacle(vehicle_type): road = Road(RoadNetwork.straight_road_network(lanes=1)) vehicle = vehicle_type(road=road, position=[0, 0], speed=20, heading=0)