akreon-convex
Typed convex modeling, conic canonicalization, and primal-dual optimization in C++26.
Loading...
Searching...
No Matches
pd_ipm_format.hpp
Go to the documentation of this file.
1/*
2 * Copyright © 2025-2026 Akreon LLC
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6#pragma once
7
8#include <cstddef>
9#include <tuple>
10#include <type_traits>
11#include <utility>
12
13#ifdef AKREON_ENABLE_IOSTREAM
14#include <ostream>
15#endif
16
21#include "akreon/linalg/norms/vector_norms.hpp"
22
23namespace convex::debug {
24
25inline constexpr const char* solver_result_to_cstr(config::SolverResult tag) {
26 switch (tag) {
28 return "INFEASIBLE_OR_UNBOUNDED";
30 return "OPTIMAL";
32 return "PRIMAL_FEASIBLE";
34 return "DUAL_FEASIBLE";
36 return "PRIMAL_DUAL_FEASIBLE";
38 return "PRIMAL_OPTIMAL";
40 return "DUAL_OPTIMAL";
42 return "INFEASIBLE_OPTIMAL";
43 }
44 return "UNKNOWN";
45}
46
47inline constexpr const char* numerical_event_to_cstr(
48 linalg::NumericalEvent event) {
49 switch (event) {
50 case linalg::NumericalEvent::NONE:
51 return "NONE";
52 case linalg::NumericalEvent::RANK_DEFICIENCY:
53 return "RANK_DEFICIENCY";
54 case linalg::NumericalEvent::INDEFINITE_FACTORIZATION:
55 return "INDEFINITE_FACTORIZATION";
56 case linalg::NumericalEvent::NEAR_SINGULAR_TRIANGULAR_SOLVE:
57 return "NEAR_SINGULAR_TRIANGULAR_SOLVE";
58 case linalg::NumericalEvent::NUMERICAL_SAFEGUARD:
59 return "NUMERICAL_SAFEGUARD";
60 case linalg::NumericalEvent::DB_MAX_ITERS_REACHED:
61 return "DB_MAX_ITERS_REACHED";
62 case linalg::NumericalEvent::FACTORIZATION_FAILED:
63 return "FACTORIZATION_FAILED";
64 case linalg::NumericalEvent::EVD_FAILED:
65 return "EVD_FAILED";
66 }
67 return "UNKNOWN";
68}
69
70inline constexpr const char* canonical_cone_kind_to_cstr(
72 switch (kind) {
74 return "RPLUS";
76 return "SOC";
78 return "PSD";
80 return "EXP";
82 return "POW";
84 return "UNKNOWN";
85 }
86 return "UNKNOWN";
87}
88
89template <typename Trace>
90inline bool append_canonical_trace(FixedBufferWriter& w, const Trace& trace) {
91 if (!w.format("canonical_trace: nodes=%zu cones=%zu equalities=%zu\n",
95 return false;
96
97 std::size_t i = 0;
98 bool ok = true;
99 std::apply(
100 [&](const auto&... nodes) {
101 ((ok = ok &&
102 w.format(" node[%zu]: id=%zu parent=%zu atom=%u child=%zu\n",
103 i++, nodes.node_id, nodes.parent_id,
104 static_cast<unsigned>(nodes.atom_type),
105 nodes.child_index)),
106 ...);
107 },
108 trace.nodes);
109 if (!ok) return false;
110
111 i = 0;
112 std::apply(
113 [&](const auto&... cones) {
114 ((ok = ok &&
115 w.format(" cone[%zu]: node=%zu local=%zu global=%zu kind=%s "
116 "offset=%zu dim=%zu\n",
117 i++, cones.node_id, cones.local_cone_index,
118 cones.global_cone_index,
119 canonical_cone_kind_to_cstr(cones.cone_kind),
120 cones.cone_offset, cones.cone_dim)),
121 ...);
122 },
123 trace.cones);
124 if (!ok) return false;
125
126 i = 0;
127 std::apply(
128 [&](const auto&... eqs) {
129 ((ok = ok &&
130 w.format(" eq[%zu]: node=%zu local=%zu offset=%zu rows=%zu\n",
131 i++, eqs.node_id, eqs.local_eq_index, eqs.row_offset,
132 eqs.row_count)),
133 ...);
134 },
135 trace.equalities);
136 return ok;
137}
138
139template <linalg::Field T, std::size_t NumCones, typename ConeTuple,
140 std::size_t... Is>
143 const ConeTuple& cones,
144 std::index_sequence<Is...>, const char* label) {
145 auto append_cone_step = [&](const ConeStepTrace<T>& cone_step) {
146 return w.format("active=%d mu=%g rhs_c=%g pz_rhs=%g", cone_step.active,
147 static_cast<double>(cone_step.mu),
148 static_cast<double>(cone_step.rhs_c_norm),
149 static_cast<double>(cone_step.pz_rhs_norm));
150 };
151 bool ok = true;
152 ((ok =
153 ok &&
154 w.format(" %s cone[%zu:%s node=%zu offset=%zu dim=%zu]: ", label, Is,
155 canonical_cone_kind_to_cstr(std::get<Is>(cones).cone_kind),
156 std::get<Is>(cones).node_id, std::get<Is>(cones).cone_offset,
157 std::get<Is>(cones).cone_dim) &&
158 append_cone_step(step.cones[Is]) && w.append("\n")),
159 ...);
160 return ok;
161}
162
163template <linalg::Field T, std::size_t NumCones, typename CanonicalTraceT>
165 const PdIpmIterTrace<T, NumCones>& iter,
166 const CanonicalTraceT& canonical_trace,
167 std::size_t k) {
168 if (!w.format(
169 "[%zu] mu=%g ax=%g ay=%g rd=%g rp=%g req=%g pz=%g pnu=%g "
170 "px=%g py=%g tag=%s event=%s stalled=%d\n",
171 k, static_cast<double>(iter.mu), static_cast<double>(iter.alpha_x),
172 static_cast<double>(iter.alpha_y), static_cast<double>(iter.rd_norm),
173 static_cast<double>(iter.rp_norm), static_cast<double>(iter.req_norm),
174 static_cast<double>(iter.pz_norm), static_cast<double>(iter.pnu_norm),
175 static_cast<double>(iter.px_norm), static_cast<double>(iter.py_norm),
177 iter.stalled))
178 return false;
179
180 constexpr std::size_t TraceCones =
182 if constexpr (TraceCones == NumCones) {
183 if (!append_cone_steps(w, iter.predictor, canonical_trace.cones,
184 std::make_index_sequence<NumCones>{}, "pred"))
185 return false;
186 if (!append_cone_steps(w, iter.corrector, canonical_trace.cones,
187 std::make_index_sequence<NumCones>{}, "corr"))
188 return false;
189 } else {
190 for (std::size_t i = 0; i < NumCones; ++i) {
191 if (!w.format(" pred cone[%zu]: ", i)) return false;
192 if (!append_cone_step(w, iter.predictor.cones[i])) return false;
193 if (!w.append("\n")) return false;
194 if (!w.format(" corr cone[%zu]: ", i)) return false;
195 if (!append_cone_step(w, iter.corrector.cones[i])) return false;
196 if (!w.append("\n")) return false;
197 }
198 }
199
200 return true;
201}
202
203template <linalg::Field T, std::size_t NumCones, typename CanonicalTraceT>
205 const PdIpmTrace<T, NumCones>& trace,
206 const CanonicalTraceT& canonical_trace) {
207 if (!w.format("solver_trace: iters=%zu max_iters=%zu cones=%zu\n",
208 trace.num_iters, PD_IPM_STEPS, NumCones))
209 return false;
210
211 const std::size_t used =
212 (trace.num_iters < PD_IPM_STEPS) ? trace.num_iters : PD_IPM_STEPS;
213 for (std::size_t k = 0; k < used; ++k) {
214 if (!append_iter_trace(w, trace.iters[k], canonical_trace, k)) return false;
215 }
216 return true;
217}
218
219template <typename Trace, typename CanonicalTraceT>
221 const CanonicalTraceT&) {
222 return w.append("solver_trace: <empty>\n");
223}
224
225template <linalg::VectorContainer Var, linalg::VectorContainer ConeVar,
226 linalg::VectorContainer NuVar, typename CanonicalTraceT,
227 typename SolverTraceT>
228inline bool append_result(
230 const convex::PrimalDualResult<Var, ConeVar, NuVar, CanonicalTraceT,
231 SolverTraceT>& res) {
232 const auto rd_norm = linalg::L2<decltype(res.rd)>()(res.rd);
233 const auto rp_norm = linalg::L2<decltype(res.rp)>()(res.rp);
234 const auto req_norm = linalg::L2<decltype(res.req)>()(res.req);
235
236 if (!w.format("tag=%s stalled=%d event=%s num_iter=%zu mu=%g "
237 "convex_obj=%g dual_obj=%g rd=%g rp=%g req=%g\n",
238 solver_result_to_cstr(res.tag), res.stalled,
239 numerical_event_to_cstr(res.event), res.num_iter,
240 static_cast<double>(res.mu),
241 static_cast<double>(res.convex_obj),
242 static_cast<double>(res.dual_obj), static_cast<double>(rd_norm),
243 static_cast<double>(rp_norm), static_cast<double>(req_norm)))
244 return false;
245
246 if (!w.append("z=")) return false;
247 if (!append_vector(w, res.iterate.z)) return false;
248 if (!w.append("\n")) return false;
249 if (!w.append("x=")) return false;
250 if (!append_vector(w, res.iterate.x)) return false;
251 if (!w.append("\n")) return false;
252 if (!w.append("y=")) return false;
253 if (!append_vector(w, res.iterate.y)) return false;
254 if (!w.append("\n")) return false;
255 if (!w.append("nu=")) return false;
256 if (!append_vector(w, res.iterate.nu)) return false;
257 if (!w.append("\n")) return false;
258 if (!w.append("rd=")) return false;
259 if (!append_vector(w, res.rd)) return false;
260 if (!w.append("\n")) return false;
261 if (!w.append("rp=")) return false;
262 if (!append_vector(w, res.rp)) return false;
263 if (!w.append("\n")) return false;
264 if (!w.append("req=")) return false;
265 if (!append_vector(w, res.req)) return false;
266 if (!w.append("\n")) return false;
267
268 if (!append_canonical_trace(w, res.canonical_trace)) return false;
269 return append_solver_trace(w, res.solver_trace, res.canonical_trace);
270}
271
272template <linalg::VectorContainer Var, linalg::VectorContainer ConeVar,
273 linalg::VectorContainer NuVar, typename CanonicalTraceT,
274 typename SolverTraceT>
275inline std::size_t result_to_cstr(
276 const convex::PrimalDualResult<Var, ConeVar, NuVar, CanonicalTraceT,
277 SolverTraceT>& res,
278 char* out, std::size_t out_size) {
279 FixedBufferWriter w{out, out_size, 0, false};
280 w.init();
281 append_result(w, res);
282 return w.size();
283}
284
285#ifdef AKREON_ENABLE_IOSTREAM
286template <linalg::VectorContainer Var, linalg::VectorContainer ConeVar,
287 linalg::VectorContainer NuVar, typename CanonicalTraceT,
288 typename SolverTraceT>
289inline std::ostream& operator<<(
290 std::ostream& os,
291 const convex::PrimalDualResult<Var, ConeVar, NuVar, CanonicalTraceT,
292 SolverTraceT>& res) {
293 char buf[64000];
294 result_to_cstr(res, buf, sizeof(buf));
295 return os << buf;
296}
297#endif
298
299} // namespace convex::debug
SolverResult
SolverResult.
bool append_vector(FixedBufferWriter &w, const Vec &v)
bool append_canonical_trace(FixedBufferWriter &w, const Trace &trace)
bool append_result(FixedBufferWriter &w, const convex::PrimalDualResult< Var, ConeVar, NuVar, CanonicalTraceT, SolverTraceT > &res)
constexpr const char * solver_result_to_cstr(config::SolverResult tag)
constexpr const char * numerical_event_to_cstr(linalg::NumericalEvent event)
bool append_iter_trace(FixedBufferWriter &w, const PdIpmIterTrace< T, NumCones > &iter, const CanonicalTraceT &canonical_trace, std::size_t k)
bool append_solver_trace(FixedBufferWriter &w, const PdIpmTrace< T, NumCones > &trace, const CanonicalTraceT &canonical_trace)
bool append_cone_steps(FixedBufferWriter &w, const PdIpmStepTrace< T, NumCones > &step, const ConeTuple &cones, std::index_sequence< Is... >, const char *label)
constexpr const char * canonical_cone_kind_to_cstr(CanonicalConeKind kind)
std::size_t result_to_cstr(const convex::PrimalDualResult< Var, ConeVar, NuVar, CanonicalTraceT, SolverTraceT > &res, char *out, std::size_t out_size)
constexpr std::size_t dom_size_v
CanonicalConeKind
Cone family emitted into the global cone block.
#define PD_IPM_STEPS
(dakre) note increase constexpr steps / depth for # steps to increase here for constexpr usage
Per-cone scalar diagnostics for one predictor or corrector pass.
Outer PD-IPM iteration diagnostics.
T px_norm
||Newton step in x||_2.
PdIpmStepTrace< T, NumCones > corrector
corrector pd ipm trace
T rd_norm
||dual residual||_2.
linalg::NumericalEvent event
event of reported numerical result
T alpha_x
Max primal cone step.
T mu
public member vars
T py_norm
||Newton step in y||_2.
T alpha_y
Max dual cone step.
PdIpmStepTrace< T, NumCones > predictor
predictor pd ipm trace
bool stalled
tag whether or not its been stalled
T pnu_norm
||Newton step in nu||_2.
config::SolverResult tag
tag of solver result
T req_norm
||equality residual||_2.
T rp_norm
||cone/primal residual||_2.
T pz_norm
||Newton step in z||_2.
Predictor or corrector cone diagnostics for one outer iteration.
Fixed-capacity PD-IPM trace.
std::array< PdIpmIterTrace< T, NumCones >, PD_IPM_STEPS > iters
public member vars
std::size_t num_iters
number of iterations
Final primal-dual solve result plus optional structural and iteration traces.
Trace objective.
Definition trace.hpp:60
Small no-heap append-only writer for debug formatting.
bool format(const char *fmt, Args... args)