You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Conception/drake-master/examples/cubic_polynomial/backward_reachability.cc

227 lines
8.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// A simple "hello world" example of computing a finite-time backward reachable
// set for a polynomial system using convex optimization.
//
// This implements the occupation measure-based SDP relaxation described in
// "Convex computation of the region of attraction of polynomial control
// systems" by Didier Henrion and Milan Korda, IEEE Transactions on Automatic
// Control, vol. 59, no. 2, pp. 297-312, Feb. 2014.
//
// TODO(jadecastro) Transcribe this example into Python.
#include <cmath>
#include <iostream>
#include "drake/common/proto/call_python.h"
#include "drake/common/symbolic/polynomial.h"
#include "drake/solvers/mathematical_program.h"
#include "drake/solvers/mosek_solver.h"
#include "drake/solvers/solve.h"
#include "drake/systems/framework/vector_system.h"
namespace drake {
namespace {
using std::cout;
using std::endl;
using std::pow;
using common::CallPython;
using symbolic::Environment;
using symbolic::Expression;
using symbolic::Polynomial;
using symbolic::Variable;
using symbolic::Variables;
using MapType = Polynomial::MapType;
using Bound = std::map<Variable, double>;
using BoundingBox = std::pair<Bound, Bound>;
/// Defines the autonomous cubic polynomial system:
/// ẋ = 100 x(x - 0.5)(x + 0.5) = 100 x³ - 25 x
template <typename T>
class CubicSystem : public systems::VectorSystem<T> {
public:
CubicSystem() : systems::VectorSystem<T>(0, 0) {
// 0 inputs, 0 outputs, 1 continuous state.
this->DeclareContinuousState(1);
}
private:
virtual void DoCalcVectorTimeDerivatives(
const systems::Context<T>&, const Eigen::VectorBlock<const VectorX<T>>&,
const Eigen::VectorBlock<const VectorX<T>>& state,
Eigen::VectorBlock<VectorX<T>>* derivatives) const {
(*derivatives)(0) = 100. * pow(state(0), 3.0) - 25. * state(0);
}
};
/// Makes a cost equal to the volume of polynomial @p p over with respect to the
/// indeterminates over the domain defined by @p b. For polynomials, this is
/// equivalent to the product of `coeff(p) * l`, where `coeff(p)` is the vector
/// of coefficients of `p` and `l` is the corresponding vector of moments of the
/// Lebesgue measure over the domain.
Expression MakeVolumetricCost(const Polynomial& p, const BoundingBox& b) {
DRAKE_DEMAND(b.first.size() == b.second.size());
DRAKE_DEMAND(p.indeterminates().size() == b.first.size());
// Build a moment vector.
const MapType& map = p.monomial_to_coefficient_map();
Expression result;
const Variables& indeterminates = p.indeterminates();
for (const auto& monomial : map) {
const std::map<Variable, int>& powers = monomial.first.get_powers();
Expression moment{1.};
for (const auto& var : indeterminates) {
if (powers.find(var) == powers.end()) {
moment *= b.second.at(var) - b.first.at(var); // zero exponent case.
continue;
}
moment *= (pow(b.second.at(var), powers.at(var) + 1) -
pow(b.first.at(var), powers.at(var) + 1)) /
(powers.at(var) + 1);
}
result += moment * monomial.second;
}
return result;
}
/// Solves the backward reachable set over T seconds on the above univariate
/// example, given a terminal set. Specifically, we solve the dual of the
/// polynomial relaxation to an infinite-dimensional LP over the space of
/// occupation measures capturing the reachable set. By restricting the space
/// of functions to polynomials and representing measures by their moments, we
/// solve the dual relaxation over the space of nonnegative functions in order
/// to find a tight over-approximation to the reachable set.
///
/// Given the target set {x | ‖x(1)‖ ≤ 0.1}, the cubic polynomial system
/// ẋ = 100x(x - 0.5)(x + 0.5) is known to have a backward reachable set
/// B = {x | ‖x(0)‖ < 0.5}.
void ComputeBackwardReachableSet() {
// Construct the system.
CubicSystem<Expression> system;
auto context = system.CreateDefaultContext();
auto derivatives = system.AllocateTimeDerivatives();
// Set up the optimization problem and declare the indeterminates.
solvers::MathematicalProgram prog;
const VectorX<Variable> tvec{prog.NewIndeterminates<1>("t")};
const VectorX<Variable> xvec{prog.NewIndeterminates<1>("x")};
const Variable& t = tvec(0);
const Variable& x = xvec(0);
// The desired order of the polynomial function approximations (noting that
// the SOS multipliers, constructed below, have order d-2).
const int d = 8;
// Extract the polynomial dynamics.
context->get_mutable_continuous_state_vector().SetAtIndex(0, x);
system.CalcTimeDerivatives(*context, derivatives.get());
// Define the domain bounds on t: 0 ≤ t ≤ T.
const double T = 1.;
const Polynomial gt = Polynomial{t * (T - t)};
// Define the (symmetric) domain bounds on x: ‖x‖ ≤ x_bound
// (X = {x | gx ≥ 0}).
const double x_bound = 1.;
const Polynomial gx = Polynomial{-(x - x_bound) * (x + x_bound)};
// Define the terminal set: ‖x(T)‖ ≤ 0.1 (X_T = {x | gxT ≥ 0}).
const Polynomial gxT{-(x - 0.1) * (x + 0.1)};
// Define the ground-truth backward reachable set (for plotting only):
// ‖x(0)‖ < 0.5 (a.k.a. X₀).
const Polynomial gx0{-(x - 0.5) * (x + 0.5)};
// Set up and solve the following optimization problem:
// Inf w'l over v, w, qₓ, qt, qT, q₀, sₓ
// s.t. v ≤ 0 on [0, T] × X (see below and Henrion and Korda 2014)
// v ≥ 0 on {T} × X_T
// w ≥ v + 1 on {0} × X
// w ≥ 0 on X
//
// The result is the T-horizon outer-approximated backward reachable set,
// B = X₀ = {x ∈ X | w ≥ 1}.
const Polynomial v{prog.NewFreePolynomial({t, x}, d)};
const Polynomial w{prog.NewFreePolynomial({x}, d)};
// operator, defined as v ↦ v := ∂v/∂t + Σ_{i=1,…,n} ∂v/∂x fᵢ(t,x).
const Polynomial Lv{v.Jacobian(tvec).coeff(0) +
v.Jacobian(xvec).coeff(0) *
Polynomial((*derivatives)[0])};
// v decreases along trajectories over t ∈ [0, T], x ∈ X.
const Polynomial qx{prog.NewSosPolynomial({t, x}, d - 2).first};
const Polynomial qt{prog.NewSosPolynomial({t, x}, d - 2).first};
prog.AddSosConstraint(-Lv - qx * gx - qt * gt);
// v(t=T, x) is SOS for all x ∈ X_T. Note that this constraint, coupled with
// the one above, implies that v(t=0, x) ≥ 0 for all x ∈ B.
// Notice that, by replacing v_at_T with v, we may solve the "free final-time"
// problem.
const Polynomial v_at_T = v.EvaluatePartial({{t, T}});
const Polynomial qT{prog.NewSosPolynomial({x}, d - 2).first};
prog.AddSosConstraint(v_at_T - qT * gxT);
// w is an outer-approximation to B over x ∈ X.
const Polynomial v_at_0 = v.EvaluatePartial({{t, 0.}});
const Polynomial q0{prog.NewSosPolynomial({x}, d - 2).first};
prog.AddSosConstraint(w - v_at_0 - 1. - q0 * gx);
// w is SOS for all x ∈ X.
const Polynomial sx{prog.NewSosPolynomial({x}, d - 2).first};
prog.AddSosConstraint(w - sx * gx);
// w represents a *tight* outer-approximation.
prog.AddCost(MakeVolumetricCost(
w, std::make_pair(Bound{{x, -x_bound}}, Bound{{x, x_bound}})));
cout << " Program attributes: " << endl;
cout << " number of decision variables: " << prog.num_vars() << endl;
cout << " number of PSD constraints: "
<< prog.positive_semidefinite_constraints().size() << endl;
const solvers::MathematicalProgramResult result = Solve(prog);
DRAKE_DEMAND(result.get_solver_id() == solvers::MosekSolver::id());
DRAKE_DEMAND(result.is_success());
// Print the solution (if one is found).
cout << " Solution found with optimal cost: " << result.get_optimal_cost()
<< endl;
Environment w_env;
for (const auto& var : w.decision_variables()) {
w_env.insert(var, result.GetSolution(var));
}
const Polynomial w_sol{w.EvaluatePartial(w_env)};
// Serialize the result and generate Python plots. In particular, plot the
// true indicator function of B and w (its polynomial outer-approximation).
// Execute:
// > bazel run //examples/cubic_polynomial:backward_reachability -c dbg
// --config mosek
// > bazel run //common/proto:call_python_client_cli
const int N{1000};
Eigen::VectorXd x_val(N), w_val(N), v_val(N), ground_val(N);
for (int i = 0; i < N; i++) {
x_val[i] = x_bound * (2. * i / N - 1.);
w_val[i] = w_sol.Evaluate({{x, x_val[i]}});
ground_val[i] = (gx0.Evaluate({{x, x_val[i]}}) >= 0.) ? 1. : 0.;
}
CallPython("figure", 1);
CallPython("clf");
CallPython("plot", x_val, w_val);
CallPython("setvars", "x_val", x_val, "w_val", w_val);
CallPython("plot", x_val, ground_val);
CallPython("setvars", "x_val", x_val, "ground_val", ground_val);
CallPython("plt.xlabel", "x");
CallPython("plt.ylabel", "w, I_B");
}
} // namespace
} // namespace drake
int main() {
drake::ComputeBackwardReachableSet();
return 0;
}