smooth_feedback
Control and estimation on Lie groups
Loading...
Searching...
No Matches
ocp_to_qp.hpp
Go to the documentation of this file.
1// Copyright (C) 2022 Petter Nilsson. MIT License.
2
3#pragma once
4
10#include <Eigen/Core>
11#include <smooth/diff.hpp>
12
13#include "collocation/mesh.hpp"
15#include "ocp.hpp"
16#include "qp.hpp"
17
18namespace smooth::feedback {
19
20// \cond
21namespace detail {
22
26struct OcpToQpWorkmemory
27{
28 MeshValue<1> cr_out;
29 MeshValue<2> int_out;
30};
31
40template<diff::Type DT = diff::Type::Default>
41void ocp_to_qp_allocate(
42 QuadraticProgramSparse<double> & qp, OcpToQpWorkmemory & work, OCPType auto & ocp, const MeshType auto & mesh)
43{
44 using ocp_t = typename std::decay_t<decltype(ocp)>;
45
46 using X = typename ocp_t::X;
47 using U = typename ocp_t::U;
48
49 static constexpr auto Nx = ocp_t::Nx;
50 static constexpr auto Nu = ocp_t::Nu;
51
55
56 // [x0 x1 ... xN u0 u1 ... uN-1]
57
58 const auto N = mesh.N_colloc();
59
60 const auto xvar_L = Nx * (N + 1);
61 const auto uvar_L = Nu * N;
62
63 const auto dcon_L = Nx * N;
64 const auto crcon_L = ocp_t::Ncr * N;
65 const auto cecon_L = ocp_t::Nce;
66
67 const auto dcon_B = 0u;
68 const auto crcon_B = dcon_L;
69 const auto cecon_B = crcon_B + crcon_L;
70
71 const auto Nvar = static_cast<Eigen::Index>(xvar_L + uvar_L);
72 const auto Ncon = static_cast<Eigen::Index>(dcon_L + crcon_L + cecon_L);
73
74 // resize qp
75 qp.P.resize(Nvar, Nvar);
76 qp.q.setZero(Nvar);
77 qp.A.resize(Ncon, Nvar);
78 qp.l.setZero(Ncon);
79 qp.u.setZero(Ncon);
80
81 // sparsity pattern of A (row-major)
82 Eigen::VectorXi A_pattern = Eigen::VectorXi::Zero(Ncon);
83 for (auto ival = 0ul, I0 = 0ul; ival < mesh.N_ivals(); I0 += mesh.N_colloc_ival(ival), ++ival) {
84 const auto Ki = mesh.N_colloc_ival(ival); // number of nodes in interval
85 A_pattern.segment(dcon_B + I0 * Nx, Ki * Nx) += Eigen::VectorXi::Constant(Ki * Nx, Nx + Ki + Nu);
86 }
87 A_pattern.segment(crcon_B, crcon_L).setConstant(Nx + Nu);
88 A_pattern.segment(cecon_B, cecon_L).setConstant(2 * Nx);
89 qp.A.reserve(A_pattern);
90
91 // sparsity pattern of P
92 Eigen::VectorXi P_pattern = Eigen::VectorXi::Zero(Nvar);
93 for (auto i = 0u; i < N; ++i) {
94 for (auto j = 0u; j < Nx; ++j) { P_pattern(Nx * i + j) = j + 1; }
95 }
96 for (auto j = 0u; j < Nx; ++j) { P_pattern(Nx * N + j) = Nx + j + 1; }
97 for (auto i = 0u; i < N; ++i) {
98 for (auto j = 0u; j < Nu; ++j) { P_pattern(Nx * (N + 1) + Nu * i + j) = Nx + (j + 1); }
99 }
100 qp.P.reserve(P_pattern);
101
102 // compute work stuff once to allocate pattern
103 const double tf = 1.;
104 auto xslin = mesh.all_nodes() | transform([&](double) { return Identity<X>(); });
105 auto uslin = mesh.all_nodes() | transform([&](double) { return Identity<U>(); });
106
107 work.int_out.lambda.setConstant(1, 1);
108 mesh_eval<1, DT>(work.cr_out, mesh, ocp.cr, 0, tf, xslin, uslin); // allocates work.cr_out
109 mesh_integrate<2, DT>(work.int_out, mesh, ocp.g, 0, tf, xslin, uslin); // allocates work.int_out
110
111 work.cr_out.dF.makeCompressed();
112 work.int_out.dF.makeCompressed();
113 work.int_out.d2F.makeCompressed();
114}
115
117template<diff::Type DT = diff::Type::Default>
118void ocp_to_qp_update_cost(
119 QuadraticProgramSparse<double> & qp,
120 OcpToQpWorkmemory & work,
121 OCPType auto & ocp,
122 const MeshType auto & mesh,
123 double tf,
124 auto && xl_fun,
125 auto && ul_fun)
126{
127 using ocp_t = typename std::decay_t<decltype(ocp)>;
128 using X = typename ocp_t::X;
129
130 static constexpr auto Nx = ocp_t::Nx;
131 static constexpr auto Nu = ocp_t::Nu;
132 static constexpr auto Nq = ocp_t::Nq;
133 const double t0 = 0.;
134
135 static_assert(ocp_t::Nq == 1, "exactly one integral supported in ocp_to_qp");
136
140
141 const auto N = mesh.N_colloc();
142 const auto xvar_L = Nx * (N + 1);
143 const auto uvar_L = Nu * N;
144 const auto xvar_B = 0u;
145 const auto uvar_B = xvar_L;
146
150
151 set_zero(qp.P);
152 qp.q.setZero();
153
157
158 const X xl0 = xl_fun(0.);
159 const X xlf = xl_fun(tf);
160
161 auto xslin = mesh.all_nodes() | transform([&](double t) { return xl_fun(t0 + (tf - t0) * t); });
162 auto uslin = mesh.all_nodes() | transform([&](double t) { return ul_fun(t0 + (tf - t0) * t); });
163
164 const Eigen::Vector<double, 1> ql{1.};
165
169
170 const auto & [th, dth, d2th] = diff::dr<2, DT>(ocp.theta, wrt(tf, xl0, xlf, ql));
171
172 const Eigen::Vector<double, Nx> qo_x0 = dth.middleCols(1, Nx).transpose();
173 const Eigen::Vector<double, Nx> qo_xf = dth.middleCols(1 + Nx, Nx).transpose();
174 const Eigen::Vector<double, Nq> qo_q = dth.middleCols(1 + 2 * Nx, Nq).transpose();
175
176 mesh_integrate<2, DT>(work.int_out, mesh, ocp.g, 0, tf, xslin, uslin);
177
178 // clang-format off
179 block_add(qp.P, 0, 0, work.int_out.d2F.block(2, 2, xvar_L + uvar_L, xvar_L + uvar_L), qo_q.x(), true);
180
181 qp.q.segment(xvar_B, xvar_L) = qo_q.x() * work.int_out.dF.middleCols(2, xvar_L).transpose();
182 qp.q.segment(uvar_B, uvar_L) = qo_q.x() * work.int_out.dF.middleCols(2 + xvar_L, uvar_L).transpose();
183 // clang-format on
184
188
189 block_add(qp.P, 0, 0, d2th.block(1, 1, Nx, Nx), 0.5, true); // d2q / dx0x0
190 block_add(qp.P, 0, Nx * N, d2th.block(1, 1 + Nx, Nx, Nx), 0.5, true); // d2q / dx0xf
191 block_add(qp.P, Nx * N, Nx * N, d2th.block(1 + Nx, 1 + Nx, Nx, Nx), 0.5, true); // d2q / dxfxf
192
193 qp.q.segment(0, Nx) += qo_x0; // dq / dx0
194 qp.q.segment(Nx * N, Nx) += qo_xf; // dq / dxf
195}
196
198template<diff::Type DT = diff::Type::Default>
199void ocp_to_qp_update_dyn(
200 QuadraticProgramSparse<double> & qp,
201 [[maybe_unused]] OcpToQpWorkmemory & work,
202 OCPType auto & ocp,
203 const MeshType auto & mesh,
204 double tf,
205 auto && xl_fun,
206 auto && ul_fun)
207{
208 using utils::zip;
209 using namespace std::views;
210 using ocp_t = typename std::decay_t<decltype(ocp)>;
211 using X = typename ocp_t::X;
212
213 static constexpr auto Nx = ocp_t::Nx;
214 static constexpr auto Nu = ocp_t::Nu;
215 const double t0 = 0.;
216
217 static_assert(ocp_t::Nq == 1, "exactly one integral supported in ocp_to_qp");
218
222
223 const auto N = mesh.N_colloc();
224 const auto xvar_L = Nx * (N + 1);
225 const auto dcon_L = Nx * N;
226 const auto xvar_B = 0u;
227 const auto uvar_B = xvar_L;
228 const auto dcon_B = 0u;
229
233
234 set_zero(qp.A.middleRows(dcon_B, dcon_L));
235
239
240 for (auto ival = 0ul, M = 0ul; ival < mesh.N_ivals(); M += mesh.N_colloc_ival(ival), ++ival) {
241 const auto Ki = mesh.N_colloc_ival(ival); // number of nodes in interval
242
243 const auto [alpha, Dus] = mesh.interval_diffmat_unscaled(ival);
244
245 // in each interval the collocation constraint is
246 // [A0 x0 ... Ak-1 xk-1 0] + [B0 u0 ... Bk-1 uk-1] + [E0 ... Ek-1] = alpha * X Dus
247
248 for (const auto & [i, tau_i] : zip(iota(0u, Ki), mesh.interval_nodes(ival))) {
249 const auto t_i = t0 + (tf - t0) * tau_i; // unscaled time
250 const auto & [xl_i, dxl_i] = diff::dr<1, DT>(xl_fun, wrt(t_i)); // x-lin
251 const auto ul_i = ul_fun(t_i); // u-lin
252
253 // linearize dynamics and insert new constraint A xi + B ui + E = [x0 ... XNi] di
254
255 const auto & [f_i, df_i] = diff::dr<1, DT>(ocp.f, wrt(t_i, xl_i, ul_i));
256
257 // clang-format off
258 block_add(qp.A, dcon_B + (M + i) * Nx, xvar_B + (M + i) * Nx, df_i.template middleCols<Nx>(1), tf); // A
259 block_add(qp.A, dcon_B + (M + i) * Nx, uvar_B + (M + i) * Nu, df_i.template middleCols<Nu>(1 + Nx), tf); // B
260 // clang-format on
261
262 if constexpr (!IsCommutative<X>) {
263 block_add(qp.A, dcon_B + (M + i) * Nx, xvar_B + (M + i) * Nx, ad<X>(f_i + dxl_i), -tf / 2);
264 }
265
266 for (auto j = 0u; j < Ki + 1; ++j) {
267 for (auto diag = 0u; diag < Nx; ++diag) {
268 qp.A.coeffRef(dcon_B + (M + i) * Nx + diag, (M + j) * Nx + diag) -= alpha * Dus(j, i);
269 }
270 }
271
272 qp.l.segment(dcon_B + (M + i) * Nx, Nx) = -tf * (f_i - dxl_i);
273 qp.u.segment(dcon_B + (M + i) * Nx, Nx) = qp.l.segment(dcon_B + (M + i) * Nx, Nx);
274 }
275 }
276}
277
279template<diff::Type DT = diff::Type::Default>
280void ocp_to_qp_update_cr(
281 QuadraticProgramSparse<double> & qp,
282 OcpToQpWorkmemory & work,
283 OCPType auto & ocp,
284 const MeshType auto & mesh,
285 double tf,
286 auto && xl_fun,
287 auto && ul_fun)
288{
289 using ocp_t = typename std::decay_t<decltype(ocp)>;
290
291 static constexpr auto Nx = ocp_t::Nx;
292 static constexpr auto Nu = ocp_t::Nu;
293
294 const double t0 = 0.;
295
299
300 const auto N = mesh.N_colloc();
301 const auto xvar_L = Nx * (N + 1);
302 const auto uvar_L = Nu * N;
303 const auto dcon_L = Nx * N;
304 const auto crcon_L = ocp_t::Ncr * N;
305 const auto crcon_B = dcon_L;
306
310
311 auto xslin = mesh.all_nodes() | transform([&](double t) { return xl_fun(t0 + (tf - t0) * t); });
312 auto uslin = mesh.all_nodes() | transform([&](double t) { return ul_fun(t0 + (tf - t0) * t); });
313
317
318 mesh_eval<1, DT>(work.cr_out, mesh, ocp.cr, 0, tf, xslin, uslin);
319
320 block_write(qp.A, crcon_B, 0, work.cr_out.dF.middleCols(2, xvar_L + uvar_L));
321 qp.l.segment(crcon_B, crcon_L) = ocp.crl.replicate(N, 1) - work.cr_out.F;
322 qp.u.segment(crcon_B, crcon_L) = ocp.cru.replicate(N, 1) - work.cr_out.F;
323}
324
326template<diff::Type DT = diff::Type::Default>
327void ocp_to_qp_update_ce(
328 QuadraticProgramSparse<double> & qp,
329 [[maybe_unused]] OcpToQpWorkmemory & work,
330 OCPType auto & ocp,
331 const MeshType auto & mesh,
332 double tf,
333 auto && xl_fun,
334 [[maybe_unused]] auto && ul_fun)
335{
336 using ocp_t = typename std::decay_t<decltype(ocp)>;
337 using X = typename ocp_t::X;
338
339 static constexpr auto Nx = ocp_t::Nx;
340
344
345 const auto N = mesh.N_colloc();
346 const auto xvar_L = Nx * (N + 1);
347 const auto xvar_B = 0u;
348 const auto dcon_L = Nx * N;
349 const auto crcon_L = ocp_t::Ncr * N;
350 const auto cecon_L = ocp_t::Nce;
351 const auto crcon_B = dcon_L;
352 const auto cecon_B = crcon_B + crcon_L;
353
357
358 const X xl0 = xl_fun(0.);
359 const X xlf = xl_fun(tf);
360
364
365 const Eigen::Vector<double, 1> ql{1.};
366 const auto & [ceval, dceval] = diff::dr<1, DT>(ocp.ce, wrt(tf, xl0, xlf, ql));
367
368 block_write(qp.A, cecon_B, xvar_B, dceval.middleCols(1, Nx)); // dce / dx0
369 block_write(qp.A, cecon_B, xvar_B + xvar_L - Nx, dceval.middleCols(1 + Nx, Nx)); // dce / dxf
370
371 qp.l.segment(cecon_B, cecon_L) = ocp.cel - ceval;
372 qp.u.segment(cecon_B, cecon_L) = ocp.ceu - ceval;
373}
374
386template<diff::Type DT = diff::Type::Default>
387void ocp_to_qp_update(
388 QuadraticProgramSparse<double> & qp,
389 [[maybe_unused]] OcpToQpWorkmemory & work,
390 OCPType auto & ocp,
391 const MeshType auto & mesh,
392 double tf,
393 const auto & xl_fun,
394 [[maybe_unused]] const auto & ul_fun)
395{
396 ocp_to_qp_update_cost<DT>(qp, work, ocp, mesh, tf, xl_fun, ul_fun);
397 ocp_to_qp_update_dyn<DT>(qp, work, ocp, mesh, tf, xl_fun, ul_fun);
398 ocp_to_qp_update_cr<DT>(qp, work, ocp, mesh, tf, xl_fun, ul_fun);
399 ocp_to_qp_update_ce<DT>(qp, work, ocp, mesh, tf, xl_fun, ul_fun);
400}
401
402} // namespace detail
403// \endcond
404
421template<diff::Type DT = diff::Type::Default>
422QuadraticProgramSparse<double>
423ocp_to_qp(const OCPType auto & ocp, const MeshType auto & mesh, double tf, auto && xl_fun, auto && ul_fun)
424{
426 detail::OcpToQpWorkmemory work;
427
428 detail::ocp_to_qp_allocate<DT>(qp, work, ocp, mesh);
429 detail::ocp_to_qp_update<DT>(qp, work, ocp, mesh, tf, xl_fun, ul_fun);
430
431 qp.A.makeCompressed();
432 qp.P.makeCompressed();
433
434 return qp;
435}
436
453 const OCPType auto & ocp,
454 const MeshType auto & mesh,
455 const QPSolution<-1, -1, double> & qpsol,
456 double tf,
457 auto && xl_fun,
458 auto && ul_fun)
459{
460 using ocp_t = std::decay_t<decltype(ocp)>;
461
462 using X = typename ocp_t::X;
463 using U = typename ocp_t::U;
464
465 static constexpr int Nx = ocp_t::Nx;
466 static constexpr int Nu = ocp_t::Nu;
467 static constexpr int Nq = ocp_t::Nq;
468 static constexpr int Ncr = ocp_t::Ncr;
469 static constexpr int Nce = ocp_t::Nce;
470
471 const auto N = mesh.N_colloc();
472
473 const auto xvar_L = Nx * (N + 1);
474 const auto uvar_L = Nu * N;
475
476 const auto xvar_B = 0u;
477 const auto uvar_B = xvar_L;
478 Eigen::MatrixXd Xmat = qpsol.primal.segment(xvar_B, xvar_L).reshaped(Nx, N + 1);
479 Eigen::MatrixXd Umat = qpsol.primal.segment(uvar_B, uvar_L).reshaped(Nu, N);
480
481 auto xfun = [t0 = 0., tf = tf, mesh = mesh, Xmat = std::move(Xmat), xl_fun = std::forward<decltype(xl_fun)>(xl_fun)](
482 double t) -> X {
483 const auto tngnt = mesh.template eval<Eigen::Vector<double, Nx>>((t - t0) / (tf - t0), Xmat.colwise(), 0, true);
484 return rplus(xl_fun(t), tngnt);
485 };
486
487 auto ufun = [t0 = 0., tf = tf, mesh = mesh, Umat = std::move(Umat), ul_fun = std::forward<decltype(ul_fun)>(ul_fun)](
488 double t) -> U {
489 const auto tngnt = mesh.template eval<Eigen::Vector<double, Nu>>((t - t0) / (tf - t0), Umat.colwise(), 0, false);
490 return rplus(ul_fun(t), tngnt);
491 };
492
494 .t0 = 0.,
495 .tf = tf,
496 .u = std::move(ufun),
497 .x = std::move(xfun),
498 };
499}
500
501} // namespace smooth::feedback
MeshType is a specialization of Mesh.
Definition: mesh.hpp:488
Concept that is true for OCP specializations.
Definition: ocp.hpp:103
Refinable Legendre-Gauss-Radau mesh of time interval [0, 1].
Evaluate transform-like functions and derivatives on collocation points.
void set_zero(MeshValue< Deriv > &mv)
Reset MeshValue to zeros.
Optimal control problem definition.
QuadraticProgramSparse< double > ocp_to_qp(const OCPType auto &ocp, const MeshType auto &mesh, double tf, auto &&xl_fun, auto &&ul_fun)
Formulate an optimal control problem as a quadratic program via linearization.
Definition: ocp_to_qp.hpp:423
auto qpsol_to_ocpsol(const OCPType auto &ocp, const MeshType auto &mesh, const QPSolution<-1, -1, double > &qpsol, double tf, auto &&xl_fun, auto &&ul_fun)
Convert QP solution to OCP solution.
Definition: ocp_to_qp.hpp:452
Quadratic Program definition.
void block_write(Eigen::SparseMatrix< double, Options > &dest, Eigen::Index row0, Eigen::Index col0, const Source &source, double scale=1, bool upper_only=false)
Write block into a sparse matrix.
Definition: sparse.hpp:70
void block_add(Eigen::SparseMatrix< double, Options > &dest, Eigen::Index row0, Eigen::Index col0, const Source &source, double scale=1, bool upper_only=false)
Add block into a sparse matrix.
Definition: sparse.hpp:35
Solution to OCP.
Definition: ocp.hpp:115
double t0
Initial and final time.
Definition: ocp.hpp:130
Solver solution.
Definition: qp.hpp:97
Eigen::Matrix< Scalar, N, 1 > primal
Primal vector.
Definition: qp.hpp:103
Sparse quadratic program definition.
Definition: qp.hpp:62
Eigen::SparseMatrix< Scalar, Eigen::RowMajor > A
Inequality matrix.
Definition: qp.hpp:74
Eigen::SparseMatrix< Scalar > P
Positive semi-definite square cost (only upper trianglular part is used)
Definition: qp.hpp:64