tinyopt 700
Tinyopt, a lightweight, header only optimization library
Loading...
Searching...
No Matches
mahalanobis.h
1// Copyright 2026 Julien Michot.
2// SPDX-License-Identifier: Apache-2.0
3
4#pragma once
5
6#include <tinyopt/math.h>
7#include <tinyopt/traits.h>
8
9namespace tinyopt::losses {
10
15
17template <typename T, typename Cov_t, typename ExportJ = std::nullptr_t>
18auto MahaSquaredNorm(const T &x, const Cov_t &cov_or_var, const ExportJ &Jx_or_bool = nullptr,
19 bool add_scale = true) {
20 using Scalar = typename traits::params_trait<T>::Scalar;
21 constexpr Index Dims = traits::params_trait<T>::Dims;
22 if constexpr (traits::is_scalar_v<T>) { // scalar
23 const T s = cov_or_var < FloatEpsilon<T>() ? T(1.0) : T(T(1.0) / cov_or_var);
24 const T n2 = x * x * s; // same as sqrt(x²*s)
25 if constexpr (traits::is_bool_v<ExportJ>) {
26 const T J = add_scale ? Scalar(2) * s * x : s * x;
27 return std::make_pair(n2, J);
28 } else if constexpr (!traits::is_nullptr_v<ExportJ>) {
29 const T J = add_scale ? Scalar(2) * s * x : s * x;
30 return std::make_pair(n2, T(J * Jx_or_bool));
31 } else {
32 return n2;
33 }
34 } else if constexpr (traits::is_nullptr_v<ExportJ>) { // x is a vector, no jacobian export
35 const auto cov_vars = cov_or_var.template cast<Scalar>().eval();
36 if (x.cols() != 1) throw std::invalid_argument("'x' must be a vector");
37 Scalar n2 = Scalar(0.0);
38 if constexpr (traits::params_trait<Cov_t>::ColsAtCompileTime == 1) { // variances
39 assert(x.size() == cov_vars.size());
40 n2 = x.dot(cov_vars.cwiseInverse().asDiagonal() * x);
41 } else { // covariance matrix
42 if (cov_vars.cols() > 1) {
43 const auto I = InvCov(cov_vars);
44 if (!I.has_value())
45 throw std::invalid_argument("Covariance is not invertible, make sure it is");
46 n2 = x.dot(I.value() * x);
48 Dynamic) { // variances
49 assert(x.size() == cov_vars.size());
50 n2 = x.dot(cov_vars.cwiseInverse().asDiagonal() * x);
51 }
52 }
53 return n2;
54 } else { // x is a vector, exporting jacobian
55 if (x.cols() != 1) throw std::invalid_argument("'x' must be a vector");
56 const auto cov_vars = cov_or_var.template cast<Scalar>().eval();
57 Scalar n2 = Scalar(0.0);
58 Vector<Scalar, Dims> Jt(x.size());
59
60 if constexpr (traits::params_trait<Cov_t>::ColsAtCompileTime == 1) { // variances
61 assert(x.size() == cov_vars.size());
62 Jt = cov_vars.cwiseInverse().asDiagonal() * x;
63 n2 = x.dot(Jt);
64 } else { // covariance matrix
65 if (cov_vars.cols() > 1) {
66 const auto I = InvCov(cov_vars);
67 if (!I.has_value())
68 throw std::invalid_argument("Covariance is not invertible, make sure it is");
69 Jt = I.value() * x;
70 n2 = x.dot(Jt);
72 Dynamic) { // variances
73 Jt = cov_vars.cwiseInverse().asDiagonal() * x;
74 n2 = x.dot(Jt);
75 }
76 }
77 if (add_scale) Jt *= Scalar(2);
78 if constexpr (traits::is_bool_v<ExportJ>)
79 return std::make_pair(n2, Jt.transpose().eval());
80 else
81 return std::make_pair(n2, (Jt.transpose() * Jx_or_bool).eval());
82 }
83}
84
86template <typename T, typename Cov_t, typename ExportJ = std::nullptr_t>
87auto MahaNorm(const T &x, const Cov_t &cov_or_var, const ExportJ &Jx_or_bool = nullptr) {
88 using Scalar = typename traits::params_trait<T>::Scalar;
89 using std::sqrt;
90 constexpr bool add_scale = false;
91 if constexpr (traits::is_nullptr_v<ExportJ>) {
92 const auto &n2 = MahaSquaredNorm(x, cov_or_var, nullptr, add_scale);
93 return sqrt(n2);
94 } else if constexpr (traits::is_scalar_v<T>) { // scalar
95 using std::abs;
96 const auto &[n2, J] = MahaSquaredNorm(x, cov_or_var, Jx_or_bool, add_scale);
97 const auto n = sqrt(n2);
98 const auto s = n > FloatEpsilon<Scalar>() ? n : Scalar(1);
99 return std::make_pair(n, J / s);
100 } else {
101 const auto &[n2, J] = MahaSquaredNorm(x, cov_or_var, Jx_or_bool, add_scale);
102 const auto n = sqrt(n2);
103 const auto s = n > FloatEpsilon<Scalar>() ? n : Scalar(1);
104 return std::make_pair(n, (J / s).eval());
105 }
106}
107
110template <typename Derived, typename Cov_t, typename ExportJ = std::nullptr_t>
111auto MahaWhitened(const MatrixBase<Derived> &res, const Cov_t &cov_stevs,
112 const ExportJ &Jx_or_bool = nullptr) {
113 using Scalar = typename Derived::Scalar;
114 if constexpr (traits::is_scalar_v<Cov_t>) { // iso
115 const auto res2 = (res / cov_stevs).eval();
116 // Jacobian
117 if constexpr (traits::is_nullptr_v<ExportJ>)
118 return res2;
119 else if constexpr (traits::is_bool_v<ExportJ>)
120 return std::make_pair(res2, 1.0 / cov_stevs);
121 else
122 return std::make_pair(res2, (Jx_or_bool / cov_stevs).eval());
123
125 1) { // standard deviations
126 assert(res.size() == cov_stevs.size());
127 const auto res2 = (res.array() / cov_stevs.array()).matrix().eval();
128 using Mat = Matrix<Scalar, Derived::RowsAtCompileTime, Derived::RowsAtCompileTime>;
129
130 // Jacobian
131 if constexpr (traits::is_nullptr_v<ExportJ>)
132 return res2;
133 else if constexpr (traits::is_bool_v<ExportJ>)
134 return std::make_pair(res2, Mat(cov_stevs.cwiseInverse().asDiagonal()));
135 else
136 return std::make_pair(res2, (cov_stevs.cwiseInverse().asDiagonal() * Jx_or_bool).eval());
137
138 } else { // cov matrix
139 const auto cov = cov_stevs.template cast<Scalar>().eval();
140 {
141 static constexpr Index Dims = Derived::RowsAtCompileTime;
142 using Mat = Matrix<Scalar, Dims, Dims>;
143 const auto chol = Eigen::SelfAdjointView<const Mat, Upper>(cov).llt();
144 const auto res2 = chol.matrixL().solve(res).eval();
145
146 // Jacobian
147 if constexpr (traits::is_nullptr_v<ExportJ>)
148 return res2;
149 else if constexpr (traits::is_bool_v<ExportJ>)
150 return std::make_pair(res2, (chol.matrixL().solve(Mat::Identity())).eval());
151 else
152 return std::make_pair(res2, (chol.matrixL().solve(Jx_or_bool)).eval());
153
154 } // TODO if constexpr (traits::params_trait<Cov_t>::ColsAtCompileTime == Dynamic)
155 }
156}
157
160template <typename Derived, typename DerivedC, typename ExportJ = std::nullptr_t>
161auto MahaWhitenedInfoU(const MatrixBase<Derived> &res, const MatrixBase<DerivedC> &U,
162 const ExportJ &Jx_or_bool = nullptr) {
163 auto UU = U.template triangularView<Upper>();
164 const auto res2 = (UU * res).eval();
165 // Jacobian
166 if constexpr (traits::is_nullptr_v<ExportJ>)
167 return res2;
168 else if constexpr (traits::is_bool_v<ExportJ>)
169 return std::make_pair(res2, U.eval());
170 else
171 return std::make_pair(res2, (UU * Jx_or_bool).eval());
172}
173
175
176} // namespace tinyopt::losses
Definition activations.h:8
auto MahaWhitened(const MatrixBase< Derived > &res, const Cov_t &cov_stevs, const ExportJ &Jx_or_bool=nullptr)
Definition mahalanobis.h:111
auto MahaNorm(const T &x, const Cov_t &cov_or_var, const ExportJ &Jx_or_bool=nullptr)
Compute the Mahalanobis distance of ´x´ with a covariance cov: n(x) = ||x||Σ
Definition mahalanobis.h:87
auto MahaWhitenedInfoU(const MatrixBase< Derived > &res, const MatrixBase< DerivedC > &U, const ExportJ &Jx_or_bool=nullptr)
Definition mahalanobis.h:161
auto MahaSquaredNorm(const T &x, const Cov_t &cov_or_var, const ExportJ &Jx_or_bool=nullptr, bool add_scale=true)
Compute the Squared Mahalanobis distance of ´x´ with a covariance cov: n(x) = ||x||Σ²
Definition mahalanobis.h:18
Definition parameter_traits.h:15