WPILibC++ 2025.1.1
Loading...
Searching...
No Matches
OCPSolver.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 sleipnir {
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 kFixed,
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 numStates The number of system states.
110 * @param numInputs The number of system inputs.
111 * @param dt The timestep for fixed-step integration.
112 * @param numSteps 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 dynamicsType The type of system evolution function.
119 * @param timestepMethod The timestep method.
120 * @param method The transcription method.
121 */
123 int numStates, int numInputs, std::chrono::duration<double> dt,
124 int numSteps,
126 const VariableMatrix& u)>
127 dynamics,
128 DynamicsType dynamicsType = DynamicsType::kExplicitODE,
129 TimestepMethod timestepMethod = TimestepMethod::kFixed,
130 TranscriptionMethod method = TranscriptionMethod::kDirectTranscription)
131 : OCPSolver{numStates,
132 numInputs,
133 dt,
134 numSteps,
135 [=]([[maybe_unused]] const VariableMatrix& t,
136 const VariableMatrix& x, const VariableMatrix& u,
137 [[maybe_unused]]
138 const VariableMatrix& dt) -> VariableMatrix {
139 return dynamics(x, u);
140 },
141 dynamicsType,
142 timestepMethod,
143 method} {}
144
145 /**
146 * Build an optimization problem using a system evolution function (explicit
147 * ODE or discrete state transition function).
148 *
149 * @param numStates The number of system states.
150 * @param numInputs The number of system inputs.
151 * @param dt The timestep for fixed-step integration.
152 * @param numSteps The number of control points.
153 * @param dynamics Function representing an explicit or implicit ODE, or a
154 * discrete state transition function.
155 * - Explicit: dx/dt = f(t, x, u, *)
156 * - Implicit: f(t, [x dx/dt]', u, *) = 0
157 * - State transition: xₖ₊₁ = f(t, xₖ, uₖ, dt)
158 * @param dynamicsType The type of system evolution function.
159 * @param timestepMethod The timestep method.
160 * @param method The transcription method.
161 */
163 int numStates, int numInputs, std::chrono::duration<double> dt,
164 int numSteps,
166 const VariableMatrix& u, const Variable& dt)>
167 dynamics,
168 DynamicsType dynamicsType = DynamicsType::kExplicitODE,
169 TimestepMethod timestepMethod = TimestepMethod::kFixed,
170 TranscriptionMethod method = TranscriptionMethod::kDirectTranscription)
171 : m_numStates{numStates},
172 m_numInputs{numInputs},
173 m_dt{dt},
174 m_numSteps{numSteps},
175 m_transcriptionMethod{method},
176 m_dynamicsType{dynamicsType},
177 m_dynamicsFunction{std::move(dynamics)},
178 m_timestepMethod{timestepMethod} {
179 // u is numSteps + 1 so that the final constraintFunction evaluation works
180 m_U = DecisionVariable(m_numInputs, m_numSteps + 1);
181
182 if (m_timestepMethod == TimestepMethod::kFixed) {
183 m_DT = VariableMatrix{1, m_numSteps + 1};
184 for (int i = 0; i < numSteps + 1; ++i) {
185 m_DT(0, i) = m_dt.count();
186 }
187 } else if (m_timestepMethod == TimestepMethod::kVariableSingle) {
188 Variable DT = DecisionVariable();
189 DT.SetValue(m_dt.count());
190
191 // Set the member variable matrix to track the decision variable
192 m_DT = VariableMatrix{1, m_numSteps + 1};
193 for (int i = 0; i < numSteps + 1; ++i) {
194 m_DT(0, i) = DT;
195 }
196 } else if (m_timestepMethod == TimestepMethod::kVariable) {
197 m_DT = DecisionVariable(1, m_numSteps + 1);
198 for (int i = 0; i < numSteps + 1; ++i) {
199 m_DT(0, i).SetValue(m_dt.count());
200 }
201 }
202
203 if (m_transcriptionMethod == TranscriptionMethod::kDirectTranscription) {
204 m_X = DecisionVariable(m_numStates, m_numSteps + 1);
205 ConstrainDirectTranscription();
206 } else if (m_transcriptionMethod ==
207 TranscriptionMethod::kDirectCollocation) {
208 m_X = DecisionVariable(m_numStates, m_numSteps + 1);
209 ConstrainDirectCollocation();
210 } else if (m_transcriptionMethod == TranscriptionMethod::kSingleShooting) {
211 // In single-shooting the states aren't decision variables, but instead
212 // depend on the input and previous states
213 m_X = VariableMatrix{m_numStates, m_numSteps + 1};
214 ConstrainSingleShooting();
215 }
216 }
217
218 /**
219 * Utility function to constrain the initial state.
220 *
221 * @param initialState the initial state to constrain to.
222 */
223 template <typename T>
224 requires ScalarLike<T> || MatrixLike<T>
225 void ConstrainInitialState(const T& initialState) {
226 SubjectTo(InitialState() == initialState);
227 }
228
229 /**
230 * Utility function to constrain the final state.
231 *
232 * @param finalState the final state to constrain to.
233 */
234 template <typename T>
235 requires ScalarLike<T> || MatrixLike<T>
236 void ConstrainFinalState(const T& finalState) {
237 SubjectTo(FinalState() == finalState);
238 }
239
240 /**
241 * Set the constraint evaluation function. This function is called
242 * `numSteps+1` times, with the corresponding state and input
243 * VariableMatrices.
244 *
245 * @param callback The callback f(x, u) where x is the state and u is the
246 * input vector.
247 */
249 const function_ref<void(const VariableMatrix& x, const VariableMatrix& u)>
250 callback) {
251 for (int i = 0; i < m_numSteps + 1; ++i) {
252 auto x = X().Col(i);
253 auto u = U().Col(i);
254 callback(x, u);
255 }
256 }
257
258 /**
259 * Set the constraint evaluation function. This function is called
260 * `numSteps+1` times, with the corresponding state and input
261 * VariableMatrices.
262 *
263 * @param callback The callback f(t, x, u, dt) where t is time, x is the state
264 * vector, u is the input vector, and dt is the timestep duration.
265 */
267 const function_ref<void(const Variable& t, const VariableMatrix& x,
268 const VariableMatrix& u, const Variable& dt)>
269 callback) {
270 Variable time = 0.0;
271
272 for (int i = 0; i < m_numSteps + 1; ++i) {
273 auto x = X().Col(i);
274 auto u = U().Col(i);
275 auto dt = DT()(0, i);
276 callback(time, x, u, dt);
277
278 time += dt;
279 }
280 }
281
282 /**
283 * Convenience function to set a lower bound on the input.
284 *
285 * @param lowerBound The lower bound that inputs must always be above. Must be
286 * shaped (numInputs)x1.
287 */
288 template <typename T>
289 requires ScalarLike<T> || MatrixLike<T>
290 void SetLowerInputBound(const T& lowerBound) {
291 for (int i = 0; i < m_numSteps + 1; ++i) {
292 SubjectTo(U().Col(i) >= lowerBound);
293 }
294 }
295
296 /**
297 * Convenience function to set an upper bound on the input.
298 *
299 * @param upperBound The upper bound that inputs must always be below. Must be
300 * shaped (numInputs)x1.
301 */
302 template <typename T>
303 requires ScalarLike<T> || MatrixLike<T>
304 void SetUpperInputBound(const T& upperBound) {
305 for (int i = 0; i < m_numSteps + 1; ++i) {
306 SubjectTo(U().Col(i) <= upperBound);
307 }
308 }
309
310 /**
311 * Convenience function to set a lower bound on the timestep.
312 *
313 * @param minTimestep The minimum timestep.
314 */
315 void SetMinTimestep(std::chrono::duration<double> minTimestep) {
316 SubjectTo(DT() >= minTimestep.count());
317 }
318
319 /**
320 * Convenience function to set an upper bound on the timestep.
321 *
322 * @param maxTimestep The maximum timestep.
323 */
324 void SetMaxTimestep(std::chrono::duration<double> maxTimestep) {
325 SubjectTo(DT() <= maxTimestep.count());
326 }
327
328 /**
329 * Get the state variables. After the problem is solved, this will contain the
330 * optimized trajectory.
331 *
332 * Shaped (numStates)x(numSteps+1).
333 *
334 * @returns The state variable matrix.
335 */
336 VariableMatrix& X() { return m_X; };
337
338 /**
339 * Get the input variables. After the problem is solved, this will contain the
340 * inputs corresponding to the optimized trajectory.
341 *
342 * Shaped (numInputs)x(numSteps+1), although the last input step is unused in
343 * the trajectory.
344 *
345 * @returns The input variable matrix.
346 */
347 VariableMatrix& U() { return m_U; };
348
349 /**
350 * Get the timestep variables. After the problem is solved, this will contain
351 * the timesteps corresponding to the optimized trajectory.
352 *
353 * Shaped 1x(numSteps+1), although the last timestep is unused in
354 * the trajectory.
355 *
356 * @returns The timestep variable matrix.
357 */
358 VariableMatrix& DT() { return m_DT; };
359
360 /**
361 * Convenience function to get the initial state in the trajectory.
362 *
363 * @returns The initial state of the trajectory.
364 */
365 VariableMatrix InitialState() { return m_X.Col(0); }
366
367 /**
368 * Convenience function to get the final state in the trajectory.
369 *
370 * @returns The final state of the trajectory.
371 */
372 VariableMatrix FinalState() { return m_X.Col(m_numSteps); }
373
374 private:
375 void ConstrainDirectCollocation() {
376 Assert(m_dynamicsType == DynamicsType::kExplicitODE);
377
378 Variable time = 0.0;
379
380 // Derivation at https://mec560sbu.github.io/2016/09/30/direct_collocation/
381 for (int i = 0; i < m_numSteps; ++i) {
382 Variable h = DT()(0, i);
383
384 auto& f = m_dynamicsFunction;
385
386 auto t_begin = time;
387 auto t_end = t_begin + h;
388
389 auto x_begin = X().Col(i);
390 auto x_end = X().Col(i + 1);
391
392 auto u_begin = U().Col(i);
393 auto u_end = U().Col(i + 1);
394
395 auto xdot_begin = f(t_begin, x_begin, u_begin, h);
396 auto xdot_end = f(t_end, x_end, u_end, h);
397 auto xdot_c =
398 -3 / (2 * h) * (x_begin - x_end) - 0.25 * (xdot_begin + xdot_end);
399
400 auto t_c = t_begin + 0.5 * h;
401 auto x_c = 0.5 * (x_begin + x_end) + h / 8 * (xdot_begin - xdot_end);
402 auto u_c = 0.5 * (u_begin + u_end);
403
404 SubjectTo(xdot_c == f(t_c, x_c, u_c, h));
405
406 time += h;
407 }
408 }
409
410 void ConstrainDirectTranscription() {
411 Variable time = 0.0;
412
413 for (int i = 0; i < m_numSteps; ++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 = DT()(0, i);
418
419 if (m_dynamicsType == DynamicsType::kExplicitODE) {
420 SubjectTo(x_end == RK4<const decltype(m_dynamicsFunction)&,
421 VariableMatrix, VariableMatrix, Variable>(
422 m_dynamicsFunction, x_begin, u, time, dt));
423 } else if (m_dynamicsType == DynamicsType::kDiscrete) {
424 SubjectTo(x_end == m_dynamicsFunction(time, x_begin, u, dt));
425 }
426
427 time += dt;
428 }
429 }
430
431 void ConstrainSingleShooting() {
432 Variable time = 0.0;
433
434 for (int i = 0; i < m_numSteps; ++i) {
435 auto x_begin = X().Col(i);
436 auto x_end = X().Col(i + 1);
437 auto u = U().Col(i);
438 Variable dt = DT()(0, i);
439
440 if (m_dynamicsType == DynamicsType::kExplicitODE) {
441 x_end = RK4<const decltype(m_dynamicsFunction)&, VariableMatrix,
442 VariableMatrix, Variable>(m_dynamicsFunction, x_begin, u,
443 time, dt);
444 } else if (m_dynamicsType == DynamicsType::kDiscrete) {
445 x_end = m_dynamicsFunction(time, x_begin, u, dt);
446 }
447
448 time += dt;
449 }
450 }
451
452 int m_numStates;
453 int m_numInputs;
454 std::chrono::duration<double> m_dt;
455 int m_numSteps;
456 TranscriptionMethod m_transcriptionMethod;
457
458 DynamicsType m_dynamicsType;
459
460 function_ref<VariableMatrix(const Variable& t, const VariableMatrix& x,
461 const VariableMatrix& u, const Variable& dt)>
462 m_dynamicsFunction;
463
464 TimestepMethod m_timestepMethod;
465
466 VariableMatrix m_X;
467 VariableMatrix m_U;
468 VariableMatrix m_DT;
469};
470
471} // namespace sleipnir
#define SLEIPNIR_DLLEXPORT
Definition SymbolExports.hpp:34
This class allows the user to pose and solve a constrained optimal control problem (OCP) in a variety...
Definition OCPSolver.hpp:103
void ConstrainInitialState(const T &initialState)
Utility function to constrain the initial state.
Definition OCPSolver.hpp:225
void SetMinTimestep(std::chrono::duration< double > minTimestep)
Convenience function to set a lower bound on the timestep.
Definition OCPSolver.hpp:315
VariableMatrix InitialState()
Convenience function to get the initial state in the trajectory.
Definition OCPSolver.hpp:365
VariableMatrix & U()
Get the input variables.
Definition OCPSolver.hpp:347
void SetMaxTimestep(std::chrono::duration< double > maxTimestep)
Convenience function to set an upper bound on the timestep.
Definition OCPSolver.hpp:324
void ForEachStep(const function_ref< void(const VariableMatrix &x, const VariableMatrix &u)> callback)
Set the constraint evaluation function.
Definition OCPSolver.hpp:248
VariableMatrix & DT()
Get the timestep variables.
Definition OCPSolver.hpp:358
void ConstrainFinalState(const T &finalState)
Utility function to constrain the final state.
Definition OCPSolver.hpp:236
void SetUpperInputBound(const T &upperBound)
Convenience function to set an upper bound on the input.
Definition OCPSolver.hpp:304
VariableMatrix FinalState()
Convenience function to get the final state in the trajectory.
Definition OCPSolver.hpp:372
void ForEachStep(const function_ref< void(const Variable &t, const VariableMatrix &x, const VariableMatrix &u, const Variable &dt)> callback)
Set the constraint evaluation function.
Definition OCPSolver.hpp:266
void SetLowerInputBound(const T &lowerBound)
Convenience function to set a lower bound on the input.
Definition OCPSolver.hpp:290
OCPSolver(int numStates, int numInputs, std::chrono::duration< double > dt, int numSteps, function_ref< VariableMatrix(const VariableMatrix &x, const VariableMatrix &u)> dynamics, DynamicsType dynamicsType=DynamicsType::kExplicitODE, TimestepMethod timestepMethod=TimestepMethod::kFixed, TranscriptionMethod method=TranscriptionMethod::kDirectTranscription)
Build an optimization problem using a system evolution function (explicit ODE or discrete state trans...
Definition OCPSolver.hpp:122
VariableMatrix & X()
Get the state variables.
Definition OCPSolver.hpp:336
OCPSolver(int numStates, int numInputs, std::chrono::duration< double > dt, int numSteps, function_ref< VariableMatrix(const Variable &t, const VariableMatrix &x, const VariableMatrix &u, const Variable &dt)> dynamics, DynamicsType dynamicsType=DynamicsType::kExplicitODE, TimestepMethod timestepMethod=TimestepMethod::kFixed, TranscriptionMethod method=TranscriptionMethod::kDirectTranscription)
Build an optimization problem using a system evolution function (explicit ODE or discrete state trans...
Definition OCPSolver.hpp:162
This class allows the user to pose a constrained nonlinear optimization problem in natural mathematic...
Definition OptimizationProblem.hpp:50
An autodiff variable pointing to an expression node.
Definition Variable.hpp:31
void SetValue(double value)
Sets Variable's internal value.
Definition Variable.hpp:75
A matrix of autodiff variables.
Definition VariableMatrix.hpp:28
VariableBlock< VariableMatrix > Col(int col)
Returns a column slice of the variable matrix.
Definition VariableMatrix.hpp:485
An implementation of std::function_ref, a lightweight non-owning reference to a callable.
Definition FunctionRef.hpp:17
Definition Concepts.hpp:28
Definition Concepts.hpp:12
Definition Hessian.hpp:18
DynamicsType
Enum describing a type of system dynamics constraints.
Definition OCPSolver.hpp:57
@ kExplicitODE
The dynamics are a function in the form dx/dt = f(t, x, u).
@ kDiscrete
The dynamics are a function in the form xₖ₊₁ = f(t, xₖ, uₖ).
TranscriptionMethod
Enum describing an OCP transcription method.
Definition OCPSolver.hpp:42
@ kSingleShooting
States depend explicitly as a function of all previous states and all previous inputs.
@ kDirectTranscription
Each state is a decision variable constrained to the integrated dynamics of the previous state.
@ kDirectCollocation
The trajectory is modeled as a series of cubic polynomials where the centerpoint slope is constrained...
TimestepMethod
Enum describing the type of system timestep.
Definition OCPSolver.hpp:67
@ kVariableSingle
The timesteps are equal length but allowed to vary as a single decision variable.
@ kFixed
The timestep is a fixed constant.
@ kVariable
The timesteps are allowed to vary as independent decision variables.
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 OCPSolver.hpp:29
function_ref(R(*)(Args...)) -> function_ref< R(Args...)>
Implement std::hash so that hash_code can be used in STL containers.
Definition PointerIntPair.h:280
#define Assert(condition)
Abort in C++.
Definition Assert.hpp:24