tinyopt 700
Tinyopt, a lightweight, header only optimization library
Loading...
Searching...
No Matches
robust_norms.h
1// Copyright 2026 Julien Michot.
2// SPDX-License-Identifier: Apache-2.0
3
6
7#pragma once
8
9#include <tinyopt/diff/jet_traits.h>
10#include <tinyopt/math.h>
11#include <tinyopt/traits.h>
12
13#include <tinyopt/losses/norms.h>
14
15#include <tuple>
16
17namespace tinyopt::losses {
18
19struct HuberTag {};
20struct CauchyTag {};
21struct TukeyTag {};
22
23template <typename ResidualT, typename LossTag>
24struct RobustResidual {
25 using StorageT = std::decay_t<ResidualT>;
26
27 explicit RobustResidual(const ResidualT &residual, double delta)
28 : residual(StorageT(residual)), delta(delta), delta2(delta * delta) {}
29
30 StorageT residual;
31 double delta = 0.0;
32 double delta2 = 0.0;
33};
34
35template <typename T>
36const T &UnwrapResidual(const T &residual) {
37 return residual;
38}
39
40template <typename ResidualT, typename LossTag>
41const ResidualT &UnwrapResidual(const RobustResidual<ResidualT, LossTag> &residual) {
42 return residual.residual;
43}
44
45inline double HuberWeight(double n2, double delta) {
46 const double delta2 = delta * delta;
47 if (n2 <= delta2) return 1.0;
48 return delta / std::sqrt(std::max(n2, std::numeric_limits<double>::min()));
49}
50
51inline double HuberLossValue(double n2, double delta) {
52 const double delta2 = delta * delta;
53 if (n2 <= delta2) return n2;
54 return 2.0 * delta * std::sqrt(std::max(n2, std::numeric_limits<double>::min())) - delta2;
55}
56
57template <typename T, typename = void>
58struct has_rows : std::false_type {};
59
60template <typename T>
61struct has_rows<T, std::void_t<decltype(std::declval<const T &>().rows())>> : std::true_type {};
62
63template <typename T>
64auto Huber(const T &residual, double delta)
65 -> std::enable_if_t<!std::is_arithmetic_v<std::decay_t<T>> && !traits::is_pair_v<T> &&
66 !traits::is_jet_type_v<std::decay_t<T>> &&
67 (traits::is_matrix_or_array_v<std::decay_t<T>> ||
68 traits::is_robust_residual_v<std::decay_t<T>>),
69 RobustResidual<std::decay_t<T>, HuberTag>> {
70 return RobustResidual<std::decay_t<T>, HuberTag>(residual, delta);
71}
72
73template <typename T>
74auto Cauchy(const T &residual, double delta)
75 -> std::enable_if_t<!std::is_arithmetic_v<std::decay_t<T>> && !traits::is_pair_v<T> &&
76 !traits::is_jet_type_v<std::decay_t<T>> &&
77 (traits::is_matrix_or_array_v<std::decay_t<T>> ||
78 traits::is_robust_residual_v<std::decay_t<T>>),
79 RobustResidual<std::decay_t<T>, CauchyTag>> {
80 return RobustResidual<std::decay_t<T>, CauchyTag>(residual, delta);
81}
82
83template <typename T>
84auto Tukey(const T &residual, double delta)
85 -> std::enable_if_t<!std::is_arithmetic_v<std::decay_t<T>> && !traits::is_pair_v<T> &&
86 !traits::is_jet_type_v<std::decay_t<T>> &&
87 (traits::is_matrix_or_array_v<std::decay_t<T>> ||
88 traits::is_robust_residual_v<std::decay_t<T>>),
89 RobustResidual<std::decay_t<T>, TukeyTag>> {
90 return RobustResidual<std::decay_t<T>, TukeyTag>(residual, delta);
91}
92
106
108
111template <typename T, typename ExportJ = std::nullptr_t>
112auto Truncated(const T &n2, typename traits::params_trait<T>::Scalar th2,
113 const ExportJ &Jx_or_bool = nullptr) {
114 if constexpr (traits::is_pair_v<T>) { // pair (loss, jacobian)
115 return Truncated(n2.first, th2, n2.second);
116 } else {
117 static_assert(!traits::is_matrix_or_array_v<T>, "`n` must be a scalar");
118 const auto l = n2 <= th2 ? n2 : T(th2);
119 if constexpr (std::is_null_pointer_v<ExportJ>) {
120 return l;
121 } else if constexpr (traits::is_bool_v<ExportJ>) {
122 return std::make_pair(l, n2 <= th2 ? T(1) : T(0));
123 } else { // Jx_or_bool is Jx
124 assert(Jx_or_bool.rows() == 1);
125 if (n2 <= th2) {
126 return std::make_pair(l, Jx_or_bool);
127 } else {
128 return std::make_pair(l, ExportJ::Zero(1, Jx_or_bool.cols()).eval());
129 }
130 }
131 }
132}
133
136template <typename T, typename ExportJ = std::nullptr_t>
137auto TruncatedLoss(const T &x, typename traits::params_trait<T>::Scalar th2,
138 const ExportJ &Jx_or_bool = nullptr) {
139 return Truncated(SquaredL2(x, Jx_or_bool), th2);
140}
141
143
146template <
147 typename T, typename ExportJ = std::nullptr_t,
148 std::enable_if_t<!traits::is_matrix_or_array_v<T> && !traits::is_robust_residual_v<T>, int> = 0>
149auto Huber(const T &n2, typename traits::params_trait<T>::Scalar th2,
150 const ExportJ &Jx_or_bool = nullptr) {
151 if constexpr (traits::is_pair_v<T>) { // pair (loss, jacobian)
152 return Huber(n2.first, th2, n2.second);
153 } else {
154 using std::max;
155 using std::sqrt;
156 static_assert(!traits::is_matrix_or_array_v<T>, "`n` must be a scalar");
157
158 if (n2 <= th2) { // Inlier
159 const auto l = n2;
160 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
161 return l;
162 } else {
163 if constexpr (traits::is_bool_v<ExportJ>)
164 return std::make_pair(l, T(1));
165 else
166 return std::make_pair(l, Jx_or_bool);
167 }
168 } else { // Outlier
169 const auto th = T(sqrt(th2)), n = sqrt(n2);
170 const auto l = T(2.0) * th * n - T(th2);
171 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
172 return l;
173 } else {
174 const T J_scale = max<T>(std::numeric_limits<T>::min(), th / n);
175 if constexpr (traits::is_bool_v<ExportJ>)
176 return std::make_pair(l, J_scale);
177 else
178 return std::make_pair(l, (J_scale * Jx_or_bool).eval());
179 }
180 }
181 }
182}
183
189template <typename T, typename ExportJ = std::nullptr_t>
190auto HuberLoss(const T &x, typename traits::params_trait<T>::Scalar th2,
191 const ExportJ &Jx_or_bool = nullptr) {
192 return Huber(SquaredL2(x, Jx_or_bool), th2);
193}
194
196
200template <
201 typename T, typename ExportJ = std::nullptr_t,
202 std::enable_if_t<!traits::is_matrix_or_array_v<T> && !traits::is_robust_residual_v<T>, int> = 0>
203auto Tukey(const T &n2, typename traits::params_trait<T>::Scalar th2,
204 const ExportJ &Jx_or_bool = nullptr) {
205 if constexpr (traits::is_pair_v<T>) { // pair (loss, jacobian)
206 return Tukey(n2.first, th2, n2.second);
207 } else {
208 static_assert(!traits::is_matrix_or_array_v<T>, "`n` must be a scalar");
209 if (n2 <= th2) { // Inlier
210 const auto s = T(1.0) - n2 / T(th2), s2 = s * s;
211 const auto l = T(th2) * (T(1.0) - s2 * s);
212 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
213 return l;
214 } else {
215 const auto J_scale = T(3.0) * (th2 - n2) * (th2 - n2) / (th2 * th2);
216 if constexpr (traits::is_bool_v<ExportJ>)
217 return std::make_pair(l, J_scale);
218 else
219 return std::make_pair(l, (J_scale * Jx_or_bool).eval());
220 }
221 } else { // Outlier
222 const T l = T(th2);
223 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
224 return l;
225 } else {
226 if constexpr (traits::is_bool_v<ExportJ>)
227 return std::make_pair(l, T(0));
228 else
229 return std::make_pair(l, ExportJ::Zero(1, Jx_or_bool.cols()).eval());
230 }
231 }
232 }
233}
234
238template <typename T, typename ExportJ = std::nullptr_t>
239auto TukeyLoss(const T &x, typename traits::params_trait<T>::Scalar th2,
240 const ExportJ &Jx_or_bool = nullptr) {
241 return Tukey(SquaredL2(x, Jx_or_bool), th2);
242}
243
245
249template <typename T, typename ExportJ = std::nullptr_t>
250auto Arctan(const T &n2, typename traits::params_trait<T>::Scalar th2,
251 const ExportJ &Jx_or_bool = nullptr) {
252 if constexpr (traits::is_pair_v<T>) { // pair (loss, jacobian)
253 return Arctan(n2.first, th2, n2.second);
254 } else {
255 using std::atan2;
256 using std::max;
257 using std::sqrt;
258 static_assert(!traits::is_matrix_or_array_v<T>, "`n` must be a scalar");
259 const T th = T(sqrt(th2));
260 const auto l = th * atan2(n2, th);
261 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
262 return l;
263 } else {
264 const auto tmp = n2 * n2 / th2;
265 const T J_scale = max<T>(std::numeric_limits<T>::min(), T(1.0) / (tmp + T(1.0)));
266 if constexpr (traits::is_bool_v<ExportJ>)
267 return std::make_pair(l, J_scale);
268 else
269 return std::make_pair(l, (J_scale * Jx_or_bool).eval());
270 }
271 }
272}
273
277template <typename T, typename ExportJ = std::nullptr_t>
278auto ArctanLoss(const T &x, typename traits::params_trait<T>::Scalar th2,
279 const ExportJ &Jx_or_bool = nullptr) {
280 return Arctan(SquaredL2(x, Jx_or_bool), th2);
281}
282
284
288template <
289 typename T, typename ExportJ = std::nullptr_t,
290 std::enable_if_t<!traits::is_matrix_or_array_v<T> && !traits::is_robust_residual_v<T>, int> = 0>
291auto Cauchy(const T &n2, typename traits::params_trait<T>::Scalar th2,
292 const ExportJ &Jx_or_bool = nullptr) {
293 if constexpr (traits::is_pair_v<T>) { // pair (loss, jacobian)
294 return Cauchy(n2.first, th2, n2.second);
295 } else {
296 using std::log;
297 using std::sqrt;
298 static_assert(!traits::is_matrix_or_array_v<T>, "`n` must be a scalar");
299 const auto s = T(1.0) + n2 / T(th2);
300 const auto l = T(th2) * log(s);
301 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
302 return l;
303 } else {
304 const T J_scale = std::max<T>(std::numeric_limits<T>::min(), T(1.0) / s);
305 if constexpr (traits::is_bool_v<ExportJ>)
306 return std::make_pair(l, J_scale);
307 else
308 return std::make_pair(l, (J_scale * Jx_or_bool).eval());
309 }
310 }
311}
312
316template <typename T, typename ExportJ = std::nullptr_t>
317auto CauchyLoss(const T &x, typename traits::params_trait<T>::Scalar th2,
318 const ExportJ &Jx_or_bool = nullptr) {
319 return Cauchy(SquaredL2(x, Jx_or_bool), th2);
320}
321
323
327template <typename T, typename ExportJ = std::nullptr_t>
328auto GemanMcClure(const T &n2, typename traits::params_trait<T>::Scalar th2,
329 const ExportJ &Jx_or_bool = nullptr) {
330 if constexpr (traits::is_pair_v<T>) { // pair (loss, jacobian)
331 return GemanMcClure(n2.first, th2, n2.second);
332 } else {
333 using std::log;
334 using std::sqrt;
335 static_assert(!traits::is_matrix_or_array_v<T>, "`n` must be a scalar");
336 const T e2_th2 = n2 + T(th2);
337 const auto l = n2 / (e2_th2);
338 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
339 return l;
340 } else {
341 const auto J_scale = th2 / (e2_th2 * e2_th2);
342 if constexpr (traits::is_bool_v<ExportJ>)
343 return std::make_pair(l, J_scale);
344 else
345 return std::make_pair(l, (J_scale * Jx_or_bool).eval());
346 }
347 }
348}
349
353template <typename T, typename ExportJ = std::nullptr_t>
354auto GemanMcClureLoss(const T &x, typename traits::params_trait<T>::Scalar th2,
355 const ExportJ &Jx_or_bool = nullptr) {
356 return GemanMcClure(SquaredL2(x, Jx_or_bool), th2);
357}
358
360
364template <typename T, typename ExportJ = std::nullptr_t>
365auto BlakeZisserman(const T &n2, typename traits::params_trait<T>::Scalar th2,
366 const ExportJ &Jx_or_bool = nullptr) {
367 if constexpr (traits::is_pair_v<T>) { // pair (loss, jacobian)
368 return BlakeZisserman(n2.first, th2, n2.second);
369 } else {
370 using std::exp;
371 using std::log;
372 using std::sqrt;
373 static_assert(!traits::is_matrix_or_array_v<T>, "`n` must be a scalar");
374 const auto epsilon = T(exp(-th2));
375 const auto l = -log(exp(-n2) + epsilon);
376 if constexpr (std::is_null_pointer_v<ExportJ>) { // no jacobians
377 return l;
378 } else {
379 const auto J_scale = T(1.0) / (epsilon * exp(n2) + T(1.0));
380 if constexpr (traits::is_bool_v<ExportJ>)
381 return std::make_pair(l, J_scale);
382 else
383 return std::make_pair(l, (J_scale * Jx_or_bool).eval());
384 }
385 }
386}
387
391template <typename T, typename ExportJ = std::nullptr_t>
392auto BlakeZissermanLoss(const T &x, typename traits::params_trait<T>::Scalar th2,
393 const ExportJ &Jx_or_bool = nullptr) {
394 return BlakeZisserman(SquaredL2(x, Jx_or_bool), th2);
395}
396
398
399} // namespace tinyopt::losses
400
401namespace tinyopt {
402
403template <typename... Args>
404auto Residuals(Args &&...args) {
405 return std::tuple<std::decay_t<Args>...>(std::forward<Args>(args)...);
406}
407
408} // namespace tinyopt
Definition activations.h:8
auto TruncatedLoss(const T &x, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
Hard Truncated L2 Loss: Clip the L2 loss of x to a max of th, also return the scale {0,...
Definition robust_norms.h:137
auto ArctanLoss(const T &x, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
Arctan: Return a scaled loss of 'x' such that , also return the scale or scaled jacobian if given as ...
Definition robust_norms.h:278
auto TukeyLoss(const T &x, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
Tukey: Return a scaled loss of 'x' such that , also return the scale or scaled jacobian if given as i...
Definition robust_norms.h:239
auto SquaredL2(const T &x)
Return the squared L2 norm of a vector or scalar (a.k.a Sum of Squares).
Definition norms.h:19
auto BlakeZisserman(const T &n2, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
BlakeZisserman: Return a scaled loss n' such that , also return the scale or scaled jacobian if given...
Definition robust_norms.h:365
auto Arctan(const T &n2, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
Arctan: Return a scaled loss , also return the scale or scaled jacobian if given as input Jx_or_bool.
Definition robust_norms.h:250
auto GemanMcClureLoss(const T &x, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
GemanMcClure: Return a scaled loss of 'x' such that , also return the scale or scaled jacobian if giv...
Definition robust_norms.h:354
auto HuberLoss(const T &x, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
Huber: Return a scaled loss of 'x' such that , also return the scale {1, th/n} or scaled jacobian if ...
Definition robust_norms.h:190
auto Truncated(const T &n2, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
Hard Truncation: Clip the given loss n to a max of th, also return the scale {0 1}...
Definition robust_norms.h:112
auto CauchyLoss(const T &x, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
Cauchy: Return a scaled loss of 'x' such that , also return the scale or scaled jacobian if given as ...
Definition robust_norms.h:317
auto GemanMcClure(const T &n2, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
GemanMcClure: Return a scaled loss , also return the scale or scaled jacobian if given as input Jx_or...
Definition robust_norms.h:328
auto BlakeZissermanLoss(const T &x, typename traits::params_trait< T >::Scalar th2, const ExportJ &Jx_or_bool=nullptr)
BlakeZisserman: Return a scaled loss of 'x' such that , also return the scale or scaled jacobian if g...
Definition robust_norms.h:392
Definition robust_norms.h:20
Definition robust_norms.h:19
Definition robust_norms.h:24
Definition robust_norms.h:21
Definition robust_norms.h:58