blob: 0329fd74b5eb4dbfbffffef4262aaf28b92f5669 [file] [log] [blame]
/*!
* \example NLOC_generalConstrained.cpp
*
* This example shows how to use general constraints alongside NLOC and requires HPIPM to be installed
* The unconstrained Riccati backward-pass is replaced by a high-performance interior-point
* constrained linear-quadratic Optimal Control solver.
*
*/
#include <ct/optcon/optcon.h>
#include "exampleDir.h"
#include "plotResultsOscillator.h"
using namespace ct::core;
using namespace ct::optcon;
/*get the state and control input dimension of the oscillator. Since we're dealing with a simple oscillator,
the state and control dimensions will be state_dim = 2, and control_dim = 1. */
static const size_t state_dim = ct::core::SecondOrderSystem::STATE_DIM;
static const size_t control_dim = ct::core::SecondOrderSystem::CONTROL_DIM;
/*!
* @brief A simple 1d constraint term.
*
* This term implements the general inequality constraints
* \f$ d_{lb} \leq u \cdot p^2 \leq d_{ub} \f$
* where \f$ p \f$ denotes the position of the oscillator mass.
*
* This constraint can be thought of a position-varying bound on the control input.
* At large oscillator deflections, the control bounds shrink
*/
class ConstraintTerm1D : public ct::optcon::ConstraintBase<state_dim, control_dim>
{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
typedef typename ct::core::tpl::TraitSelector<double>::Trait Trait;
typedef typename ct::core::tpl::TraitSelector<ct::core::ADCGScalar>::Trait TraitCG;
typedef ct::optcon::ConstraintBase<state_dim, control_dim> Base;
typedef ct::core::StateVector<state_dim> state_vector_t;
typedef ct::core::ControlVector<control_dim> control_vector_t;
//! constructor with hard-coded constraint boundaries.
ConstraintTerm1D()
{
Base::lb_.resize(1);
Base::ub_.resize(1);
Base::lb_.setConstant(-0.5);
Base::ub_.setConstant(0.5);
}
virtual ~ConstraintTerm1D() {}
virtual ConstraintTerm1D* clone() const override { return new ConstraintTerm1D(); }
virtual size_t getConstraintSize() const override { return 1; }
virtual Eigen::VectorXd evaluate(const state_vector_t& x, const control_vector_t& u, const double t) override
{
Eigen::Matrix<double, 1, 1> val;
val.template segment<1>(0) << u(0) * x(0) * x(0);
return val;
}
virtual Eigen::Matrix<ct::core::ADCGScalar, Eigen::Dynamic, 1> evaluateCppadCg(
const ct::core::StateVector<state_dim, ct::core::ADCGScalar>& x,
const ct::core::ControlVector<control_dim, ct::core::ADCGScalar>& u,
ct::core::ADCGScalar t) override
{
Eigen::Matrix<ct::core::ADCGScalar, 1, 1> val;
val.template segment<1>(0) << u(0) * x(0) * x(0);
return val;
}
};
int main(int argc, char** argv)
{
/* STEP 1: set up the Nonlinear Optimal Control Problem
* First of all, we need to create instances of the system dynamics, the linearized system and the cost function. */
/* STEP 1-A: create a instance of the oscillator dynamics for the optimal control problem.
* Please also compare the documentation of SecondOrderSystem.h */
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));
/* STEP 1-B: Although the first order derivatives of the oscillator are easy to derive, let's illustrate the use of the System Linearizer,
* which performs numerical differentiation by the finite-difference method. The system linearizer simply takes the
* the system dynamics as argument. Alternatively, you could implement your own first-order derivatives by overloading the class LinearSystem.h */
std::shared_ptr<ct::core::SystemLinearizer<state_dim, control_dim>> adLinearizer(
new ct::core::SystemLinearizer<state_dim, control_dim>(oscillatorDynamics));
/* STEP 1-C: create a cost function. We have pre-specified the cost-function weights for this problem in "nlocCost.info".
* Here, we show how to create terms for intermediate and final cost and how to automatically load them from the configuration file.
* The verbose option allows to print information about the loaded terms on the terminal. */
std::shared_ptr<ct::optcon::TermQuadratic<state_dim, control_dim>> intermediateCost(
new ct::optcon::TermQuadratic<state_dim, control_dim>());
std::shared_ptr<ct::optcon::TermQuadratic<state_dim, control_dim>> finalCost(
new ct::optcon::TermQuadratic<state_dim, control_dim>());
bool verbose = true;
intermediateCost->loadConfigFile(ct::optcon::exampleDir + "/nlocCost.info", "intermediateCost", verbose);
finalCost->loadConfigFile(ct::optcon::exampleDir + "/nlocCost.info", "finalCost", verbose);
// Since we are using quadratic cost function terms in this example, the first and second order derivatives are immediately known and we
// define the cost function to be an "Analytical Cost Function". Let's create the corresponding object and add the previously loaded
// intermediate and final term.
std::shared_ptr<CostFunctionQuadratic<state_dim, control_dim>> costFunction(
new CostFunctionAnalytical<state_dim, control_dim>());
costFunction->addIntermediateTerm(intermediateCost);
costFunction->addFinalTerm(finalCost);
/* STEP 1-D: set up the general constraints */
// constraint terms
std::shared_ptr<ConstraintTerm1D> pathConstraintTerm(new ConstraintTerm1D());
// create constraint container
std::shared_ptr<ct::optcon::ConstraintContainerAD<state_dim, control_dim>> generalConstraints(
new ct::optcon::ConstraintContainerAD<state_dim, control_dim>());
// add and initialize constraint terms
generalConstraints->addIntermediateConstraint(pathConstraintTerm, verbose);
generalConstraints->initialize();
/* STEP 1-E: initialization with initial state and desired time horizon */
StateVector<state_dim> x0;
x0.setRandom(); // in this example, we choose a random initial state x0
ct::core::Time timeHorizon = 3.0; // and a final time horizon in [sec]
// STEP 1-E: create and initialize an "optimal control problem"
OptConProblem<state_dim, control_dim> optConProblem(
timeHorizon, x0, oscillatorDynamics, costFunction, adLinearizer);
// add the box constraints to the optimal control problem
optConProblem.setGeneralConstraints(generalConstraints);
/* STEP 2: set up a nonlinear optimal control solver. */
/* STEP 2-A: Create the settings.
* the type of solver, and most parameters, like number of shooting intervals, etc.,
* can be chosen using the following settings struct. Let's use, the iterative
* linear quadratic regulator, iLQR, for this example. In the following, we
* modify only a few settings, for more detail, check out the NLOptConSettings class. */
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.min_cost_improvement = 1e-6;
ilqr_settings.meritFunctionRhoConstraints = 10;
ilqr_settings.nThreads = 1;
ilqr_settings.nlocp_algorithm = NLOptConSettings::NLOCP_ALGORITHM::ILQR;
ilqr_settings.lqocp_solver = NLOptConSettings::LQOCP_SOLVER::HPIPM_SOLVER; // solve LQ-problems using HPIPM
ilqr_settings.lqoc_solver_settings.num_lqoc_iterations = 10; // number of riccati sub-iterations
ilqr_settings.lineSearchSettings.active = true;
ilqr_settings.lineSearchSettings.debugPrint = true;
ilqr_settings.printSummary = true;
/* STEP 2-B: provide an initial guess */
// calculate the number of time steps K
size_t K = ilqr_settings.computeK(timeHorizon);
/* design trivial initial controller for iLQR. Note that in this simple example,
* we can simply use zero feedforward with zero feedback gains around the initial position.
* In more complex examples, a more elaborate initial guess may be required.*/
FeedbackArray<state_dim, control_dim> u0_fb(K, FeedbackMatrix<state_dim, control_dim>::Zero());
ControlVectorArray<control_dim> u0_ff(K, ControlVector<control_dim>::Zero());
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);
// STEP 3: solve the optimal control problem
iLQR.solve();
// STEP 4: retrieve the solution
ct::core::StateFeedbackController<state_dim, control_dim> solution = iLQR.getSolution();
// plot the output
plotResultsOscillator<state_dim, control_dim>(solution.x_ref(), solution.uff(), solution.time());
}