Implementation of the HMPC (Hybrid MPC) method. A multi-layer perceptron is used to supplement a dynamic model of a system, enabling the resulting hybrid model to learn via an online appoach and correct model uncertainties.
Regular MPC with a model without uncertainties:

HMPC with a slight model mismatch:

HMPC with a large model mismatch:

Specifically, this repository implements the following:
- A simulator for a planar quadrotor system. The planar quadrotor (or 2D quadrotor) is a simplification of the quadrotor system for two dimensions. The simulation only includes gravity, as well as two forces acting on the PQ, one on each rotor, in the direction of the first normal of the PQ.
- A simulator for a quadrotor system. Similarly, the simulator includes gravity, as well as the four rotor speeds. The forces and torques are calculated by multiplying specific constants
$K_f$ and$K_t$ with the rotor speeds. - An optimizer, utlizing the CEM algorithm to control the PQ and lead it to a specified position. This is implemented for the planar quadrotor only.
- An optimizer, utilizing numerical optimization using CasADi to control the quadrotor and lead it to a specified position. This is implemented for both the 2D and the 3D quadrotors.
- A symbolic neural network, created using CasADi.
The planar quadrotor simulator is implemented in src/sim/PlanarQuadrotor.hpp. The PlanarQuadrotor class initializes a new PQ with the given mass, moment of inertia and length. The PQ's initial position is
Using the update method, the system is integrated for a given time step, using the provided controls (forces for each rotor). The controls are considered constant for that time step.
The get_state method returns the current state of the PQ. The state is a 6D vector, which consists of the linear (
The get_sim_time method returns the total time the simulation has been integrated for. The get_last_ddq method returns the acceleration computed during the previous integration step.
The visualizer class, located at src/sim/Visualizer.hpp, can be used to visualize the PQ in a (linux) command-line enviroment. The show method accepts a PlanarQuadrotor object, as well as a pair of integers representing the target position (
The set_message method can be used to set a specific message to be printed at the bottom of the screen, for every subsequent frame.
To determine the controls required for the PQ to move towards the target position, we use MPC, utilizing the CEM algorithm, implemented in the linked algevo library. This repository defines the ControlIndividual struct. This consists of a vector of
The algorithm runs iteratively and, once finished, provides the individual with the best overall fitness value. Of this individual, we only use the first two values (
In order to run this simulation at each time step, for each individual, we need a model: a method for calculating the accelerations at that time step, based on the input controls. For the PQ, we can easily derive the following forward dynamics model:
Where:
-
$c_1$ and$c_2$ are the provided controls. -
$q_3$ is the angular position of the PQ. -
$g$ is the gravitational acceleration. -
$m$ ,$l$ and$I$ are the mass, length and moment of inertia of the PQ, respectively.
Since the simulator and the optimizer use the exact same dynamic model, this works perfectly on its own. As long as an appropriate cost function is used, the optimizer is easily able to calculate the required controls to reach the target position.
Next, it is necessary to mimic a situation in which the dynamic model is either not known, or is too complex to account for. In order to do this, there needs to be a mismatch between the actual dynamic model used by the simulator and the dynamic model used by the optimizer to find the optimal controls. There are multiple ways to do this, but in this repository, we alter the mass (and subsequently inertia) used by the optimizer.
With a large enough difference between the two gravitational acceleration values, the controls provided by the optimizer result in movement that leads the PQ far from the desired target position.
Now that the true and used model are different to each other, the predicted state on each time step is different to the true state of the system. This difference can be learned, so that the existing dynamic model can be enhanced to be more accurate to the true model.
More specifically, the input of the learned model needs to be the same as that of the dynamic model: the state of the PQ, as well as the controls used for the next time step. The training target needs to be the difference between the actual acceleration caused and the predicted acceleration of the existing dynamic model.
Where:
-
$\textbf{x}$ and$\textbf{t}$ are the input and desired output, respectively. -
$\textbf{q}$ is the position vector. -
$\dot{\textbf{q}}$ is the velocity vector. -
$c_1$ and$c_2$ are the controls. -
$\textbf{a}$ is the actual acceleration caused by integrating using these controls. -
$d(\cdot)$ is the dynamic model.
A learned model trained in this way allows us to then express the predicted value for each time step as such:
Where
Note that the dynamic model only uses the angular position value,
In this repository, a neural network is used to learn this difference. We use the linked library simple_nn to initialize a neural network and train it using an episodic approach. This consists of running the simulation and optimization as-is for n steps, which make up an episode. We then train the neural network using all collected data during the episode. Following this, we repeat the process again for m episodes, or until the change in error between episodes is smaller than a specific threshold value.
This entire process is repeated for k runs. We do this to collect data for multiple runs and get a mean error value per step per episode.
Instead of using CEM to calculate the optimal controls, we also provide the option of using numerical optimization. This is implemented using CasADi. The dynamic model has been implemented symbolically, allowing for optimization of the control forces based on the given dynamics.
Additionally, using the symbolic neural network, a learned model can be trained similarly to the above, to learn differences between the actual and the known dynamics.
The equivalent simulator for the quadrotor is located at src/sim/Quadrotor.hpp. This operates similarly to the PQ, with the expected differences found in 3 dimensions. The moment of inertia is represented by a
It should be noted that orientation in 3 dimensions is represented using a unit quaternion. As such, the quadrotor state consists of a 13-dimensional vector, which includes the linear position (size 3), the quaternion (size 4), the linear velocity (size 3) and the angular velocity (size 3). The position and orientation are kept in world frame, but the velocity and angular velocity are kept in body frame.
To visualize the quadrotor system's states, we use matplotlib. During execution, system state data is written to src/train/data/quad.txt. At the same time, this data can be read and visualized by the src/train/VisualizeQuad.py script.
The script is also able to recognize certain commands, specifically for setting the target position, as well as the title of the graph.
As mentioned, only numerical optimization is implemented with the 3D quadrotor system. The dynamics for the 3D quadrotor are slightly more complicated than those of the planar quadrotor.
Since our model uses rotor speeds as the control input, those need to be converted to thrust and torque before they can be used to calculate acceleration. This is generally done by multiplying with constants
Where:
-
$\mathbf{R}$ is the rotation matrix, derived from the quaternion representing the state of the quadrotor. We transpose this matrix since the gravity vector must be converted from world frame to body frame. -
$\mathbf{c}$ is a vector of size 4, containing the rotor speeds.
Where:
-
$c_i$ are the rotor speeds. -
$\ell$ is the length from the center of the quadrotor to each rotor.
Once the values for thrust and torque have been calculated, we can calculate the linear and angular acceleration using the following formulas.
Where:
-
$\mathbf{u}_b$ is the linear velocity vector. -
$\mathbf{\omega}_b$ is the angular velocity vector. -
$I_m$ is the inertia matrix of the quadrotor.
It should be added that at this point, in the planar quadrotor version, we would simply integrate using the calculated acceleration. In this case, however, there are still two issues. Firstly, both of these accelerations are in the body frame, but the position and orientation are in the world frame. Therefore, after integrating the velocities, we need to convert to world frame in order to integrate the position and orientation. Additionally, our orientation is kept as a quaternion, so we need to update it according to this angular acceleration.
Contrary to the planar quadrotor version, we perform semi-implicit Euler integration. As such, we update the quadrotor velocity first, after which we use the updated velocity to integrate the position for the given timespan.
To get the world frame linear velocity, all that is needed is to multiply it from the left by the rotation matrix. We then use this velocity to integrate the linear position.
As for the orientation:
Where
is the quaternion representing the orientation.
Using
The learned model works in the same way as in the planar quadrotor version. The only difference is that here, the input is a 17-dimensional vector (13 for the state and 4 for the input controls) and the output is a 6-dimensional vector (3 for linear and 3 for angular acceleration.)