smooth_feedback
Control and estimation on Lie groups
Loading...
Searching...
No Matches
ocp_to_nlp.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/concepts/lie_group.hpp>
12
13#include "collocation/mesh.hpp"
15#include "nlp.hpp"
16#include "ocp.hpp"
17
18namespace smooth::feedback {
19
20// \cond
21namespace detail {
22
24auto ocp_nlp_structure(const FlatOCPType auto & ocp, const MeshType auto & mesh)
25{
26 std::size_t N = mesh.N_colloc();
27
28 // variable layout
29 std::array<std::size_t, 4> var_len{
30 1, // tf
31 ocp.Nq, // integrals
32 ocp.Nx * (N + 1), // states
33 ocp.Nu * N, // inputs
34 };
35
36 // constraint layout
37 std::array<std::size_t, 4> con_len{
38 ocp.Nx * N, // derivatives
39 ocp.Nq, // other integrals
40 ocp.Ncr * N, // running constraints
41 ocp.Nce, // end constraints
42 };
43
44 std::array<std::size_t, 5> var_beg{0};
45 std::partial_sum(var_len.begin(), var_len.end(), var_beg.begin() + 1);
46
47 std::array<std::size_t, 5> con_beg{0};
48 std::partial_sum(con_len.begin(), con_len.end(), con_beg.begin() + 1);
49
50 return std::make_tuple(var_beg, var_len, con_beg, con_len);
51}
52
58template<FlatOCPType Ocp, MeshType Mesh, diff::Type DT = diff::Type::Default>
59class OCPNLP
60{
61private:
62 static constexpr auto Nx = std::decay_t<Ocp>::Nx;
63 static constexpr auto Nu = std::decay_t<Ocp>::Nu;
64 static constexpr auto Nq = std::decay_t<Ocp>::Nq;
65
66 Ocp ocp_; // optimal control problem
67 Mesh mesh_; // discretization mesh
68 std::size_t N_; // number of collocation points in mesh
69
70 std::size_t tfvar_B, qvar_B, xvar_B, uvar_B, n_; // variable start indices
71 std::size_t tfvar_L, qvar_L, xvar_L, uvar_L; // variable lengths
72
73 std::size_t dcon_B, qcon_B, crcon_B, cecon_B, m_; // constraint start indices
74 std::size_t dcon_L, qcon_L, crcon_L, cecon_L; // constraint lengths
75
76 // scaling
77 double w_scaling_{1};
78
79 // variable and constraint bounds
80 Eigen::VectorXd xl_, xu_, gl_, gu_;
81
82 // allocated return arguments
83 Eigen::VectorXd g_;
84 Eigen::SparseMatrix<double> df_dx_, dg_dx_, d2f_dx2_, d2g_dx2_;
85
86 // allocated computation
87 MeshValue<0> dyn_out0_, int_out0_, cr_out0_;
88 MeshValue<1> dyn_out1_, int_out1_, cr_out1_;
89 MeshValue<2> dyn_out2_, int_out2_, cr_out2_;
90
91public:
93 template<typename OcpArg, typename MeshArg>
94 OCPNLP(OcpArg && ocp, MeshArg && mesh)
95 : ocp_(std::forward<OcpArg>(ocp)), mesh_(std::forward<MeshArg>(mesh)), N_(mesh_.N_colloc())
96 {
97 const auto [var_beg, var_len, con_beg, con_len] = detail::ocp_nlp_structure(ocp_, mesh_);
98
99 tfvar_B = var_beg[0];
100 qvar_B = var_beg[1];
101 xvar_B = var_beg[2];
102 uvar_B = var_beg[3];
103 n_ = var_beg[4];
104
105 tfvar_L = var_len[0];
106 qvar_L = var_len[1];
107 xvar_L = var_len[2];
108 uvar_L = var_len[3];
109
110 dcon_B = con_beg[0];
111 qcon_B = con_beg[1];
112 crcon_B = con_beg[2];
113 cecon_B = con_beg[3];
114 m_ = con_beg[4];
115
116 dcon_L = con_len[0];
117 qcon_L = con_len[1];
118 crcon_L = con_len[2];
119 cecon_L = con_len[3];
120
121 // Mesh weight
122 double max_weight = 1e-6;
123 for (auto w : mesh_.all_weights()) { max_weight = std::max(max_weight, w); }
124 w_scaling_ = 1. / max_weight;
125
126 // VARIABLE BOUNDS
127
128 xl_.setConstant(n_, -std::numeric_limits<double>::infinity());
129 xl_.segment(tfvar_B, tfvar_L).setZero(); // tf lower bounded by zero
130 xu_.setConstant(n_, std::numeric_limits<double>::infinity());
131
132 // CONSTRAINT BOUNDS
133
134 gl_.resize(m_);
135 gu_.resize(m_);
136
137 // derivative constraints are equalities
138 gl_.segment(dcon_B, dcon_L).setZero();
139 gu_.segment(dcon_B, dcon_L).setZero();
140
141 // integral constraints are equalities
142 gl_.segment(qcon_B, qcon_L).setZero();
143 gu_.segment(qcon_B, qcon_L).setZero();
144
145 // running constraints (scaled by quadrature weights)
146 gl_.segment(crcon_B, crcon_L) = ocp.crl.replicate(N_, 1);
147 gu_.segment(crcon_B, crcon_L) = ocp.cru.replicate(N_, 1);
148 for (const auto & [i, w] : utils::zip(std::views::iota(0u, N_), mesh_.all_weights())) {
149 gl_.segment(crcon_B + i * ocp_.Ncr, ocp_.Ncr) *= w_scaling_ * w;
150 gu_.segment(crcon_B + i * ocp_.Ncr, ocp_.Ncr) *= w_scaling_ * w;
151 }
152
153 // end constraints
154 gl_.segment(cecon_B, cecon_L) = ocp.cel;
155 gu_.segment(cecon_B, cecon_L) = ocp.ceu;
156
157 // allocate output args
158 g_.setZero(m_);
159
160 df_dx_.resize(1, n_);
161 df_dx_.reserve(Eigen::VectorXi::Constant(n_, 1));
162
163 d2f_dx2_.resize(n_, n_);
164 dg_dx_.resize(m_, n_);
165 d2g_dx2_.resize(n_, n_);
166
168 }
169
170 std::size_t n() const { return n_; }
171 std::size_t m() const { return m_; }
172 const Eigen::VectorXd & xl() const { return xl_; }
173 const Eigen::VectorXd & xu() const { return xu_; }
174
175 double f(const Eigen::Ref<const Eigen::VectorXd> x) const
176 {
177 assert(static_cast<std::size_t>(x.size()) == n_);
178
179 const auto x0var_B = xvar_B;
180 const auto xfvar_B = xvar_B + xvar_L - Nx;
181
182 const double tf = x(tfvar_B);
183 const Eigen::Vector<double, Nx> x0 = x.segment(x0var_B, Nx);
184 const Eigen::Vector<double, Nx> xf = x.segment(xfvar_B, Nx);
185 const Eigen::Vector<double, Nq> q = x.segment(qvar_B, qvar_L);
186
187 return ocp_.theta(tf, x0, xf, q);
188 }
189
190 const Eigen::SparseMatrix<double> & df_dx(const Eigen::Ref<const Eigen::VectorXd> x)
191 {
192 assert(static_cast<std::size_t>(x.size()) == n_);
193
194 const auto x0var_B = xvar_B;
195 const auto xfvar_B = xvar_B + xvar_L - Nx;
196
197 const double tf = x(tfvar_B);
198 const Eigen::Vector<double, Nx> x0 = x.segment(x0var_B, Nx);
199 const Eigen::Vector<double, Nx> xf = x.segment(xfvar_B, Nx);
200 const Eigen::Vector<double, Nq> q = x.segment(qvar_B, qvar_L);
201
202 const auto & [fval, dfval] = diff::dr<1, DT>(ocp_.theta, wrt(tf, x0, xf, q));
203
204 set_zero(df_dx_);
205
206 block_add(df_dx_, 0, tfvar_B, dfval.middleCols(0, 1)); // df / dtf
207 block_add(df_dx_, 0, x0var_B, dfval.middleCols(1, Nx)); // df / dx0
208 block_add(df_dx_, 0, xfvar_B, dfval.middleCols(1 + Nx, Nx)); // df / dxf
209 block_add(df_dx_, 0, qvar_B, dfval.middleCols(1 + 2 * Nx, Nq)); // df / dq
210
211 df_dx_.makeCompressed();
212 return df_dx_;
213 }
214
215 const Eigen::SparseMatrix<double> & d2f_dx2(Eigen::Ref<const Eigen::VectorXd> x)
216 {
217 assert(static_cast<std::size_t>(x.size()) == n_);
218
219 const auto x0var_B = xvar_B;
220 const auto xfvar_B = xvar_B + xvar_L - Nx;
221
222 const double tf = x(tfvar_B);
223 const Eigen::Vector<double, Nx> x0 = x.segment(x0var_B, Nx);
224 const Eigen::Vector<double, Nx> xf = x.segment(xfvar_B, Nx);
225 const Eigen::Vector<double, Nq> q = x.segment(qvar_B, qvar_L);
226
227 const auto & [fval, dfval, d2fval] = diff::dr<2, DT>(ocp_.theta, wrt(tf, x0, xf, q));
228
229 set_zero(d2f_dx2_);
230
231 // clang-format off
232 block_add(d2f_dx2_, tfvar_B, tfvar_B, d2fval.block( 0, 0, 1, 1), 1, true); // tftf
233 block_add(d2f_dx2_, tfvar_B, x0var_B, d2fval.block( 0, 1, 1, Nx), 1, true); // tfx0
234 block_add(d2f_dx2_, tfvar_B, xfvar_B, d2fval.block( 0, 1 + Nx, 1, Nx), 1, true); // tfxf
235 block_add(d2f_dx2_, tfvar_B, qvar_B, d2fval.block( 0, 1 + 2 * Nx, 1, Nq), 1, true); // tfq
236
237 block_add(d2f_dx2_, x0var_B, x0var_B, d2fval.block( 1, 1, Nx, Nx), 1, true); // x0x0
238 block_add(d2f_dx2_, x0var_B, xfvar_B, d2fval.block( 1, 1 + Nx, Nx, Nx), 1, true); // x0xf
239 block_add(d2f_dx2_, x0var_B, qvar_B, d2fval.block( 1, 1 + 2 * Nx, Nx, Nq), 1, true); // x0q
240
241 block_add(d2f_dx2_, xfvar_B, xfvar_B, d2fval.block( 1 + Nx, 1 + Nx, Nx, Nx), 1, true); // xfxf
242 block_add(d2f_dx2_, xfvar_B, qvar_B, d2fval.block( 1 + Nx, 1 + 2 * Nx, Nx, Nq), 1, true); // xfq
243
244 block_add(d2f_dx2_, qvar_B, qvar_B, d2fval.block(1 + 2 * Nx, 1 + 2 * Nx, Nq, Nq), 1, true); // qq
245 // clang-format on
246
247 d2f_dx2_.makeCompressed();
248 return d2f_dx2_;
249 }
250
251 const Eigen::VectorXd & g(const Eigen::Ref<const Eigen::VectorXd> x)
252 {
253 assert(static_cast<std::size_t>(x.size()) == n_);
254
255 const auto x0var_B = xvar_B;
256 const auto xfvar_B = xvar_B + xvar_L - Nx;
257
258 const double t0 = 0;
259 const double tf = x(tfvar_B);
260 const Eigen::Vector<double, Nx> x0 = x.segment(x0var_B, Nx);
261 const Eigen::Vector<double, Nx> xf = x.segment(xfvar_B, Nx);
262 const Eigen::Vector<double, Nq> q = x.segment(qvar_B, qvar_L);
263
264 const Eigen::Map<const Eigen::Matrix<double, Nx, -1>> X(x.data() + xvar_B, Nx, N_ + 1);
265 const Eigen::Map<const Eigen::Matrix<double, Nu, -1>> U(x.data() + uvar_B, Nu, N_);
266
267 mesh_dyn<0>(dyn_out0_, mesh_, ocp_.f, t0, tf, X.colwise(), U.colwise());
268 mesh_integrate<0>(int_out0_, mesh_, ocp_.g, t0, tf, X.colwise(), U.colwise());
269 mesh_eval<0>(cr_out0_, mesh_, ocp_.cr, t0, tf, X.colwise(), U.colwise(), true);
270
271 g_.segment(dcon_B, dcon_L) = w_scaling_ * dyn_out0_.F;
272 g_.segment(qcon_B, qcon_L) = w_scaling_ * (int_out0_.F - q);
273 g_.segment(crcon_B, crcon_L) = w_scaling_ * cr_out0_.F;
274 g_.segment(cecon_B, cecon_L) = ocp_.ce(tf, x0, xf, q);
275
276 return g_;
277 }
278 const Eigen::VectorXd & gl() const { return gl_; }
279 const Eigen::VectorXd & gu() const { return gu_; }
280 const Eigen::SparseMatrix<double> & dg_dx(const Eigen::Ref<const Eigen::VectorXd> x)
281 {
282 assert(static_cast<std::size_t>(x.size()) == n_);
283
284 const auto x0var_B = xvar_B;
285 const auto xfvar_B = xvar_B + xvar_L - Nx;
286
287 const double t0 = 0;
288 const double tf = x(tfvar_B);
289 const Eigen::Vector<double, Nx> x0 = x.segment(x0var_B, Nx);
290 const Eigen::Vector<double, Nx> xf = x.segment(xfvar_B, Nx);
291 const Eigen::Vector<double, Nq> q = x.segment(qvar_B, qvar_L);
292
293 const Eigen::Map<const Eigen::Matrix<double, Nx, -1>> X(x.data() + xvar_B, Nx, N_ + 1);
294 const Eigen::Map<const Eigen::Matrix<double, Nu, -1>> U(x.data() + uvar_B, Nu, N_);
295
296 mesh_dyn<1, DT>(dyn_out1_, mesh_, ocp_.f, t0, tf, X.colwise(), U.colwise());
297 mesh_integrate<1, DT>(int_out1_, mesh_, ocp_.g, t0, tf, X.colwise(), U.colwise());
298 mesh_eval<1, DT>(cr_out1_, mesh_, ocp_.cr, t0, tf, X.colwise(), U.colwise(), true);
299 const auto & [ceval, dceval] = diff::dr<1, DT>(ocp_.ce, wrt(tf, x0, xf, q));
300
301 dyn_out1_.dF.makeCompressed();
302 int_out1_.dF.makeCompressed();
303 cr_out1_.dF.makeCompressed();
304
305 set_zero(dg_dx_);
306
307 // dynamics constraint
308 block_add(dg_dx_, dcon_B, tfvar_B, dyn_out1_.dF.middleCols(1, 1), w_scaling_);
309 block_add(dg_dx_, dcon_B, xvar_B, dyn_out1_.dF.middleCols(2, xvar_L), w_scaling_);
310 block_add(dg_dx_, dcon_B, uvar_B, dyn_out1_.dF.middleCols(2 + xvar_L, uvar_L), w_scaling_);
311
312 // integral constraint
313 block_add(dg_dx_, qcon_B, tfvar_B, int_out1_.dF.middleCols(1, 1), w_scaling_);
314 block_add(dg_dx_, qcon_B, xvar_B, int_out1_.dF.middleCols(2, xvar_L), w_scaling_);
315 block_add(dg_dx_, qcon_B, uvar_B, int_out1_.dF.middleCols(2 + xvar_L, uvar_L), w_scaling_);
316 block_add_identity(dg_dx_, qcon_B, qvar_B, qvar_L, -w_scaling_);
317
318 // running constraint
319 block_add(dg_dx_, crcon_B, tfvar_B, cr_out1_.dF.middleCols(1, 1), w_scaling_);
320 block_add(dg_dx_, crcon_B, xvar_B, cr_out1_.dF.middleCols(2, xvar_L), w_scaling_);
321 block_add(dg_dx_, crcon_B, uvar_B, cr_out1_.dF.middleCols(2 + xvar_L, uvar_L), w_scaling_);
322
323 // end constraint
324 block_add(dg_dx_, cecon_B, tfvar_B, dceval.middleCols(0, 1));
325 block_add(dg_dx_, cecon_B, xvar_B, dceval.middleCols(1, Nx));
326 block_add(dg_dx_, cecon_B, xvar_B + xvar_L - Nx, dceval.middleCols(1 + Nx, Nx));
327 block_add(dg_dx_, cecon_B, qvar_B, dceval.middleCols(1 + 2 * Nx, Nq));
328
329 dg_dx_.makeCompressed();
330 return dg_dx_;
331 }
332
333 const Eigen::SparseMatrix<double> &
334 d2g_dx2(const Eigen::Ref<const Eigen::VectorXd> x, const Eigen::Ref<const Eigen::VectorXd> lambda)
335 {
336 assert(static_cast<std::size_t>(x.size()) == n_);
337 assert(static_cast<std::size_t>(lambda.size()) == m_);
338
339 const auto x0var_B = xvar_B;
340 const auto xfvar_B = xvar_B + xvar_L - Nx;
341
342 const double t0 = 0;
343 const double tf = x(tfvar_B);
344 const Eigen::Vector<double, Nx> x0 = x.segment(x0var_B, Nx);
345 const Eigen::Vector<double, Nx> xf = x.segment(xfvar_B, Nx);
346 const Eigen::Vector<double, Nq> q = x.segment(qvar_B, qvar_L);
347
348 const Eigen::Map<const Eigen::Matrix<double, Nx, -1>> X(x.data() + xvar_B, Nx, N_ + 1);
349 const Eigen::Map<const Eigen::Matrix<double, Nu, -1>> U(x.data() + uvar_B, Nu, N_);
350
351 dyn_out2_.lambda = lambda.segment(dcon_B, dcon_L);
352 int_out2_.lambda = lambda.segment(qcon_B, qcon_L);
353 cr_out2_.lambda = lambda.segment(crcon_B, crcon_L);
354 mesh_dyn<2, DT>(dyn_out2_, mesh_, ocp_.f, t0, tf, X.colwise(), U.colwise());
355 mesh_integrate<2, DT>(int_out2_, mesh_, ocp_.g, t0, tf, X.colwise(), U.colwise());
356 mesh_eval<2, DT>(cr_out2_, mesh_, ocp_.cr, t0, tf, X.colwise(), U.colwise(), true);
357 const auto & [ceval, dceval, d2ceval] = diff::dr<2, DT>(ocp_.ce, wrt(tf, x0, xf, q));
358
359 dyn_out2_.dF.makeCompressed();
360 int_out2_.dF.makeCompressed();
361 cr_out2_.dF.makeCompressed();
362
363 dyn_out2_.d2F.makeCompressed();
364 int_out2_.d2F.makeCompressed();
365 cr_out2_.d2F.makeCompressed();
366
367 set_zero(d2g_dx2_);
368
369 // clang-format off
370 block_add(d2g_dx2_, tfvar_B, tfvar_B, dyn_out2_.d2F.block(1, 1, 1, 1), w_scaling_, true); // tftf
371 block_add(d2g_dx2_, tfvar_B, tfvar_B, int_out2_.d2F.block(1, 1, 1, 1), w_scaling_, true); // tftf
372 block_add(d2g_dx2_, tfvar_B, tfvar_B, cr_out2_.d2F.block(1, 1, 1, 1), w_scaling_, true); // tftf
373
374 block_add(d2g_dx2_, tfvar_B, xvar_B, dyn_out2_.d2F.block(1, 2, 1, xvar_L), w_scaling_, true); // tfx
375 block_add(d2g_dx2_, tfvar_B, xvar_B, int_out2_.d2F.block(1, 2, 1, xvar_L), w_scaling_, true); // tfx
376 block_add(d2g_dx2_, tfvar_B, xvar_B, cr_out2_.d2F.block(1, 2, 1, xvar_L), w_scaling_, true); // tfx
377
378 block_add(d2g_dx2_, tfvar_B, uvar_B, dyn_out2_.d2F.block(1, 2 + xvar_L, 1, uvar_L), w_scaling_, true); // tfu
379 block_add(d2g_dx2_, tfvar_B, uvar_B, int_out2_.d2F.block(1, 2 + xvar_L, 1, uvar_L), w_scaling_, true); // tfu
380 block_add(d2g_dx2_, tfvar_B, uvar_B, cr_out2_.d2F.block(1, 2 + xvar_L, 1, uvar_L), w_scaling_, true); // tfu
381
382 block_add(d2g_dx2_, xvar_B, xvar_B, dyn_out2_.d2F.block(2, 2, xvar_L, xvar_L), w_scaling_, true); // xx
383 block_add(d2g_dx2_, xvar_B, xvar_B, int_out2_.d2F.block(2, 2, xvar_L, xvar_L), w_scaling_, true); // xx
384 block_add(d2g_dx2_, xvar_B, xvar_B, cr_out2_.d2F.block(2, 2, xvar_L, xvar_L), w_scaling_, true); // xx
385
386 block_add(d2g_dx2_, xvar_B, uvar_B, dyn_out2_.d2F.block(2, 2 + xvar_L, xvar_L, uvar_L), w_scaling_, true); // xu
387 block_add(d2g_dx2_, xvar_B, uvar_B, int_out2_.d2F.block(2, 2 + xvar_L, xvar_L, uvar_L), w_scaling_, true); // xu
388 block_add(d2g_dx2_, xvar_B, uvar_B, cr_out2_.d2F.block(2, 2 + xvar_L, xvar_L, uvar_L), w_scaling_, true); // xu
389
390 block_add(d2g_dx2_, uvar_B, uvar_B, dyn_out2_.d2F.block(2 + xvar_L, 2 + xvar_L, uvar_L, uvar_L), w_scaling_, true); // uu
391 block_add(d2g_dx2_, uvar_B, uvar_B, int_out2_.d2F.block(2 + xvar_L, 2 + xvar_L, uvar_L, uvar_L), w_scaling_, true); // uu
392 block_add(d2g_dx2_, uvar_B, uvar_B, cr_out2_.d2F.block(2 + xvar_L, 2 + xvar_L, uvar_L, uvar_L), w_scaling_, true); // uu
393 // clang-format on
394
395 for (auto j = 0u; j < ocp_.Nce; ++j) {
396 const auto b0 = (1 + 2 * Nx + ocp_.Nq) * j;
397 // clang-format off
398 block_add(d2g_dx2_, tfvar_B, tfvar_B, d2ceval.block( 0, b0 + 0, 1, 1), lambda(cecon_B + j), true); // tftf
399 block_add(d2g_dx2_, tfvar_B, x0var_B, d2ceval.block( 0, b0 + 1, 1, Nx), lambda(cecon_B + j), true); // tfx0
400 block_add(d2g_dx2_, tfvar_B, xfvar_B, d2ceval.block( 0, b0 + 1 + Nx, 1, Nx), lambda(cecon_B + j), true); // tfxf
401 block_add(d2g_dx2_, tfvar_B, qvar_B, d2ceval.block( 0, b0 + 1 + 2 * Nx, 1, Nq), lambda(cecon_B + j), true); // tfq
402
403 block_add(d2g_dx2_, x0var_B, x0var_B, d2ceval.block( 1, b0 + 1, Nx, Nx), lambda(cecon_B + j), true); // x0x0
404 block_add(d2g_dx2_, x0var_B, xfvar_B, d2ceval.block( 1, b0 + 1 + Nx, Nx, Nx), lambda(cecon_B + j), true); // x0xf
405 block_add(d2g_dx2_, x0var_B, qvar_B, d2ceval.block( 1, b0 + 1 + 2 * Nx, Nx, Nq), lambda(cecon_B + j), true); // x0q
406
407 block_add(d2g_dx2_, xfvar_B, xfvar_B, d2ceval.block( 1 + Nx, b0 + 1 + Nx, Nx, Nx), lambda(cecon_B + j), true); // xfxf
408 block_add(d2g_dx2_, xfvar_B, qvar_B, d2ceval.block( 1 + Nx, b0 + 1 + 2 * Nx, Nx, Nq), lambda(cecon_B + j), true); // xfq
409
410 block_add(d2g_dx2_, qvar_B, qvar_B, d2ceval.block(1 + 2 * Nx, b0 + 1 + 2 * Nx, Nq, Nq), lambda(cecon_B + j), true); // qq
411 // clang-format on
412 }
413
414 d2g_dx2_.makeCompressed();
415 return d2g_dx2_;
416 }
417};
418
419} // namespace detail
420// \endcond
421
431template<diff::Type DT = diff::Type::Default>
432auto ocp_to_nlp(FlatOCPType auto && ocp, MeshType auto && mesh)
433 -> detail::OCPNLP<std::decay_t<decltype(ocp)>, std::decay_t<decltype(mesh)>, DT>
434{
435 return detail::OCPNLP<std::decay_t<decltype(ocp)>, std::decay_t<decltype(mesh)>, DT>(
436 std::forward<decltype(ocp)>(ocp), std::forward<decltype(mesh)>(mesh));
437}
438
442auto nlpsol_to_ocpsol(const FlatOCPType auto & ocp, const MeshType auto & mesh, const NLPSolution & nlp_sol)
443{
444 using ocp_t = std::decay_t<decltype(ocp)>;
445
446 static constexpr auto Nx = ocp_t::Nx;
447 static constexpr auto Nu = ocp_t::Nu;
448 static constexpr auto Nq = ocp_t::Nq;
449 static constexpr auto Ncr = ocp_t::Ncr;
450
451 const std::size_t N = mesh.N_colloc();
452 const auto [var_beg, var_len, con_beg, con_len] = detail::ocp_nlp_structure(ocp, mesh);
453
454 const auto [tfvar_B, qvar_B, xvar_B, uvar_B, n] = var_beg;
455 const auto [tfvar_L, qvar_L, xvar_L, uvar_L] = var_len;
456
457 const auto [dcon_B, qcon_B, crcon_B, cecon_B, m] = con_beg;
458 const auto [dcon_L, qcon_L, crcon_L, cecon_L] = con_len;
459
460 const double t0 = 0;
461 const double tf = nlp_sol.x(tfvar_B);
462
463 const Eigen::Vector<double, Nq> Q = nlp_sol.x.segment(qvar_B, qvar_L);
464
465 // state vector has a value at the endpoint
466
467 Eigen::MatrixXd X(ocp.Nx, N + 1);
468 X = nlp_sol.x.segment(xvar_B, xvar_L).reshaped(ocp.Nx, xvar_L / ocp.Nx);
469
470 auto xfun = [t0 = t0, tf = tf, mesh = mesh, X = std::move(X)](double t) -> Eigen::Vector<double, Nx> {
471 return mesh.template eval<Eigen::Vector<double, Nx>>((t - t0) / (tf - t0), X.colwise(), 0, true);
472 };
473
474 // for these we repeat last point since there are no values for endpoint
475
476 Eigen::MatrixXd U(ocp.Nu, N);
477 U = nlp_sol.x.segment(uvar_B, uvar_L).reshaped(ocp.Nu, uvar_L / ocp.Nu);
478
479 auto ufun = [t0 = t0, tf = tf, mesh = mesh, U = std::move(U)](double t) -> Eigen::Vector<double, Nu> {
480 return mesh.template eval<Eigen::Vector<double, Nu>>((t - t0) / (tf - t0), U.colwise(), 0, false);
481 };
482
483 Eigen::MatrixXd Ldyn(ocp.Nx, N);
484 Ldyn = nlp_sol.lambda.segment(dcon_B, dcon_L).reshaped(ocp.Nx, dcon_L / ocp.Nx);
485
486 auto ldfun = [t0 = t0, tf = tf, mesh = mesh, Ldyn = std::move(Ldyn)](double t) -> Eigen::Vector<double, Nx> {
487 return mesh.template eval<Eigen::Vector<double, Nx>>((t - t0) / (tf - t0), Ldyn.colwise(), 0, false);
488 };
489
490 Eigen::MatrixXd Lcr(ocp.Ncr, N);
491 Lcr = nlp_sol.lambda.segment(crcon_B, crcon_L).reshaped(ocp.Ncr, crcon_L / ocp.Ncr);
492
493 auto lcrfun = [t0 = t0, tf = tf, mesh = mesh, Lcr = std::move(Lcr)](double t) -> Eigen::Vector<double, Ncr> {
494 return mesh.template eval<Eigen::Vector<double, Ncr>>((t - t0) / (tf - t0), Lcr.colwise(), 0, false);
495 };
496
498 .t0 = t0,
499 .tf = tf,
500 .Q = std::move(Q),
501 .u = std::move(ufun),
502 .x = std::move(xfun),
503 .lambda_q = nlp_sol.lambda.segment(qcon_B, qcon_L),
504 .lambda_ce = nlp_sol.lambda.segment(cecon_B, cecon_L),
505 .lambda_dyn = std::move(ldfun),
506 .lambda_cr = std::move(lcrfun),
507 };
508}
509
515NLPSolution ocpsol_to_nlpsol(const FlatOCPType auto & ocp, const MeshType auto & mesh, const auto & ocpsol)
516{
517 const auto N = mesh.N_colloc();
518
519 const auto [var_beg, var_len, con_beg, con_len] = detail::ocp_nlp_structure(ocp, mesh);
520
521 const auto [tfvar_B, qvar_B, xvar_B, uvar_B, n] = var_beg;
522 const auto [tfvar_L, qvar_L, xvar_L, uvar_L] = var_len;
523
524 const auto [dcon_B, qcon_B, crcon_B, cecon_B, m] = con_beg;
525 const auto [dcon_L, qcon_L, crcon_L, cecon_L] = con_len;
526
527 const double t0 = 0;
528 const double tf = ocpsol.tf;
529
530 Eigen::VectorXd x(n), lambda(m);
531
532 x(tfvar_B) = ocpsol.tf;
533 x.segment(qvar_B, qvar_L) = ocpsol.Q;
534
535 lambda.segment(qcon_B, qcon_L) = ocpsol.lambda_q;
536 lambda.segment(cecon_B, cecon_L) = ocpsol.lambda_ce;
537
538 for (const auto & [i, tau] : utils::zip(std::views::iota(0u), mesh.all_nodes())) {
539 x.segment(xvar_B + i * ocp.Nx, ocp.Nx) = ocpsol.x(t0 + tau * (tf - t0));
540 if (i < N) {
541 x.segment(uvar_B + i * ocp.Nu, ocp.Nu) = ocpsol.u(t0 + tau * (tf - t0));
542 lambda.segment(dcon_B + i * ocp.Nx, ocp.Nx) = ocpsol.lambda_dyn(t0 + tau * (tf - t0));
543 lambda.segment(crcon_B + i * ocp.Ncr, ocp.Ncr) = ocpsol.lambda_cr(t0 + tau * (tf - t0));
544 }
545 }
546
547 return {
548 .status = NLPSolution::Status::Unknown,
549 .x = std::move(x),
550 .zl = Eigen::VectorXd::Zero(n),
551 .zu = Eigen::VectorXd::Zero(n),
552 .lambda = std::move(lambda),
553 };
554}
555
556} // namespace smooth::feedback
Concept that is true for FlatOCP specializations.
Definition: ocp.hpp:107
MeshType is a specialization of Mesh.
Definition: mesh.hpp:488
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.
Nonlinear program definition.
Optimal control problem definition.
NLPSolution ocpsol_to_nlpsol(const FlatOCPType auto &ocp, const MeshType auto &mesh, const auto &ocpsol)
Convert ocp solution to nonlinear program solution.
Definition: ocp_to_nlp.hpp:515
auto ocp_to_nlp(FlatOCPType auto &&ocp, MeshType auto &&mesh) -> detail::OCPNLP< std::decay_t< decltype(ocp)>, std::decay_t< decltype(mesh)>, DT >
Formulate an OCP as a NLP using collocation on a Mesh.
Definition: ocp_to_nlp.hpp:432
auto nlpsol_to_ocpsol(const FlatOCPType auto &ocp, const MeshType auto &mesh, const NLPSolution &nlp_sol)
Convert nonlinear program solution to ocp solution.
Definition: ocp_to_nlp.hpp:442
void block_add_identity(Eigen::SparseMatrix< double, Options > &dest, Eigen::Index row0, Eigen::Index col0, Eigen::Index n, double scale=1)
Add identity matrix block into sparse matrix.
Definition: sparse.hpp:103
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 a Nonlinear Programming Problem.
Definition: nlp.hpp:70
Eigen::VectorXd lambda
Constraint multipliers.
Definition: nlp.hpp:96
Eigen::VectorXd x
Variable values.
Definition: nlp.hpp:88
Solution to OCP.
Definition: ocp.hpp:115
double t0
Initial and final time.
Definition: ocp.hpp:130