- 3.0.2 optimal control module.
NLOC_MPC.cpp

This tutorial example shows how to use the MPC class. In the CT, every optimal control solver can be wrapped into the MPC-class, allowing for very rapid prototyping of different MPC applications. In this example, we apply iLQR-MPC to a simple second order system, a damped oscillator. This tutorial builds up on the example NLOC.cpp, please consider this one as well.

#include "exampleDir.h"
using namespace ct::core;
using namespace ct::optcon;
int main(int argc, char** argv)
{
/* PRELIMINIARIES, see example NLOC.cpp */
double w_n = 0.1;
double zeta = 5.0;
std::shared_ptr<ct::core::ControlledSystem<state_dim, control_dim>> oscillatorDynamics(
new ct::core::SecondOrderSystem(w_n, zeta));
std::shared_ptr<ct::core::SystemLinearizer<state_dim, control_dim>> adLinearizer(
std::shared_ptr<ct::optcon::TermQuadratic<state_dim, control_dim>> intermediateCost(
std::shared_ptr<ct::optcon::TermQuadratic<state_dim, control_dim>> finalCost(
bool verbose = true;
intermediateCost->loadConfigFile(ct::optcon::exampleDir + "/mpcCost.info", "intermediateCost", verbose);
finalCost->loadConfigFile(ct::optcon::exampleDir + "/mpcCost.info", "finalCost", verbose);
std::shared_ptr<CostFunctionQuadratic<state_dim, control_dim>> costFunction(
costFunction->addIntermediateTerm(intermediateCost);
costFunction->addFinalTerm(finalCost);
x0.setRandom();
timeHorizon, x0, oscillatorDynamics, costFunction, adLinearizer);
NLOptConSettings ilqr_settings;
ilqr_settings.dt = 0.01; // the control discretization in [sec]
ilqr_settings.integrator = ct::core::IntegrationType::EULERCT;
ilqr_settings.discretization = NLOptConSettings::APPROXIMATION::FORWARD_EULER;
ilqr_settings.max_iterations = 10;
ilqr_settings.nlocp_algorithm = NLOptConSettings::NLOCP_ALGORITHM::ILQR;
ilqr_settings.lqocp_solver = NLOptConSettings::LQOCP_SOLVER::
GNRICCATI_SOLVER; // the LQ-problems are solved using a custom Gauss-Newton Riccati solver
ilqr_settings.printSummary = true;
size_t K = ilqr_settings.computeK(timeHorizon);
StateVectorArray<state_dim> x_ref_init(K + 1, x0);
NLOptConSolver<state_dim, control_dim>::Policy_t initController(x_ref_init, u0_ff, u0_fb, ilqr_settings.dt);
// STEP 2-C: create an NLOptConSolver instance
NLOptConSolver<state_dim, control_dim> iLQR(optConProblem, ilqr_settings);
// set the initial guess
iLQR.setInitialGuess(initController);
// we solve the optimal control problem and retrieve the solution
iLQR.solve();
/* MPC-EXAMPLE
* we store the initial solution obtained from solving the initial optimal control problem,
* and re-use it to initialize the MPC solver in the following. */
/* STEP 1: first, we set up an MPC instance for the iLQR solver and configure it. Since the MPC
* class is wrapped around normal Optimal Control Solvers, we need to different kind of settings,
* those for the optimal control solver, and those specific to MPC: */
// 1) settings for the iLQR instance used in MPC. Of course, we use the same settings
// as for solving the initial problem ...
NLOptConSettings ilqr_settings_mpc = ilqr_settings;
// ... however, in MPC-mode, it makes sense to limit the overall number of iLQR iterations (real-time iteration scheme)
ilqr_settings_mpc.max_iterations = 1;
// and we limited the printouts, too.
ilqr_settings_mpc.printSummary = false;
// 2) settings specific to model predictive control. For a more detailed description of those, visit ct/optcon/mpc/MpcSettings.h
mpc_settings.stateForwardIntegration_ = true;
mpc_settings.postTruncation_ = true;
mpc_settings.measureDelay_ = true;
mpc_settings.delayMeasurementMultiplier_ = 1.0;
mpc_settings.coldStart_ = false;
// STEP 2 : Create the iLQR-MPC object, based on the optimal control problem and the selected settings.
MPC<NLOptConSolver<state_dim, control_dim>> ilqr_mpc(optConProblem, ilqr_settings_mpc, mpc_settings);
// initialize it using the previously computed initial controller
ilqr_mpc.setInitialGuess(initialSolution);
/* STEP 3: running MPC
* Here, we run the MPC loop. Note that the general underlying idea is that you receive a state-estimate
* together with a time-stamp from your robot or system. MPC needs to receive both that time information and
* the state from your control system. Here, "simulate" the time measurement using std::chrono and wrap
* everything into a for-loop.
* The basic idea of operation is that after receiving time and state information, one executes the finishIteration() method of MPC.
*/
auto start_time = std::chrono::high_resolution_clock::now();
// limit the maximum number of runs in this example
size_t maxNumRuns = 100;
std::cout << "Starting to run MPC" << std::endl;
for (size_t i = 0; i < maxNumRuns; i++)
{
// let's for simplicity, assume that the "measured" state is the first state from the optimal trajectory plus some noise
if (i > 0)
// time which has passed since start of MPC
auto current_time = std::chrono::high_resolution_clock::now();
1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(current_time - start_time).count();
// prepare mpc iteration
ilqr_mpc.prepareIteration(t);
// new optimal policy
// timestamp of the new optimal policy
ct::core::Time ts_newPolicy;
current_time = std::chrono::high_resolution_clock::now();
t = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(current_time - start_time).count();
bool success = ilqr_mpc.finishIteration(x0, t, newPolicy, ts_newPolicy);
// we break the loop in case the time horizon is reached or solve() failed
if (ilqr_mpc.timeHorizonReached() | !success)
break;
}
// the summary contains some statistical data about time delays, etc.
ilqr_mpc.printMpcSummary();
}