Sleipnir C++ API
Loading...
Searching...
No Matches
ocp.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <stdint.h>
6
7#include <algorithm>
8#include <chrono>
9#include <utility>
10
11#include "sleipnir/autodiff/expression_type.hpp"
12#include "sleipnir/autodiff/variable.hpp"
13#include "sleipnir/autodiff/variable_matrix.hpp"
14#include "sleipnir/optimization/ocp/dynamics_type.hpp"
15#include "sleipnir/optimization/ocp/timestep_method.hpp"
16#include "sleipnir/optimization/ocp/transcription_method.hpp"
17#include "sleipnir/optimization/problem.hpp"
18#include "sleipnir/util/assert.hpp"
19#include "sleipnir/util/concepts.hpp"
20#include "sleipnir/util/empty.hpp"
21#include "sleipnir/util/function_ref.hpp"
22#include "sleipnir/util/symbol_exports.hpp"
23
24namespace slp {
25
52template <typename Scalar>
53class OCP : public Problem<Scalar> {
54 public:
72 OCP(int num_states, int num_inputs, std::chrono::duration<Scalar> dt,
73 int num_steps,
75 const VariableMatrix<Scalar>& u)>
76 dynamics,
77 DynamicsType dynamics_type = DynamicsType::EXPLICIT_ODE,
78 TimestepMethod timestep_method = TimestepMethod::FIXED,
79 TranscriptionMethod transcription_method =
80 TranscriptionMethod::DIRECT_TRANSCRIPTION)
81 : OCP{num_states,
82 num_inputs,
83 dt,
84 num_steps,
85 [=]([[maybe_unused]] const VariableMatrix<Scalar>& t,
86 const VariableMatrix<Scalar>& x,
87 const VariableMatrix<Scalar>& u,
88 [[maybe_unused]] const VariableMatrix<Scalar>& dt)
89 -> VariableMatrix<Scalar> { return dynamics(x, u); },
90 dynamics_type,
91 timestep_method,
92 transcription_method} {}
93
111 OCP(int num_states, int num_inputs, std::chrono::duration<Scalar> dt,
112 int num_steps,
114 const Variable<Scalar>& t, const VariableMatrix<Scalar>& x,
115 const VariableMatrix<Scalar>& u, const Variable<Scalar>& dt)>
116 dynamics,
117 DynamicsType dynamics_type = DynamicsType::EXPLICIT_ODE,
118 TimestepMethod timestep_method = TimestepMethod::FIXED,
119 TranscriptionMethod transcription_method =
120 TranscriptionMethod::DIRECT_TRANSCRIPTION)
121 : m_num_steps{num_steps},
122 m_dynamics{std::move(dynamics)},
123 m_dynamics_type{dynamics_type},
124 m_transcription_method{transcription_method} {
125 // u is num_steps + 1 so that the final constraint function evaluation works
126 m_U = this->decision_variable(num_inputs, m_num_steps + 1);
127
128 if (timestep_method == TimestepMethod::FIXED) {
129 m_DT = VariableMatrix<Scalar>{1, m_num_steps + 1};
130 for (int i = 0; i < num_steps + 1; ++i) {
131 m_DT[0, i] = dt.count();
132 }
133 } else if (timestep_method == TimestepMethod::VARIABLE_SINGLE) {
134 Variable single_dt = this->decision_variable();
135 single_dt.set_value(dt.count());
136
137 // Set the member variable matrix to track the decision variable
138 m_DT = VariableMatrix<Scalar>{1, m_num_steps + 1};
139 for (int i = 0; i < num_steps + 1; ++i) {
140 m_DT[0, i] = single_dt;
141 }
142 } else if (timestep_method == TimestepMethod::VARIABLE) {
143 m_DT = this->decision_variable(1, m_num_steps + 1);
144 for (int i = 0; i < num_steps + 1; ++i) {
145 m_DT[0, i].set_value(dt.count());
146 }
147 }
148
149 if (transcription_method == TranscriptionMethod::DIRECT_TRANSCRIPTION) {
150 m_X = this->decision_variable(num_states, m_num_steps + 1);
151 constrain_direct_transcription();
152 } else if (transcription_method ==
153 TranscriptionMethod::DIRECT_COLLOCATION) {
154 m_X = this->decision_variable(num_states, m_num_steps + 1);
155 constrain_direct_collocation();
156 } else if (transcription_method == TranscriptionMethod::SINGLE_SHOOTING) {
157 // In single-shooting the states aren't decision variables, but instead
158 // depend on the input and previous states
159 m_X = VariableMatrix<Scalar>{num_states, m_num_steps + 1};
160 constrain_single_shooting();
161 }
162 }
163
167 template <typename T>
168 requires ScalarLike<T> || MatrixLike<T>
169 void constrain_initial_state(const T& initial_state) {
170 auto x_0 = this->initial_state();
171
172 // Broadcast scalars to the initial state's shape
173 VariableMatrix<Scalar> x_0_value;
174 if constexpr (ScalarLike<T>) {
175 x_0_value = VariableMatrix<Scalar>{detail::empty, x_0.rows(), x_0.cols()};
176 for (auto& elem : x_0_value) {
177 elem = initial_state;
178 }
179 } else {
180 x_0_value = VariableMatrix<Scalar>{initial_state};
181 }
182
183 // In single-shooting, the initial state isn't a decision variable, so set
184 // its value instead if it's constant
185 if (m_transcription_method == TranscriptionMethod::SINGLE_SHOOTING &&
186 std::ranges::all_of(x_0_value, [](const auto& elem) {
187 return elem.type() == ExpressionType::CONSTANT;
188 })) {
189 x_0.set_value(x_0_value.value());
190 } else {
191 this->subject_to(x_0 == x_0_value);
192 }
193 }
194
198 template <typename T>
199 requires ScalarLike<T> || MatrixLike<T>
200 void constrain_final_state(const T& final_state) {
201 this->subject_to(this->final_state() == final_state);
202 }
203
211 const VariableMatrix<Scalar>& u)>
212 callback) {
213 for (int i = 0; i < m_num_steps + 1; ++i) {
214 auto x = X().col(i);
215 auto u = U().col(i);
216 callback(x, u);
217 }
218 }
219
227 const function_ref<
228 void(const Variable<Scalar>& t, const VariableMatrix<Scalar>& x,
229 const VariableMatrix<Scalar>& u, const Variable<Scalar>& dt)>
230 callback) {
231 Variable<Scalar> time{0};
232
233 for (int i = 0; i < m_num_steps + 1; ++i) {
234 auto x = X().col(i);
235 auto u = U().col(i);
236 auto dt = this->dt()[0, i];
237 callback(time, x, u, dt);
238
239 time += dt;
240 }
241 }
242
247 template <typename T>
248 requires ScalarLike<T> || MatrixLike<T>
249 void set_lower_input_bound(const T& lower_bound) {
250 for (int i = 0; i < m_num_steps + 1; ++i) {
251 this->subject_to(U().col(i) >= lower_bound);
252 }
253 }
254
259 template <typename T>
260 requires ScalarLike<T> || MatrixLike<T>
261 void set_upper_input_bound(const T& upper_bound) {
262 for (int i = 0; i < m_num_steps + 1; ++i) {
263 this->subject_to(U().col(i) <= upper_bound);
264 }
265 }
266
270 void set_min_timestep(std::chrono::duration<Scalar> min_timestep) {
271 this->subject_to(dt() >= min_timestep.count());
272 }
273
277 void set_max_timestep(std::chrono::duration<Scalar> max_timestep) {
278 this->subject_to(dt() <= max_timestep.count());
279 }
280
287 VariableMatrix<Scalar>& X() { return m_X; }
288
296 VariableMatrix<Scalar>& U() { return m_U; }
297
305 VariableMatrix<Scalar>& dt() { return m_DT; }
306
311
315 VariableMatrix<Scalar> final_state() { return m_X.col(m_num_steps); }
316
317 private:
318 int m_num_steps;
319
321 const Variable<Scalar>& t, const VariableMatrix<Scalar>& x,
322 const VariableMatrix<Scalar>& u, const Variable<Scalar>& dt)>
323 m_dynamics;
324 DynamicsType m_dynamics_type;
325 TranscriptionMethod m_transcription_method;
326
330
338 template <typename F, typename State, typename Input, typename Time>
339 State rk4(F&& f, State x, Input u, Time t0, Time dt) {
340 auto halfdt = dt * Scalar(0.5);
341 State k1 = f(t0, x, u, dt);
342 State k2 = f(t0 + halfdt, x + k1 * halfdt, u, dt);
343 State k3 = f(t0 + halfdt, x + k2 * halfdt, u, dt);
344 State k4 = f(t0 + dt, x + k3 * dt, u, dt);
345
346 return x + (k1 + k2 * Scalar(2) + k3 * Scalar(2) + k4) * (dt / Scalar(6));
347 }
348
350 void constrain_direct_collocation() {
351 slp_assert(m_dynamics_type == DynamicsType::EXPLICIT_ODE);
352
353 Variable<Scalar> time{0};
354
355 // Derivation at https://mec560sbu.github.io/2016/09/30/direct_collocation/
356 for (int i = 0; i < m_num_steps; ++i) {
357 Variable h = dt()[0, i];
358
359 auto& f = m_dynamics;
360
361 auto t_begin = time;
362 auto t_end = t_begin + h;
363
364 auto x_begin = X().col(i);
365 auto x_end = X().col(i + 1);
366
367 auto u_begin = U().col(i);
368 auto u_end = U().col(i + 1);
369
370 auto xdot_begin = f(t_begin, x_begin, u_begin, h);
371 auto xdot_end = f(t_end, x_end, u_end, h);
372 auto xdot_c = Scalar(-3) / (Scalar(2) * h) * (x_begin - x_end) -
373 Scalar(0.25) * (xdot_begin + xdot_end);
374
375 auto t_c = t_begin + Scalar(0.5) * h;
376 auto x_c = Scalar(0.5) * (x_begin + x_end) +
377 h / Scalar(8) * (xdot_begin - xdot_end);
378 auto u_c = Scalar(0.5) * (u_begin + u_end);
379
380 this->subject_to(xdot_c == f(t_c, x_c, u_c, h));
381
382 time += h;
383 }
384 }
385
387 void constrain_direct_transcription() {
388 Variable<Scalar> time{0};
389
390 for (int i = 0; i < m_num_steps; ++i) {
391 auto x_begin = X().col(i);
392 auto x_end = X().col(i + 1);
393 auto u = U().col(i);
394 Variable dt = this->dt()[0, i];
395
396 if (m_dynamics_type == DynamicsType::EXPLICIT_ODE) {
397 this->subject_to(
398 x_end == rk4<const decltype(m_dynamics)&, VariableMatrix<Scalar>,
399 VariableMatrix<Scalar>, Variable<Scalar>>(
400 m_dynamics, x_begin, u, time, dt));
401 } else if (m_dynamics_type == DynamicsType::DISCRETE) {
402 this->subject_to(x_end == m_dynamics(time, x_begin, u, dt));
403 }
404
405 time += dt;
406 }
407 }
408
410 void constrain_single_shooting() {
411 Variable<Scalar> time{0};
412
413 for (int i = 0; i < m_num_steps; ++i) {
414 auto x_begin = X().col(i);
415 auto x_end = X().col(i + 1);
416 auto u = U().col(i);
417 Variable dt = this->dt()[0, i];
418
419 if (m_dynamics_type == DynamicsType::EXPLICIT_ODE) {
420 x_end = rk4<const decltype(m_dynamics)&, VariableMatrix<Scalar>,
421 VariableMatrix<Scalar>, Variable<Scalar>>(
422 m_dynamics, x_begin, u, time, dt);
423 } else if (m_dynamics_type == DynamicsType::DISCRETE) {
424 x_end = m_dynamics(time, x_begin, u, dt);
425 }
426
427 time += dt;
428 }
429 }
430};
431
432extern template class EXPORT_TEMPLATE_DECLARE(SLEIPNIR_DLLEXPORT) OCP<double>;
433
434} // namespace slp
Definition ocp.hpp:53
void constrain_initial_state(const T &initial_state)
Definition ocp.hpp:169
void constrain_final_state(const T &final_state)
Definition ocp.hpp:200
VariableMatrix< Scalar > & U()
Definition ocp.hpp:296
VariableMatrix< Scalar > initial_state()
Definition ocp.hpp:310
void set_min_timestep(std::chrono::duration< Scalar > min_timestep)
Definition ocp.hpp:270
VariableMatrix< Scalar > & X()
Definition ocp.hpp:287
void set_upper_input_bound(const T &upper_bound)
Definition ocp.hpp:261
OCP(int num_states, int num_inputs, std::chrono::duration< Scalar > dt, int num_steps, function_ref< VariableMatrix< Scalar >(const Variable< Scalar > &t, const VariableMatrix< Scalar > &x, const VariableMatrix< Scalar > &u, const Variable< Scalar > &dt)> dynamics, DynamicsType dynamics_type=DynamicsType::EXPLICIT_ODE, TimestepMethod timestep_method=TimestepMethod::FIXED, TranscriptionMethod transcription_method=TranscriptionMethod::DIRECT_TRANSCRIPTION)
Definition ocp.hpp:111
void set_lower_input_bound(const T &lower_bound)
Definition ocp.hpp:249
void for_each_step(const function_ref< void(const VariableMatrix< Scalar > &x, const VariableMatrix< Scalar > &u)> callback)
Definition ocp.hpp:210
void set_max_timestep(std::chrono::duration< Scalar > max_timestep)
Definition ocp.hpp:277
OCP(int num_states, int num_inputs, std::chrono::duration< Scalar > dt, int num_steps, function_ref< VariableMatrix< Scalar >(const VariableMatrix< Scalar > &x, const VariableMatrix< Scalar > &u)> dynamics, DynamicsType dynamics_type=DynamicsType::EXPLICIT_ODE, TimestepMethod timestep_method=TimestepMethod::FIXED, TranscriptionMethod transcription_method=TranscriptionMethod::DIRECT_TRANSCRIPTION)
Definition ocp.hpp:72
VariableMatrix< Scalar > final_state()
Definition ocp.hpp:315
VariableMatrix< Scalar > & dt()
Definition ocp.hpp:305
void for_each_step(const function_ref< void(const Variable< Scalar > &t, const VariableMatrix< Scalar > &x, const VariableMatrix< Scalar > &u, const Variable< Scalar > &dt)> callback)
Definition ocp.hpp:226
Definition problem.hpp:72
Definition variable_matrix.hpp:37
VariableBlock< VariableMatrix > col(int col)
Definition variable_matrix.hpp:486
Scalar value(int row, int col)
Definition variable_matrix.hpp:982
int rows() const
Definition variable_matrix.hpp:970
void set_value(const Eigen::MatrixBase< Derived > &values)
Definition variable_matrix.hpp:295
Definition variable.hpp:56
void set_value(Scalar value)
Definition variable.hpp:126
Definition function_ref.hpp:13
Definition concepts.hpp:18
Definition concepts.hpp:24