F1TENTH development gym: physics simulation, classical controllers (MPC, Pure Pursuit, …), neural predictors (SI Toolkit), and reinforcement learning (TrainingLite). The same car-driver abstraction runs in sim and on the physical car via the ROS bridge.
Contents: Setup · Run · Architecture & Observations · Car Models · Develop · SI Toolkit · TrainingLite / RL · RaceTuner
(Tested on Ubuntu20, MacOS at 2024-06-25) Clone the repo including submodules
git clone --recurse-submodules git@github.com:F1Tenth-INI/f1tenth_development_gym.git
cd f1tenth_development_gym/I highly recommend using Conda for virtual environments. First create a conda environment.
conda create -n f1t python=3.11
conda activate f1tAnd now you can install the gym package.
pip install --user -e sim/Install the SI Toolkit package.
pip install --user -e ./SI_Toolkitpip install -e utilities/minimum_curvature_optimization/trajectory-planning-helpersDone. Check if everything works with:
python run.pyAfter renaming gym to sim (updating from an oplder to a newer verfsion of this project), the JIT compiled functions might still be cached. If you face errors of caching scripts, that are looking for f110_gym or similar, delete the jit cache by running:
rm -rf ~/.numba_cache
rm -rf ~/.cache/numbaCheck if the submodules are present. The folder SI_Toolkit and Control_Toolkit should not be empty. If they are empty, run:
git submodule update --init --recursiveAnd then install the SI_Toolkit again
python -m pip install --user -e ./SI_ToolkitAdd the aliases to .bashrc
echo -e "\nalias f1t='conda activate f1t'\nalias pypa='export PYTHONPATH=./'" >> ~/.bashrc
source ~/.bashrcnow you can use them like this
f1t
pypaRun the simulation
python run.pyYou can override any setting from Settings.py using command-line arguments. For example:
# Run with different map and controller
python run.py --MAP_NAME RCA2 --CONTROLLER pure_pursuit
# Run with custom simulation length and rendering
python run.py --SIMULATION_LENGTH 5000 --RENDER_MODE human_fast
# Run with browser backend renderer
python run.py --SIMULATION_LENGTH 5000 --RENDER_MODE human_fast --RENDER_BACKEND web
# Save recordings with custom settings
python run.py --SAVE_RECORDINGS True --MAP_NAME RCA1 --CONTROLLER sac_agent --SAC_INFERENCE_MODEL_NAME OriginalReward1
The browser renderer uses:
RENDER_MODEfor pacing (None,human,human_fast)RENDER_BACKENDfor backend selection (pyglet,pygame,web)
Example:
python run.py --RENDER_MODE human_fast --RENDER_BACKEND webThen open: http://localhost:8765
Controls in browser:
Space: toggle follow-car / free camera- Mouse wheel: zoom
- Left mouse drag: pan
Implementation notes:
- Backend server:
sim/f110_sim/envs/web_renderer.py - Browser client:
sim/f110_sim/envs/WebRenderer/index.html - Web overlay builder:
sim/f110_sim/envs/rendering/WebRenderer/overlay_builder.py
All settings from Settings.py can be overridden this way.
If you are running from terminal, please run all python scripts from the project's root folder. You might want to export the Python Path env variable:
export PYTHONPATH=./Have a look at the Settings file: Settings.py. This file gives you an idea of what can be adjusted in the gym.
Let's go through the most important ones:
- MAP_NAME: There are multiple maps available (from easy to quite tricky). Change the map name to face different challenges.
- CONTROLLER: We have implemented multiple controllers (again from easy to complicated). If you are new with F1TGym, checkout 'ftg' (Follow the Gap) first, then the 'pp' (Pure Pursuit) controller. There are a lot of Tutorials about how these controllers work online.
- SAVE_RECORDINGS: IF set to true, a Recording will be created (in the folder ExperimentRecordings), which contains all the information about the car state and sensory inputs during the simulation. Recordings can also be raplayed.
The codebase is organized in layers. Each layer adds information or logic on top of the one below.
flowchart TB
subgraph sim_pkg["sim/ — physics & rendering"]
RC[RaceCar agents]
WS[Simulator / world_sim]
RC --> WS
end
subgraph race["run/run_simulation.py — RacingSimulation"]
RS[Sensor simulation: LiDAR, IMU, motor]
RS --> DO[driver_observation]
end
subgraph car["utilities/car_system.py — CarSystem"]
WP[Waypoints & Frenet]
LID[Lidar processing]
WP --> CO[controller_observation]
LID --> CO
end
subgraph planner["Control_Toolkit_ASF — Planner / Controller"]
PL[process_observation]
PL --> CTRL[steering + accel]
end
WS --> RS
DO --> WP
CO --> PL
| Layer | Main files | Responsibility |
|---|---|---|
| sim → world | sim/f110_sim/envs/base_classes.py |
Low-level F1TENTH gym: RaceCar dynamics, Simulator multi-agent physics step, collision checks |
| run_simulation → Race | run/run_simulation.py, run.py |
RacingSimulation orchestrates the race: loads map, runs physics substeps, simulates sensors, calls drivers, renders |
| car_system → car driver | utilities/car_system.py |
Everything a physical car has in common: waypoints, lidar helpers, recording, reward, episode termination, planner instance |
| planner / controller | Control_Toolkit_ASF/Controllers/ |
Algorithm-specific logic: MPC, Pure Pursuit, SAC agent, neural imitator, … |
run.py is the entry point. It parses CLI overrides for Settings.py and starts RacingSimulation.run_experiments().
The main car and opponents are all CarSystem instances in the drivers array:
drivers = [main_car] + opponents # see RacingSimulation.init_drivers()The same layering is mirrored on the physical car via the ROS bridge: driver_observation matches what the real sensors publish.
Observations are built in stages. Raw physics outputs are enriched at each layer until a planner (or RL policy) sees the features it needs.
Built in RacingSimulation.build_driver_observation() from the physics world. This is the raw sensor packet — the same structure used on the real car.
| Field | Description |
|---|---|
car_state |
10-element state vector (see Car State) |
scans |
Raw LiDAR ranges (1080 beams, 360°) |
sensors.imu |
Simulated IMU accelerations |
sensors.motor_sensors |
Motor angular velocity, ERPM, current, … |
env |
time, sim_index, surface_friction |
env_state |
Full-environment snapshot (all car states, controls, sim outputs) |
collision, terminated, interrupted, done |
Episode flags |
Sensors are derived from physics state history (IMUSimulator, MotorSensorSimulator, LidarSimulator), not from noisy odometry.
Built in CarSystem._build_controller_observation() after waypoints and lidar have been updated. This is what every planner's process_observation() receives.
Adds on top of driver_observation:
| Field | Description |
|---|---|
next_waypoints |
Look-ahead raceline window (s, x, y, ψ, κ, v, a, borders, …) |
state_history |
Recent car_state snapshots |
control_history |
Recent applied controls [steering, accel] |
frenet_coordinates |
(s, d, e, ψ) — progress, lateral offset, heading error |
processed_ranges |
Filtered/downsampled LiDAR used by controllers |
lidar_points |
LiDAR hit points in map coordinates |
imu, motor_sensors |
Flattened copies of sensor dicts |
virtual_opponent_* |
Opponent poses / clearance (when virtual opponents enabled) |
Enrichment steps inside CarSystem.process_observation():
- Update car state and sensors from
driver_observation - Refresh nearest waypoint and look-ahead window
- Process LiDAR (optionally inject virtual-opponent hits into the scan)
- Run obstacle checks and raceline selection
- Compute Frenet coordinates
- Pass
controller_observationto the planner
After the physics substep, on_step_end() adds reward, episode-termination info, and calls planner.on_step_end() for transition logging (RL).
For the SAC agent (sac_agent controller), RLAgentPlanner._build_super_observation() collects everything available for feature engineering:
car_state,state_history,imu,motor_sensorsnext_waypoints,border_points(track bounds relative to waypoints)lidar_ranges,lidar_history(multi-frame LiDAR buffer)last_actions(last 3 normalized actions)frenet_coordinates,global_waypoint_vel_factorpp_action— Pure Pursuit fallback used during warmupenv_state
The actual SAC input is built by build_observation(super_obs) in:
TrainingLite/rl_racing/observation_builder_template.py
At training start the learner server copies this template into TrainingLite/rl_racing/models/<model_name>/client/observation_builder.py. The default builder concatenates (with manual scaling):
- State history (velocities, yaw rate, steering)
- Waypoint curvatures
- Downsampled track border points
- Last actions
- Frenet
(d, e) - Downsampled target speeds
- Pure Pursuit fallback action
- Multi-frame downsampled LiDAR
- IMU accelerations
Edit the template to change the policy input. Start a new model name if you change feature order, dimension, or scaling — old checkpoints are tied to the schema.
See also TrainingLite/rl_racing/Readme.md for the full async SAC pipeline.
Planner (Control_Toolkit_ASF/Controllers/): car-environment specific. Gathers and formats data, may embed a controller (Pure Pursuit) or wrap a system-agnostic one (MPC + Control Toolkit).
Controller (Control Toolkit submodule): system agnostic. Receives objectives (errors, references) and outputs control — the same MPC core runs on the F1TENTH car and on CartPole.
The car state is a fixed-order array defined in utilities/state_utilities.py (indices are alphabetical by name):
| Variable | Description |
|---|---|
angular_vel_z |
Yaw rate |
linear_vel_x, linear_vel_y |
Body-frame velocities |
pose_theta, pose_theta_cos, pose_theta_sin |
Yaw angle and trig helpers |
pose_x, pose_y |
Global position |
slip_angle |
Slip angle at vehicle center (deprecated, kept for compatibility) |
steering_angle |
Front wheel steering angle |
Always index by name, e.g. s[POSE_X_IDX]. See TUM CommonRoad vehicle models for background — our index order differs.
Vehicle dynamics and geometry are configured via YAML files in utilities/car_files/.
| Setting | Purpose |
|---|---|
ENV_CAR_PARAMETER_FILE |
Parameters for the simulated car (physics, geometry, tire model) |
CONTROLLER_CAR_PARAMETER_FILE |
Parameters the controller / predictor assumes — can differ to simulate model mismatch |
SIM_ODE_IMPLEMENTATION |
Which dynamics backend RaceCar uses |
Available parameter files include gym_car_parameters.yml, yokomo1_car_parameters.yml, ini_car_parameters.yml, mpc_car_parameters.yml, and gym_car_parameters_finetune.yml.
| Value | Description |
|---|---|
jax_pacejka |
Fast JAX Pacejka tire model (default for simulation) |
pacejka / std |
NumPy implementations |
ODE_TF |
SI Toolkit batch model — same dynamics used inside MPC predictors |
residual |
Pacejka + learned residual correction (TrainingLite/dynamic_residual_jax) |
Key parameters in the YAML files: wheelbase (l_wb, lf, lr), mass and inertia (m, I_z), Pacejka coefficients (C_Pf, C_Pr), steering/accel limits, friction (mu), and sensor mount positions for IMU simulation.
The dynamics code lives in sim/f110_sim/envs/ and SI_Toolkit_ASF/car_model.py (for ODE_TF).
A waypoint is an array with indices defined in utilities/waypoint_utils.py:
| Index | Field | Description |
|---|---|---|
WP_S_IDX |
s | Curvilinear distance along raceline |
WP_X_IDX, WP_Y_IDX |
x, y | Position |
WP_PSI_IDX |
ψ | Heading |
WP_KAPPA_IDX |
κ | Curvature |
WP_VX_IDX, WP_A_X_IDX |
v, a | Target speed and acceleration |
WP_D_LEFT_IDX, WP_D_RIGHT_IDX |
borders | Distance to track bounds |
Waypoints are stored per map as <MAP_NAME>_wp.yaml. Access by name, e.g. wp[WP_X_IDX].
For a new map, generate waypoints via minimum-curvature optimization (fast driving) or draw-by-hand (simple).
All maps are loceted at utilities/maps/ A map called [ExampleMap] consist of a folder at utilities/maps/ExampleMap/` containing the following files:
ExampleMap.yaml- Contains meta information about the resolution etc.ExampleMap.pgm- Original image of the map (from SLAM on physical car)ExampleMap.png- Original image in .png format (used by localiyation stack on phcsical car, should not be changed!)ExampleMap_wp_min_curve.png- Map that's used to calculate minimum curvature waypoints (can be editted to contain a closed contour)ExampleMap_wp_hand.png- Is used to create waypoints by hand
(only present after waypoint generation)
ExampleMap_wp.yaml- List of waypoints in classic directionExampleMap_wp_reverse.yaml- List of waypoints in reverfse directiondata/- Containing image processing and other data of the map
Please work on your own branches and do pull requests to the main branch. If possible, separate your code into your own folders.
Every planner must implement process_observation(self, controller_observation) and return (angular_control, translational_control). The observation is the enriched controller_observation dict described above — not the legacy raw lidar/odom interface.
def process_observation(self, controller_observation):
car_state = controller_observation["car_state"]
next_waypoints = controller_observation["next_waypoints"]
# ...
return angular_control, translational_controlControl Toolkit is a system-agnostic submodule with the cores of MPC, MPPI, RPGD, and other controllers. Application-specific wrappers for this gym live in Control_Toolkit_ASF/.
Config files:
Control_Toolkit_ASF/config_controllers.ymlControl_Toolkit_ASF/config_optimizers.ymlControl_Toolkit_ASF/config_cost_function.yml
MPC is available with two optimizers: MPPI and RPGD. Cost functions are in Control_Toolkit_ASF/Cost_Functions/ (template: f1t_cost_function.py).
SI Toolkit (SI_Toolkit/ submodule) provides neural system identification, predictors, Brunton plotting, and online learning. Application-specific files for this project are in SI_Toolkit_ASF/.
| Component | Location | Purpose |
|---|---|---|
| Car dynamics model | SI_Toolkit_ASF/car_model.py |
Kinematic, Pacejka, and neural dynamics used by MPC (ODE_TF) |
| Training config | SI_Toolkit_ASF/config_training.yml |
Network architecture, data paths, hyperparameters |
| Testing config | SI_Toolkit_ASF/config_testing.yml |
Brunton test reference recordings and models |
| Predictors config | SI_Toolkit_ASF/config_predictors.yml |
Predictor selection for controllers |
| Online learning | SI_Toolkit_ASF/config_onlinelearning.yml |
Adapt predictor weights during driving |
| Run scripts | SI_Toolkit_ASF/run/ |
Data generation, preprocessing, training, Brunton test |
Install during setup: pip install --user -e ./SI_Toolkit
Collect experiment recordings with a controller of choise (fe. MPC - MPPI).
- For a higher variance data, set control noise up to 0.5
NOISE_LEVEL_TRANSLATIONAL_CONTROL = 0.5 NOISE_LEVEL_ANGULAR_CONTROL = 0.5
-
Tune the controller for robustness ( it needs to be able to complete laps reliably )
-
Delete all old experiment recordings in ExperimentRecordings/
-
Set EXPERIMENT_LENGTH such that the car completes more than 2 laps
-
set NUMBER_OF_EXPERIMENTS >= 10 depending on how much data you want to have
-
Run experiments
-
Create the following folders:
- SI_Toolkit_ASF/Experiments/[Controller Name]/Recordings/Train
- SI_Toolkit_ASF/Experiments/[Controller Name]/Recordings/Test
- SI_Toolkit_ASF/Experiments/[Controller Name]/Recordings/Validate
-
Distribute the experiment's CSV files into these 3 folders ( each 80%, 10%, 10% of the data points)
-
in config_training.yml set path_to_experiment to [Controller Name]
-
Create normalization file:
python SI_Toolkit_ASF/run/Create_normalization_file.py- Check the histograms if training data makes sense
- in config_training.yml set NET_NAME, inputs and training settings
- Train Network:
python SI_Toolkit_ASF/run/Train_Network.py- create a file at SI_Toolkit_ASF/Experiments/[Controller Name]/Models/[Model Name]/notes.txt and write minimal documentation about the network (Maps, Controller, Settings, thoughts etc...) Congratulations, the Neural Controller is now ready to use.
- in Settings.py, select the neural controller and the model name
- Deactivate the control noise
- Deactivate control averaging (NN does not like it )
CONTROLLER = 'neural'
...
PATH_TO_MODELS = 'SI_Toolkit_ASF/Experiments/[Controller Name]/Models/'
NET_NAME = '[Model Name]'
...
NOISE_LEVEL_TRANSLATIONAL_CONTROL = 0.0
NOISE_LEVEL_ANGULAR_CONTROL = 0.0
...
CONTROL_AVERAGE_WINDOW = (1, 1)
...- Make sure that the control_inputs in config_training.yml and nni_planner.py match. (Otherwise correct them in nni_planner)
- Run experiment Enjoy your realtime neural network MPPI imitator (or how we call it: the INItator).
Check config_testting.yml:
- Select a file in experiment recordings for reference
- Select the network you want to test
python SI_Toolkit_ASF/run/A3_Run_Brunton_Test.pyTo generate data, the best and fastest way is to use the data generator:
- Settings:
DataGen/config_data_gen.yml - Run:
python3 -m SI_Toolkit_ASF.run.run_data_generator_for_ML_Pipeline
The provided settings are the ones that were found to work best for a time step of 0.04s. For other time steps, they might need to be adjusted. This creates a new folder in SI_Toolkit_ASF/Experiments with the data split into a train, test and validation folder.
To remove outliers and add a derivative column, we need to preprocess the data.
- Settings: Everything need to be adjusted in the code itself (
SI_Toolkit_ASF/run/preprocess_data.py) - Run:
python3 -m SI_Toolkit_ASF.run.preprocess_data
To only add derivative columns to the dataset:
- Run:
python3 -m SI_Toolkit_ASF.run.Add_derivative_to_csv - Settings: Adjust directly in file
SI_Toolkit_ASF/run/Add_derivative_to_csv.py
To train, we first need to rename the columns of our training data. Use your IDE to rename all instances of [translational]/[angular]_control_applied in your dataset to [translational]/[angular]_control.
Then we need to create the normalization file:
- Settings:
SI_Toolkit_ASF/config_training.ymland specify the desiredpath_to_experiments - Run:
python3 -m SI_Toolkit_ASF.run
Then set the desired model in config_training.yml. Settings that worked well are:
- Dense:
NET_NAME: 'Dense-128H1-128H2', 20 epochs, batch size of 32, wash out length 0, post wash out length 1, shift_labels 1 (important!). - LSTM:
NET_NAME: 'Dense-128H1-128H2', 20 epochs, batch size of 32, wash out length 10, post wash out length 1, shift_labels 1.
Then run the training:
- Settings:
SI_Toolkit_ASF/config_training.yml - Run:
python3 -m SI_Toolkit_ASF.run.Train_Network
To check that your predictor works, run the Brunton test using:
- Settings:
SI_Toolkit_ASF/config_testing.py - Run:
python3 -m SI_Toolkit_ASF.run.Run_Brunton_Test
TrainingLite/ contains lightweight ML modules that extend the gym without pulling in the full SI Toolkit training stack.
| Module | Path | Purpose |
|---|---|---|
| RL Racing | TrainingLite/rl_racing/ |
Async SAC learner server + actor client for end-to-end RL |
| Dynamic residual (JAX) | TrainingLite/dynamic_residual_jax/ |
Learned residual on top of Pacejka dynamics (SIM_ODE_IMPLEMENTATION = residual) |
| MPC imitator | TrainingLite/mpc_immitator_mu/ |
PyTorch GRU imitator for MPC control sequences |
| Slip prediction | TrainingLite/slip_prediction/ |
Slip-angle predictor |
| Visualization | TrainingLite/Visualization/ |
Web-based training / experiment visualization |
RL uses an async SAC setup: a learner server trains on a replay buffer while one or more actors (simulation clients) collect transitions over TCP. See TrainingLite/rl_racing/Readme.md for full details.
[ Simulator + sac_agent ] ──TCP──► [ Learner Server (SAC) ]
actor replay buffer + GPU training
Start server and simulation together:
python TrainingLite/rl_racing/run_training.py \
--auto-start-client \
--CONTROLLER sac_agent \
--SIMULATION_LENGTH 200000 \
--save-model-name MyModel-1The server waits for the actor, ingests (obs, action, reward, next_obs, done) episodes, trains SAC periodically, and broadcasts updated weights back to the actor.
TrainingLite/rl_racing/run_experiments.sh shows batch experiment commands. Example pattern:
python TrainingLite/rl_racing/run_training.py \
--auto-start-client \
--batch-size 1024 \
--learning-rate 3e-4 \
--discount-factor 0.97 \
--SAC_TARGET_UTD None \
--SAVE_RECORDINGS False \
--SAC_CHECKPOINT_FREQUENCY 500000 \
--CONTROLLER sac_agent \
--SIMULATION_LENGTH 200000 \
--save-model-name Sweep-gamma_97-aCommon flags:
| Flag | Description |
|---|---|
--save-model-name |
New model directory under TrainingLite/rl_racing/models/ |
--load-model-name |
Continue from an existing checkpoint |
--auto-start-client |
Launch run.py automatically as the actor |
--batch-size |
SAC minibatch size (overrides SAC_BATCH_SIZE for one run) |
--discount-factor |
SAC γ |
--SAC_CHECKPOINT_FREQUENCY |
Save checkpoint every N actor timesteps |
--SAC_TARGET_UTD |
Target update-to-data ratio (training steps per sample) |
--MAX_SIM_FREQUENCY |
Cap sim speed (useful when GPU training is the bottleneck) |
Physical-car oriented base training is in TrainingLite/rl_racing/scripts/train_base_for_physical.sh:
python TrainingLite/rl_racing/run_training.py \
--auto-start-client \
--CONTROLLER sac_agent \
--MAP_NAME TheTrack2 \
--SAC_TARGET_UTD 1.0 \
--batch-size 512 \
--SAC_CHECKPOINT_FREQUENCY 50000 \
--SIMULATION_LENGTH 250000 \
--MAX_SIM_FREQUENCY 250 \
--RANDOM_WAYPOINT_VEL_FACTOR True \
--save-model-name Physical-51Terminal 1 — learner server:
python TrainingLite/rl_racing/run_training.py --SIMULATION_LENGTH 300000 --save-model-name MyModel-1Terminal 2 — actor (simulation):
python run.py --CONTROLLER sac_agentEvaluate a trained policy without the server:
python run.py \
--CONTROLLER sac_agent \
--SAC_INFERENCE_MODEL_NAME MyModel-1 \
--RENDER_MODE human_fast \
--SIMULATION_LENGTH 2000Edit TrainingLite/rl_racing/observation_builder_template.py, then start training with a new --save-model-name. The server copies the template into the model's client/ folder.
- Select the map yopu want to create the waypoints in Settings.py => MAP_NAME
- Make sure there is a valid MAP_NAME.yaml and MAP_NAME_wp_min_curve.png file in the map folder
- If there is no MAP_NAME_wp_min_curve, you can just copy and rename the original map PNG
- In the MAP_NAME_wp_min_curve.png, you can draw corrections, do deliver a nice closed contour.
- Set the MIN_CURV_SAFETY_WIDTH in Settings.py. Note that the car's width is included. It should not be < 0.8m.
- Run the script:
python run/create_min_curve_waypoints.pyThe waypoints (and additional data) will be saved in the map folder.
We can modify the raceline / speed profile with the RaceTuner. Check RaceTuner/RunerSettings.py.
- Remote: True: Directly operate on the currrent map on the RaceCar, False: Operate on the local map
- Remote config: For connection to the physical car
Run RaceTunder:
export PYTHONPATH=./
python RaceTuner/WaypointsEditor.pyYou can drag&drop any point from the race line / speed profile. Ctrl/Cmd + s to save the waypoints either on the maps folder or on the remote machine.
This is the repository of the F1TENTH Gym environment.
This project is still under heavy developement.
You can find the documentation of the environment here.
- Library support issues on Windows. You must use Python 3.8 as of 10-2021
- On MacOS Big Sur and above, when rendering is turned on, you might encounter the error:
ImportError: Can't find framework /System/Library/Frameworks/OpenGL.framework.
You can fix the error by installing a newer version of pyglet:
$ pip3 install pyglet==1.5.11And you might see an error similar to
gym 0.17.3 requires pyglet<=1.5.0,>=1.4.0, but you'll have pyglet 1.5.11 which is incompatible.
which could be ignored. The environment should still work without error.
If you find this Gym environment useful, please consider citing:
@inproceedings{okelly2020f1tenth,
title={F1TENTH: An Open-source Evaluation Environment for Continuous Control and Reinforcement Learning},
author={O’Kelly, Matthew and Zheng, Hongrui and Karthik, Dhruv and Mangharam, Rahul},
booktitle={NeurIPS 2019 Competition and Demonstration Track},
pages={77--89},
year={2020},
organization={PMLR}
}