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