Sleipnir C++ API
Loading...
Searching...
No Matches
print_diagnostics.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <stdint.h>
6
7#include <algorithm>
8#include <array>
9#include <chrono>
10#include <cmath>
11#include <ranges>
12#include <string>
13#include <utility>
14
15#include <Eigen/Core>
16#include <gch/small_vector.hpp>
17
18#include "sleipnir/util/print.hpp"
19#include "sleipnir/util/profiler.hpp"
20
21namespace slp {
22
24enum class IterationType : uint8_t {
26 NORMAL,
28 ACCEPTED_SOC,
30 REJECTED_SOC
31};
32
35template <typename Rep, typename Period = std::ratio<1>>
36constexpr double to_ms(const std::chrono::duration<Rep, Period>& duration) {
37 using std::chrono::duration_cast;
38 using std::chrono::microseconds;
39 return duration_cast<microseconds>(duration).count() / 1e3;
40}
41
46template <typename Scalar>
47std::string power_of_10(Scalar value) {
48 if (value == Scalar(0)) {
49 return " 0";
50 } else {
51 using std::log10;
52 int exponent = static_cast<int>(log10(value));
53
54 if (exponent == 0) {
55 return " 1";
56 } else if (exponent == 1) {
57 return "10";
58 } else {
59 // Gather exponent digits
60 int n = std::abs(exponent);
61 gch::small_vector<int> digits;
62 do {
63 digits.emplace_back(n % 10);
64 n /= 10;
65 } while (n > 0);
66
67 std::string output = "10";
68
69 // Append exponent
70 if (exponent < 0) {
71 output += "⁻";
72 }
73 constexpr std::array strs = {"⁰", "¹", "²", "³", "⁴",
74 "⁵", "⁶", "⁷", "⁸", "⁹"};
75 for (const auto& digit : digits | std::views::reverse) {
76 output += strs[digit];
77 }
78
79 return output;
80 }
81 }
82}
83
84#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
90template <typename Scalar>
91void print_too_few_dofs_error(
92 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
93 slp::println("The problem has too few degrees of freedom.");
94 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
95 for (int row = 0; row < c_e.rows(); ++row) {
96 if (c_e[row] < Scalar(0)) {
97 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
98 }
99 }
100}
101#else
102#define print_too_few_dofs_error(...)
103#endif
104
105#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
111template <typename Scalar>
112void print_c_e_local_infeasibility_error(
113 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
114 slp::println(
115 "The problem is locally infeasible due to violated equality "
116 "constraints.");
117 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
118 for (int row = 0; row < c_e.rows(); ++row) {
119 if (c_e[row] < Scalar(0)) {
120 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
121 }
122 }
123}
124#else
125#define print_c_e_local_infeasibility_error(...)
126#endif
127
128#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
134template <typename Scalar>
135void print_c_i_local_infeasibility_error(
136 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_i) {
137 slp::println(
138 "The problem is locally infeasible due to violated inequality "
139 "constraints.");
140 slp::println("Violated constraints (cᵢ(x) ≥ 0) in order of declaration:");
141 for (int row = 0; row < c_i.rows(); ++row) {
142 if (c_i[row] < Scalar(0)) {
143 slp::println(" {}/{}: {} ≥ 0", row + 1, c_i.rows(), c_i[row]);
144 }
145 }
146}
147#else
148#define print_c_i_local_infeasibility_error(...)
149#endif
150
151#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
152inline void print_bound_constraint_global_infeasibility_error(
153 const std::span<const std::pair<Eigen::Index, Eigen::Index>>
154 conflicting_lower_upper_bound_indices) {
155 slp::println(
156 "The problem is globally infeasible due to conflicting bound "
157 "constraints:");
158 for (const auto& [lower_bound_idx, upper_bound_idx] :
159 conflicting_lower_upper_bound_indices) {
160 slp::println(
161 " Inequality constraint {} gives a lower bound that is greater than "
162 "the upper bound given by inequality constraint {}",
163 lower_bound_idx, upper_bound_idx);
164 }
165}
166#else
167#define print_bound_constraint_global_infeasibility_error(...)
168#endif
169
170#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
188template <typename Scalar, typename Rep, typename Period = std::ratio<1>>
189void print_iteration_diagnostics(int iterations, IterationType type,
190 const std::chrono::duration<Rep, Period>& time,
191 Scalar error, Scalar cost,
192 Scalar infeasibility, Scalar complementarity,
193 Scalar μ, Scalar δ, Scalar primal_α,
194 Scalar primal_α_max, Scalar α_reduction_factor,
195 Scalar dual_α) {
196 if (iterations % 20 == 0) {
197 if (iterations == 0) {
198 slp::print("┏");
199 } else {
200 slp::print("┢");
201 }
202 slp::print(
203 "{:━^4}┯{:━^4}┯{:━^9}┯{:━^12}┯{:━^13}┯{:━^12}┯{:━^12}┯{:━^8}┯{:━^5}┯"
204 "{:━^8}┯{:━^8}┯{:━^2}",
205 "", "", "", "", "", "", "", "", "", "", "", "");
206 if (iterations == 0) {
207 slp::println("┓");
208 } else {
209 slp::println("┪");
210 }
211 slp::println(
212 "┃{:^4}│{:^4}│{:^9}│{:^12}│{:^13}│{:^12}│{:^12}│{:^8}│{:^5}│{:^8}│{:^8}"
213 "│{:^2}┃",
214 "iter", "type", "time (ms)", "error", "cost", "infeas.", "complement.",
215 "μ", "reg", "primal α", "dual α", "↩");
216 slp::println(
217 "┡{:━^4}┷{:━^4}┷{:━^9}┷{:━^12}┷{:━^13}┷{:━^12}┷{:━^12}┷{:━^8}┷{:━^5}┷"
218 "{:━^8}┷{:━^8}┷{:━^2}┩",
219 "", "", "", "", "", "", "", "", "", "", "", "");
220 }
221
222 // For the number of backtracks, we want x such that:
223 //
224 // αᵐᵃˣrˣ = α
225 //
226 // where r ∈ (0, 1) is the reduction factor.
227 //
228 // rˣ = α/αᵐᵃˣ
229 // ln(rˣ) = ln(α/αᵐᵃˣ)
230 // x ln(r) = ln(α/αᵐᵃˣ)
231 // x = ln(α/αᵐᵃˣ)/ln(r)
232 using std::log;
233 int backtracks =
234 static_cast<int>(log(primal_α / primal_α_max) / log(α_reduction_factor));
235
236 constexpr std::array ITERATION_TYPES = {"norm", "✓SOC", "XSOC"};
237 slp::println(
238 "│{:4} {:4} {:9.3f} {:12e} {:13e} {:12e} {:12e} {:.2e} {:<5} {:.2e} "
239 "{:.2e} {:2d}│",
240 iterations, ITERATION_TYPES[std::to_underlying(type)], to_ms(time), error,
241 cost, infeasibility, complementarity, μ, power_of_10(δ), primal_α, dual_α,
242 backtracks);
243}
244#else
245#define print_iteration_diagnostics(...)
246#endif
247
248#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
250inline void print_bottom_iteration_diagnostics() {
251 slp::println("└{:─^108}┘", "");
252}
253#else
254#define print_bottom_iteration_diagnostics(...)
255#endif
256
261template <int Width>
262 requires(Width > 0)
263std::string histogram(double value) {
264 value = std::clamp(value, 0.0, 1.0);
265
266 double ipart;
267 int fpart = static_cast<int>(std::modf(value * Width, &ipart) * 8);
268
269 constexpr std::array strs = {" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"};
270 std::string hist;
271
272 int index = 0;
273 while (index < ipart) {
274 hist += strs[8];
275 ++index;
276 }
277 if (fpart > 0) {
278 hist += strs[fpart];
279 ++index;
280 }
281 while (index < Width) {
282 hist += strs[0];
283 ++index;
284 }
285
286 return hist;
287}
288
289#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
293inline void print_solver_diagnostics(
294 const gch::small_vector<SolveProfiler>& solve_profilers) {
295 auto solve_duration = to_ms(solve_profilers[0].total_duration());
296
297 slp::println("┏{:━^23}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
298 slp::println("┃{:^23}│{:^18}│{:^10}│{:^9}│{:^4}┃", "solver trace", "percent",
299 "total (ms)", "each (ms)", "runs");
300 slp::println("┡{:━^23}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
301
302 for (auto& profiler : solve_profilers) {
303 double norm = solve_duration == 0.0
304 ? (&profiler == &solve_profilers[0] ? 1.0 : 0.0)
305 : to_ms(profiler.total_duration()) / solve_duration;
306 slp::println("│{:<23} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
307 profiler.name(), norm * 100.0, histogram<9>(norm),
308 to_ms(profiler.total_duration()),
309 to_ms(profiler.average_duration()), profiler.num_solves());
310 }
311
312 slp::println("└{:─^68}┘", "");
313}
314#else
315#define print_solver_diagnostics(...)
316#endif
317
318#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
322inline void print_autodiff_diagnostics(
323 const gch::small_vector<SetupProfiler>& setup_profilers) {
324 auto setup_duration = to_ms(setup_profilers[0].duration());
325
326 // Print heading
327 slp::println("┏{:━^23}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
328 slp::println("┃{:^23}│{:^18}│{:^10}│{:^9}│{:^4}┃", "autodiff trace",
329 "percent", "total (ms)", "each (ms)", "runs");
330 slp::println("┡{:━^23}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
331
332 // Print setup profilers
333 for (auto& profiler : setup_profilers) {
334 double norm = setup_duration == 0.0
335 ? (&profiler == &setup_profilers[0] ? 1.0 : 0.0)
336 : to_ms(profiler.duration()) / setup_duration;
337 slp::println("│{:<23} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
338 profiler.name(), norm * 100.0, histogram<9>(norm),
339 to_ms(profiler.duration()), to_ms(profiler.duration()), "1");
340 }
341
342 slp::println("└{:─^68}┘", "");
343}
344#else
345#define print_autodiff_diagnostics(...)
346#endif
347
348} // namespace slp