Sleipnir C++ API
Loading...
Searching...
No Matches
problem.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <algorithm>
6#include <array>
7#include <cmath>
8#include <concepts>
9#include <functional>
10#include <iterator>
11#include <optional>
12#include <ranges>
13#include <string_view>
14#include <utility>
15
16#include <Eigen/Core>
17
18#include "sleipnir/autodiff/expression_type.hpp"
19#include "sleipnir/autodiff/variable.hpp"
20#include "sleipnir/autodiff/variable_matrix.hpp"
21#include "sleipnir/optimization/solver/exit_status.hpp"
22#include "sleipnir/optimization/solver/interior_point.hpp"
23#include "sleipnir/optimization/solver/iteration_info.hpp"
24#include "sleipnir/optimization/solver/newton.hpp"
25#include "sleipnir/optimization/solver/options.hpp"
26#include "sleipnir/optimization/solver/sqp.hpp"
27#include "sleipnir/util/small_vector.hpp"
28#include "sleipnir/util/symbol_exports.hpp"
29
30#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
31#include "sleipnir/util/print.hpp"
32#endif
33
34namespace slp {
35
57class SLEIPNIR_DLLEXPORT Problem {
58 public:
62 Problem() noexcept = default;
63
69 [[nodiscard]]
70 Variable decision_variable() {
71 m_decision_variables.emplace_back();
72 return m_decision_variables.back();
73 }
74
82 [[nodiscard]]
83 VariableMatrix decision_variable(int rows, int cols = 1) {
84 m_decision_variables.reserve(m_decision_variables.size() + rows * cols);
85
86 VariableMatrix vars{rows, cols};
87
88 for (int row = 0; row < rows; ++row) {
89 for (int col = 0; col < cols; ++col) {
90 m_decision_variables.emplace_back();
91 vars(row, col) = m_decision_variables.back();
92 }
93 }
94
95 return vars;
96 }
97
109 [[nodiscard]]
111 // We only need to store the lower triangle of an n x n symmetric matrix;
112 // the other elements are duplicates. The lower triangle has (n² + n)/2
113 // elements.
114 //
115 // n
116 // Σ k = (n² + n)/2
117 // k=1
118 m_decision_variables.reserve(m_decision_variables.size() +
119 (rows * rows + rows) / 2);
120
121 VariableMatrix vars{rows, rows};
122
123 for (int row = 0; row < rows; ++row) {
124 for (int col = 0; col <= row; ++col) {
125 m_decision_variables.emplace_back();
126 vars(row, col) = m_decision_variables.back();
127 vars(col, row) = m_decision_variables.back();
128 }
129 }
130
131 return vars;
132 }
133
143 void minimize(const Variable& cost) { m_f = cost; }
144
154 void minimize(Variable&& cost) { m_f = std::move(cost); }
155
165 void maximize(const Variable& objective) {
166 // Maximizing a cost function is the same as minimizing its negative
167 m_f = -objective;
168 }
169
179 void maximize(Variable&& objective) {
180 // Maximizing a cost function is the same as minimizing its negative
181 m_f = -std::move(objective);
182 }
183
190 void subject_to(const EqualityConstraints& constraint) {
191 m_equality_constraints.reserve(m_equality_constraints.size() +
192 constraint.constraints.size());
193 std::ranges::copy(constraint.constraints,
194 std::back_inserter(m_equality_constraints));
195 }
196
203 void subject_to(EqualityConstraints&& constraint) {
204 m_equality_constraints.reserve(m_equality_constraints.size() +
205 constraint.constraints.size());
206 std::ranges::copy(constraint.constraints,
207 std::back_inserter(m_equality_constraints));
208 }
209
216 void subject_to(const InequalityConstraints& constraint) {
217 m_inequality_constraints.reserve(m_inequality_constraints.size() +
218 constraint.constraints.size());
219 std::ranges::copy(constraint.constraints,
220 std::back_inserter(m_inequality_constraints));
221 }
222
230 m_inequality_constraints.reserve(m_inequality_constraints.size() +
231 constraint.constraints.size());
232 std::ranges::copy(constraint.constraints,
233 std::back_inserter(m_inequality_constraints));
234 }
235
241 ExpressionType cost_function_type() const {
242 if (m_f) {
243 return m_f.value().type();
244 } else {
245 return ExpressionType::NONE;
246 }
247 }
248
254 ExpressionType equality_constraint_type() const {
255 if (!m_equality_constraints.empty()) {
256 return std::ranges::max(m_equality_constraints, {}, &Variable::type)
257 .type();
258 } else {
259 return ExpressionType::NONE;
260 }
261 }
262
268 ExpressionType inequality_constraint_type() const {
269 if (!m_inequality_constraints.empty()) {
270 return std::ranges::max(m_inequality_constraints, {}, &Variable::type)
271 .type();
272 } else {
273 return ExpressionType::NONE;
274 }
275 }
276
284 ExitStatus solve(const Options& options = Options{}) {
285 // Create the initial value column vector
286 Eigen::VectorXd x{m_decision_variables.size()};
287 for (size_t i = 0; i < m_decision_variables.size(); ++i) {
288 x[i] = m_decision_variables[i].value();
289 }
290
291#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
292 if (options.diagnostics) {
293 // Print possible exit conditions
294 slp::println("User-configured exit conditions:");
295 slp::println(" ↳ error below {}", options.tolerance);
296 slp::println(" ↳ error below {} for {} iterations",
297 options.acceptable_tolerance,
298 options.max_acceptable_iterations);
299 if (!m_callbacks.empty()) {
300 slp::println(" ↳ user callback requested stop");
301 }
302 if (std::isfinite(options.max_iterations)) {
303 slp::println(" ↳ executed {} iterations", options.max_iterations);
304 }
305 if (std::isfinite(options.timeout.count())) {
306 slp::println(" ↳ {} elapsed", options.timeout);
307 }
308
309 if (m_decision_variables.size() == 1) {
310 slp::println("\n1 decision variable");
311 } else {
312 slp::println("\n{} decision variables", m_decision_variables.size());
313 }
314
315 auto print_constraint_types =
316 [](const small_vector<Variable>& constraints) {
317 std::array<size_t, 5> type_counts{};
318 for (const auto& constraint : constraints) {
319 ++type_counts[std::to_underlying(constraint.type())];
320 }
321 for (const auto& [count, name] : std::views::zip(
322 type_counts, std::array{"empty", "constant", "linear",
323 "quadratic", "nonlinear"})) {
324 if (count > 0) {
325 slp::println(" ↳ {} {}", count, name);
326 }
327 }
328 };
329
330 // Print constraint types
331 if (m_equality_constraints.size() == 1) {
332 slp::println("1 equality constraint");
333 } else {
334 slp::println("{} equality constraints", m_equality_constraints.size());
335 }
336 print_constraint_types(m_equality_constraints);
337 if (m_inequality_constraints.size() == 1) {
338 slp::println("1 inequality constraint");
339 } else {
340 slp::println("{} inequality constraints",
341 m_inequality_constraints.size());
342 }
343 print_constraint_types(m_inequality_constraints);
344 }
345
346 auto print_chosen_solver =
347 [](std::string_view solver_name, const ExpressionType& f_type,
348 const ExpressionType& c_e_type, const ExpressionType& c_i_type) {
349 slp::println("\nUsing {} solver due to:", solver_name);
350 slp::println(" ↳ {} cost function", ToMessage(f_type));
351 slp::println(" ↳ {} equality constraints", ToMessage(c_e_type));
352 slp::println(" ↳ {} inequality constraints", ToMessage(c_i_type));
353 slp::println("");
354 };
355#endif
356
357 // Get the highest order constraint expression types
358 auto f_type = cost_function_type();
359 auto c_e_type = equality_constraint_type();
360 auto c_i_type = inequality_constraint_type();
361
362 // If the problem is empty or constant, there's nothing to do
363 if (f_type <= ExpressionType::CONSTANT &&
364 c_e_type <= ExpressionType::CONSTANT &&
365 c_i_type <= ExpressionType::CONSTANT) {
366#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
367 if (options.diagnostics) {
368 print_chosen_solver("no-op", f_type, c_e_type, c_i_type);
369 }
370#endif
371 return ExitStatus::SUCCESS;
372 }
373
374 // Solve the optimization problem
375 ExitStatus status;
376 if (m_equality_constraints.empty() && m_inequality_constraints.empty()) {
377#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
378 if (options.diagnostics) {
379 print_chosen_solver("Newton", f_type, c_e_type, c_i_type);
380 }
381#endif
382 if (m_f) {
383 status =
384 newton(m_decision_variables, m_f.value(), m_callbacks, options, x);
385 } else {
386 Variable zero = 0.0;
387 status = newton(m_decision_variables, zero, m_callbacks, options, x);
388 }
389 } else if (m_inequality_constraints.empty()) {
390#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
391 if (options.diagnostics) {
392 print_chosen_solver("SQP", f_type, c_e_type, c_i_type);
393 }
394#endif
395 if (m_f) {
396 status = sqp(m_decision_variables, m_equality_constraints, m_f.value(),
397 m_callbacks, options, x);
398 } else {
399 Variable zero = 0.0;
400 status = sqp(m_decision_variables, m_equality_constraints, zero,
401 m_callbacks, options, x);
402 }
403 } else {
404#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
405 if (options.diagnostics) {
406 print_chosen_solver("IPM", f_type, c_e_type, c_i_type);
407 }
408#endif
409 if (m_f) {
410 status = interior_point(m_decision_variables, m_equality_constraints,
411 m_inequality_constraints, m_f.value(),
412 m_callbacks, options, x);
413 } else {
414 Variable zero = 0.0;
415 status = interior_point(m_decision_variables, m_equality_constraints,
416 m_inequality_constraints, zero, m_callbacks,
417 options, x);
418 }
419 }
420
421#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
422 if (options.diagnostics) {
423 slp::println("\nExit: {}", ToMessage(status));
424 }
425#endif
426
427 // Assign the solution to the original Variable instances
428 VariableMatrix{m_decision_variables}.set_value(x);
429
430 return status;
431 }
432
440 template <typename F>
441 requires requires(F callback, const IterationInfo& info) {
442 { callback(info) } -> std::same_as<void>;
443 }
444 void add_callback(F&& callback) {
445 m_callbacks.emplace_back(
446 [=, callback = std::forward<F>(callback)](const IterationInfo& info) {
447 callback(info);
448 return false;
449 });
450 }
451
460 template <typename F>
461 requires requires(F callback, const IterationInfo& info) {
462 { callback(info) } -> std::same_as<bool>;
463 }
464 void add_callback(F&& callback) {
465 m_callbacks.emplace_back(std::forward<F>(callback));
466 }
467
471 void clear_callbacks() { m_callbacks.clear(); }
472
473 private:
474 // The list of decision variables, which are the root of the problem's
475 // expression tree
476 small_vector<Variable> m_decision_variables;
477
478 // The cost function: f(x)
479 std::optional<Variable> m_f;
480
481 // The list of equality constraints: cₑ(x) = 0
482 small_vector<Variable> m_equality_constraints;
483
484 // The list of inequality constraints: cᵢ(x) ≥ 0
485 small_vector<Variable> m_inequality_constraints;
486
487 // The user callback
488 small_vector<std::function<bool(const IterationInfo& info)>> m_callbacks;
489};
490
491} // namespace slp
Definition problem.hpp:57
void clear_callbacks()
Definition problem.hpp:471
void subject_to(const EqualityConstraints &constraint)
Definition problem.hpp:190
VariableMatrix decision_variable(int rows, int cols=1)
Definition problem.hpp:83
Problem() noexcept=default
void maximize(Variable &&objective)
Definition problem.hpp:179
ExpressionType equality_constraint_type() const
Definition problem.hpp:254
void subject_to(InequalityConstraints &&constraint)
Definition problem.hpp:229
void minimize(const Variable &cost)
Definition problem.hpp:143
VariableMatrix symmetric_decision_variable(int rows)
Definition problem.hpp:110
void subject_to(EqualityConstraints &&constraint)
Definition problem.hpp:203
ExitStatus solve(const Options &options=Options{})
Definition problem.hpp:284
void maximize(const Variable &objective)
Definition problem.hpp:165
void add_callback(F &&callback)
Definition problem.hpp:444
ExpressionType cost_function_type() const
Definition problem.hpp:241
void add_callback(F &&callback)
Definition problem.hpp:464
void minimize(Variable &&cost)
Definition problem.hpp:154
void subject_to(const InequalityConstraints &constraint)
Definition problem.hpp:216
ExpressionType inequality_constraint_type() const
Definition problem.hpp:268
Definition variable_matrix.hpp:29
Definition variable.hpp:41
Definition variable.hpp:537
small_vector< Variable > constraints
A vector of scalar equality constraints.
Definition variable.hpp:539
Definition variable.hpp:599
small_vector< Variable > constraints
A vector of scalar inequality constraints.
Definition variable.hpp:601
Definition iteration_info.hpp:13
Definition options.hpp:15