akreon-convex
Typed convex modeling, conic canonicalization, and primal-dual optimization in C++26.
Loading...
Searching...
No Matches
pred_corr.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
10#include "equality_coupling.hpp"
11#include "akreon/linalg/linalg_util.hpp"
12#include "newton_step.hpp"
13#ifdef AKREON_REPORT_OUTPUT
14#include "pd_ipm_trace.hpp"
15#endif
16
17namespace convex {
18
22template <std::size_t NumCones, linalg::MatrixContainer ConeMat,
23 linalg::MatrixContainer EqMat, linalg::VectorContainer Var,
24 linalg::VectorContainer ConeVar>
27 using T = linalg::matrix_scalar_t<ConeMat>;
28 static constexpr std::size_t N = linalg::mat_cols_v<ConeMat>;
29
30 static constexpr std::size_t EqRows = linalg::mat_rows_v<EqMat>;
31 using NuVar = linalg::Vector<T, EqRows>;
33
50 template <std::size_t idx = 0, std::size_t Start = 0>
51 constexpr auto predictor(Var&& pz_rhs, const auto& K, const auto& M_inv,
52 const auto& eq, const ConeMat& F, const ConeVar& rp,
53 const Var& rd, const NuVar& req, const ConeVar& x,
54 const ConeVar& y
56 ,
58#endif
59 ) const {
60 if constexpr (idx >= NumCones)
61 return std::tuple(std::move(pz_rhs), Step{},
62 linalg::NumericalEvent::NONE);
63 else {
64 const auto Ki = std::get<idx>(K);
65 using Slack = conic::cone_slack_t<decltype(Ki)>;
66 constexpr std::size_t KiRows = linalg::vector_dim_v<Slack>;
67 constexpr std::size_t End = Start + KiRows;
68
69 using ConePoint = linalg::Vector<T, End - Start>;
70 ConePoint xk{}, yk{}, rpk{};
71
72 for (std::size_t i = Start, j = 0; i < End; ++i, ++j) {
73 xk(j) = x(i);
74 yk(j) = y(i);
75 rpk(j) = rp(i);
76 }
77
78 const auto [P, event] = Ki.H(xk, yk);
79 if (linalg::is_fatal_event(event))
80 return std::tuple(std::move(pz_rhs), Step{}, event);
81
82 const auto F_blk = linalg::get_block<Start, End, 0, N>(F);
83 const auto F_blk_T = linalg::transpose(F_blk);
84
85 ConePoint rhs_c = yk;
86
87 pz_rhs = pz_rhs - F_blk_T * P.apply(rpk) - F_blk_T * rhs_c;
88#ifdef AKREON_REPORT_OUTPUT
89 auto& cone_trace = trace_state.predictor.cone(idx);
90 cone_trace.mu = Ki.trace(xk, yk) / Ki.nu();
91 cone_trace.rhs_c_norm = linalg::L2<ConePoint>()(rhs_c);
92 cone_trace.pz_rhs_norm = linalg::L2<Var>()(pz_rhs);
93 cone_trace.active = true;
94#endif
95
97 std::move(pz_rhs), K, M_inv, eq, F, rp, rd, req, x, y
99 ,
101#endif
102 );
103 if (linalg::is_fatal_event(next_event))
104 return std::tuple(std::move(new_pz_rhs), std::move(step), next_event);
105
106 if constexpr (idx + 1 == NumCones) {
107 const auto [pz_next, pnu_next, solve_event] =
108 eq.solve_step(M_inv, new_pz_rhs, rd, req);
109 if (linalg::is_fatal_event(solve_event))
110 return std::tuple(std::move(new_pz_rhs), std::move(step),
112 step.pz = pz_next;
113 step.pnu = pnu_next;
114 }
115
116 const auto px_local = F_blk * step.pz + rpk;
117 const auto py_local = T(-1) * rhs_c - P.apply(px_local);
118
119 for (std::size_t i = Start, j = 0; i < End; ++i, ++j) {
120 step.px(i) = px_local(j), step.py(i) = py_local(j);
121 }
122
123 return std::tuple(std::move(new_pz_rhs), std::move(step),
124 linalg::NumericalEvent::NONE);
125 }
126 }
127
145 template <std::size_t idx = 0, std::size_t Start = 0>
146 constexpr auto corrector(Var&& pz_rhs, const ConeVar& px_aff,
147 const ConeVar& py_aff, const auto& K,
148 const auto& M_inv, const auto& eq, const ConeMat& F,
149 const ConeVar& rp, const Var& rd, const NuVar& req,
150 const ConeVar& x, const ConeVar& y
152 ,
154#endif
155 ) const {
156 if constexpr (idx >= NumCones)
157 return std::tuple(std::move(pz_rhs), Step{},
158 linalg::NumericalEvent::NONE);
159 else {
160 const auto Ki = std::get<idx>(K);
161 using Slack = conic::cone_slack_t<decltype(Ki)>;
162 constexpr std::size_t KiRows = linalg::vector_dim_v<Slack>;
163 constexpr std::size_t End = Start + KiRows;
164
165 using ConePoint = linalg::Vector<T, End - Start>;
166 ConePoint xk{}, yk{}, px_aff_k{}, py_aff_k{}, rpk{};
167
168 for (std::size_t i = Start, j = 0; i < End; ++i, ++j) {
169 xk(j) = x(i);
170 yk(j) = y(i);
171 px_aff_k(j) = px_aff(i);
172 py_aff_k(j) = py_aff(i);
173 rpk(j) = rp(i);
174 }
175
176 const auto [P, event] = Ki.H(xk, yk);
177 if (linalg::is_fatal_event(event))
178 return std::tuple(std::move(pz_rhs), Step{}, event);
179
180 const auto F_blk = linalg::get_block<Start, End, 0, N>(F);
181 const auto F_blk_T = linalg::transpose(F_blk);
182
183 // Center every block toward the same product-cone complementarity:
184 // mu_global = sum_k <x_k,y_k> / sum_k nu_k (Euclidean coordinates).
185 // A block's Jordan trace satisfies trace_k(u,v) = weight_k * <u,v>.
186 // Its reduced residual therefore uses mu_k = weight_k * mu_global
187 // with grad(x_k) = -inv(x_k). At the target, x_k circ y_k = mu_k * e_k.
188 // Orthant/PSD have weight 1; SOC has weight 2. This converts units,
189 // rather than centering each block at its own trace_k(x_k,y_k)/nu_k.
190 const T barrier = std::apply(
191 [](const auto&... cones) { return (T(0) + ... + cones.nu()); }, K);
192 const T mu_global = linalg::inner_product(x, y) / barrier;
193 const T mu_k = mu_global * Ki.trace_weight();
194 const auto [grad_xk, grad_event] = Ki.grad(xk);
195 if (linalg::is_fatal_event(grad_event))
196 return std::tuple(std::move(pz_rhs), Step{}, grad_event);
197
198 const auto [affine_correction, correction_event] =
199 Ki.scaled_affine_correction(xk, P, px_aff_k, py_aff_k);
200 if (linalg::is_fatal_event(correction_event))
201 return std::tuple(std::move(pz_rhs), Step{}, correction_event);
202 ConePoint rhs_c = yk + mu_k * grad_xk + affine_correction;
203 pz_rhs = pz_rhs - F_blk_T * P.apply(rpk) - F_blk_T * rhs_c;
204#ifdef AKREON_REPORT_OUTPUT
205 auto& cone_trace = trace_state.corrector.cone(idx);
206 cone_trace.mu = mu_k;
207 cone_trace.rhs_c_norm = linalg::L2<ConePoint>()(rhs_c);
208 cone_trace.pz_rhs_norm = linalg::L2<Var>()(pz_rhs);
209 cone_trace.active = true;
210#endif
211
213 std::move(pz_rhs), px_aff, py_aff, K, M_inv, eq, F, rp, rd, req, x, y
215 ,
217#endif
218 );
219 if (linalg::is_fatal_event(next_event))
220 return std::tuple(std::move(new_pz_rhs), std::move(step), next_event);
221
222 if constexpr (idx + 1 == NumCones) {
223 const auto [pz_next, pnu_next, solve_event] =
224 eq.solve_step(M_inv, new_pz_rhs, rd, req);
225 if (linalg::is_fatal_event(solve_event))
226 return std::tuple(std::move(new_pz_rhs), std::move(step),
228 step.pz = pz_next;
229 step.pnu = pnu_next;
230 }
231
232 const auto px_local = F_blk * step.pz + rpk;
233 const auto py_local = T(-1) * rhs_c - P.apply(px_local);
234
235 for (std::size_t i = Start, j = 0; i < End; ++i, ++j) {
236 step.px(i) = px_local(j), step.py(i) = py_local(j);
237 }
238
239 return std::tuple(std::move(new_pz_rhs), std::move(step),
240 linalg::NumericalEvent::NONE);
241 }
242 }
243};
244
245} // namespace convex
typename cone_traits< linalg::remove_cvref_t< Cone > >::SlackT cone_slack_t
alias for slack type in cone
constexpr std::size_t dom_size_v
Newton step bundle for the primal-dual interior-point iterate.
Outer PD-IPM iteration diagnostics.
Predictor Corrector.
Definition pred_corr.hpp:25
linalg::Vector< T, EqRows > NuVar
Definition pred_corr.hpp:31
constexpr auto corrector(Var &&pz_rhs, const ConeVar &px_aff, const ConeVar &py_aff, const auto &K, const auto &M_inv, const auto &eq, const ConeMat &F, const ConeVar &rp, const Var &rd, const NuVar &req, const ConeVar &x, const ConeVar &y) const
corrector (centering) direction
linalg::matrix_scalar_t< ConeMat > T
extract T and N from Mat
Definition pred_corr.hpp:27
static constexpr std::size_t EqRows
Definition pred_corr.hpp:30
static constexpr std::size_t N
Definition pred_corr.hpp:28
constexpr auto predictor(Var &&pz_rhs, const auto &K, const auto &M_inv, const auto &eq, const ConeMat &F, const ConeVar &rp, const Var &rd, const NuVar &req, const ConeVar &x, const ConeVar &y) const
predictor (affine-scaling) direction
Definition pred_corr.hpp:51