Research code for data-driven trajectory-following controllers in simulation. The repository is designed for:
- Reproducible experiments
- Easy onboarding for new students
- Modular integration with simulation environments (e.g., F1TENTH Gym)
-
Collect data:
python -m experiment.main +experiment=collect_data
This configuration will:
- Run for 50 seconds
- Create column names for collected data
- Apply default data collection settings
Outputs are stored under:
outputs/single/yyyy-mm-dd-HH-MM-SS_+experiment=collect_data -
Promote a run to the data directory:
- If the run looks acceptable, move it to
data(configured viaexperiment.data_dir) and give it a descriptive name:data/drone/your_run
- If the run looks acceptable, move it to
-
Train/run a DPC experiment:
python -m experiment.main
This uses the current Hydra config. You can customize configs and overrides as needed (see βHydra configurationβ below).
First lets get you an outline of what this project contains, and
trajectory-following-framework/
βββ controllers/ # Controller implementations (MPC, DPC, etc.)
β βββ base/ # Base DPC controllers
β βββ data_processing/ # Data loading and preprocessing
β β βββ filtering/ # Data filtering
β βββ data_sampling/ # Offline/online sampling for DPC
β βββ drone_dpc/ # Drone base controller + DPC for drone model
β βββ drone_mpc/ # MPC for the drone model
β βββ online/ # Online controller (TBI)
β βββ vehicle/ # Vehicle controllers
βββ data/ # Collected input-output data
βββ evaluation/ # Visualization for multi-runs
βββ experiment/ # Experiment scripts and configurations
β βββ config/ # Hydra configuration
β βββ drone/ # Drone experiments
β βββ vehicle/ # Vehicle experiments (not implemented)
β βββ base_drone_experiment.py # Shared drone experiment
β βββ main.py # Hydra entry point
βββ scenarios/ # Scenario definitions (e.g., tracks)
βββ test/ # Pytest environment
βββ utils/ # Helpers
β βββ paths/ # Path creation utilities
β βββ config_logger.py # Logger configuration
βββ README.md
βββ requirements.txt
Hydra controls all experiment settings via layered config files.
- Main config:
experiment/config/config.yaml - Defaults: define which sub-configs load and where they are mounted.
- Example: Parameters in
controller/default_drone_dpc_controller.yamlappear underconfig.controller.params.
You can re-run past configurations by pointing Hydra to a previous runβs .hydra folder:
python -m experiment.main --config-dir outputs/single/2025-12-05_11-02-06_/.hydra --config-name overwriteNote: Rename the runβs config.yaml to overwrite.yaml (or any name you plan to use with --config-name).
Important: Configs are applied top-to-bottom. Later-loaded configs (e.g., via +experiment=name) override earlier parameters.
When loading the configuration:
- The top-level configuration is a standard
dict. - Nested sections are
DictConfigobjects (from OmegaConf).
-
Error if missing (preferred for required values)
- Dot access (compact):
config.key - Key access (useful in scripts):
config['key']
- Dot access (compact):
-
Provide a default (for optional values)
config.get('key', default_value)
- The configuration is a Hydra/OmegaConf object, similar to a normal dictionary.
- Some functions may require a plain
dict. Convert with:from omegaconf import OmegaConf plain_dict = OmegaConf.to_container(cfg)
Above we have already used a bit of the overides. But bellow one can see how to add overrides to a run.
It is very usefull to change a single parameter or a collection within the configuration.
On top of that within the saving of the runs all of the overrides are included in the name of the folder.
Like yyyy-mm-dd-HH-MM-SS_parameter=value
Hydra supports fine-grained overrides. Overrides are also encoded in the output directory name for reproducibility (e.g., yyyy-mm-dd-HH-MM-SS_parameter=value).
- Add (introduce) a parameter:
python -m experiment.main +parameter.subparameter=value python -m experiment.main +parameter_collection=override_file - Replace (override) a parameter:
python -m experiment.main parameter.subparameter=value python -m experiment.main parameter_collection=override_file
Launch sweeps to evaluate multiple settings:
python -m experiment.main --multirun +hydra=sweep_config
Recommendations:
- Use a dedicated sweep file:
+hydra=sweep_filefor:- Consistent post-processing and visualization
- Reproducibility
- Sweep 1β2 parameters at a time for easier visualization.
Contains the full YAML configuration used for a run. This ensures the run is always traceable and reproducible.
Extensive logging keeps the console focused and preserves detailed traces:
- Configured via
utils/config_logger.pyandconfig.loggerin Hydra - Topics are defined under
config.logger.topics; create with:log = logging.getLogger("desired_topic")
- A
levelsfolder aggregates high-severity logs for rapid triage; a clean run should have none. main.login the runβs parent folder contains a full log of all events.
- Controlled via Hydra settings
- Saved in run subfolders
- Enable only what you need to keep runs clear and efficient
Structured run statistics are captured via the custom DataLogger. These stats are summarized (min, max, mean, spread) and used during post-processing.
-
Register stats keys before logging values:
self.data_logger.add_tracked_stats(["new_stats_key"])
If a key isnβt registered, it wonβt be savedβthis safeguards against typos and name mismatches.
-
Log values during the run:
self.data_logger.stats_log({"new_stats_key": value})
-
Summary metrics produced per key:
min: Minimum observed valuemax: Maximum observed valuemean: Average valuespread:max - min
-
Typical usage pattern:
# 1) Register once (e.g., in setup) self.data_logger.add_tracked_stats(["tracking_error", "control_effort"]) # 2) Log on each step or event self.data_logger.stats_log({ "tracking_error": current_error, "control_effort": np.linalg.norm(u), })
-
Downstream consumption:
- These summaries are saved with the run artifacts
- Post-processing scripts read the stats to aggregate results across runs/sweeps
To accelerate large multiruns, preprocessed data is cached in .npy format.
- The system compares the saved
config.controller.preprocessagainst the current configuration to determine compatibility. preprocess_optionsare ignored for compatibility checks.
Modes:
- Default: scan the
datafolder and load the most recent compatible dataset - Manual (recommended): specify a target data folder in the config
Preprocessing also generates comparison plots saved in a similarly named folder. These are created once per preprocessing step.
A base experiment provides shared functionality for reusability, testability, and consistency. Individual experiments:
- Implement their own controllers
- Add experiment-specific features
- Clearly separate functional differences
Example usage:
-
2D sweep heatmap:
python evaluation/sweep_2d_heatmap.py --n-before 0 --metric drone0.[LOG-TRAC].tracking.abs_tracking_error -
Batched 2D sweep heatmap:
python evaluation/sweep_2d_heatmap_batch.py --all -- --metric drone0.[LOG-TRAC].abs_tracking_error
Scenario definitions for vehicle experiments (e.g., race tracks).
Notable:
f1tenth_gym(installed via pip from GitHub)dtaidistance(DTW-based trajectory comparison)
Install all dependencies:
pip install -r requirements.txtLicensed under the MIT License. See LICENSE for details.