signal_statistics_test.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. * (C) Copyright Nick Thompson 2018.
  3. * Use, modification and distribution are subject to the
  4. * Boost Software License, Version 1.0. (See accompanying file
  5. * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. */
  7. #include <vector>
  8. #include <array>
  9. #include <forward_list>
  10. #include <algorithm>
  11. #include <random>
  12. #include <boost/core/lightweight_test.hpp>
  13. #include <boost/numeric/ublas/vector.hpp>
  14. #include <boost/math/constants/constants.hpp>
  15. #include <boost/math/statistics/univariate_statistics.hpp>
  16. #include <boost/math/statistics/signal_statistics.hpp>
  17. #include <boost/multiprecision/cpp_bin_float.hpp>
  18. #include <boost/multiprecision/cpp_complex.hpp>
  19. using std::abs;
  20. using boost::multiprecision::cpp_bin_float_50;
  21. using boost::multiprecision::cpp_complex_50;
  22. using boost::math::constants::two_pi;
  23. /*
  24. * Test checklist:
  25. * 1) Does it work with multiprecision?
  26. * 2) Does it work with .cbegin()/.cend() if the data is not altered?
  27. * 3) Does it work with ublas and std::array? (Checking Eigen and Armadillo will make the CI system really unhappy.)
  28. * 4) Does it work with std::forward_list if a forward iterator is all that is required?
  29. * 5) Does it work with complex data if complex data is sensible?
  30. * 6) Does it work with integer data if sensible?
  31. */
  32. template<class Real>
  33. void test_hoyer_sparsity()
  34. {
  35. using std::sqrt;
  36. Real tol = 5*std::numeric_limits<Real>::epsilon();
  37. std::vector<Real> v{1,0,0};
  38. Real hs = boost::math::statistics::hoyer_sparsity(v.begin(), v.end());
  39. BOOST_TEST(abs(hs - 1) < tol);
  40. hs = boost::math::statistics::hoyer_sparsity(v);
  41. BOOST_TEST(abs(hs - 1) < tol);
  42. // Does it work with constant iterators?
  43. hs = boost::math::statistics::hoyer_sparsity(v.cbegin(), v.cend());
  44. BOOST_TEST(abs(hs - 1) < tol);
  45. v[0] = 1;
  46. v[1] = 1;
  47. v[2] = 1;
  48. hs = boost::math::statistics::hoyer_sparsity(v.cbegin(), v.cend());
  49. BOOST_TEST(abs(hs) < tol);
  50. std::array<Real, 3> w{1,1,1};
  51. hs = boost::math::statistics::hoyer_sparsity(w);
  52. BOOST_TEST(abs(hs) < tol);
  53. // Now some statistics:
  54. // If x_i ~ Unif(0,1), E[x_i] = 1/2, E[x_i^2] = 1/3.
  55. // Therefore, E[||x||_1] = N/2, E[||x||_2] = sqrt(N/3),
  56. // and hoyer_sparsity(x) is close to (1-sqrt(3)/2)/(1-1/sqrt(N))
  57. std::mt19937 gen(82);
  58. std::uniform_real_distribution<long double> dis(0, 1);
  59. v.resize(5000);
  60. for (size_t i = 0; i < v.size(); ++i) {
  61. v[i] = dis(gen);
  62. }
  63. hs = boost::math::statistics::hoyer_sparsity(v);
  64. Real expected = (1.0 - boost::math::constants::root_three<Real>()/2)/(1.0 - 1.0/sqrt(v.size()));
  65. BOOST_TEST(abs(expected - hs) < 0.01);
  66. // Does it work with a forward list?
  67. std::forward_list<Real> u1{1, 1, 1};
  68. hs = boost::math::statistics::hoyer_sparsity(u1);
  69. BOOST_TEST(abs(hs) < tol);
  70. // Does it work with a boost ublas vector?
  71. boost::numeric::ublas::vector<Real> u2(3);
  72. u2[0] = 1;
  73. u2[1] = 1;
  74. u2[2] = 1;
  75. hs = boost::math::statistics::hoyer_sparsity(u2);
  76. BOOST_TEST(abs(hs) < tol);
  77. }
  78. template<class Z>
  79. void test_integer_hoyer_sparsity()
  80. {
  81. using std::sqrt;
  82. double tol = 5*std::numeric_limits<double>::epsilon();
  83. std::vector<Z> v{1,0,0};
  84. double hs = boost::math::statistics::hoyer_sparsity(v);
  85. BOOST_TEST(abs(hs - 1) < tol);
  86. v[0] = 1;
  87. v[1] = 1;
  88. v[2] = 1;
  89. hs = boost::math::statistics::hoyer_sparsity(v);
  90. BOOST_TEST(abs(hs) < tol);
  91. }
  92. template<class Complex>
  93. void test_complex_hoyer_sparsity()
  94. {
  95. typedef typename Complex::value_type Real;
  96. using std::sqrt;
  97. Real tol = 5*std::numeric_limits<Real>::epsilon();
  98. std::vector<Complex> v{{0,1}, {0, 0}, {0,0}};
  99. Real hs = boost::math::statistics::hoyer_sparsity(v.begin(), v.end());
  100. BOOST_TEST(abs(hs - 1) < tol);
  101. hs = boost::math::statistics::hoyer_sparsity(v);
  102. BOOST_TEST(abs(hs - 1) < tol);
  103. // Does it work with constant iterators?
  104. hs = boost::math::statistics::hoyer_sparsity(v.cbegin(), v.cend());
  105. BOOST_TEST(abs(hs - 1) < tol);
  106. // All are the same magnitude:
  107. v[0] = {0, 1};
  108. v[1] = {1, 0};
  109. v[2] = {0,-1};
  110. hs = boost::math::statistics::hoyer_sparsity(v.cbegin(), v.cend());
  111. BOOST_TEST(abs(hs) < tol);
  112. }
  113. template<class Real>
  114. void test_absolute_gini_coefficient()
  115. {
  116. using boost::math::statistics::absolute_gini_coefficient;
  117. using boost::math::statistics::sample_absolute_gini_coefficient;
  118. Real tol = std::numeric_limits<Real>::epsilon();
  119. std::vector<Real> v{-1,0,0};
  120. Real gini = sample_absolute_gini_coefficient(v.begin(), v.end());
  121. BOOST_TEST(abs(gini - 1) < tol);
  122. gini = absolute_gini_coefficient(v);
  123. BOOST_TEST(abs(gini - Real(2)/Real(3)) < tol);
  124. v[0] = 1;
  125. v[1] = -1;
  126. v[2] = 1;
  127. gini = absolute_gini_coefficient(v.begin(), v.end());
  128. BOOST_TEST(abs(gini) < tol);
  129. gini = sample_absolute_gini_coefficient(v.begin(), v.end());
  130. BOOST_TEST(abs(gini) < tol);
  131. std::vector<std::complex<Real>> w(128);
  132. std::complex<Real> i{0,1};
  133. for(size_t k = 0; k < w.size(); ++k)
  134. {
  135. w[k] = exp(i*static_cast<Real>(k)/static_cast<Real>(w.size()));
  136. }
  137. gini = absolute_gini_coefficient(w.begin(), w.end());
  138. BOOST_TEST(abs(gini) < tol);
  139. gini = sample_absolute_gini_coefficient(w.begin(), w.end());
  140. BOOST_TEST(abs(gini) < tol);
  141. // The population Gini index is invariant under "cloning": If w = v \oplus v, then G(w) = G(v).
  142. // We use the sample Gini index, so we need to rescale
  143. std::vector<Real> u(1000);
  144. std::mt19937 gen(35);
  145. std::uniform_real_distribution<long double> dis(0, 50);
  146. for (size_t i = 0; i < u.size()/2; ++i)
  147. {
  148. u[i] = dis(gen);
  149. }
  150. for (size_t i = 0; i < u.size()/2; ++i)
  151. {
  152. u[i + u.size()/2] = u[i];
  153. }
  154. Real population_gini1 = absolute_gini_coefficient(u.begin(), u.begin() + u.size()/2);
  155. Real population_gini2 = absolute_gini_coefficient(u.begin(), u.end());
  156. BOOST_TEST(abs(population_gini1 - population_gini2) < 10*tol);
  157. // The Gini coefficient of a uniform distribution is (b-a)/(3*(b+a)), see https://en.wikipedia.org/wiki/Gini_coefficient
  158. Real expected = (dis.b() - dis.a() )/(3*(dis.a() + dis.b()));
  159. BOOST_TEST(abs(expected - population_gini1) < 0.01);
  160. std::exponential_distribution<long double> exp_dis(1);
  161. for (size_t i = 0; i < u.size(); ++i)
  162. {
  163. u[i] = exp_dis(gen);
  164. }
  165. population_gini2 = absolute_gini_coefficient(u);
  166. BOOST_TEST(abs(population_gini2 - 0.5) < 0.01);
  167. }
  168. template<class Real>
  169. void test_oracle_snr()
  170. {
  171. using std::abs;
  172. Real tol = 100*std::numeric_limits<Real>::epsilon();
  173. size_t length = 100;
  174. std::vector<Real> signal(length, 1);
  175. std::vector<Real> noisy_signal = signal;
  176. noisy_signal[0] += 1;
  177. Real snr = boost::math::statistics::oracle_snr(signal, noisy_signal);
  178. Real snr_db = boost::math::statistics::oracle_snr_db(signal, noisy_signal);
  179. BOOST_TEST(abs(snr - length) < tol);
  180. BOOST_TEST(abs(snr_db - 10*log10(length)) < tol);
  181. }
  182. template<class Z>
  183. void test_integer_oracle_snr()
  184. {
  185. double tol = std::numeric_limits<double>::epsilon();
  186. size_t length = 100;
  187. std::vector<Z> signal(length, 1);
  188. std::vector<Z> noisy_signal = signal;
  189. noisy_signal[0] += 1;
  190. double snr = boost::math::statistics::oracle_snr(signal, noisy_signal);
  191. double snr_db = boost::math::statistics::oracle_snr_db(signal, noisy_signal);
  192. BOOST_TEST(abs(snr - length) < tol);
  193. BOOST_TEST(abs(snr_db - 10*log10(length)) < tol);
  194. }
  195. template<class Complex>
  196. void test_complex_oracle_snr()
  197. {
  198. using Real = typename Complex::value_type;
  199. using std::abs;
  200. using std::log10;
  201. Real tol = 100*std::numeric_limits<Real>::epsilon();
  202. size_t length = 100;
  203. std::vector<Complex> signal(length, {1,0});
  204. std::vector<Complex> noisy_signal = signal;
  205. noisy_signal[0] += Complex(1,0);
  206. Real snr = boost::math::statistics::oracle_snr(signal, noisy_signal);
  207. Real snr_db = boost::math::statistics::oracle_snr_db(signal, noisy_signal);
  208. BOOST_TEST(abs(snr - length) < tol);
  209. BOOST_TEST(abs(snr_db - 10*log10(length)) < tol);
  210. }
  211. template<class Real>
  212. void test_m2m4_snr_estimator()
  213. {
  214. Real tol = std::numeric_limits<Real>::epsilon();
  215. std::vector<Real> signal(5000, 1);
  216. std::vector<Real> x(signal.size());
  217. std::mt19937 gen(18);
  218. std::normal_distribution<Real> dis{0, 1.0};
  219. for (size_t i = 0; i < x.size(); ++i)
  220. {
  221. signal[i] = 5*sin(100*6.28*i/x.size());
  222. x[i] = signal[i] + dis(gen);
  223. }
  224. // Kurtosis of a sine wave is 1.5:
  225. auto m2m4_db = boost::math::statistics::m2m4_snr_estimator_db(x, 1.5);
  226. auto oracle_snr_db = boost::math::statistics::mean_invariant_oracle_snr_db(signal, x);
  227. BOOST_TEST(abs(m2m4_db - oracle_snr_db) < 0.2);
  228. std::uniform_real_distribution<Real> uni_dis{-1,1};
  229. for (size_t i = 0; i < x.size(); ++i)
  230. {
  231. x[i] = signal[i] + uni_dis(gen);
  232. }
  233. // Kurtosis of continuous uniform distribution over [-1,1] is 1.8:
  234. m2m4_db = boost::math::statistics::m2m4_snr_estimator_db(x, 1.5, 1.8);
  235. oracle_snr_db = boost::math::statistics::mean_invariant_oracle_snr_db(signal, x);
  236. // The performance depends on the exact numbers generated by the distribution, but this isn't bad:
  237. BOOST_TEST(abs(m2m4_db - oracle_snr_db) < 0.2);
  238. // The SNR estimator should be scale invariant.
  239. // If x has snr y, then kx should have snr y.
  240. Real ka = 1.5;
  241. Real kw = 1.8;
  242. auto m2m4 = boost::math::statistics::m2m4_snr_estimator(x.begin(), x.end(), ka, kw);
  243. for(size_t i = 0; i < x.size(); ++i)
  244. {
  245. x[i] *= 4096;
  246. }
  247. auto m2m4_2 = boost::math::statistics::m2m4_snr_estimator(x.begin(), x.end(), ka, kw);
  248. BOOST_TEST(abs(m2m4 - m2m4_2) < tol);
  249. }
  250. int main()
  251. {
  252. test_absolute_gini_coefficient<float>();
  253. test_absolute_gini_coefficient<double>();
  254. test_absolute_gini_coefficient<long double>();
  255. test_hoyer_sparsity<float>();
  256. test_hoyer_sparsity<double>();
  257. test_hoyer_sparsity<long double>();
  258. test_hoyer_sparsity<cpp_bin_float_50>();
  259. test_integer_hoyer_sparsity<int>();
  260. test_integer_hoyer_sparsity<unsigned>();
  261. test_complex_hoyer_sparsity<std::complex<float>>();
  262. test_complex_hoyer_sparsity<std::complex<double>>();
  263. test_complex_hoyer_sparsity<std::complex<long double>>();
  264. test_complex_hoyer_sparsity<cpp_complex_50>();
  265. test_oracle_snr<float>();
  266. test_oracle_snr<double>();
  267. test_oracle_snr<long double>();
  268. test_oracle_snr<cpp_bin_float_50>();
  269. test_integer_oracle_snr<int>();
  270. test_integer_oracle_snr<unsigned>();
  271. test_complex_oracle_snr<std::complex<float>>();
  272. test_complex_oracle_snr<std::complex<double>>();
  273. test_complex_oracle_snr<std::complex<long double>>();
  274. test_complex_oracle_snr<cpp_complex_50>();
  275. test_m2m4_snr_estimator<float>();
  276. test_m2m4_snr_estimator<double>();
  277. test_m2m4_snr_estimator<long double>();
  278. return boost::report_errors();
  279. }