akreon-convex
Typed convex modeling, conic canonicalization, and primal-dual optimization in C++26.
Loading...
Searching...
No Matches
norm_ball.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 "convex_set.hpp"
9#include "ellipsoid.hpp"
10#include "polyhedron.hpp"
11
12namespace convex {
13
15template <linalg::RnSpace Space>
17
19template <linalg::RnSpace Space>
21
23template <linalg::RnSpace Space>
26
34template <typename Meta, typename Child, NormBallType Type>
35struct NormBall : ConvexSet<Meta, NormBall<Meta, Child, Type>, Child> {
38 using ConvexBase::N;
41
47 constexpr explicit NormBall(const PointT& c_in, T r_in, const Child& child_in)
48 : ConvexBase(child_in), c(c_in), r(r_in) {}
49
58 constexpr bool contains_impl(const PointT& x, T /* tol */) const {
59 switch (Type) {
61 return linalg::L2<PointT>()(x - c) <= r;
63 return linalg::L1<PointT>()(x - c) <= r;
65 default:
66 return linalg::Linf<PointT>()(x - c) <= r;
67 }
68 }
69
75 constexpr bool is_well_formed() const { return r >= 0; }
76
84 template <typename Other>
85 constexpr bool is_equal_impl(const Other& other) const {
86 return other.self().c == c && other.self().r == r;
87 }
88
103 template <linalg::SquareMatrix Mat>
104 requires(linalg::mat_cols_v<Mat> == N &&
105 std::same_as<T, linalg::matrix_scalar_t<Mat>>)
106 constexpr auto transform_impl(const affine::AffineMap<Mat>& map) const {
107 const auto& U = map.linear_part();
108 const auto& t = map.translation();
109 /* L2 case
110 * Let z = y - (U c + t) implies ||U^{-1} z||_2^2 <= r^2 iff z^T (U^-T
111 * U^-1) z <= r^2 So we have f(Br(x)) = {y | (y - (U c + t))^T (U U^T)^{-1}
112 * (y - (U c + t)) <= r^2
113 *
114 * (i) c' = U c + t
115 * (ii) r' = ||U^{-1}|| to r sigma(U) OR U U^T r^2
116 */
117 if constexpr (Type == NormBallType::L2) {
118 const auto point = U * c + t;
119 Mat U_T;
120 if constexpr (linalg::is_complex_v<T>) U_T = conjugate_transpose(U);
121 else U_T = transpose(U);
122 const auto P = (r * r) * (U * U_T);
123
125 point, P, std::get<0>(this->children));
126 }
127 /* L1 case
128 *
129 * L1 ball now represented as a polyhedron (LP equalities & inequalities):
130 * We have || U^{-1} z || <= r from above, where c transforms similarly to
131 * the above however r scales differently now (forms a type of polyhedron).
132 *
133 * Note the following relationships for L1:
134 * || U^{-1} z ||_1 to sum_{i=1}^n | U^{-1} z | <= r
135 * let s = [s_0 s_1 ... s_n] >= 0 not use slack var s to represent abs value
136 * respecting the constraint sum_i^n s_i <= r
137 * furthermore note z = y - c', c' = Uc + t so the following constraints
138 * are:
139 *
140 * U^{-1} z to U^{-1} y - U^{-1} U c - t
141 *
142 * (i) 1^T s <= r
143 * (ii) -(U^{-1} z)_i <= s_i to -U^{-1} y - s <= -(c + U^{-1} t)
144 * (iii) (U^{-1} z)_i <= s_i to U^{-1} y - s <= (c + U^{-1} t)
145 * (iv) s >= 0 to -s <= 0
146 *
147 * P = { x | Ax <= b, Cx = d } where x has an expanded dim including s
148 * x = [y s] in R^{2*n} and b = [r 0 0 0] in R^{3*n + 1}
149 *
150 * Affine Inequalities:
151 * (i) A_1 = [ 0^T; 1^T ], b_1 = r ... first row in A
152 * (ii) A_2 = [ -U^{-1}; -I ], b_2 = -(c + U^{-1} t)
153 * (iii) A_3 = [ U^{-1}; -I ], b_3 = (c + U^{-1} t)
154 * (iv) A_4 = [ 0; -I ], b_4 = 0
155 *
156 * Linear Equality C x = d, where z = y - c'... c' = U x + t
157 *
158 * L1 in Complex domain is different where |z_i| = sqrt(re(z)^2 + im(z)^2)
159 * so... sum_i |z_i| <= r transforms to Second order cone program:
160 *
161 * z = y - c'... c' = U c + t
162 *
163 * (i) ||z_i||_2 <= s_i
164 * (ii) sum_i s_i <= r
165 */
166 else if constexpr (Type == NormBallType::L1) {
167 linalg::Matrix<T, 3 * N + 1, 2 * N> A;
168 linalg::Vector<T, 3 * N + 1> b;
169 b(0) = r;
170
171 const auto [U_inv, event] = linalg::MatrixInverse<decltype(U)>()(U);
172 static_cast<void>(event);
173 const auto c_plus_U_inv_t = c + U_inv * t;
174 for (std::size_t i = 1; i < 2 * N + 1; ++i) {
175 const std::size_t idx = (i - 1) % N;
176 if (i < N + 1) b(i) = T(-1) * c_plus_U_inv_t(idx);
177 else b(i) = c_plus_U_inv_t(idx);
178 }
179
180 const auto I = linalg::make_identity<T, N>();
181 // (i) first row assignment 1^T s <= r
182 for (std::size_t i = 0; i < N; ++i) A(0, N + i) = T(1);
183 // (ii) first block assignment -U^{-1} z - s <= 0
184 A = linalg::set_block<1, N + 1, 0, N>(A, T(-1) * U_inv);
185 A = linalg::set_block<1, N + 1, N, 2 * N>(A, T(-1) * I);
186 // (iii) second block assignment U^{-1} z - s <= 0
187 A = linalg::set_block<N + 1, 2 * N + 1, 0, N>(A, U_inv);
188 A = linalg::set_block<N + 1, 2 * N + 1, N, 2 * N>(A, T(-1) * I);
189 // (iv) third block assignment s >= 0
190 A = linalg::set_block<2 * N + 1, 3 * N + 1, N, 2 * N>(A, T(-1) * I);
191
192 using Dom = linalg::Matrix<T, 1, 2 * N>;
193 using Codom = linalg::Matrix<T, 1, 3 * N + 1>;
195 A, b, std::get<0>(this->children));
196 }
197 /* Linf case
198 *
199 * Real case starts similar to L1 but no need for a vector of slack vars
200 * as Linf encodes maximum abs value for the full matrix.
201 *
202 * ||U^{-1} z||_inf = max |U^{-1} z|... largest magnitude among all
203 * components
204 *
205 * z = y - c'... c' = Uc + t so, U^{-1} y - U^{-1}(U c + t)
206 *
207 * -U^{-1} z <= r to -U^{-1} y <= r - U^{-1}(U c + t)
208 * U^{-1} z <= r to U^{-1} y <= r + U^{-1}(U c + t)
209 *
210 * Affine Inequality is Ax <= b, x = [y]
211 * A = [ U^{-1} ; -U^{-1} ]
212 * b = [ r + c + U^{-1} t; r - c - U^{-1} t ]
213 *
214 * Complex case |z| <= t -> ||z||_2 <= t
215 * (U^{-1} (y - (U c + t)))^T (U^{-1} (y - (U c + t))) <= t^2
216 *
217 */
218 else {
219 linalg::Matrix<T, 2 * N, N> A;
220 linalg::Vector<T, 2 * N> b;
221
222 const auto [U_inv, event] = linalg::MatrixInverse<decltype(U)>()(U);
223 static_cast<void>(event);
224 const auto c_plus_U_inv_t = c + U_inv * t;
225 for (std::size_t i = 0; i < 2 * N; ++i) {
226 const auto idx = i % N;
227 if (i < N) b(i) = r + c_plus_U_inv_t(idx);
228 else b(i) = r - c_plus_U_inv_t(idx);
229 }
230
231 A = linalg::set_block<0, N, 0, N>(A, U_inv);
232 A = linalg::set_block<N, 2 * N, 0, N>(A, T(-1) * U_inv);
233
234 using Dom = linalg::Matrix<T, 1, N>;
235 using Codom = linalg::Matrix<T, 1, 2 * N>;
237 A, b, std::get<0>(this->children));
238 }
239 }
240
263 template <AtomType AType, conic::BuilderLike Builder>
264 constexpr auto canonicalize_set(const Builder& builder) const {
265 const auto child_builder =
266 std::get<0>(this->children)
267 .template canonicalize<AtomType::SET>(builder);
268 const auto child_coord = child_builder.coord_map;
269
270 static constexpr std::size_t ChildCodom =
271 linalg::mat_cols_v<decltype(child_coord.linear_part())>;
272 static_assert(ChildCodom == N,
273 "Convex set domain does not equal the child coordinate "
274 "linear map's codomain in size");
275
276 const auto I = linalg::make_identity<T, N>();
277 if constexpr (Type == NormBallType::L2) {
278 linalg::Matrix<T, N + 1, N> F;
279 F = linalg::set_block<0, N, 0, N>(F, I);
280 linalg::Vector<T, N + 1> g;
281 for (std::size_t i = 0; i < N; ++i) g(i) = T(-1) * c(i);
282 g(N) = r;
283 const auto map = affine::AffineMap<decltype(F)>(F, g);
285 const auto cones = std::make_tuple(soc);
286 const auto emission =
287 conic::LocalEmission(map, cones, child_builder.eq_map);
288
289 return child_builder.template realize<AType>(emission);
290 } else if constexpr (Type == NormBallType::Linf) {
291 linalg::Matrix<T, 2 * N, N> F;
292 F = linalg::set_block<0, N, 0, N>(F, I);
293 F = linalg::set_block<N, 2 * N, 0, N>(F, T(-1) * I);
294
295 linalg::Vector<T, 2 * N> g;
296 for (std::size_t i = 0; i < N; ++i) g(i) = T(-1) * c(i) + r;
297 for (std::size_t i = N, j = 0; i < 2 * N; ++i, ++j) g(i) = c(j) + r;
298 const auto map = affine::AffineMap<decltype(F)>(F, g);
300 const auto cones = std::make_tuple(rplus, rplus);
301 const auto emission =
302 conic::LocalEmission(map, cones, child_builder.eq_map);
303
304 return child_builder.template realize<AType>(emission);
305 } else {
306 linalg::Matrix<T, 3 * N + 1, 2 * N> F;
307 F = linalg::set_block<0, N, 0, N>(F, I);
308 F = linalg::set_block<N, 2 * N, 0, N>(F, T(-1) * I);
309 F = linalg::set_block<0, N, N, 2 * N>(F, I);
310 F = linalg::set_block<N, 2 * N, N, 2 * N>(F, I);
311 for (std::size_t i = 0; i < N; ++i) F(2 * N, N + i) = T(-1);
312 F = linalg::set_block<2 * N + 1, 3 * N + 1, N, 2 * N>(F, I);
313 linalg::Vector<T, 3 * N + 1> g;
314 for (std::size_t i = 0; i < N; ++i) g(i) = T(-1) * c(i);
315 for (std::size_t i = N, j = 0; i < 2 * N; ++i, ++j) g(i) = c(j);
316 g(2 * N) = r;
317 const auto map = affine::AffineMap<decltype(F)>(F, g);
320 const auto cones = std::make_tuple(rplus1, rplus1, rplus2, rplus1);
321 const auto emission =
322 conic::LocalEmission(map, cones, child_builder.eq_map);
323
324 return child_builder.template realize<AType>(emission);
325 }
326 }
327
329 PointT c; //< n-dim vector of type T representing center of ball
330 T r; //< scalar of type T representing radius
331};
332
333} // namespace convex
constexpr std::size_t dom_size_v
Atom-local canonical emission bundle.
forward decl for nonneg orthant cone
Definition rplus.hpp:47
forward decl for soc
Definition soc.hpp:128
ChildrenT children
Tuple of normalized child atoms.
Definition atom.hpp:240
dom_point_t< Domain > PointT
Point type accepted by the atom domain.
Definition atom.hpp:32
dom_type_t< Domain > T
Scalar field for the atom domain.
Definition atom.hpp:30
static constexpr std::size_t N
Domain dimension.
Definition atom.hpp:35
Atom metadata bundling domain, codomain, and curvature.
Domain wrapper with a static sign label.
forward declaration of convex set
forward declaration of ellipsoid set
Definition ellipsoid.hpp:32
forward declaration of norm ball set
Definition norm_ball.hpp:35
constexpr auto canonicalize_set(const Builder &builder) const
canonicalize impl
constexpr bool is_well_formed() const
is_well_formed checks to see if underlying set is a valid convex set
Definition norm_ball.hpp:75
constexpr NormBall(const PointT &c_in, T r_in, const Child &child_in)
CTOR for NormBall set.
Definition norm_ball.hpp:47
constexpr bool contains_impl(const PointT &x, T) const
contains function to see if x is in the norm ball (norm(x - c) <= r)
Definition norm_ball.hpp:58
constexpr auto transform_impl(const affine::AffineMap< Mat > &map) const
transform norm ball by affine map
constexpr bool is_equal_impl(const Other &other) const
equality comparison
Definition norm_ball.hpp:85
PointT c
member variables in the norm ball
forward declaration of polyhedron set