-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
backward_reachability.cc
226 lines (195 loc) · 8.7 KB
/
backward_reachability.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// 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;
}