WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
print_diagnostics.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 <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
21
22namespace slp {
23
24/// Iteration type.
25enum class IterationType : uint8_t {
26 /// Normal iteration.
28 /// Accepted second-order correction iteration.
30 /// Rejected second-order correction iteration.
32};
33
34/// Converts std::chrono::duration to a number of milliseconds rounded to three
35/// decimals.
36template <typename Rep, typename Period = std::ratio<1>>
37constexpr double to_ms(const std::chrono::duration<Rep, Period>& duration) {
38 using std::chrono::duration_cast;
39 using std::chrono::microseconds;
40 return duration_cast<microseconds>(duration).count() / 1e3;
41}
42
43/// Renders value as power of 10.
44///
45/// @tparam Scalar Scalar type.
46/// @param value Value.
47template <typename Scalar>
48std::string power_of_10(Scalar value) {
49 if (value == Scalar(0)) {
50 return " 0";
51 } else {
52 using std::log10;
53 int exponent = static_cast<int>(log10(value));
54
55 if (exponent == 0) {
56 return " 1";
57 } else if (exponent == 1) {
58 return "10";
59 } else {
60 // Gather exponent digits
61 int n = std::abs(exponent);
63 do {
64 digits.emplace_back(n % 10);
65 n /= 10;
66 } while (n > 0);
67
68 std::string output = "10";
69
70 // Append exponent
71 if (exponent < 0) {
72 output += "⁻";
73 }
74 constexpr std::array strs = {"⁰", "¹", "²", "³", "⁴",
75 "⁵", "⁶", "⁷", "⁸", "⁹"};
76 for (const auto& digit : digits | std::views::reverse) {
77 output += strs[digit];
78 }
79
80 return output;
81 }
82 }
83}
84
85#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
86/// Prints error for too few degrees of freedom.
87///
88/// @tparam Scalar Scalar type.
89/// @param c_e The problem's equality constraints cₑ(x) evaluated at the current
90/// iterate.
91template <typename Scalar>
93 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
94 slp::println("The problem has too few degrees of freedom.");
95 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
96 for (int row = 0; row < c_e.rows(); ++row) {
97 if (c_e[row] < Scalar(0)) {
98 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
99 }
100 }
101}
102#else
103#define print_too_few_dofs_error(...)
104#endif
105
106#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
107/// Prints equality constraint local infeasibility error.
108///
109/// @tparam Scalar Scalar type.
110/// @param c_e The problem's equality constraints cₑ(x) evaluated at the current
111/// iterate.
112template <typename Scalar>
114 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
116 "The problem is locally infeasible due to violated equality "
117 "constraints.");
118 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
119 for (int row = 0; row < c_e.rows(); ++row) {
120 if (c_e[row] < Scalar(0)) {
121 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
122 }
123 }
124}
125#else
126#define print_c_e_local_infeasibility_error(...)
127#endif
128
129#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
130/// Prints inequality constraint local infeasibility error.
131///
132/// @tparam Scalar Scalar type.
133/// @param c_i The problem's inequality constraints cᵢ(x) evaluated at the
134/// current iterate.
135template <typename Scalar>
137 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_i) {
139 "The problem is locally infeasible due to violated inequality "
140 "constraints.");
141 slp::println("Violated constraints (cᵢ(x) ≥ 0) in order of declaration:");
142 for (int row = 0; row < c_i.rows(); ++row) {
143 if (c_i[row] < Scalar(0)) {
144 slp::println(" {}/{}: {} ≥ 0", row + 1, c_i.rows(), c_i[row]);
145 }
146 }
147}
148#else
149#define print_c_i_local_infeasibility_error(...)
150#endif
151
152#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
154 const std::span<const std::pair<Eigen::Index, Eigen::Index>>
155 conflicting_lower_upper_bound_indices) {
157 "The problem is globally infeasible due to conflicting bound "
158 "constraints:");
159 for (const auto& [lower_bound_idx, upper_bound_idx] :
160 conflicting_lower_upper_bound_indices) {
162 " Inequality constraint {} gives a lower bound that is greater than "
163 "the upper bound given by inequality constraint {}",
164 lower_bound_idx, upper_bound_idx);
165 }
166}
167#else
168#define print_bound_constraint_global_infeasibility_error(...)
169#endif
170
171#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
172/// Prints diagnostics for the current iteration.
173///
174/// @tparam Scalar Scalar type.
175/// @param iterations Number of iterations.
176/// @param type The iteration's type.
177/// @param time The iteration duration.
178/// @param error The error.
179/// @param cost The cost.
180/// @param infeasibility The infeasibility.
181/// @param complementarity The complementarity.
182/// @param μ The barrier parameter.
183/// @param δ The Hessian regularization factor.
184/// @param primal_α The primal step size.
185/// @param primal_α_max The max primal step size.
186/// @param α_reduction_factor Factor by which primal_α is reduced during
187/// backtracking.
188/// @param dual_α The dual step size.
189template <typename Scalar, typename Rep, typename Period = std::ratio<1>>
191 const std::chrono::duration<Rep, Period>& time,
192 Scalar error, Scalar cost,
193 Scalar infeasibility, Scalar complementarity,
194 Scalar μ, Scalar δ, Scalar primal_α,
195 Scalar primal_α_max, Scalar α_reduction_factor,
196 Scalar dual_α) {
197 if (iterations % 20 == 0) {
198 if (iterations == 0) {
199 slp::print("┏");
200 } else {
201 slp::print("┢");
202 }
204 "{:━^4}┯{:━^4}┯{:━^9}┯{:━^12}┯{:━^13}┯{:━^12}┯{:━^12}┯{:━^8}┯{:━^5}┯"
205 "{:━^8}┯{:━^8}┯{:━^2}",
206 "", "", "", "", "", "", "", "", "", "", "", "");
207 if (iterations == 0) {
208 slp::println("┓");
209 } else {
210 slp::println("┪");
211 }
213 "┃{:^4}│{:^4}│{:^9}│{:^12}│{:^13}│{:^12}│{:^12}│{:^8}│{:^5}│{:^8}│{:^8}"
214 "│{:^2}┃",
215 "iter", "type", "time (ms)", "error", "cost", "infeas.", "complement.",
216 "μ", "reg", "primal α", "dual α", "↩");
218 "┡{:━^4}┷{:━^4}┷{:━^9}┷{:━^12}┷{:━^13}┷{:━^12}┷{:━^12}┷{:━^8}┷{:━^5}┷"
219 "{:━^8}┷{:━^8}┷{:━^2}┩",
220 "", "", "", "", "", "", "", "", "", "", "", "");
221 }
222
223 // For the number of backtracks, we want x such that:
224 //
225 // αᵐᵃˣrˣ = α
226 //
227 // where r ∈ (0, 1) is the reduction factor.
228 //
229 // rˣ = α/αᵐᵃˣ
230 // ln(rˣ) = ln(α/αᵐᵃˣ)
231 // x ln(r) = ln(α/αᵐᵃˣ)
232 // x = ln(α/αᵐᵃˣ)/ln(r)
233 using std::log;
234 int backtracks =
235 static_cast<int>(log(primal_α / primal_α_max) / log(α_reduction_factor));
236
237 constexpr std::array ITERATION_TYPES = {"norm", "✓SOC", "XSOC"};
239 "│{:4} {:4} {:9.3f} {:12e} {:13e} {:12e} {:12e} {:.2e} {:<5} {:.2e} "
240 "{:.2e} {:2d}│",
241 iterations, ITERATION_TYPES[static_cast<uint8_t>(type)], to_ms(time),
242 error, cost, infeasibility, complementarity, μ, power_of_10(δ), primal_α,
243 dual_α, backtracks);
244}
245#else
246#define print_iteration_diagnostics(...)
247#endif
248
249#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
250/// Prints bottom of iteration diagnostics table.
252 slp::println("└{:─^108}┘", "");
253}
254#else
255#define print_bottom_iteration_diagnostics(...)
256#endif
257
258/// Renders histogram of the given normalized value.
259///
260/// @tparam Width Width of the histogram in characters.
261/// @param value Normalized value from 0 to 1.
262template <int Width>
263 requires(Width > 0)
264std::string histogram(double value) {
265 value = std::clamp(value, 0.0, 1.0);
266
267 double ipart;
268 int fpart = static_cast<int>(std::modf(value * Width, &ipart) * 8);
269
270 constexpr std::array strs = {" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"};
271 std::string hist;
272
273 int index = 0;
274 while (index < ipart) {
275 hist += strs[8];
276 ++index;
277 }
278 if (fpart > 0) {
279 hist += strs[fpart];
280 ++index;
281 }
282 while (index < Width) {
283 hist += strs[0];
284 ++index;
285 }
286
287 return hist;
288}
289
290#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
291/// Prints solver diagnostics.
292///
293/// @param solve_profilers Solve profilers.
295 const gch::small_vector<SolveProfiler>& solve_profilers) {
296 auto solve_duration = to_ms(solve_profilers[0].total_duration());
297
298 slp::println("┏{:━^23}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
299 slp::println("┃{:^23}│{:^18}│{:^10}│{:^9}│{:^4}┃", "solver trace", "percent",
300 "total (ms)", "each (ms)", "runs");
301 slp::println("┡{:━^23}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
302
303 for (auto& profiler : solve_profilers) {
304 double norm = solve_duration == 0.0
305 ? (&profiler == &solve_profilers[0] ? 1.0 : 0.0)
306 : to_ms(profiler.total_duration()) / solve_duration;
307 slp::println("│{:<23} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
308 profiler.name(), norm * 100.0, histogram<9>(norm),
309 to_ms(profiler.total_duration()),
310 to_ms(profiler.average_duration()), profiler.num_solves());
311 }
312
313 slp::println("└{:─^68}┘", "");
314}
315#else
316#define print_solver_diagnostics(...)
317#endif
318
319#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
320/// Prints autodiff diagnostics.
321///
322/// @param setup_profilers Autodiff setup profilers.
324 const gch::small_vector<SetupProfiler>& setup_profilers) {
325 auto setup_duration = to_ms(setup_profilers[0].duration());
326
327 // Print heading
328 slp::println("┏{:━^23}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
329 slp::println("┃{:^23}│{:^18}│{:^10}│{:^9}│{:^4}┃", "autodiff trace",
330 "percent", "total (ms)", "each (ms)", "runs");
331 slp::println("┡{:━^23}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
332
333 // Print setup profilers
334 for (auto& profiler : setup_profilers) {
335 double norm = setup_duration == 0.0
336 ? (&profiler == &setup_profilers[0] ? 1.0 : 0.0)
337 : to_ms(profiler.duration()) / setup_duration;
338 slp::println("│{:<23} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
339 profiler.name(), norm * 100.0, histogram<9>(norm),
340 to_ms(profiler.duration()), to_ms(profiler.duration()), "1");
341 }
342
343 slp::println("└{:─^68}┘", "");
344}
345#else
346#define print_autodiff_diagnostics(...)
347#endif
348
349} // namespace slp
@ index
Definition base.h:690
wpi::util::SmallVector< T > small_vector
Definition small_vector.hpp:10
Definition expression_graph.hpp:11
void print_iteration_diagnostics(int iterations, IterationType type, const std::chrono::duration< Rep, Period > &time, Scalar error, Scalar cost, Scalar infeasibility, Scalar complementarity, Scalar μ, Scalar δ, Scalar primal_α, Scalar primal_α_max, Scalar α_reduction_factor, Scalar dual_α)
Prints diagnostics for the current iteration.
Definition print_diagnostics.hpp:190
Variable< Scalar > log10(const Variable< Scalar > &x)
log10() for Variables.
Definition variable.hpp:512
void print_c_i_local_infeasibility_error(const Eigen::Vector< Scalar, Eigen::Dynamic > &c_i)
Prints inequality constraint local infeasibility error.
Definition print_diagnostics.hpp:136
constexpr double to_ms(const std::chrono::duration< Rep, Period > &duration)
Converts std::chrono::duration to a number of milliseconds rounded to three decimals.
Definition print_diagnostics.hpp:37
std::string histogram(double value)
Renders histogram of the given normalized value.
Definition print_diagnostics.hpp:264
IterationType
Iteration type.
Definition print_diagnostics.hpp:25
@ NORMAL
Normal iteration.
Definition print_diagnostics.hpp:27
@ ACCEPTED_SOC
Accepted second-order correction iteration.
Definition print_diagnostics.hpp:29
@ REJECTED_SOC
Rejected second-order correction iteration.
Definition print_diagnostics.hpp:31
void print_bound_constraint_global_infeasibility_error(const std::span< const std::pair< Eigen::Index, Eigen::Index > > conflicting_lower_upper_bound_indices)
Definition print_diagnostics.hpp:153
Variable< Scalar > log(const Variable< Scalar > &x)
log() for Variables.
Definition variable.hpp:503
std::string power_of_10(Scalar value)
Renders value as power of 10.
Definition print_diagnostics.hpp:48
void print_bottom_iteration_diagnostics()
Prints bottom of iteration diagnostics table.
Definition print_diagnostics.hpp:251
void println(fmt::format_string< T... > fmt, T &&... args)
Wrapper around fmt::println() that squelches write failure exceptions.
Definition print.hpp:37
void print_solver_diagnostics(const gch::small_vector< SolveProfiler > &solve_profilers)
Prints solver diagnostics.
Definition print_diagnostics.hpp:294
void print_too_few_dofs_error(const Eigen::Vector< Scalar, Eigen::Dynamic > &c_e)
Prints error for too few degrees of freedom.
Definition print_diagnostics.hpp:92
void print(fmt::format_string< T... > fmt, T &&... args)
Wrapper around fmt::print() that squelches write failure exceptions.
Definition print.hpp:19
void print_autodiff_diagnostics(const gch::small_vector< SetupProfiler > &setup_profilers)
Prints autodiff diagnostics.
Definition print_diagnostics.hpp:323
void print_c_e_local_infeasibility_error(const Eigen::Vector< Scalar, Eigen::Dynamic > &c_e)
Prints equality constraint local infeasibility error.
Definition print_diagnostics.hpp:113