WPILibC++ 2027.0.0-alpha-3
Loading...
Searching...
No Matches
ocp.hpp
Go to the documentation of this file.
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <stdint.h>
6
7#include <chrono>
8#include <utility>
9
19
20namespace slp {
21
22/**
23 * This class allows the user to pose and solve a constrained optimal control
24 * problem (OCP) in a variety of ways.
25 *
26 * The system is transcripted by one of three methods (direct transcription,
27 * direct collocation, or single-shooting) and additional constraints can be
28 * added.
29 *
30 * In direct transcription, each state is a decision variable constrained to the
31 * integrated dynamics of the previous state. In direct collocation, the
32 * trajectory is modeled as a series of cubic polynomials where the centerpoint
33 * slope is constrained. In single-shooting, states depend explicitly as a
34 * function of all previous states and all previous inputs.
35 *
36 * Explicit ODEs are integrated using RK4.
37 *
38 * For explicit ODEs, the function must be in the form dx/dt = f(t, x, u).
39 * For discrete state transition functions, the function must be in the form
40 * xₖ₊₁ = f(t, xₖ, uₖ).
41 *
42 * Direct collocation requires an explicit ODE. Direct transcription and
43 * single-shooting can use either an ODE or state transition function.
44 *
45 * https://underactuated.mit.edu/trajopt.html goes into more detail on each
46 * transcription method.
47 */
49 public:
50 /**
51 * Build an optimization problem using a system evolution function (explicit
52 * ODE or discrete state transition function).
53 *
54 * @param num_states The number of system states.
55 * @param num_inputs The number of system inputs.
56 * @param dt The timestep for fixed-step integration.
57 * @param num_steps The number of control points.
58 * @param dynamics Function representing an explicit or implicit ODE, or a
59 * discrete state transition function.
60 * - Explicit: dx/dt = f(x, u, *)
61 * - Implicit: f([x dx/dt]', u, *) = 0
62 * - State transition: xₖ₊₁ = f(xₖ, uₖ)
63 * @param dynamics_type The type of system evolution function.
64 * @param timestep_method The timestep method.
65 * @param transcription_method The transcription method.
66 */
67 OCP(int num_states, int num_inputs, std::chrono::duration<double> dt,
68 int num_steps,
70 const VariableMatrix& u)>
71 dynamics,
72 DynamicsType dynamics_type = DynamicsType::EXPLICIT_ODE,
73 TimestepMethod timestep_method = TimestepMethod::FIXED,
74 TranscriptionMethod transcription_method =
75 TranscriptionMethod::DIRECT_TRANSCRIPTION)
76 : OCP{num_states,
77 num_inputs,
78 dt,
79 num_steps,
80 [=]([[maybe_unused]] const VariableMatrix& t,
81 const VariableMatrix& x, const VariableMatrix& u,
82 [[maybe_unused]]
83 const VariableMatrix& dt) -> VariableMatrix {
84 return dynamics(x, u);
85 },
86 dynamics_type,
87 timestep_method,
88 transcription_method} {}
89
90 /**
91 * Build an optimization problem using a system evolution function (explicit
92 * ODE or discrete state transition function).
93 *
94 * @param num_states The number of system states.
95 * @param num_inputs The number of system inputs.
96 * @param dt The timestep for fixed-step integration.
97 * @param num_steps The number of control points.
98 * @param dynamics Function representing an explicit or implicit ODE, or a
99 * discrete state transition function.
100 * - Explicit: dx/dt = f(t, x, u, *)
101 * - Implicit: f(t, [x dx/dt]', u, *) = 0
102 * - State transition: xₖ₊₁ = f(t, xₖ, uₖ, dt)
103 * @param dynamics_type The type of system evolution function.
104 * @param timestep_method The timestep method.
105 * @param transcription_method The transcription method.
106 */
107 OCP(int num_states, int num_inputs, std::chrono::duration<double> dt,
108 int num_steps,
110 const VariableMatrix& u, const Variable& dt)>
111 dynamics,
112 DynamicsType dynamics_type = DynamicsType::EXPLICIT_ODE,
113 TimestepMethod timestep_method = TimestepMethod::FIXED,
114 TranscriptionMethod transcription_method =
115 TranscriptionMethod::DIRECT_TRANSCRIPTION)
116 : m_num_steps{num_steps},
117 m_dynamics{std::move(dynamics)},
118 m_dynamics_type{dynamics_type} {
119 // u is num_steps + 1 so that the final constraint function evaluation works
120 m_U = decision_variable(num_inputs, m_num_steps + 1);
121
122 if (timestep_method == TimestepMethod::FIXED) {
123 m_DT = VariableMatrix{1, m_num_steps + 1};
124 for (int i = 0; i < num_steps + 1; ++i) {
125 m_DT(0, i) = dt.count();
126 }
127 } else if (timestep_method == TimestepMethod::VARIABLE_SINGLE) {
128 Variable single_dt = decision_variable();
129 single_dt.set_value(dt.count());
130
131 // Set the member variable matrix to track the decision variable
132 m_DT = VariableMatrix{1, m_num_steps + 1};
133 for (int i = 0; i < num_steps + 1; ++i) {
134 m_DT(0, i) = single_dt;
135 }
136 } else if (timestep_method == TimestepMethod::VARIABLE) {
137 m_DT = decision_variable(1, m_num_steps + 1);
138 for (int i = 0; i < num_steps + 1; ++i) {
139 m_DT(0, i).set_value(dt.count());
140 }
141 }
142
143 if (transcription_method == TranscriptionMethod::DIRECT_TRANSCRIPTION) {
144 m_X = decision_variable(num_states, m_num_steps + 1);
145 constrain_direct_transcription();
146 } else if (transcription_method ==
147 TranscriptionMethod::DIRECT_COLLOCATION) {
148 m_X = decision_variable(num_states, m_num_steps + 1);
149 constrain_direct_collocation();
150 } else if (transcription_method == TranscriptionMethod::SINGLE_SHOOTING) {
151 // In single-shooting the states aren't decision variables, but instead
152 // depend on the input and previous states
153 m_X = VariableMatrix{num_states, m_num_steps + 1};
154 constrain_single_shooting();
155 }
156 }
157
158 /**
159 * Utility function to constrain the initial state.
160 *
161 * @param initial_state the initial state to constrain to.
162 */
163 template <typename T>
164 requires ScalarLike<T> || MatrixLike<T>
165 void constrain_initial_state(const T& initial_state) {
166 subject_to(this->initial_state() == initial_state);
167 }
168
169 /**
170 * Utility function to constrain the final state.
171 *
172 * @param final_state the final state to constrain to.
173 */
174 template <typename T>
175 requires ScalarLike<T> || MatrixLike<T>
176 void constrain_final_state(const T& final_state) {
177 subject_to(this->final_state() == final_state);
178 }
179
180 /**
181 * Set the constraint evaluation function. This function is called
182 * `num_steps+1` times, with the corresponding state and input
183 * VariableMatrices.
184 *
185 * @param callback The callback f(x, u) where x is the state and u is the
186 * input vector.
187 */
189 const function_ref<void(const VariableMatrix& x, const VariableMatrix& u)>
190 callback) {
191 for (int i = 0; i < m_num_steps + 1; ++i) {
192 auto x = X().col(i);
193 auto u = U().col(i);
194 callback(x, u);
195 }
196 }
197
198 /**
199 * Set the constraint evaluation function. This function is called
200 * `num_steps+1` times, with the corresponding state and input
201 * VariableMatrices.
202 *
203 * @param callback The callback f(t, x, u, dt) where t is time, x is the state
204 * vector, u is the input vector, and dt is the timestep duration.
205 */
207 const function_ref<void(const Variable& t, const VariableMatrix& x,
208 const VariableMatrix& u, const Variable& dt)>
209 callback) {
210 Variable time = 0.0;
211
212 for (int i = 0; i < m_num_steps + 1; ++i) {
213 auto x = X().col(i);
214 auto u = U().col(i);
215 auto dt = this->dt()(0, i);
216 callback(time, x, u, dt);
217
218 time += dt;
219 }
220 }
221
222 /**
223 * Convenience function to set a lower bound on the input.
224 *
225 * @param lower_bound The lower bound that inputs must always be above. Must
226 * be shaped (num_inputs)x1.
227 */
228 template <typename T>
229 requires ScalarLike<T> || MatrixLike<T>
230 void set_lower_input_bound(const T& lower_bound) {
231 for (int i = 0; i < m_num_steps + 1; ++i) {
232 subject_to(U().col(i) >= lower_bound);
233 }
234 }
235
236 /**
237 * Convenience function to set an upper bound on the input.
238 *
239 * @param upper_bound The upper bound that inputs must always be below. Must
240 * be shaped (num_inputs)x1.
241 */
242 template <typename T>
243 requires ScalarLike<T> || MatrixLike<T>
244 void set_upper_input_bound(const T& upper_bound) {
245 for (int i = 0; i < m_num_steps + 1; ++i) {
246 subject_to(U().col(i) <= upper_bound);
247 }
248 }
249
250 /**
251 * Convenience function to set a lower bound on the timestep.
252 *
253 * @param min_timestep The minimum timestep.
254 */
255 void set_min_timestep(std::chrono::duration<double> min_timestep) {
256 subject_to(dt() >= min_timestep.count());
257 }
258
259 /**
260 * Convenience function to set an upper bound on the timestep.
261 *
262 * @param max_timestep The maximum timestep.
263 */
264 void set_max_timestep(std::chrono::duration<double> max_timestep) {
265 subject_to(dt() <= max_timestep.count());
266 }
267
268 /**
269 * Get the state variables. After the problem is solved, this will contain the
270 * optimized trajectory.
271 *
272 * Shaped (num_states)x(num_steps+1).
273 *
274 * @return The state variable matrix.
275 */
276 VariableMatrix& X() { return m_X; }
277
278 /**
279 * Get the input variables. After the problem is solved, this will contain the
280 * inputs corresponding to the optimized trajectory.
281 *
282 * Shaped (num_inputs)x(num_steps+1), although the last input step is unused
283 * in the trajectory.
284 *
285 * @return The input variable matrix.
286 */
287 VariableMatrix& U() { return m_U; }
288
289 /**
290 * Get the timestep variables. After the problem is solved, this will contain
291 * the timesteps corresponding to the optimized trajectory.
292 *
293 * Shaped 1x(num_steps+1), although the last timestep is unused in
294 * the trajectory.
295 *
296 * @return The timestep variable matrix.
297 */
298 VariableMatrix& dt() { return m_DT; }
299
300 /**
301 * Convenience function to get the initial state in the trajectory.
302 *
303 * @return The initial state of the trajectory.
304 */
305 VariableMatrix initial_state() { return m_X.col(0); }
306
307 /**
308 * Convenience function to get the final state in the trajectory.
309 *
310 * @return The final state of the trajectory.
311 */
312 VariableMatrix final_state() { return m_X.col(m_num_steps); }
313
314 private:
315 int m_num_steps;
316
318 const VariableMatrix& u, const Variable& dt)>
319 m_dynamics;
320 DynamicsType m_dynamics_type;
321
322 VariableMatrix m_X;
323 VariableMatrix m_U;
324 VariableMatrix m_DT;
325
326 /**
327 * Performs 4th order Runge-Kutta integration of dx/dt = f(t, x, u) for dt.
328 *
329 * @param f The function to integrate. It must take two arguments x and u.
330 * @param x The initial value of x.
331 * @param u The value u held constant over the integration period.
332 * @param t0 The initial time.
333 * @param dt The time over which to integrate.
334 */
335 template <typename F, typename State, typename Input, typename Time>
336 State rk4(F&& f, State x, Input u, Time t0, Time dt) {
337 auto halfdt = dt * 0.5;
338 State k1 = f(t0, x, u, dt);
339 State k2 = f(t0 + halfdt, x + k1 * halfdt, u, dt);
340 State k3 = f(t0 + halfdt, x + k2 * halfdt, u, dt);
341 State k4 = f(t0 + dt, x + k3 * dt, u, dt);
342
343 return x + (k1 + k2 * 2.0 + k3 * 2.0 + k4) * (dt / 6.0);
344 }
345
346 /**
347 * Apply direct collocation dynamics constraints.
348 */
349 void constrain_direct_collocation() {
350 slp_assert(m_dynamics_type == DynamicsType::EXPLICIT_ODE);
351
352 Variable time = 0.0;
353
354 // Derivation at https://mec560sbu.github.io/2016/09/30/direct_collocation/
355 for (int i = 0; i < m_num_steps; ++i) {
356 Variable h = dt()(0, i);
357
358 auto& f = m_dynamics;
359
360 auto t_begin = time;
361 auto t_end = t_begin + h;
362
363 auto x_begin = X().col(i);
364 auto x_end = X().col(i + 1);
365
366 auto u_begin = U().col(i);
367 auto u_end = U().col(i + 1);
368
369 auto xdot_begin = f(t_begin, x_begin, u_begin, h);
370 auto xdot_end = f(t_end, x_end, u_end, h);
371 auto xdot_c =
372 -3 / (2 * h) * (x_begin - x_end) - 0.25 * (xdot_begin + xdot_end);
373
374 auto t_c = t_begin + 0.5 * h;
375 auto x_c = 0.5 * (x_begin + x_end) + h / 8 * (xdot_begin - xdot_end);
376 auto u_c = 0.5 * (u_begin + u_end);
377
378 subject_to(xdot_c == f(t_c, x_c, u_c, h));
379
380 time += h;
381 }
382 }
383
384 /**
385 * Apply direct transcription dynamics constraints.
386 */
387 void constrain_direct_transcription() {
388 Variable time = 0.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 subject_to(x_end == rk4<const decltype(m_dynamics)&, VariableMatrix,
398 VariableMatrix, Variable>(m_dynamics, x_begin,
399 u, time, dt));
400 } else if (m_dynamics_type == DynamicsType::DISCRETE) {
401 subject_to(x_end == m_dynamics(time, x_begin, u, dt));
402 }
403
404 time += dt;
405 }
406 }
407
408 /**
409 * Apply single shooting dynamics constraints.
410 */
411 void constrain_single_shooting() {
412 Variable time = 0.0;
413
414 for (int i = 0; i < m_num_steps; ++i) {
415 auto x_begin = X().col(i);
416 auto x_end = X().col(i + 1);
417 auto u = U().col(i);
418 Variable dt = this->dt()(0, i);
419
420 if (m_dynamics_type == DynamicsType::EXPLICIT_ODE) {
421 x_end = rk4<const decltype(m_dynamics)&, VariableMatrix, VariableMatrix,
422 Variable>(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
432} // namespace slp
#define slp_assert(condition)
Abort in C++.
Definition assert.hpp:27
This class allows the user to pose and solve a constrained optimal control problem (OCP) in a variety...
Definition ocp.hpp:48
VariableMatrix & U()
Get the input variables.
Definition ocp.hpp:287
void constrain_initial_state(const T &initial_state)
Utility function to constrain the initial state.
Definition ocp.hpp:165
void for_each_step(const function_ref< void(const Variable &t, const VariableMatrix &x, const VariableMatrix &u, const Variable &dt)> callback)
Set the constraint evaluation function.
Definition ocp.hpp:206
OCP(int num_states, int num_inputs, std::chrono::duration< double > dt, int num_steps, function_ref< VariableMatrix(const VariableMatrix &x, const VariableMatrix &u)> dynamics, DynamicsType dynamics_type=DynamicsType::EXPLICIT_ODE, TimestepMethod timestep_method=TimestepMethod::FIXED, TranscriptionMethod transcription_method=TranscriptionMethod::DIRECT_TRANSCRIPTION)
Build an optimization problem using a system evolution function (explicit ODE or discrete state trans...
Definition ocp.hpp:67
VariableMatrix & dt()
Get the timestep variables.
Definition ocp.hpp:298
VariableMatrix & X()
Get the state variables.
Definition ocp.hpp:276
void set_min_timestep(std::chrono::duration< double > min_timestep)
Convenience function to set a lower bound on the timestep.
Definition ocp.hpp:255
VariableMatrix initial_state()
Convenience function to get the initial state in the trajectory.
Definition ocp.hpp:305
void set_upper_input_bound(const T &upper_bound)
Convenience function to set an upper bound on the input.
Definition ocp.hpp:244
void for_each_step(const function_ref< void(const VariableMatrix &x, const VariableMatrix &u)> callback)
Set the constraint evaluation function.
Definition ocp.hpp:188
void set_lower_input_bound(const T &lower_bound)
Convenience function to set a lower bound on the input.
Definition ocp.hpp:230
VariableMatrix final_state()
Convenience function to get the final state in the trajectory.
Definition ocp.hpp:312
void set_max_timestep(std::chrono::duration< double > max_timestep)
Convenience function to set an upper bound on the timestep.
Definition ocp.hpp:264
void constrain_final_state(const T &final_state)
Utility function to constrain the final state.
Definition ocp.hpp:176
OCP(int num_states, int num_inputs, std::chrono::duration< double > dt, int num_steps, function_ref< VariableMatrix(const Variable &t, const VariableMatrix &x, const VariableMatrix &u, const Variable &dt)> dynamics, DynamicsType dynamics_type=DynamicsType::EXPLICIT_ODE, TimestepMethod timestep_method=TimestepMethod::FIXED, TranscriptionMethod transcription_method=TranscriptionMethod::DIRECT_TRANSCRIPTION)
Build an optimization problem using a system evolution function (explicit ODE or discrete state trans...
Definition ocp.hpp:107
This class allows the user to pose a constrained nonlinear optimization problem in natural mathematic...
Definition problem.hpp:47
An autodiff variable pointing to an expression node.
Definition variable.hpp:40
void set_value(double value)
Sets Variable's internal value.
Definition variable.hpp:100
A matrix of autodiff variables.
Definition variable_matrix.hpp:29
VariableBlock< VariableMatrix > col(int col)
Returns a column slice of the variable matrix.
Definition variable_matrix.hpp:546
Definition function_ref.hpp:13
Definition concepts.hpp:40
Definition concepts.hpp:13
Definition expression_graph.hpp:11
TimestepMethod
Enum describing the type of system timestep.
Definition timestep_method.hpp:12
DynamicsType
Enum describing a type of system dynamics constraints.
Definition dynamics_type.hpp:12
TranscriptionMethod
Enum describing an OCP transcription method.
Definition transcription_method.hpp:12
Definition PointerIntPair.h:280
#define SLEIPNIR_DLLEXPORT
Definition symbol_exports.hpp:34