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