WPILibC++ 2025.1.1
Loading...
Searching...
No Matches
OptimizationProblem.hpp
Go to the documentation of this file.
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <algorithm>
6#include <array>
7#include <concepts>
8#include <functional>
9#include <iterator>
10#include <optional>
11#include <utility>
12
13#include <Eigen/Core>
14#include <wpi/SmallVector.h>
15
26
27namespace sleipnir {
28
29/**
30 * This class allows the user to pose a constrained nonlinear optimization
31 * problem in natural mathematical notation and solve it.
32 *
33 * This class supports problems of the form:
34@verbatim
35 minₓ f(x)
36subject to cₑ(x) = 0
37 cᵢ(x) ≥ 0
38@endverbatim
39 *
40 * where f(x) is the scalar cost function, x is the vector of decision variables
41 * (variables the solver can tweak to minimize the cost function), cᵢ(x) are the
42 * inequality constraints, and cₑ(x) are the equality constraints. Constraints
43 * are equations or inequalities of the decision variables that constrain what
44 * values the solver is allowed to use when searching for an optimal solution.
45 *
46 * The nice thing about this class is users don't have to put their system in
47 * the form shown above manually; they can write it in natural mathematical form
48 * and it'll be converted for them.
49 */
51 public:
52 /**
53 * Construct the optimization problem.
54 */
55 OptimizationProblem() noexcept = default;
56
57 /**
58 * Create a decision variable in the optimization problem.
59 */
60 [[nodiscard]]
61 Variable DecisionVariable() {
62 m_decisionVariables.emplace_back();
63 return m_decisionVariables.back();
64 }
65
66 /**
67 * Create a matrix of decision variables in the optimization problem.
68 *
69 * @param rows Number of matrix rows.
70 * @param cols Number of matrix columns.
71 */
72 [[nodiscard]]
73 VariableMatrix DecisionVariable(int rows, int cols = 1) {
74 m_decisionVariables.reserve(m_decisionVariables.size() + rows * cols);
75
76 VariableMatrix vars{rows, cols};
77
78 for (int row = 0; row < rows; ++row) {
79 for (int col = 0; col < cols; ++col) {
80 m_decisionVariables.emplace_back();
81 vars(row, col) = m_decisionVariables.back();
82 }
83 }
84
85 return vars;
86 }
87
88 /**
89 * Create a symmetric matrix of decision variables in the optimization
90 * problem.
91 *
92 * Variable instances are reused across the diagonal, which helps reduce
93 * problem dimensionality.
94 *
95 * @param rows Number of matrix rows.
96 */
97 [[nodiscard]]
99 // We only need to store the lower triangle of an n x n symmetric matrix;
100 // the other elements are duplicates. The lower triangle has (n² + n)/2
101 // elements.
102 //
103 // n
104 // Σ k = (n² + n)/2
105 // k=1
106 m_decisionVariables.reserve(m_decisionVariables.size() +
107 (rows * rows + rows) / 2);
108
109 VariableMatrix vars{rows, rows};
110
111 for (int row = 0; row < rows; ++row) {
112 for (int col = 0; col <= row; ++col) {
113 m_decisionVariables.emplace_back();
114 vars(row, col) = m_decisionVariables.back();
115 vars(col, row) = m_decisionVariables.back();
116 }
117 }
118
119 return vars;
120 }
121
122 /**
123 * Tells the solver to minimize the output of the given cost function.
124 *
125 * Note that this is optional. If only constraints are specified, the solver
126 * will find the closest solution to the initial conditions that's in the
127 * feasible set.
128 *
129 * @param cost The cost function to minimize.
130 */
131 void Minimize(const Variable& cost) {
132 m_f = cost;
133 status.costFunctionType = m_f.value().Type();
134 }
135
136 /**
137 * Tells the solver to minimize the output of the given cost function.
138 *
139 * Note that this is optional. If only constraints are specified, the solver
140 * will find the closest solution to the initial conditions that's in the
141 * feasible set.
142 *
143 * @param cost The cost function to minimize.
144 */
145 void Minimize(Variable&& cost) {
146 m_f = std::move(cost);
147 status.costFunctionType = m_f.value().Type();
148 }
149
150 /**
151 * Tells the solver to maximize the output of the given objective function.
152 *
153 * Note that this is optional. If only constraints are specified, the solver
154 * will find the closest solution to the initial conditions that's in the
155 * feasible set.
156 *
157 * @param objective The objective function to maximize.
158 */
159 void Maximize(const Variable& objective) {
160 // Maximizing a cost function is the same as minimizing its negative
161 m_f = -objective;
162 status.costFunctionType = m_f.value().Type();
163 }
164
165 /**
166 * Tells the solver to maximize the output of the given objective function.
167 *
168 * Note that this is optional. If only constraints are specified, the solver
169 * will find the closest solution to the initial conditions that's in the
170 * feasible set.
171 *
172 * @param objective The objective function to maximize.
173 */
174 void Maximize(Variable&& objective) {
175 // Maximizing a cost function is the same as minimizing its negative
176 m_f = -std::move(objective);
177 status.costFunctionType = m_f.value().Type();
178 }
179
180 /**
181 * Tells the solver to solve the problem while satisfying the given equality
182 * constraint.
183 *
184 * @param constraint The constraint to satisfy.
185 */
186 void SubjectTo(const EqualityConstraints& constraint) {
187 // Get the highest order equality constraint expression type
188 for (const auto& c : constraint.constraints) {
189 status.equalityConstraintType =
190 std::max(status.equalityConstraintType, c.Type());
191 }
192
193 m_equalityConstraints.reserve(m_equalityConstraints.size() +
194 constraint.constraints.size());
195 std::copy(constraint.constraints.begin(), constraint.constraints.end(),
196 std::back_inserter(m_equalityConstraints));
197 }
198
199 /**
200 * Tells the solver to solve the problem while satisfying the given equality
201 * constraint.
202 *
203 * @param constraint The constraint to satisfy.
204 */
205 void SubjectTo(EqualityConstraints&& constraint) {
206 // Get the highest order equality constraint expression type
207 for (const auto& c : constraint.constraints) {
208 status.equalityConstraintType =
209 std::max(status.equalityConstraintType, c.Type());
210 }
211
212 m_equalityConstraints.reserve(m_equalityConstraints.size() +
213 constraint.constraints.size());
214 std::copy(constraint.constraints.begin(), constraint.constraints.end(),
215 std::back_inserter(m_equalityConstraints));
216 }
217
218 /**
219 * Tells the solver to solve the problem while satisfying the given inequality
220 * constraint.
221 *
222 * @param constraint The constraint to satisfy.
223 */
224 void SubjectTo(const InequalityConstraints& constraint) {
225 // Get the highest order inequality constraint expression type
226 for (const auto& c : constraint.constraints) {
227 status.inequalityConstraintType =
228 std::max(status.inequalityConstraintType, c.Type());
229 }
230
231 m_inequalityConstraints.reserve(m_inequalityConstraints.size() +
232 constraint.constraints.size());
233 std::copy(constraint.constraints.begin(), constraint.constraints.end(),
234 std::back_inserter(m_inequalityConstraints));
235 }
236
237 /**
238 * Tells the solver to solve the problem while satisfying the given inequality
239 * constraint.
240 *
241 * @param constraint The constraint to satisfy.
242 */
243 void SubjectTo(InequalityConstraints&& constraint) {
244 // Get the highest order inequality constraint expression type
245 for (const auto& c : constraint.constraints) {
246 status.inequalityConstraintType =
247 std::max(status.inequalityConstraintType, c.Type());
248 }
249
250 m_inequalityConstraints.reserve(m_inequalityConstraints.size() +
251 constraint.constraints.size());
252 std::copy(constraint.constraints.begin(), constraint.constraints.end(),
253 std::back_inserter(m_inequalityConstraints));
254 }
255
256 /**
257 * Solve the optimization problem. The solution will be stored in the original
258 * variables used to construct the problem.
259 *
260 * @param config Configuration options for the solver.
261 */
263 // Create the initial value column vector
264 Eigen::VectorXd x{m_decisionVariables.size()};
265 for (size_t i = 0; i < m_decisionVariables.size(); ++i) {
266 x(i) = m_decisionVariables[i].Value();
267 }
268
269 status.exitCondition = SolverExitCondition::kSuccess;
270
271 // If there's no cost function, make it zero and continue
272 if (!m_f.has_value()) {
273 m_f = Variable();
274 }
275
276 if (config.diagnostics) {
277 constexpr std::array kExprTypeToName{"empty", "constant", "linear",
278 "quadratic", "nonlinear"};
279
280 // Print cost function and constraint expression types
282 "The cost function is {}.",
283 kExprTypeToName[static_cast<int>(status.costFunctionType)]);
285 "The equality constraints are {}.",
286 kExprTypeToName[static_cast<int>(status.equalityConstraintType)]);
288 "The inequality constraints are {}.",
289 kExprTypeToName[static_cast<int>(status.inequalityConstraintType)]);
291
292 // Print problem dimensionality
293 sleipnir::println("Number of decision variables: {}",
294 m_decisionVariables.size());
295 sleipnir::println("Number of equality constraints: {}",
296 m_equalityConstraints.size());
297 sleipnir::println("Number of inequality constraints: {}\n",
298 m_inequalityConstraints.size());
299 }
300
301 // If the problem is empty or constant, there's nothing to do
302 if (status.costFunctionType <= ExpressionType::kConstant &&
303 status.equalityConstraintType <= ExpressionType::kConstant &&
304 status.inequalityConstraintType <= ExpressionType::kConstant) {
305 return status;
306 }
307
308 // Solve the optimization problem
309 if (m_inequalityConstraints.empty()) {
310 SQP(m_decisionVariables, m_equalityConstraints, m_f.value(), m_callback,
311 config, x, &status);
312 } else {
313 Eigen::VectorXd s = Eigen::VectorXd::Ones(m_inequalityConstraints.size());
314 InteriorPoint(m_decisionVariables, m_equalityConstraints,
315 m_inequalityConstraints, m_f.value(), m_callback, config,
316 false, x, s, &status);
317 }
318
319 if (config.diagnostics) {
320 sleipnir::println("Exit condition: {}", ToMessage(status.exitCondition));
321 }
322
323 // Assign the solution to the original Variable instances
324 VariableMatrix{m_decisionVariables}.SetValue(x);
325
326 return status;
327 }
328
329 /**
330 * Sets a callback to be called at each solver iteration.
331 *
332 * The callback for this overload should return void.
333 *
334 * @param callback The callback.
335 */
336 template <typename F>
337 requires requires(F callback, const SolverIterationInfo& info) {
338 { callback(info) } -> std::same_as<void>;
339 }
340 void Callback(F&& callback) {
341 m_callback = [=, callback = std::forward<F>(callback)](
342 const SolverIterationInfo& info) {
343 callback(info);
344 return false;
345 };
346 }
347
348 /**
349 * Sets a callback to be called at each solver iteration.
350 *
351 * The callback for this overload should return bool.
352 *
353 * @param callback The callback. Returning true from the callback causes the
354 * solver to exit early with the solution it has so far.
355 */
356 template <typename F>
357 requires requires(F callback, const SolverIterationInfo& info) {
358 { callback(info) } -> std::same_as<bool>;
359 }
360 void Callback(F&& callback) {
361 m_callback = std::forward<F>(callback);
362 }
363
364 private:
365 // The list of decision variables, which are the root of the problem's
366 // expression tree
367 wpi::SmallVector<Variable> m_decisionVariables;
368
369 // The cost function: f(x)
370 std::optional<Variable> m_f;
371
372 // The list of equality constraints: cₑ(x) = 0
373 wpi::SmallVector<Variable> m_equalityConstraints;
374
375 // The list of inequality constraints: cᵢ(x) ≥ 0
376 wpi::SmallVector<Variable> m_inequalityConstraints;
377
378 // The user callback
379 std::function<bool(const SolverIterationInfo& info)> m_callback =
380 [](const SolverIterationInfo&) { return false; };
381
382 // The solver status
383 SolverStatus status;
384};
385
386} // namespace sleipnir
This file defines the SmallVector class.
#define SLEIPNIR_DLLEXPORT
Definition SymbolExports.hpp:34
This class allows the user to pose a constrained nonlinear optimization problem in natural mathematic...
Definition OptimizationProblem.hpp:50
void SubjectTo(InequalityConstraints &&constraint)
Tells the solver to solve the problem while satisfying the given inequality constraint.
Definition OptimizationProblem.hpp:243
void Callback(F &&callback)
Sets a callback to be called at each solver iteration.
Definition OptimizationProblem.hpp:360
OptimizationProblem() noexcept=default
Construct the optimization problem.
void SubjectTo(EqualityConstraints &&constraint)
Tells the solver to solve the problem while satisfying the given equality constraint.
Definition OptimizationProblem.hpp:205
void Callback(F &&callback)
Sets a callback to be called at each solver iteration.
Definition OptimizationProblem.hpp:340
VariableMatrix DecisionVariable(int rows, int cols=1)
Create a matrix of decision variables in the optimization problem.
Definition OptimizationProblem.hpp:73
SolverStatus Solve(const SolverConfig &config=SolverConfig{})
Solve the optimization problem.
Definition OptimizationProblem.hpp:262
void SubjectTo(const EqualityConstraints &constraint)
Tells the solver to solve the problem while satisfying the given equality constraint.
Definition OptimizationProblem.hpp:186
void Minimize(const Variable &cost)
Tells the solver to minimize the output of the given cost function.
Definition OptimizationProblem.hpp:131
void Minimize(Variable &&cost)
Tells the solver to minimize the output of the given cost function.
Definition OptimizationProblem.hpp:145
void SubjectTo(const InequalityConstraints &constraint)
Tells the solver to solve the problem while satisfying the given inequality constraint.
Definition OptimizationProblem.hpp:224
void Maximize(Variable &&objective)
Tells the solver to maximize the output of the given objective function.
Definition OptimizationProblem.hpp:174
void Maximize(const Variable &objective)
Tells the solver to maximize the output of the given objective function.
Definition OptimizationProblem.hpp:159
VariableMatrix SymmetricDecisionVariable(int rows)
Create a symmetric matrix of decision variables in the optimization problem.
Definition OptimizationProblem.hpp:98
An autodiff variable pointing to an expression node.
Definition Variable.hpp:31
ExpressionType Type() const
Returns the type of this expression (constant, linear, quadratic, or nonlinear).
Definition Variable.hpp:208
A matrix of autodiff variables.
Definition VariableMatrix.hpp:28
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
Definition SmallVector.h:1212
Definition Hessian.hpp:18
void println(fmt::format_string< T... > fmt, T &&... args)
Wrapper around fmt::println() that squelches write failure exceptions.
Definition Print.hpp:43
SLEIPNIR_DLLEXPORT void SQP(std::span< Variable > decisionVariables, std::span< Variable > equalityConstraints, Variable &f, function_ref< bool(const SolverIterationInfo &info)> callback, const SolverConfig &config, Eigen::VectorXd &x, SolverStatus *status)
Finds the optimal solution to a nonlinear program using Sequential Quadratic Programming (SQP).
SLEIPNIR_DLLEXPORT void InteriorPoint(std::span< Variable > decisionVariables, std::span< Variable > equalityConstraints, std::span< Variable > inequalityConstraints, Variable &f, function_ref< bool(const SolverIterationInfo &info)> callback, const SolverConfig &config, bool feasibilityRestoration, Eigen::VectorXd &x, Eigen::VectorXd &s, SolverStatus *status)
Finds the optimal solution to a nonlinear program using the interior-point method.
SLEIPNIR_DLLEXPORT constexpr std::string_view ToMessage(const SolverExitCondition &exitCondition)
Returns user-readable message corresponding to the exit condition.
Definition SolverExitCondition.hpp:47
A vector of equality constraints of the form cₑ(x) = 0.
Definition Variable.hpp:535
wpi::SmallVector< Variable > constraints
A vector of scalar equality constraints.
Definition Variable.hpp:537
A vector of inequality constraints of the form cᵢ(x) ≥ 0.
Definition Variable.hpp:597
wpi::SmallVector< Variable > constraints
A vector of scalar inequality constraints.
Definition Variable.hpp:599
Solver configuration.
Definition SolverConfig.hpp:15
Solver iteration information exposed to a user callback.
Definition SolverIterationInfo.hpp:13
Return value of OptimizationProblem::Solve() containing the cost function and constraint types and so...
Definition SolverStatus.hpp:15