11#include <smooth/diff.hpp>
18namespace smooth::feedback {
26struct OcpToQpWorkmemory
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)
44 using ocp_t =
typename std::decay_t<
decltype(ocp)>;
46 using X =
typename ocp_t::X;
47 using U =
typename ocp_t::U;
49 static constexpr auto Nx = ocp_t::Nx;
50 static constexpr auto Nu = ocp_t::Nu;
58 const auto N = mesh.N_colloc();
60 const auto xvar_L = Nx * (N + 1);
61 const auto uvar_L = Nu * N;
63 const auto dcon_L = Nx * N;
64 const auto crcon_L = ocp_t::Ncr * N;
65 const auto cecon_L = ocp_t::Nce;
67 const auto dcon_B = 0u;
68 const auto crcon_B = dcon_L;
69 const auto cecon_B = crcon_B + crcon_L;
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);
75 qp.P.resize(Nvar, Nvar);
77 qp.A.resize(Ncon, Nvar);
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);
85 A_pattern.segment(dcon_B + I0 * Nx, Ki * Nx) += Eigen::VectorXi::Constant(Ki * Nx, Nx + Ki + Nu);
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);
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; }
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); }
100 qp.P.reserve(P_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>(); });
107 work.int_out.lambda.setConstant(1, 1);
108 mesh_eval<1, DT>(work.cr_out, mesh, ocp.cr, 0, tf, xslin, uslin);
109 mesh_integrate<2, DT>(work.int_out, mesh, ocp.g, 0, tf, xslin, uslin);
111 work.cr_out.dF.makeCompressed();
112 work.int_out.dF.makeCompressed();
113 work.int_out.d2F.makeCompressed();
117template<diff::Type DT = diff::Type::Default>
118void ocp_to_qp_update_cost(
119 QuadraticProgramSparse<double> & qp,
120 OcpToQpWorkmemory & work,
122 const MeshType
auto & mesh,
127 using ocp_t =
typename std::decay_t<
decltype(ocp)>;
128 using X =
typename ocp_t::X;
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.;
135 static_assert(ocp_t::Nq == 1,
"exactly one integral supported in ocp_to_qp");
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;
158 const X xl0 = xl_fun(0.);
159 const X xlf = xl_fun(tf);
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); });
164 const Eigen::Vector<double, 1> ql{1.};
170 const auto & [th, dth, d2th] = diff::dr<2, DT>(ocp.theta, wrt(tf, xl0, xlf, ql));
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();
176 mesh_integrate<2, DT>(work.int_out, mesh, ocp.g, 0, tf, xslin, uslin);
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);
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();
189 block_add(qp.P, 0, 0, d2th.block(1, 1, Nx, Nx), 0.5,
true);
190 block_add(qp.P, 0, Nx * N, d2th.block(1, 1 + Nx, Nx, Nx), 0.5,
true);
191 block_add(qp.P, Nx * N, Nx * N, d2th.block(1 + Nx, 1 + Nx, Nx, Nx), 0.5,
true);
193 qp.q.segment(0, Nx) += qo_x0;
194 qp.q.segment(Nx * N, Nx) += qo_xf;
198template<diff::Type DT = diff::Type::Default>
199void ocp_to_qp_update_dyn(
200 QuadraticProgramSparse<double> & qp,
201 [[maybe_unused]] OcpToQpWorkmemory & work,
203 const MeshType
auto & mesh,
209 using namespace std::views;
210 using ocp_t =
typename std::decay_t<
decltype(ocp)>;
211 using X =
typename ocp_t::X;
213 static constexpr auto Nx = ocp_t::Nx;
214 static constexpr auto Nu = ocp_t::Nu;
215 const double t0 = 0.;
217 static_assert(ocp_t::Nq == 1,
"exactly one integral supported in ocp_to_qp");
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;
234 set_zero(qp.A.middleRows(dcon_B, dcon_L));
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);
243 const auto [alpha, Dus] = mesh.interval_diffmat_unscaled(ival);
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;
250 const auto & [xl_i, dxl_i] = diff::dr<1, DT>(xl_fun, wrt(t_i));
251 const auto ul_i = ul_fun(t_i);
255 const auto & [f_i, df_i] = diff::dr<1, DT>(ocp.f, wrt(t_i, xl_i, ul_i));
258 block_add(qp.A, dcon_B + (M + i) * Nx, xvar_B + (M + i) * Nx, df_i.template middleCols<Nx>(1), tf);
259 block_add(qp.A, dcon_B + (M + i) * Nx, uvar_B + (M + i) * Nu, df_i.template middleCols<Nu>(1 + Nx), tf);
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);
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);
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);
279template<diff::Type DT = diff::Type::Default>
280void ocp_to_qp_update_cr(
281 QuadraticProgramSparse<double> & qp,
282 OcpToQpWorkmemory & work,
284 const MeshType
auto & mesh,
289 using ocp_t =
typename std::decay_t<
decltype(ocp)>;
291 static constexpr auto Nx = ocp_t::Nx;
292 static constexpr auto Nu = ocp_t::Nu;
294 const double t0 = 0.;
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;
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); });
318 mesh_eval<1, DT>(work.cr_out, mesh, ocp.cr, 0, tf, xslin, uslin);
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;
326template<diff::Type DT = diff::Type::Default>
327void ocp_to_qp_update_ce(
328 QuadraticProgramSparse<double> & qp,
329 [[maybe_unused]] OcpToQpWorkmemory & work,
331 const MeshType
auto & mesh,
334 [[maybe_unused]]
auto && ul_fun)
336 using ocp_t =
typename std::decay_t<
decltype(ocp)>;
337 using X =
typename ocp_t::X;
339 static constexpr auto Nx = ocp_t::Nx;
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;
358 const X xl0 = xl_fun(0.);
359 const X xlf = xl_fun(tf);
365 const Eigen::Vector<double, 1> ql{1.};
366 const auto & [ceval, dceval] = diff::dr<1, DT>(ocp.ce, wrt(tf, xl0, xlf, ql));
368 block_write(qp.A, cecon_B, xvar_B, dceval.middleCols(1, Nx));
369 block_write(qp.A, cecon_B, xvar_B + xvar_L - Nx, dceval.middleCols(1 + Nx, Nx));
371 qp.l.segment(cecon_B, cecon_L) = ocp.cel - ceval;
372 qp.u.segment(cecon_B, cecon_L) = ocp.ceu - ceval;
386template<diff::Type DT = diff::Type::Default>
387void ocp_to_qp_update(
388 QuadraticProgramSparse<double> & qp,
389 [[maybe_unused]] OcpToQpWorkmemory & work,
391 const MeshType
auto & mesh,
394 [[maybe_unused]]
const auto & ul_fun)
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);
421template<diff::Type DT = diff::Type::Default>
422QuadraticProgramSparse<double>
426 detail::OcpToQpWorkmemory work;
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);
431 qp.
A.makeCompressed();
432 qp.
P.makeCompressed();
460 using ocp_t = std::decay_t<
decltype(ocp)>;
462 using X =
typename ocp_t::X;
463 using U =
typename ocp_t::U;
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;
471 const auto N = mesh.N_colloc();
473 const auto xvar_L = Nx * (N + 1);
474 const auto uvar_L = Nu * N;
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);
481 auto xfun = [t0 = 0., tf = tf, mesh = mesh, Xmat = std::move(Xmat), xl_fun = std::forward<decltype(xl_fun)>(xl_fun)](
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);
487 auto ufun = [t0 = 0., tf = tf, mesh = mesh, Umat = std::move(Umat), ul_fun = std::forward<decltype(ul_fun)>(ul_fun)](
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);
496 .u = std::move(ufun),
497 .x = std::move(xfun),
MeshType is a specialization of Mesh.
Concept that is true for OCP specializations.
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.
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.
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.
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.
double t0
Initial and final time.
Eigen::Matrix< Scalar, N, 1 > primal
Primal vector.
Sparse quadratic program definition.
Eigen::SparseMatrix< Scalar, Eigen::RowMajor > A
Inequality matrix.
Eigen::SparseMatrix< Scalar > P
Positive semi-definite square cost (only upper trianglular part is used)