Optimization-based system identification for race cars¶
This repository allows the user to perform system identification using a nonlinear programming approach. It is made to be expandible, allowing users to add their own parametric models. The use of the library is shown in a variety of demonstration jupyter notebooks.
Structure¶
.
├── datasets
│ └── real # Contains data sets recorded in real-world experiments
├── notebooks # Contains jupyter notebooks used to demonstrate the use of the library
│ └── *.ipynb # Jupyter notebooks used to demonstrate the use of the library
├── src
│ ├── opt_sys_id # The main library for system identification
│ │ ├── model # Used to define system models
│ │ ├── optimizer # Implementation of the system identification algorithm
│ │ ├── sensor_model # Used to define measurement functions
│ │ ├── simulator # A simple simulator able to produce open-loop and closed-loop data
│ │ ├── state_estimator # An optimization-based state estimator used for validation
│ │ └── ...
│ ├── bag_to_np # A script to extract data from rosbags
│ └── ...
└── ...
Demo files¶
The repo contains a demonstration notebook that show how the library can be used to run the system identification procedure and visualizes some of the results.
opt_demo_real.ipynb: This notebook loads two data sets collected on the real system. One data set is used as a training data set to find a set of parameters. The second data set is used for validation. Validation is achieved by using both the system parameters used as a prior and the posterior system parameters to generate state predictions for a fixed time horizon. Those predictions are then plotted and the prediction MSE is calculated.opt_demo_sim.ipynb: This file loads two simulated data sets created using the same target trajectory but with different amounts of process and measurement noise. System identification is preformed on both data sets, using a perturbed version of the true system parameters as prior parameters. The parameters are then validated by plotting predictions made with both the prior and the posterior parameters and by calculating the resulting prediction MSEs.
How to perform system identification¶
- Create an optimizer object, speficying the model, the sampling rate
dtand the length of the data setN. The following optimizers are available:MeasurementOptimizer: This optimizer assumes the measurements to be made as specified in the provided sensor models.SubtrajectoryOptimiuer(recommended): Works the same as theMeasurementOptimizer, but splits the state trajectory into decoupled segments of lengthM.
-
Use the
find_paramsmethod to perform the optimization. The following arguments are required to perform the optimization.y: An array conatining all measurements as column vectors. Its shape is(num_measurements, N+1). TheFullStateOptimizerrequires the state trajectoryxof shape(num_states, N+1)instead.u: An array containing all inputs as column vectors. Its shape is(num_inputs, N).P: The parameter regularization cost matrix of shape(num_params, num_params).Q: The state error cost matrix of shape(num_states, num_states).R: The measurement error cost matrix of shape(num_measurements, num_measurements).S: The initial state regularization cost matrix of shape(num_states, num_states). Can be set to zero if no initial state regularization is desired.t(optional): An array of shape(N+1), containing the timestamps corresponding to the measurements.
Follow the instructions on how to use
bag_to_npin order to extract data from rosbags and how to bring it into the correct format. -
The resulting parameters are stored as the instance variable
theta_hat.
Refer to src/opt_demo_real.ipynb for an example of this procedure in practice.
How to extract data from rosbags¶
The optimization framework requires the measurements and state inputs to be provided as numpy arrays. The script bag_to_np can be used to extract data from rosbags and store them in the desired format. To do so, run the script, specifying the path to the rosbags in question:
Run ./bag_to_np -h to see all available options. The script extracts the data and stores the arrays in individual folders at src/datasets/real. Some modification may be necessary depending on the topics and message types used.
The script extracts the measurements including their timestamps. The timestamps are then used to find the most recent system input at that time.
How to add new models¶
- Create a new directory in
src/opt_sys_id/modelwith the name of the new model you'd like to add. - Inside the newly created directory, add
<model_name>.py. This file should contain a child class ofModel. The following methods need to be implemented:__init__: Use this method to specify the values of the variablesnum_params,num_states,num_inputsandnum_measurements. Also, you can specify a set of parameters that should not be optimized, by storing their indices in thefixed_paramsvariable.calc_dx: This method should be used to specify the dynamic model of the system in continuous time. Use the current statex, inputsuand parametersparamsto calculate \(\dot{x}(x,u,\theta)\). Ifnumericalis set toFalse, the calculations should be done usingcasadicommands, so they can be used by the optimizer.calc_y(optional): This method is used to specify the measurement model. This method can be omitted if the full state vector is observed. Ifnumericalis set toFalse, the calculations should be done usingcasadicommands, so they can be used by the optimizer.initial_estimate(optional): This method is used to create an initial estimate of the state trajectory for the optimizer to use, given all observationsyand state inputsu. The default for this method is an all-zero state trajectory. Overwrite this method if evaluating the dynamic model for an all-zero state can lead to numerical problems or if you with to provide a better initial estimate.
- Optionally, add a file
structures.py, which can be used to define some structures that make handling the model a bit easier. The same could also directly be done in<model_name>.pyif preferred. - Finally, add a file
__init__.py, in which you import all the created classes and other methods you would like to make public.
Refer to src/opt_sys_id/models/awd_model for an example of a model that has been added using the described process.
How to evaluate multiple bags at once¶
The script eval_data.py allows you to run the optimization based sys id on multple bags at once. The script will run the optimization on each bag and store the results in a csv file under data/sol/bag_name.
The following parameters can be set:
train: name of the training data file.val: name of the validation data file.model: name of the model to use. Default: rwdM: Subtrajectory length in parameter optimization on training data. Default: 50M_val: Subtrajectory length in parameter optimization on validation data. Default: 50start_v1: first data point in training data to be used. Default: 100end_v1: last data point in training data to be used. Default: -100start_v2: first data point in validation data to be used. Default: 100end_v2: last data point in validation data to be used. Default: -100
The script can be run with the following command:
./eval_data.py --train [training_data_name] --val [validation_data_name] --model [model_name] --M [] --M_val [] --start_v1 [] --end_v1 [] --start_v2 [] --end_v2 []
An example of how to run the script¶
Assume you have bags for a RWD car and a Buggy car (training and validation bags for each car). First extrcat the data using ./bag_to_np -h. This will result in datasets rwd_data_train, buggy_data_train, rwd_data_val, buggy_data_val. You can run the following command to evaluate the data: