diff --git a/docs/src/assets/random_trackgen.png b/docs/src/assets/random_trackgen.png new file mode 100644 index 00000000..0a89d70d Binary files /dev/null and b/docs/src/assets/random_trackgen.png differ diff --git a/docs/src/usage/actions.rst b/docs/src/usage/actions.rst new file mode 100644 index 00000000..ae996b82 --- /dev/null +++ b/docs/src/usage/actions.rst @@ -0,0 +1,6 @@ +.. _actions: + +Actions +===================== + +Work in progress on actions. \ No newline at end of file diff --git a/docs/src/usage/basic_usage.rst b/docs/src/usage/basic_usage.rst index 9fbdf928..c1b327cc 100644 --- a/docs/src/usage/basic_usage.rst +++ b/docs/src/usage/basic_usage.rst @@ -5,46 +5,66 @@ Basic Usage Example The environment can work out of the box without too much customization. -A gym env could be instantiated without any extra arguments. By default, it spawns two agents in the Vegas (IROS 2020) map. You can find the image of the map at ``gym/f110_gym/envs/maps/vegas.png``. At instantiation, the index of the ego agent in the list of agents is 0. +A gym env could be instantiated without any extra arguments using the ``gym.make()`` function. +By default, it spawns two agents in the Spielberg racetrack. -The agents can be reset by calling the ``reset()`` method using a numpy ndarray of size ``(num_agents, 2)``, where each row represents an agent, and the columns are the ``(x, y)`` coordinate of each agent. - -The ``reset()`` and ``step()`` method returns: - - An *observation* dictionary - - A *step reward*, which in the current release is the physics timestep used. - - A *done* boolean indicator, flips to true when either a collision happens or the ego agent finishes 2 laps. - - An *info* dictionary. Empty in the current release. - -The action taken by the ``step()`` function is a numpy ndarray of size ``(num_agents, 2)``, where each row represents an agent's action (indices corresponds to the list of agents), and the columns are control inputs (steering angle, velocity). +The simulation can be reset by calling the ``reset()`` method +and step forward in time by calling the ``step()`` method with a joint action for all agents. A working example can be found in ``examples/waypoint_follow.py``. -The following pseudo code provides a skeleton for creating a simulation loop. -.. code:: python +Usage +----- - import gym - import numpy as np - from your_custom_policy import planner # the policy/motion planner that you create +.. code:: python - # instantiating the environment - racecar_env = gym.make('f110_gym:f110-v0') - obs, step_reward, done, info = racecar_env.reset(np.array([[0., 0., 0.], # pose of ego - [2., 0., 0.]])) # pose of 2nd agent - # instantiating your policy - planner = planner() + import gymnasium as gym - # simulation loop - lap_time = 0. + env = gym.make("f110_gym:f110-v0", render_mode="human") + obs, infos = env.reset() - # loops when env not done while not done: - # get action based on the observation - actions = planner.plan(obs) - - # stepping through the environment + # sample random action + actions = env.action_space.sample() + # step simulation obs, step_reward, done, info = racecar_env.step(actions) + env.render() + +Default configuration +--------------------- - lap_time += step_reward +.. code:: python -For a more in-depth example that provides more customization to the environment, see :ref:`custom_usage`. + { + "seed": 12345, + "map": "Spielberg", + "params": { + "mu": 1.0489, + "C_Sf": 4.718, + "C_Sr": 5.4562, + "lf": 0.15875, + "lr": 0.17145, + "h": 0.074, + "m": 3.74, + "I": 0.04712, + "s_min": -0.4189, + "s_max": 0.4189, + "sv_min": -3.2, + "sv_max": 3.2, + "v_switch": 7.319, + "a_max": 9.51, + "v_min": -5.0, + "v_max": 20.0, + "width": 0.31, + "length": 0.58, + }, + "num_agents": 2, + "timestep": 0.01, + "ego_idx": 0, + "integrator": "rk4", + "model": "st", + "control_input": ["speed", "steering_angle"], + "observation_config": {"type": None}, + "reset_config": {"type": None}, + } diff --git a/docs/src/usage/customized_usage.rst b/docs/src/usage/customized_usage.rst index 7025de86..cd6bec02 100644 --- a/docs/src/usage/customized_usage.rst +++ b/docs/src/usage/customized_usage.rst @@ -9,157 +9,32 @@ The environment also provides options for customization. Custom Map ------------ +Work in progress on how to add a custom map. -The environment uses a convention that is similar to the ROS map convention. A map for the environment is created by two files: a ``yaml`` file containing the metadata of the map, and a single channel black and white image that represents the map, where black pixels are obstacles and white pixels are free space. +Random Track Generator +----------------------- -Map Metadata File (yaml) -~~~~~~~~~~~~~~~~~~~~~~~~~~ +The script `examples/random_trackgen.py` allows to generate random tracks. -Only the ``resolution`` and the ``origin`` fields in the yaml files are used by the environment, and only the first two coordinates in the ``origin`` field are used. The unit of the resolution is *m/pixel*. The x and y (first two) numbers in the origin field are used to determine where the origin of the map frame is. Note that these two numbers follow the ROS convention. They represent the **bottom left** corner of the map image's coordinate in the world. +To use it, the following dependencies are required: -Map Image File -~~~~~~~~~~~~~~~~~~~~~~~~~~ +:: -A black and white, single channel image is used to represent free space and obstacles. For example, the Vegas map looks like this: + pip install cv2 + pip install numpy + pip install shapely + pip install matplotlib -.. image:: ../gym/f110_gym/envs/maps/vegas.png - :width: 300 - :align: center -Using a Custom Map -~~~~~~~~~~~~~~~~~~~~~~~~~~ +The script can be run by specifying `seed`, number of maps to generate `n_maps` and the output directory `output_dir`. -The environment can be instantiated with arguments for a custom map. First, you can place your custom map files (.yaml and the image file) in the same directory, for example ``/your/path/to/map.yaml`` and ``/your/path/to/map.png``. Then you can create the environment with the absolute path to these files like this: +For example, to generate 3 random maps and store them in the directory `custom_maps`: -.. code:: python +:: - env = gym.make('f110_gym:f110-v0', - map='/your/path/to/map', - map_ext='.png') + python examples/random_trackgen.py --seed 42 --n-maps 3 --outdir custom_maps -The ``map`` argument takes the absolute path to the map files **without** any extensions, and the ``map_ext`` argument takes the extension to the map image file. **Note** that it is assumed that the two files are in the **same directory**, and have the **same filename** (not including the extensions, for example map.png and map.yaml) -Random Track Generator (Beta) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A random track generator derived from the OpenAI CarRacing environment is also provided in ``gym/f110_gym/unittest/random_trackgen.py``. Note that it requires extra dependencies. ``OpenCV`` is required for image manipulation and IO operations, ``shapely`` and ``matplotlib`` are required for image generation. For OpenCV, you can follow the tutorial at https://docs.opencv.org/master/df/d65/tutorial_table_of_content_introduction.html for installation on different platforms. Then you can install ``shapely`` and ``matplotlib`` with: ``$ pip3 install shapely matplotlib``. - -After you've installed the dependencies, you can run the track generator by: - -.. code:: bash - - $ python3 random_trackgen.py --seed 12345 --num_maps 1 - -where the ``--seed`` argument (int, default 123) is for reproducibility, and the ``--num_maps`` argument is for the number of maps you want to generate. By default, the script will create two directories in ``unittest``: ``unittest/maps`` and ``unittest/centerline``. The former contains all the map metadata and map image files, and the latter contains a csv file of the (x, y) coordinates of the points on the centerline of the track. - -An example of a randomly generated track: - -.. image:: ../examples/example_map.png - :width: 300 - :align: center - -Multiple Agents ------------------ - -You can instantiate an environment with any number of agents (default is 2). For example: - -.. code:: python - - env = gym.make('f110_gym:f110-v0', - num_agents=3) - -This will create an environment with 3 agents. Note that your call to the ``reset()`` and ``step()`` function will have to change accordingly: - -.. code:: python - - _, _, _, _ = env.reset(np.array([[ego_x, ego_y], - [opp1_x, opp1_y], - [opp2_x, opp2_y]])) - _, _, _, _ = env.step(np.array([[ego_steer, ego_speed], - [opp1_steer, opp1_speed], - [opp2_steer, opp2_speed]])) - -Note that performance of the environment will start to degrade as more and more agents are added. - -Changing Parameters in Vehicle Dynamics ------------------------------------------- - -The vehicle dynamic model used in the environment is the Single-Track Model from https://gitlab.lrz.de/tum-cps/commonroad-vehicle-models/. - -You can change the default paramters (identified on concrete floor with the default configuration F1TENTH vehicle) used in the environment in two ways. - -1. You could instantiate the environment with a parameter dictionary: - -.. code:: python - - params_dict = {'mu': 1.0489, - 'C_Sf': 4.718, - 'C_Sr': 5.4562, - 'lf': 0.15875, - 'lr': 0.17145, - 'h': 0.074, - 'm': 3.74, - 'I': 0.04712, - 's_min': -0.4189, - 's_max': 0.4189, - 'sv_min': -3.2, - 'sv_max': 3.2, - 'v_switch':7.319, - 'a_max': 9.51, - 'v_min':-5.0, - 'v_max': 20.0, - 'width': 0.31, - 'length': 0.58} - env = gym.make('f110_gym:f110-v0', params=params_dict) - -2. Or you could update the parameters of a specific vehicle in the list of vehicles (or all vehicles): - -.. code:: python - - # env with default params and 2 agents - env = gym.make('f110_gym:f110-v0') - - # new params - params_dict = {'mu': 1.0489, - 'C_Sf': 4.718, - 'C_Sr': 5.4562, - 'lf': 0.15875, - 'lr': 0.17145, - 'h': 0.074, - 'm': 3.74, - 'I': 0.04712, - 's_min': -0.4189, - 's_max': 0.4189, - 'sv_min': -3.2, - 'sv_max': 3.2, - 'v_switch':7.319, - 'a_max': 9.51, - 'v_min':-5.0, - 'v_max': 20.0, - 'width': 0.31, - 'length': 0.58} - - # update params of only the 2nd vehicles - env.update_params(params_dict, index=1) - - # update params of all vehicles - env.update_params(params_dict) - -The dynamic model's physical parameters are: - - **mu**: surface friction coefficient *[-]* - - **C_Sf**: Cornering stiffness coefficient, front *[1/rad]* - - **C_Sr**: Cornering stiffness coefficient, rear *[1/rad]* - - **lf**: Distance from center of gravity to front axle *[m]* - - **lr**: Distance from center of gravity to rear axle *[m]* - - **h**: Height of center of gravity *[m]* - - **m**: Total mass of the vehicle *[kg]* - - **I**: Moment of inertial of the entire vehicle about the z axis *[kgm^2]* - - **s_min**: Minimum steering angle constraint *[rad]* - - **s_max**: Maximum steering angle constraint *[rad]* - - **sv_min**: Minimum steering velocity constraint *[rad/s]* - - **sv_max**: Maximum steering velocity constraint *[rad/s]* - - **v_switch**: Switching velocity (velocity at which the acceleration is no longer able to create wheel spin) *[m/s]* - - **a_max**: Maximum longitudinal acceleration *[m/s^2]* - - **v_min**: Minimum longitudinal velocity *[m/s]* - - **v_max**: Maximum longitudinal velocity *[m/s]* - - **width**: width of the vehicle *[m]* - - **length**: length of the vehicle *[m]* +.. image:: ../../../../src/assets/random_trackgen.png + :width: 800 + :align: center \ No newline at end of file diff --git a/docs/src/usage/dynamics.rst b/docs/src/usage/dynamics.rst new file mode 100644 index 00000000..ca9894e0 --- /dev/null +++ b/docs/src/usage/dynamics.rst @@ -0,0 +1,6 @@ +_dynamics: + +Dynamics +===================== + +Work in progress. \ No newline at end of file diff --git a/docs/src/usage/index.rst b/docs/src/usage/index.rst index 904c3971..421bea7f 100644 --- a/docs/src/usage/index.rst +++ b/docs/src/usage/index.rst @@ -6,5 +6,9 @@ Usage :maxdepth: 2 basic_usage + actions + observations + rewards + dynamics customized_usage - reproduce \ No newline at end of file + rendering \ No newline at end of file diff --git a/docs/src/usage/observations.rst b/docs/src/usage/observations.rst new file mode 100644 index 00000000..f85a7598 --- /dev/null +++ b/docs/src/usage/observations.rst @@ -0,0 +1,6 @@ +.. _observations: + +Observations +===================== + +Work in progress. \ No newline at end of file diff --git a/docs/src/usage/rendering.rst b/docs/src/usage/rendering.rst new file mode 100644 index 00000000..942f19ed --- /dev/null +++ b/docs/src/usage/rendering.rst @@ -0,0 +1,6 @@ +.. _rendering: + +Rendering +===================== + +Work in progress. \ No newline at end of file diff --git a/docs/src/usage/reproduce.rst b/docs/src/usage/reproduce.rst deleted file mode 100644 index 2d70229e..00000000 --- a/docs/src/usage/reproduce.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _reproduce: - -Reproducing experiments when using the environment -==================================================== - -COMING SOON... \ No newline at end of file diff --git a/docs/src/usage/rewards.rst b/docs/src/usage/rewards.rst new file mode 100644 index 00000000..660fe3f8 --- /dev/null +++ b/docs/src/usage/rewards.rst @@ -0,0 +1,6 @@ +.. _rewards: + +Rewards +===================== + +Work in progress. \ No newline at end of file diff --git a/examples/random_trackgen.py b/examples/random_trackgen.py index 6179492a..ee24f959 100644 --- a/examples/random_trackgen.py +++ b/examples/random_trackgen.py @@ -49,15 +49,17 @@ def main(args): outdir.mkdir(parents=True, exist_ok=True) for i in range(n_maps): - try: - print(f"[info] creating track {i}") - track, track_int, track_ext = create_track() - convert_track(track, track_int, track_ext, i, outdir) - print(f"[info] saved track {i} in {outdir}/") - except Exception as _: # noqa: F841 - print("[error] failed to create track. Retrying...") - continue - print() + while True: + try: + print(f"[info] creating track {i}") + track, track_int, track_ext = create_track() + convert_track(track, track_int, track_ext, i, outdir) + print(f"[info] saved track {i} in {outdir}/") + break + except Exception as _: # noqa: F841 + print("[error] failed to create track. Retrying...") + continue + print() def create_track(): @@ -250,7 +252,7 @@ def convert_track(track, track_int, track_ext, track_id, outdir): "--seed", type=int, default=123, help="The seed for the numpy rng" ) parser.add_argument( - "--n_maps", type=int, default=3, help="Number of maps to create" + "--n-maps", type=int, default=3, help="Number of maps to create" ) parser.add_argument( "--outdir", type=pathlib.Path, default="./maps", help="Out directory"