From c7005914e025872a2a51cc34bc3142e7a3df2d9c Mon Sep 17 00:00:00 2001 From: "aremonda@leonardo" Date: Wed, 12 Jun 2024 21:50:59 +0200 Subject: [PATCH] update --- .gitignore | 59 +++++++++++ INSTALL.md | 67 ++++++++++++ LICENSE | 2 +- README.md | 210 +++++++++++++++++++------------------ config.yml | 78 ++++++++++++++ motec_to_pickle.py | 62 +++++++++++ motec_to_pickle_config.yml | 5 + requirements.txt | 33 ++++++ test_client.ipynb | 173 ++++++++++++++++++++++++++++++ test_gym.ipynb | 187 +++++++++++++++++++++++++++++++++ test_maps_creator.ipynb | 180 +++++++++++++++++++++++++++++++ train.py | 144 +++++++++++++++++++++++++ 12 files changed, 1096 insertions(+), 104 deletions(-) create mode 100644 .gitignore create mode 100644 INSTALL.md create mode 100644 config.yml create mode 100644 motec_to_pickle.py create mode 100644 motec_to_pickle_config.yml create mode 100644 requirements.txt create mode 100644 test_client.ipynb create mode 100644 test_gym.ipynb create mode 100644 test_maps_creator.ipynb create mode 100644 train.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..336caa0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# user +results +data/data_sets +archive +*.ptorch +*.pkl? + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +.ipynb_checkpoints + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Sphinx documentation +docs/_build/ + +# +*.zip + +# mujoco +MUJOCO_LOG.TXT + +# custom +output +outputs +assetto_corsa_gym.code-workspace diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..50f69bb --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,67 @@ +

Assetto Corsa Gym

+ +Plug-in installation instructions + +## Assetto Corsa plugin Installation +1. **Install vJoy** + - Needed to send commands to Assetto Corsa + - Download and install [Vjoy](https://sourceforge.net/projects/vjoystick/) + +2. **Copy the plugin files** + - Navigate to the plugin folder located in this repo: + ``` + .\assetto_corsa_gym\assetto-corsa-autonomous-racing-plugin\plugins\sensors_par + ``` + - Copy this folder to the Assetto Corsa installation folder under `apps\python\`. The default AC installation folder is usually located at: + ``` + \assettocorsa\apps\python\ + ``` + where `` is typically in: + ``` + C:\Program Files (x86)\Steam\steamapps\common\assettocorsa\ + ``` + - The destination path should look like this: + ``` + C:\Program Files (x86)\Steam\steamapps\common\assettocorsa\apps\python\sensor_par + ``` +4. **Installation of Configuration Files** +Install the following configurations from `assetto_corsa_gym\assetto-corsa-autonomous-racing-plugin\windows-libs` + + - **Vjoy Configuration** + - The `Vjoy.ini` file is a configuration file for Assetto Corsa. It should be copied to: + ``` + C:\Users\%user%\Documents\Assetto Corsa\cfg\controllers\savedsetups + ``` + - **WASD Controls** + - The `WASD.ini` file allows you to control the car using WASD keys. Copy it to: + ``` + C:\Users\%user%\Documents\Assetto Corsa\cfg\controllers\savedsetups + ``` + - **Dlls and Lib Folders** + - These folders contain a Python version of the socket library, used by the plugin. They should be copied to: + ``` + C:\Program Files (x86)\Steam\steamapps\common\assettocorsa\system\x64 + ``` + +4. **Custom Shaders Patch** + - This patch is needed to be able to restart the car. Install Content Manager from: + [Content Manager Download](https://acstuff.ru/app/) + - After installation, open Content Manager and navigate to: + - Settings > Custom Shaders Patch + - Click "install" to complete the setup. + +5. **Run Assetto Corsa** + - In `options -> general -> UI Modules`: **enable sensor_par** + - In `options -> control`: you should have vJoy and WASD. Load **vJoy** + - In `options -> video -> Display -> Framerate limit` set to **50fps** + See `assetto_corsa_gym\assetto-corsa-autonomous-racing-plugin\plugins\sensors_par\config.py` + +--- + +## Acknoledgements + +- Adrian Remonda (TU Graz) +- Francesco Gatti (TII EuroRacing - Hipert) +- Andrea Serafini (TII EuroRacing - Unimore) +- Francesco Moretti (TII EuroRacing - Unimore) +- Ayoub Raji (TII EuroRacing - Unimore) diff --git a/LICENSE b/LICENSE index 976fd5a..2e21c99 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) Nicklas Hansen (2023). +Copyright (c) 2024 Adrian Remonda Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5a11f2d..6bf7bcf 100644 --- a/README.md +++ b/README.md @@ -1,135 +1,139 @@ -# Assetto Corsa Gym Environment +

Assetto Corsa Gym

+Official implementation of the paper: +[A Simulation Benchmark for Autonomous Racing with Large-Scale Human Data](https://dasgringuen.github.io/assetto_corsa_gym/) + + + + + +[[Website]](https://dasgringuen.github.io/assetto_corsa_gym/) [[Paper]]() [[Dataset]](https://huggingface.co/datasets/dasgringuen/assettoCorsaGym) [[ROS2 interface]](https://github.com/ayoubraji/assetto_corsa_ros2) + +---- + +## Overview Gym interfaces with Assetto Corsa for Autonomous Racing. This repository integrates the Assetto Corsa racing simulator with the OpenAI's Gym interface, providing a high-fidelity environment for developing and testing Autonomous Racing algorithms in realistic racing scenarios. -Features +Features: - High-Fidelity Simulation: Realistic car dynamics and track environments. - Customizable Scenarios: Various cars, tracks, and weather conditions. - RL Integration: Compatible with Gym for easy RL algorithm application. -- ROS integration (see link) +- [ROS2 interface](https://github.com/ayoubraji/assetto_corsa_ros2) +This repository contains all the necessary code to run the Gym interface for Assetto Corsa and RL benchmarks. For the ROS2 interface, refer to [ROS2 interface](https://github.com/ayoubraji/assetto_corsa_ros2) -## Run +## Source Code Tree: -### Train -``` -python sac_train.py --config ../algorithm/discor/config/assettocorsa.yaml --track --car -``` +- **assetto_corsa_gym** + - **assetto-corsa-autonomous-racing-plugin** + - Plugin for AC + - **AssettoCorsaConfigs** + - **cars** + - **controls maps** + - **tracks** + - Track bounds + - Occupancy + - Racing line + - **AssettoCorsaEnv** + - Communication client + - OpenAI Gym interface +- **algorithm** + - SAC baseline +- **data** + - Extra information of the dataset and statistics. Instructions to record human demonstrations -### Test -``` -python sac_train.py --config ../algorithm/discor/config/assettocorsa.yaml --disable_wandb --track --car --load_path --test -``` -# Download datasets +## Getting started -Create a path and navigate to it -``` -mkdir data_sets && cd data_sets -``` +1. **Install the AC plug-in** + Follow the instructions in [`INSTALL.md`](INSTALL.md) -# Tracks -## Available -Barcelona: ks_barcelona-layout_gp -Monza: monza -Austria: ks_red_bull_ring-layout_gp -Oval: indianapolis_sp +1. **Install Python in Windows** -## Monza -``` -wget -O downloaded_file.zip "https://www.dropbox.com/scl/fo/u8q1stkafbxw6mgavc9oo/AG64cejNqerJc8Rj9h0FTYk?rlkey=3i7hx2q7f528mkbfhy163e9r9&dl=1" && unzip downloaded_file.zip -d monza; rm downloaded_file.zip -``` + - **Install Visual Studio Compiler** + - To compile the necessary components for the plugin, download and install the Visual Studio compiler from: + [Visual Studio C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) + - Make sure to install the C++ build tools component -## Barcelona -``` -wget -O downloaded_file.zip "https://www.dropbox.com/scl/fo/jy7szdlsb6z5hceqbcipe/ABZ5IgqCLNEmoGeu-xxtRdU?rlkey=a8dketk9c77wgzx8am32dvrvy&dl=1" && unzip downloaded_file.zip -d ks_barcelona-layout_gp; rm downloaded_file.zip -``` + - **Install Python using Anaconda** + ``` + conda create -n p309 python=3.9.13 + conda activate p309 + pip install setuptools==65.5.0 + pip install -r requirements.txt + conda install pytorch==1.12.1 cudatoolkit=11.6 -c pytorch -c conda-forge + ``` + +2. **Download the tracks occupancy grid** + - Get the files from here [tracks](https://huggingface.co/datasets/dasgringuen/assettoCorsaGym/tree/main/AssettoCorsaConfigs/tracks). + - Optionally, download them using the Huggingface Hub interface: + - Install it: `conda install -c conda-forge huggingface_hub` + - And run: + ```sh + python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='dasgringuen/assettoCorsaGym', repo_type='dataset', local_dir='AssettoCorsaGymDataSet', allow_patterns='AssettoCorsaConfigs/tracks/*')" + ``` + - Move the pickle files to `assetto_corsa_gym/AssettoCorsaConfigs/tracks`. + + - To create a new track: + - Each track needs an occupancy grid (pickle), a reference line (can be the one from AC but also something else), and the track bounds. These files are located in `assetto_corsa_gym/AssettoCorsaConfigs/tracks`. + - The reference line and the occupancy grid are needed to run the Gym interface. + - To create a new track: + - Start Assetto Corsa and set the new track. + - Run `assetto_corsa_gym/AssettoCorsaConfigs/tracks/generate_track.ipynb` to create a new track. This script creates a pickle file with the occupancy grid, downloads the AC reference path, and visualizes the track. Make sure to update the `assetto_corsa_gym/AssettoCorsaConfigs/tracks/config.yaml` file. + + + +## Demos +- **test_client.ipynb** + - Demonstrates the use of the Assetto Corsa Sim interface (client) without the OpenAI Gym interface layer. + +- **test_gym.ipynb** + - Demonstrates the use of the Gym interface without an agent. -## Indianapolis_sp (Oval) -``` -wget -O downloaded_file.zip "https://www.dropbox.com/scl/fo/3vp25we2h8g7knuhpioic/ADHx7qb88-TdZk9su1KXWKg?rlkey=cgwdjdujuzhfquaiz2cjdo241&dl=1" && unzip downloaded_file.zip -d indianapolis_sp; rm downloaded_file.zip -``` -## Austria +## Train RL models ``` -wget -O downloaded_file.zip "https://www.dropbox.com/scl/fo/dezimtveazd1w70ttggf0/AMmK8ue5VaKc6vYZT_t0-4Q?rlkey=mpyb2axbzzyebc6l85lnmh9ls&dl=1" && unzip downloaded_file.zip -d ks_red_bull_ring-layout_gp; rm downloaded_file.zip +python train.py track --load_path ``` +## Download Datasets +- Currently supported tracks: + - Barcelona: `ks_barcelona-layout_gp` + - Monza: `monza` + - Austria: `ks_red_bull_ring-layout_gp` + - Oval: `indianapolis_sp` + - Silverstone: `ks_silverstone-gp` -## Python Installation +- To download -**Install Visual Studio Compiler** -- To compile the necessary components for the plugin, download and install the Visual Studio compiler from: -[Visual Studio C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) -- Make sure to install the C++ build tools component + Run one track and car at a time. Replace `` and ``: -**Install Python using Anaconda** -``` -conda create -n p309 python=3.9.13 -conda activate p309 + ```python + from huggingface_hub import snapshot_download -pip install setuptools==65.5.0 -pip install -r requirements.txt + snapshot_download( + repo_id="dasgringuen/assettoCorsaGym", + repo_type="dataset", + local_dir="AssettoCorsaGymDataSet", + allow_patterns="data_sets///*" + ) + ``` -conda install pytorch==1.12.1 cudatoolkit=11.6 -c pytorch -c conda-forge -conda install -c conda-forge shapely # (linux) pip install shapely intersect -``` + Optionally, download everything at once (120GB): -### Problems -- Complains about OpenCV. Fix: -```pip install setuptools==65.5.0 "wheel<0.40.0"``` - -## Assetto Corsa plugin Installation -1. **Follow the manual** - -2. **Install vJoy** - - Needed to send commands to Assetto Corsa - - Download and install [Vjoy](https://sourceforge.net/projects/vjoystick/) - -2. **Copy the plugin files** - - Navigate to your plugin folder: - ``` - .\assetto-corsa-autonomous-racing-plugin\plugins\sensors_par - ``` - - Copy it to your Assetto Corsa installation folder under `apps\python\`. The default AC installation folder is usually located at: - ``` - \assettocorsa\apps\python\ - ``` - where `` is typically: - ``` - C:\Program Files (x86)\Steam\steamapps\common\assettocorsa\ - ``` - - The destination path should look something like this: - ``` - C:\Program Files (x86)\Steam\steamapps\common\assettocorsa\apps\python\sensor_par - ``` -3. **Installation of Configuration Files** - - - **Vjoy Configuration** - - The `Vjoy.ini` file is a configuration file for Assetto Corsa. It should be copied to: - ``` - C:\Users\%user%\Documents\Assetto Corsa\cfg\controllers\savedsetups - ``` - - **WASD Controls** - - The `WASD.ini` file allows you to control the car using WASD keys. Copy it to: - ``` - C:\Users\%user%\Documents\Assetto Corsa\cfg\controllers\savedsetups - ``` - - **Dlls and Lib Folders** - - These folders contain a Python version of the socket library, used by the plugin. They should be copied to: - ``` - C:\Program Files (x86)\Steam\steamapps\common\assettocorsa\system\x64 - ``` + ```python + from huggingface_hub import snapshot_download -5. **Custom Shaders Patch** - - This patch is needed to be able to restart the car. Install Content Manager from: - [Content Manager Download](https://acstuff.ru/app/) - - After installation, open Content Manager and navigate to: - - Settings > Custom Shaders Patch - - Click "install" to complete the setup. + snapshot_download( + repo_id="dasgringuen/assettoCorsaGym", + repo_type="dataset", + local_dir="AssettoCorsaGymDataSet", + allow_patterns="data_sets/*" + ) + ``` ---- @@ -147,4 +151,4 @@ You are very welcome to contribute to this project. Feel free to open an issue o ## License -This project is licensed under the MIT License - see the `LICENSE` file for details. Note that the repository relies on third-party code, which is subject to their respective licenses. +This project is licensed under the MIT License - see the `LICENSE` file for details. Note that the repository relies on third-party code, which is subject to their respective licenses. \ No newline at end of file diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..0d87e45 --- /dev/null +++ b/config.yml @@ -0,0 +1,78 @@ +load_offline_data: False # offline pre-training and data loading +pre_train: False # pre train using offline data before start online training +dataset_path: null # Path to the datasets, null means it will be overridden by the command line if needed +work_dir: null # Path to run the model from (default: None) +task: AssettoCorsaEnv +seed: 0 + +# wandb +disable_wandb: True +wandb_entity: null # user +wandb_project: sac +wandb_silent: False +exp_name: null +task_title: null +save_csv: True +save_video: False +save_agent: False +action_dim: null # placeholder +steps: null # placeholder + +Agent: + num_steps: 10_000_000 + batch_size: 128 + memory_size: 10_000_000 + offline_buffer_size: 10_000_000 + use_offline_buffer: False # offline dual buffer + update_interval: 1 + start_steps: 2000 + log_interval: 10 + eval_interval: 100000 + num_eval_episodes: 1 + checkpoint_freq: 200_000 + +SAC: + gamma: 0.992 + nstep: 3 + policy_lr: 0.0003 + q_lr: 0.0003 + entropy_lr: 0.0003 + policy_hidden_units: [256, 256, 256] + q_hidden_units: [256, 256, 256] + target_update_coef: 0.005 + log_interval: 10 + +DisCor: + error_lr: 0.0003 + error_hidden_units: [256, 256, 256, 256] + tau_init: 10.0 + +AssettoCorsa: + ego_sampling_freq: 25 # in Hz + max_episode_py_time: 600. # in seconds + train_steps: 10_000_000 + gap_const: 12. + eval_number_of_laps: 4 # including out lap which is incomplete + save_lap_csv: False + remote_machine_ip: "localhost" # "192.168.0.1" # "localhost" + ego_server_port: 2345 + opponents_server_port: 2346 + simulation_management_server_port: 2347 + use_relative_actions: True + enable_sensors: True + vjoy_executed_by_server: False + enable_out_of_track_termination: True + enable_low_speed_termination: True + recover_car_on_done: False + send_reset_at_start: True + record_controls_from_client: False + enable_out_of_track_penalty: False + max_laps_number: null + enable_task_id_in_obs: False + penalize_actions_diff: False + penalize_actions_diff_coef: 0.1 + track: "ks_barcelona-layout_gp" # monza ks_barcelona-layout_gp ks_red_bull_ring-layout_gp, indianapolis_sp, ks_silverstone-gp + car: "dallara_f317" # dallara_f317, ks_mazda_miata, bmw_z4_gt3 + use_ac_out_of_track: True + add_previous_obs_to_state: True + use_reference_line_in_reward: True diff --git a/motec_to_pickle.py b/motec_to_pickle.py new file mode 100644 index 0000000..7454075 --- /dev/null +++ b/motec_to_pickle.py @@ -0,0 +1,62 @@ +import os +import sys +import logging +from pathlib import Path +import argparse +from omegaconf import OmegaConf + +# Custom module imports +sys.path.append(os.path.abspath('./assetto_corsa_gym')) +import AssettoCorsaEnv.ac_env as ac_env +import AssettoCorsaEnv.motec_loader as motec_loader +import AssettoCorsaEnv.data_loader as data_loader +import AssettoCorsaEnv.assettoCorsa as assettoCorsa + +def setup_logging(): + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + ) + +def parse_arguments(): + parser = argparse.ArgumentParser(description="Process dataset for Assetto Corsa.") + parser.add_argument("--config", default="config.yml", type=str, help="Path to configuration file") + parser.add_argument("overrides", nargs=argparse.REMAINDER, help="Any key=value arguments to override config values") + return parser.parse_args() + +def main(): + setup_logging() + logger = logging.getLogger(__name__) + + # cmd line args and config + args = parse_arguments() + config = OmegaConf.load(args.config) + cli_conf = OmegaConf.from_dotlist(args.overrides) + config = OmegaConf.merge(config, cli_conf) + + # Path configurations + dataset_path = Path(config.dataset_path) + work_dir = "outputs" + + # Module configuration + config.AssettoCorsa.enable_out_of_track_termination = False + config.AssettoCorsa.enable_low_speed_termination = False + + load_paths = data_loader.get_all_paths_in_file(dataset_path=dataset_path, file='motec_to_pickle_config.yml', filter_tags={"type": "human", "data_type": "motec"}) + + for laps_path, car, track in load_paths: + logger.info(f"******************************************") + logger.info(f"Processing {track} {car}") + logger.info(f"from {laps_path}") + logger.info(f"******************************************") + laps_path = Path(laps_path) + assert laps_path.exists(), f"{laps_path} not found" + + config.AssettoCorsa.track = track + config.AssettoCorsa.car = car + env = assettoCorsa.make_ac_env(cfg=config, work_dir=work_dir) + motec_loader.convert_all_in_path(laps_path.as_posix(), env, ac_fps=50, target_freq=25, ignore_fps_missmatch=True) + +if __name__ == "__main__": + main() diff --git a/motec_to_pickle_config.yml b/motec_to_pickle_config.yml new file mode 100644 index 0000000..ed852a6 --- /dev/null +++ b/motec_to_pickle_config.yml @@ -0,0 +1,5 @@ +ks_barcelona-layout_gp: + bmw_z4_gt3: + # humans + - {id: 20240423_IN, type: human, auto_shift: True, ff: True, data_type: motec} + - {id: 20240424_KW, type: human, auto_shift: True, ff: True, data_type: motec} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9526a8f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,33 @@ +setuptools==65.5.0 +numpy==1.23.3 +gym==0.21.0 +gym[classic_control]==0.21.0 +cloudpickle==2.2.0 +cycler==0.11.0 +fonttools==4.37.1 +kiwisolver==1.4.4 +matplotlib==3.5.3 +packaging==21.3 +pandas==1.4.4 +Pillow==9.2.0 +pyparsing==3.0.9 +python-dateutil==2.8.2 +pytz==2022.2.1 +six==1.16.0 +#torch==1.12.1 +typing_extensions==4.3.0 +mpld3 +tqdm +jupyter +pyglet +pybullet +scikit-optimize +gym-minigrid +tensorboard +intersect +termcolor +python-box +omegaconf +wandb +pygame +numba \ No newline at end of file diff --git a/test_client.ipynb b/test_client.ipynb new file mode 100644 index 0000000..715eaa1 --- /dev/null +++ b/test_client.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Client demo\n", + "\n", + "Demonstrate the use of the Client (Assetto Corsa Sim interface) without the OpenAI Gym interface layer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import sys\n", + "import os\n", + "import numpy as np\n", + "import pandas as pd\n", + "import glob as glob\n", + "import time\n", + "import pickle\n", + "from omegaconf import OmegaConf\n", + "\n", + "# add custom paths\n", + "sys.path.extend([os.path.abspath('./assetto_corsa_gym')])\n", + "import AssettoCorsaEnv.assettoCorsa as assettoCorsa\n", + "\n", + "# Configure the logging system\n", + "import logging\n", + "logger = logging.getLogger(__name__)\n", + "logging.basicConfig(\n", + " level=logging.INFO, # Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)\n", + " format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # Format of the log messages\n", + " datefmt='%Y-%m-%d %H:%M:%S', # Format of the timestamp\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# load config file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "config = OmegaConf.load(\"config.yml\")\n", + "client = assettoCorsa.make_client_only(config.AssettoCorsa)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Show static info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "static_info = client.simulation_management.get_static_info()\n", + "ac_mod_config = client.simulation_management.get_config()\n", + "\n", + "logger.info(\"Static info:\")\n", + "for i in static_info:\n", + " logger.info(f\"{i}: {static_info[i]}\")\n", + "logger.info(\"AC Mod config:\")\n", + "for i in ac_mod_config:\n", + " logger.info(f\"{i}: {ac_mod_config[i]}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Apply actions and recover the car" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "states = []\n", + "\n", + "client.reset()\n", + "\n", + "for i in range(100):\n", + " if i % 2 == 0:\n", + " steer = .1\n", + " else:\n", + " steer = -.1\n", + " client.controls.set_controls(steer=steer, acc=0.5, brake=-1.)\n", + " client.respond_to_server()\n", + " state = client.step_sim()\n", + " states.append(state.copy())\n", + " time.sleep(0.01)\n", + "\n", + "client.controls.set_defaults()\n", + "client.respond_to_server()\n", + "client.simulation_management.send_reset()\n", + "client.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plot states" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.DataFrame(states)\n", + "df.columns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.title(\"Speed\")\n", + "plt.plot(df.speed)\n", + "plt.show()\n", + "\n", + "plt.title(\"steerAngle\")\n", + "plt.plot(df.steerAngle)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/test_gym.ipynb b/test_gym.ipynb new file mode 100644 index 0000000..f2ace67 --- /dev/null +++ b/test_gym.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Gym demo\n", + "\n", + "Demonstrate the use of the the Gym interface" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import sys\n", + "import os\n", + "import numpy as np\n", + "import pandas as pd\n", + "import glob as glob\n", + "import time\n", + "import pickle\n", + "from omegaconf import OmegaConf\n", + "\n", + "# add custom paths\n", + "sys.path.extend([os.path.abspath('./assetto_corsa_gym')])\n", + "import AssettoCorsaEnv.assettoCorsa as assettoCorsa\n", + "\n", + "# Configure the logging system\n", + "import logging\n", + "logger = logging.getLogger(__name__)\n", + "logging.basicConfig(\n", + " level=logging.INFO, # Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)\n", + " format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # Format of the log messages\n", + " datefmt='%Y-%m-%d %H:%M:%S', # Format of the timestamp\n", + ")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load config file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "config = OmegaConf.load(\"config.yml\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create env object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "env = assettoCorsa.make_ac_env(cfg=config, work_dir=\"output\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Show static info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "static_info = env.client.simulation_management.get_static_info()\n", + "ac_mod_config = env.client.simulation_management.get_config()\n", + "\n", + "logger.info(\"Static info:\")\n", + "for i in static_info:\n", + " logger.info(f\"{i}: {static_info[i]}\")\n", + "logger.info(\"AC Mod config:\")\n", + "for i in ac_mod_config:\n", + " logger.info(f\"{i}: {ac_mod_config[i]}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Apply actions and recover the car" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "states = []\n", + "\n", + "env.reset()\n", + "\n", + "for i in range(100):\n", + " if i % 2 == 0:\n", + " steer = .1\n", + " else:\n", + " steer = -.1\n", + " env.set_actions(np.array([steer, 0.5, -1.]))\n", + " next_state, reward, done, info = env.step(action=None) # action is already applied\n", + " time.sleep(0.01)\n", + " if done:\n", + " break\n", + "\n", + "env.recover_car()\n", + "env.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plot states" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = env.get_history()\n", + "df.columns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.title(\"Speed\")\n", + "plt.plot(df.speed)\n", + "plt.show()\n", + "\n", + "plt.title(\"steerAngle\")\n", + "plt.plot(df.steerAngle)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/test_maps_creator.ipynb b/test_maps_creator.ipynb new file mode 100644 index 0000000..11136dc --- /dev/null +++ b/test_maps_creator.ipynb @@ -0,0 +1,180 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import sys\n", + "import os\n", + "import numpy as np\n", + "import pandas as pd\n", + "import glob as glob\n", + "import time\n", + "import pickle\n", + "from omegaconf import OmegaConf\n", + "\n", + "# add custom paths\n", + "sys.path.extend([os.path.abspath('./assetto_corsa_gym')])\n", + "import AssettoCorsaEnv.assettoCorsa as assettoCorsa\n", + "from AssettoCorsaEnv.brake_map import BrakeMap\n", + "\n", + "# Configure the logging system\n", + "import logging\n", + "logger = logging.getLogger(__name__)\n", + "logging.basicConfig(\n", + " level=logging.INFO, # Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)\n", + " format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # Format of the log messages\n", + " datefmt='%Y-%m-%d %H:%M:%S', # Format of the timestamp\n", + ")\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create environment\n", + "config = OmegaConf.load(\"config.yml\")\n", + "\n", + "# Set the configuration\n", + "config.AssettoCorsa.use_relative_actions = False\n", + "config.AssettoCorsa.enable_low_speed_termination = False\n", + "config.AssettoCorsa.car = \"dallara_f317\" # ks_mazda_miata\" # \"bmw_z4_gt3\"\n", + "\n", + "env = assettoCorsa.make_ac_env(cfg=config, work_dir=\"output\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create the brake map by setting values throughout the full range and observing the telemetry" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "res = []\n", + "\n", + "env.reset()\n", + "for b in np.linspace(-1,1, 21):\n", + " for i in range(25):\n", + " _, _, done, _ = env.step([0.0, -1.0, b])\n", + " r = {'brake': b, 'brakeStatus': env.state['brakeStatus']}\n", + " res.append(r.copy())\n", + " print(r)\n", + "\n", + "# Save\n", + "x = [r['brake'] for r in res]\n", + "y = [r['brakeStatus'] for r in res]\n", + "brake_map = BrakeMap(x, y, kind='cubic')\n", + "brake_map.save()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#brake_map = BrakeMap.load()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_int = brake_map.get_y(np.arange(-1, 1, 0.01))\n", + "\n", + "# Create the plot\n", + "plt.figure(figsize=(8, 6))\n", + "plt.plot(x, y, 'o-', label='Data Points', alpha=0.5) # 'o-' creates a line plot with circle markers\n", + "plt.scatter(np.arange(-1, 1, 0.01), y_int, s=2, label='Data Points', color='red') # 'o-' creates a line plot with circle markers\n", + "\n", + "# Adding plot labels and title\n", + "plt.xlabel('brake')\n", + "plt.ylabel('brake AC Feedback')\n", + "plt.title('brake map forward')\n", + "plt.grid(True) # Show grid\n", + "plt.legend() # Show legend\n", + "\n", + "# Display the plot\n", + "plt.show()\n", + "\n", + "y = np.arange(0, 1, 0.01) # brake status\n", + "x_int = brake_map.get_x(y)\n", + "\n", + "# Create the plot\n", + "plt.figure(figsize=(8, 6))\n", + "plt.scatter(y, x_int, s=2, label='Data Points', color='red') # 'o-' creates a line plot with circle markers\n", + "\n", + "# Adding plot labels and title\n", + "plt.xlabel('brake AC Feedback')\n", + "plt.ylabel('brake')\n", + "plt.title('Reverse')\n", + "plt.grid(True) # Show grid\n", + "plt.legend() # Show legend\n", + "\n", + "# Display the plot\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# steer map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "res = []\n", + "\n", + "env.reset()\n", + "for b in [-1 , 1]:\n", + " for i in range(25):\n", + " _, _, done, _ = env.step([b, -1.0, -1.0])\n", + " r = {'action_steerAngle': b, 'steerAngle': env.state['steerAngle']}\n", + " res.append(r.copy())\n", + "print(res)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/train.py b/train.py new file mode 100644 index 0000000..1eb272c --- /dev/null +++ b/train.py @@ -0,0 +1,144 @@ +import os +import sys +import argparse +import logging +from datetime import datetime +from pathlib import Path +import copy +from omegaconf import OmegaConf +import torch + +# Add paths +sys.path.extend([os.path.abspath('./assetto_corsa_gym'), './algorithm/discor']) + +# Custom module imports +import AssettoCorsaEnv.assettoCorsa as assettoCorsa +import AssettoCorsaEnv.data_loader as data_loader +from discor.algorithm import SAC, DisCor +from discor.agent import Agent +import common.misc as misc +import common.logging_config as logging_config +from common.logger import Logger + +logger = logging.getLogger(__name__) + +def parse_args(hardcode=None): + parser = argparse.ArgumentParser(description="Description of your program.") + parser.add_argument("--config", default="config.yml", type=str, help="Path to configuration file") + parser.add_argument("--load_path", type=str, default=None, help="Path to load the model from (default: None)") + parser.add_argument("--algo", type=str, default="sac", help="Algorithm type (default: sac)") + parser.add_argument("--test", action="store_true") + parser.add_argument("overrides", nargs=argparse.REMAINDER, help="Any key=value arguments to override config values") + if hardcode is not None: + args = parser.parse_args(hardcode.split()) + else: + args = parser.parse_args() + args.load_path = os.path.abspath(args.load_path) + os.sep if args.load_path is not None else None + return args + +def main(): + args = parse_args() + + # Load base configuration + config = OmegaConf.load(args.config) + + # Apply command line overrides + cli_conf = OmegaConf.from_dotlist(args.overrides) + config = OmegaConf.merge(config, cli_conf) + + if config.work_dir is not None: + work_dir = os.path.abspath(args.work_dir) + os.sep + config.track + os.sep + config.car + os.sep + os.makedirs(work_dir, exist_ok=True) + else: + work_dir = "outputs" + os.sep + datetime.now().strftime('%Y%m%d_%H%M%S.%f')[:-3] + work_dir = os.path.abspath(work_dir) + os.sep + os.makedirs(work_dir, exist_ok=True) + config.work_dir = work_dir + + logging_config.create_logging(level=logging.DEBUG, file_name=work_dir + "log.log") + logging.getLogger().setLevel(logging.INFO) + + # log system and git info + misc.get_system_info() + misc.get_git_commit_info() + + logger.info("Configuration:") + logger.info(OmegaConf.to_yaml(config)) + logger.info("work_dir: " + work_dir) + + env = assettoCorsa.make_ac_env(cfg=config, work_dir=work_dir) + + # Device to use + device = torch.device("cuda") + assert device.type == "cuda", "Only cuda is supported" + + if args.algo == 'discor': + algo = DisCor( + state_dim=env.observation_space.shape[0], + action_dim=env.action_space.shape[0], + device=device, seed=config.seed, + **OmegaConf.to_container(config.SAC), **OmegaConf.to_container(config.DisCor)) + elif args.algo == 'sac': + algo = SAC( + state_dim=env.observation_space.shape[0], + action_dim=env.action_space.shape[0], + device=device, seed=config.seed, **OmegaConf.to_container(config.SAC)) + else: + raise Exception('You need to set algo sac or discor') + + + # Update the logger configuration with dynamic values + config.exp_name = f'{config.AssettoCorsa.car}-{config.AssettoCorsa.track}' + config.action_dim = env.action_dim + config.steps = config.Agent.num_steps + + # Initialize wandb logger + if not config.disable_wandb: + wandb_logger = Logger(config) + else: + wandb_logger = None + + agent = Agent(env=env, test_env=env, algo=algo, log_dir=config.work_dir, + device=device, seed=config.seed, **config.Agent, wandb_logger=wandb_logger) + + if not args.test and config.load_offline_data: + data_config_file = os.path.abspath(r"./ac_offline_train_paths.yml") + logger.info("Loading offline dataset...") + assert config.dataset_path, "dataset_path not set in config" + dataset_path = Path(config.dataset_path + os.sep) + + # load data set + data = data_loader.read_yml(data_config_file) + + for track in data: + for car in data[track]: + paths = data[track][car] + paths = [dataset_path / Path(f"{track}/{car}") / p["id"] / "laps" for p in paths] + env_load_config = copy.deepcopy(config) + env_load_config.AssettoCorsa.track = track + env_load_config.AssettoCorsa.car = car + env_load = assettoCorsa.make_ac_env(cfg=env_load_config, work_dir=work_dir) + for laps_path in paths: + assert laps_path.exists(), f"{laps_path} not found" + agent.load_pre_train_data(laps_path.as_posix(), env_load) + + if config.Agent.use_offline_buffer: + agent._replay_buffer.online(True) + + if config.pre_train: + agent.pre_train() + + if args.load_path is not None: + load_buffer = False if args.test else True + agent.load(args.load_path, load_buffer=load_buffer) + + if args.test: + agent._env.set_eval_mode() + agent.evaluate() + logger.info("done evaluation") + else: + agent.run() + logger.info("done training") + +if __name__ == "__main__": + main() \ No newline at end of file