signal_statistics.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // (C) Copyright Nick Thompson 2018.
  2. // Use, modification and distribution are subject to the
  3. // Boost Software License, Version 1.0. (See accompanying file
  4. // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. #ifndef BOOST_MATH_TOOLS_SIGNAL_STATISTICS_HPP
  6. #define BOOST_MATH_TOOLS_SIGNAL_STATISTICS_HPP
  7. #include <algorithm>
  8. #include <iterator>
  9. #include <boost/type_traits/is_complex.hpp>
  10. #include <boost/assert.hpp>
  11. #include <boost/multiprecision/detail/number_base.hpp>
  12. #include <boost/math/tools/roots.hpp>
  13. #include <boost/math/tools/univariate_statistics.hpp>
  14. namespace boost::math::tools {
  15. template<class ForwardIterator>
  16. auto absolute_gini_coefficient(ForwardIterator first, ForwardIterator last)
  17. {
  18. using std::abs;
  19. using RealOrComplex = typename std::iterator_traits<ForwardIterator>::value_type;
  20. BOOST_ASSERT_MSG(first != last && std::next(first) != last, "Computation of the Gini coefficient requires at least two samples.");
  21. std::sort(first, last, [](RealOrComplex a, RealOrComplex b) { return abs(b) > abs(a); });
  22. decltype(abs(*first)) i = 1;
  23. decltype(abs(*first)) num = 0;
  24. decltype(abs(*first)) denom = 0;
  25. for (auto it = first; it != last; ++it)
  26. {
  27. decltype(abs(*first)) tmp = abs(*it);
  28. num += tmp*i;
  29. denom += tmp;
  30. ++i;
  31. }
  32. // If the l1 norm is zero, all elements are zero, so every element is the same.
  33. if (denom == 0)
  34. {
  35. decltype(abs(*first)) zero = 0;
  36. return zero;
  37. }
  38. return ((2*num)/denom - i)/(i-1);
  39. }
  40. template<class RandomAccessContainer>
  41. inline auto absolute_gini_coefficient(RandomAccessContainer & v)
  42. {
  43. return boost::math::tools::absolute_gini_coefficient(v.begin(), v.end());
  44. }
  45. template<class ForwardIterator>
  46. auto sample_absolute_gini_coefficient(ForwardIterator first, ForwardIterator last)
  47. {
  48. size_t n = std::distance(first, last);
  49. return n*boost::math::tools::absolute_gini_coefficient(first, last)/(n-1);
  50. }
  51. template<class RandomAccessContainer>
  52. inline auto sample_absolute_gini_coefficient(RandomAccessContainer & v)
  53. {
  54. return boost::math::tools::sample_absolute_gini_coefficient(v.begin(), v.end());
  55. }
  56. // The Hoyer sparsity measure is defined in:
  57. // https://arxiv.org/pdf/0811.4706.pdf
  58. template<class ForwardIterator>
  59. auto hoyer_sparsity(const ForwardIterator first, const ForwardIterator last)
  60. {
  61. using T = typename std::iterator_traits<ForwardIterator>::value_type;
  62. using std::abs;
  63. using std::sqrt;
  64. BOOST_ASSERT_MSG(first != last && std::next(first) != last, "Computation of the Hoyer sparsity requires at least two samples.");
  65. if constexpr (std::is_unsigned<T>::value)
  66. {
  67. T l1 = 0;
  68. T l2 = 0;
  69. size_t n = 0;
  70. for (auto it = first; it != last; ++it)
  71. {
  72. l1 += *it;
  73. l2 += (*it)*(*it);
  74. n += 1;
  75. }
  76. double rootn = sqrt(n);
  77. return (rootn - l1/sqrt(l2) )/ (rootn - 1);
  78. }
  79. else {
  80. decltype(abs(*first)) l1 = 0;
  81. decltype(abs(*first)) l2 = 0;
  82. // We wouldn't need to count the elements if it was a random access iterator,
  83. // but our only constraint is that it's a forward iterator.
  84. size_t n = 0;
  85. for (auto it = first; it != last; ++it)
  86. {
  87. decltype(abs(*first)) tmp = abs(*it);
  88. l1 += tmp;
  89. l2 += tmp*tmp;
  90. n += 1;
  91. }
  92. if constexpr (std::is_integral<T>::value)
  93. {
  94. double rootn = sqrt(n);
  95. return (rootn - l1/sqrt(l2) )/ (rootn - 1);
  96. }
  97. else
  98. {
  99. decltype(abs(*first)) rootn = sqrt(static_cast<decltype(abs(*first))>(n));
  100. return (rootn - l1/sqrt(l2) )/ (rootn - 1);
  101. }
  102. }
  103. }
  104. template<class Container>
  105. inline auto hoyer_sparsity(Container const & v)
  106. {
  107. return boost::math::tools::hoyer_sparsity(v.cbegin(), v.cend());
  108. }
  109. template<class Container>
  110. auto oracle_snr(Container const & signal, Container const & noisy_signal)
  111. {
  112. using Real = typename Container::value_type;
  113. BOOST_ASSERT_MSG(signal.size() == noisy_signal.size(),
  114. "Signal and noisy_signal must be have the same number of elements.");
  115. if constexpr (std::is_integral<Real>::value)
  116. {
  117. double numerator = 0;
  118. double denominator = 0;
  119. for (size_t i = 0; i < signal.size(); ++i)
  120. {
  121. numerator += signal[i]*signal[i];
  122. denominator += (noisy_signal[i] - signal[i])*(noisy_signal[i] - signal[i]);
  123. }
  124. if (numerator == 0 && denominator == 0)
  125. {
  126. return std::numeric_limits<double>::quiet_NaN();
  127. }
  128. if (denominator == 0)
  129. {
  130. return std::numeric_limits<double>::infinity();
  131. }
  132. return numerator/denominator;
  133. }
  134. else if constexpr (boost::is_complex<Real>::value ||
  135. boost::multiprecision::number_category<Real>::value == boost::multiprecision::number_kind_complex)
  136. {
  137. using std::norm;
  138. typename Real::value_type numerator = 0;
  139. typename Real::value_type denominator = 0;
  140. for (size_t i = 0; i < signal.size(); ++i)
  141. {
  142. numerator += norm(signal[i]);
  143. denominator += norm(noisy_signal[i] - signal[i]);
  144. }
  145. if (numerator == 0 && denominator == 0)
  146. {
  147. return std::numeric_limits<typename Real::value_type>::quiet_NaN();
  148. }
  149. if (denominator == 0)
  150. {
  151. return std::numeric_limits<typename Real::value_type>::infinity();
  152. }
  153. return numerator/denominator;
  154. }
  155. else
  156. {
  157. Real numerator = 0;
  158. Real denominator = 0;
  159. for (size_t i = 0; i < signal.size(); ++i)
  160. {
  161. numerator += signal[i]*signal[i];
  162. denominator += (signal[i] - noisy_signal[i])*(signal[i] - noisy_signal[i]);
  163. }
  164. if (numerator == 0 && denominator == 0)
  165. {
  166. return std::numeric_limits<Real>::quiet_NaN();
  167. }
  168. if (denominator == 0)
  169. {
  170. return std::numeric_limits<Real>::infinity();
  171. }
  172. return numerator/denominator;
  173. }
  174. }
  175. template<class Container>
  176. auto mean_invariant_oracle_snr(Container const & signal, Container const & noisy_signal)
  177. {
  178. using Real = typename Container::value_type;
  179. BOOST_ASSERT_MSG(signal.size() == noisy_signal.size(), "Signal and noisy signal must be have the same number of elements.");
  180. Real mu = boost::math::tools::mean(signal);
  181. Real numerator = 0;
  182. Real denominator = 0;
  183. for (size_t i = 0; i < signal.size(); ++i)
  184. {
  185. Real tmp = signal[i] - mu;
  186. numerator += tmp*tmp;
  187. denominator += (signal[i] - noisy_signal[i])*(signal[i] - noisy_signal[i]);
  188. }
  189. if (numerator == 0 && denominator == 0)
  190. {
  191. return std::numeric_limits<Real>::quiet_NaN();
  192. }
  193. if (denominator == 0)
  194. {
  195. return std::numeric_limits<Real>::infinity();
  196. }
  197. return numerator/denominator;
  198. }
  199. template<class Container>
  200. auto mean_invariant_oracle_snr_db(Container const & signal, Container const & noisy_signal)
  201. {
  202. using std::log10;
  203. return 10*log10(boost::math::tools::mean_invariant_oracle_snr(signal, noisy_signal));
  204. }
  205. // Follows the definition of SNR given in Mallat, A Wavelet Tour of Signal Processing, equation 11.16.
  206. template<class Container>
  207. auto oracle_snr_db(Container const & signal, Container const & noisy_signal)
  208. {
  209. using std::log10;
  210. return 10*log10(boost::math::tools::oracle_snr(signal, noisy_signal));
  211. }
  212. // A good reference on the M2M4 estimator:
  213. // D. R. Pauluzzi and N. C. Beaulieu, "A comparison of SNR estimation techniques for the AWGN channel," IEEE Trans. Communications, Vol. 48, No. 10, pp. 1681-1691, 2000.
  214. // A nice python implementation:
  215. // https://github.com/gnuradio/gnuradio/blob/master/gr-digital/examples/snr_estimators.py
  216. template<class ForwardIterator>
  217. auto m2m4_snr_estimator(ForwardIterator first, ForwardIterator last, decltype(*first) estimated_signal_kurtosis=1, decltype(*first) estimated_noise_kurtosis=3)
  218. {
  219. BOOST_ASSERT_MSG(estimated_signal_kurtosis > 0, "The estimated signal kurtosis must be positive");
  220. BOOST_ASSERT_MSG(estimated_noise_kurtosis > 0, "The estimated noise kurtosis must be positive.");
  221. using Real = typename std::iterator_traits<ForwardIterator>::value_type;
  222. using std::sqrt;
  223. if constexpr (std::is_floating_point<Real>::value ||
  224. boost::multiprecision::number_category<Real>::value == boost::multiprecision::number_kind_floating_point)
  225. {
  226. // If we first eliminate N, we obtain the quadratic equation:
  227. // (ka+kw-6)S^2 + 2M2(3-kw)S + kw*M2^2 - M4 = 0 =: a*S^2 + bs*N + cs = 0
  228. // If we first eliminate S, we obtain the quadratic equation:
  229. // (ka+kw-6)N^2 + 2M2(3-ka)N + ka*M2^2 - M4 = 0 =: a*N^2 + bn*N + cn = 0
  230. // I believe these equations are totally independent quadratics;
  231. // if one has a complex solution it is not necessarily the case that the other must also.
  232. // However, I can't prove that, so there is a chance that this does unnecessary work.
  233. // Future improvements: There are algorithms which can solve quadratics much more effectively than the naive implementation found here.
  234. // See: https://stackoverflow.com/questions/48979861/numerically-stable-method-for-solving-quadratic-equations/50065711#50065711
  235. auto [M1, M2, M3, M4] = boost::math::tools::first_four_moments(first, last);
  236. if (M4 == 0)
  237. {
  238. // The signal is constant. There is no noise:
  239. return std::numeric_limits<Real>::infinity();
  240. }
  241. // Change to notation in Pauluzzi, equation 41:
  242. auto kw = estimated_noise_kurtosis;
  243. auto ka = estimated_signal_kurtosis;
  244. // A common case, since it's the default:
  245. Real a = (ka+kw-6);
  246. Real bs = 2*M2*(3-kw);
  247. Real cs = kw*M2*M2 - M4;
  248. Real bn = 2*M2*(3-ka);
  249. Real cn = ka*M2*M2 - M4;
  250. auto [S0, S1] = boost::math::tools::quadratic_roots(a, bs, cs);
  251. if (S1 > 0)
  252. {
  253. auto N = M2 - S1;
  254. if (N > 0)
  255. {
  256. return S1/N;
  257. }
  258. if (S0 > 0)
  259. {
  260. N = M2 - S0;
  261. if (N > 0)
  262. {
  263. return S0/N;
  264. }
  265. }
  266. }
  267. auto [N0, N1] = boost::math::tools::quadratic_roots(a, bn, cn);
  268. if (N1 > 0)
  269. {
  270. auto S = M2 - N1;
  271. if (S > 0)
  272. {
  273. return S/N1;
  274. }
  275. if (N0 > 0)
  276. {
  277. S = M2 - N0;
  278. if (S > 0)
  279. {
  280. return S/N0;
  281. }
  282. }
  283. }
  284. // This happens distressingly often. It's a limitation of the method.
  285. return std::numeric_limits<Real>::quiet_NaN();
  286. }
  287. else
  288. {
  289. BOOST_ASSERT_MSG(false, "The M2M4 estimator has not been implemented for this type.");
  290. return std::numeric_limits<Real>::quiet_NaN();
  291. }
  292. }
  293. template<class Container>
  294. inline auto m2m4_snr_estimator(Container const & noisy_signal, typename Container::value_type estimated_signal_kurtosis=1, typename Container::value_type estimated_noise_kurtosis=3)
  295. {
  296. return m2m4_snr_estimator(noisy_signal.cbegin(), noisy_signal.cend(), estimated_signal_kurtosis, estimated_noise_kurtosis);
  297. }
  298. template<class ForwardIterator>
  299. inline auto m2m4_snr_estimator_db(ForwardIterator first, ForwardIterator last, decltype(*first) estimated_signal_kurtosis=1, decltype(*first) estimated_noise_kurtosis=3)
  300. {
  301. using std::log10;
  302. return 10*log10(m2m4_snr_estimator(first, last, estimated_signal_kurtosis, estimated_noise_kurtosis));
  303. }
  304. template<class Container>
  305. inline auto m2m4_snr_estimator_db(Container const & noisy_signal, typename Container::value_type estimated_signal_kurtosis=1, typename Container::value_type estimated_noise_kurtosis=3)
  306. {
  307. using std::log10;
  308. return 10*log10(m2m4_snr_estimator(noisy_signal, estimated_signal_kurtosis, estimated_noise_kurtosis));
  309. }
  310. }
  311. #endif