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