summaryrefslogtreecommitdiff
path: root/boost/math/tools
diff options
context:
space:
mode:
Diffstat (limited to 'boost/math/tools')
-rw-r--r--boost/math/tools/bivariate_statistics.hpp97
-rw-r--r--boost/math/tools/condition_numbers.hpp139
-rw-r--r--boost/math/tools/config.hpp2
-rw-r--r--boost/math/tools/detail/is_const_iterable.hpp40
-rw-r--r--boost/math/tools/norms.hpp640
-rw-r--r--boost/math/tools/numerical_differentiation.hpp261
-rw-r--r--boost/math/tools/polynomial.hpp73
-rw-r--r--boost/math/tools/roots.hpp283
-rw-r--r--boost/math/tools/signal_statistics.hpp346
-rw-r--r--boost/math/tools/univariate_statistics.hpp393
10 files changed, 1991 insertions, 283 deletions
diff --git a/boost/math/tools/bivariate_statistics.hpp b/boost/math/tools/bivariate_statistics.hpp
new file mode 100644
index 0000000000..20b7500ed3
--- /dev/null
+++ b/boost/math/tools/bivariate_statistics.hpp
@@ -0,0 +1,97 @@
+// (C) Copyright Nick Thompson 2018.
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef BOOST_MATH_TOOLS_BIVARIATE_STATISTICS_HPP
+#define BOOST_MATH_TOOLS_BIVARIATE_STATISTICS_HPP
+
+#include <iterator>
+#include <tuple>
+#include <boost/assert.hpp>
+#include <boost/multiprecision/detail/number_base.hpp>
+
+
+namespace boost{ namespace math{ namespace tools {
+
+template<class Container>
+auto means_and_covariance(Container const & u, Container const & v)
+{
+ using Real = typename Container::value_type;
+ using std::size;
+ BOOST_ASSERT_MSG(size(u) == size(v), "The size of each vector must be the same to compute covariance.");
+ BOOST_ASSERT_MSG(size(u) > 0, "Computing covariance requires at least one sample.");
+
+ // See Equation III.9 of "Numerically Stable, Single-Pass, Parallel Statistics Algorithms", Bennet et al.
+ Real cov = 0;
+ Real mu_u = u[0];
+ Real mu_v = v[0];
+
+ for(size_t i = 1; i < size(u); ++i)
+ {
+ Real u_tmp = (u[i] - mu_u)/(i+1);
+ Real v_tmp = v[i] - mu_v;
+ cov += i*u_tmp*v_tmp;
+ mu_u = mu_u + u_tmp;
+ mu_v = mu_v + v_tmp/(i+1);
+ }
+
+ return std::make_tuple(mu_u, mu_v, cov/size(u));
+}
+
+template<class Container>
+auto covariance(Container const & u, Container const & v)
+{
+ auto [mu_u, mu_v, cov] = boost::math::tools::means_and_covariance(u, v);
+ return cov;
+}
+
+template<class Container>
+auto correlation_coefficient(Container const & u, Container const & v)
+{
+ using Real = typename Container::value_type;
+ using std::size;
+ BOOST_ASSERT_MSG(size(u) == size(v), "The size of each vector must be the same to compute covariance.");
+ BOOST_ASSERT_MSG(size(u) > 0, "Computing covariance requires at least two samples.");
+
+ Real cov = 0;
+ Real mu_u = u[0];
+ Real mu_v = v[0];
+ Real Qu = 0;
+ Real Qv = 0;
+
+ for(size_t i = 1; i < size(u); ++i)
+ {
+ Real u_tmp = u[i] - mu_u;
+ Real v_tmp = v[i] - mu_v;
+ Qu = Qu + (i*u_tmp*u_tmp)/(i+1);
+ Qv = Qv + (i*v_tmp*v_tmp)/(i+1);
+ cov += i*u_tmp*v_tmp/(i+1);
+ mu_u = mu_u + u_tmp/(i+1);
+ mu_v = mu_v + v_tmp/(i+1);
+ }
+
+ // If both datasets are constant, then they are perfectly correlated.
+ if (Qu == 0 && Qv == 0)
+ {
+ return Real(1);
+ }
+ // If one dataset is constant and the other isn't, then they have no correlation:
+ if (Qu == 0 || Qv == 0)
+ {
+ return Real(0);
+ }
+
+ // Make sure rho in [-1, 1], even in the presence of numerical noise.
+ Real rho = cov/sqrt(Qu*Qv);
+ if (rho > 1) {
+ rho = 1;
+ }
+ if (rho < -1) {
+ rho = -1;
+ }
+ return rho;
+}
+
+}}}
+#endif
diff --git a/boost/math/tools/condition_numbers.hpp b/boost/math/tools/condition_numbers.hpp
new file mode 100644
index 0000000000..afbea75cb5
--- /dev/null
+++ b/boost/math/tools/condition_numbers.hpp
@@ -0,0 +1,139 @@
+// (C) Copyright Nick Thompson 2019.
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef BOOST_MATH_TOOLS_CONDITION_NUMBERS_HPP
+#define BOOST_MATH_TOOLS_CONDITION_NUMBERS_HPP
+#include <cmath>
+#include <boost/math/differentiation/finite_difference.hpp>
+
+namespace boost::math::tools {
+
+template<class Real, bool kahan=true>
+class summation_condition_number {
+public:
+ summation_condition_number(Real const x = 0)
+ {
+ using std::abs;
+ m_l1 = abs(x);
+ m_sum = x;
+ m_c = 0;
+ }
+
+ void operator+=(Real const & x)
+ {
+ using std::abs;
+ // No need to Kahan the l1 calc; it's well conditioned:
+ m_l1 += abs(x);
+ if constexpr(kahan)
+ {
+ Real y = x - m_c;
+ Real t = m_sum + y;
+ m_c = (t-m_sum) -y;
+ m_sum = t;
+ }
+ else
+ {
+ m_sum += x;
+ }
+ }
+
+ inline void operator-=(Real const & x)
+ {
+ this->operator+=(-x);
+ }
+
+ // Is operator*= relevant? Presumably everything gets rescaled,
+ // (m_sum -> k*m_sum, m_l1->k*m_l1, m_c->k*m_c),
+ // but is this sensible? More important is it useful?
+ // In addition, it might change the condition number.
+
+ [[nodiscard]] Real operator()() const
+ {
+ using std::abs;
+ if (m_sum == Real(0) && m_l1 != Real(0))
+ {
+ return std::numeric_limits<Real>::infinity();
+ }
+ return m_l1/abs(m_sum);
+ }
+
+ [[nodiscard]] Real sum() const
+ {
+ // Higham, 1993, "The Accuracy of Floating Point Summation":
+ // "In [17] and [18], Kahan describes a variation of compensated summation in which the final sum is also corrected
+ // thus s=s+e is appended to the algorithm above)."
+ return m_sum + m_c;
+ }
+
+ [[nodiscard]] Real l1_norm() const
+ {
+ return m_l1;
+ }
+
+private:
+ Real m_l1;
+ Real m_sum;
+ Real m_c;
+};
+
+template<class F, class Real>
+auto evaluation_condition_number(F const & f, Real const & x)
+{
+ using std::abs;
+ using std::isnan;
+ using std::sqrt;
+ using boost::math::differentiation::finite_difference_derivative;
+
+ Real fx = f(x);
+ if (isnan(fx))
+ {
+ return std::numeric_limits<Real>::quiet_NaN();
+ }
+ bool caught_exception = false;
+ Real fp;
+ try
+ {
+ fp = finite_difference_derivative(f, x);
+ }
+ catch(...)
+ {
+ caught_exception = true;
+ }
+
+ if (isnan(fp) || caught_exception)
+ {
+ // Check if the right derivative exists:
+ fp = finite_difference_derivative<decltype(f), Real, 1>(f, x);
+ if (isnan(fp))
+ {
+ // Check if a left derivative exists:
+ const Real eps = (std::numeric_limits<Real>::epsilon)();
+ Real h = - 2 * sqrt(eps);
+ h = boost::math::differentiation::detail::make_xph_representable(x, h);
+ Real yh = f(x + h);
+ Real y0 = f(x);
+ Real diff = yh - y0;
+ fp = diff / h;
+ if (isnan(fp))
+ {
+ return std::numeric_limits<Real>::quiet_NaN();
+ }
+ }
+ }
+
+ if (fx == 0)
+ {
+ if (x==0 || fp==0)
+ {
+ return std::numeric_limits<Real>::quiet_NaN();
+ }
+ return std::numeric_limits<Real>::infinity();
+ }
+
+ return abs(x*fp/fx);
+}
+
+}
+#endif
diff --git a/boost/math/tools/config.hpp b/boost/math/tools/config.hpp
index 17bfec16fe..47fdea65c8 100644
--- a/boost/math/tools/config.hpp
+++ b/boost/math/tools/config.hpp
@@ -11,7 +11,7 @@
#endif
#include <boost/config.hpp>
-#include <boost/predef.h>
+#include <boost/predef/architecture/x86.h>
#include <boost/cstdint.hpp> // for boost::uintmax_t
#include <boost/detail/workaround.hpp>
#include <boost/type_traits/is_integral.hpp>
diff --git a/boost/math/tools/detail/is_const_iterable.hpp b/boost/math/tools/detail/is_const_iterable.hpp
new file mode 100644
index 0000000000..805b66e22a
--- /dev/null
+++ b/boost/math/tools/detail/is_const_iterable.hpp
@@ -0,0 +1,40 @@
+// (C) Copyright John Maddock 2018.
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef BOOST_MATH_TOOLS_IS_CONST_ITERABLE_HPP
+#define BOOST_MATH_TOOLS_IS_CONST_ITERABLE_HPP
+
+#if !defined(BOOST_NO_CXX14_VARIABLE_TEMPLATES) && !defined(BOOST_NO_CXX11_DECLTYPE) && !defined(BOOST_NO_CXX11_SFINAE_EXPR)
+
+#define BOOST_MATH_HAS_IS_CONST_ITERABLE
+
+#include <boost/type_traits/is_detected.hpp>
+#include <utility>
+
+namespace boost {
+ namespace math {
+ namespace tools {
+ namespace detail {
+
+ template<class T>
+ using begin_t = decltype(std::declval<const T&>().begin());
+ template<class T>
+ using end_t = decltype(std::declval<const T&>().end());
+ template<class T>
+ using const_iterator_t = typename T::const_iterator;
+
+ template <class T>
+ struct is_const_iterable
+ : public boost::integral_constant<bool,
+ boost::is_detected<begin_t, T>::value
+ && boost::is_detected<end_t, T>::value
+ && boost::is_detected<const_iterator_t, T>::value
+ > {};
+
+} } } }
+
+#endif
+
+#endif // BOOST_MATH_TOOLS_IS_CONST_ITERABLE_HPP
diff --git a/boost/math/tools/norms.hpp b/boost/math/tools/norms.hpp
new file mode 100644
index 0000000000..478fe04db2
--- /dev/null
+++ b/boost/math/tools/norms.hpp
@@ -0,0 +1,640 @@
+// (C) Copyright Nick Thompson 2018.
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef BOOST_MATH_TOOLS_NORMS_HPP
+#define BOOST_MATH_TOOLS_NORMS_HPP
+#include <algorithm>
+#include <iterator>
+#include <boost/type_traits/is_complex.hpp>
+#include <boost/assert.hpp>
+#include <boost/multiprecision/detail/number_base.hpp>
+
+
+namespace boost::math::tools {
+
+// Mallat, "A Wavelet Tour of Signal Processing", equation 2.60:
+template<class ForwardIterator>
+auto total_variation(ForwardIterator first, ForwardIterator last)
+{
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ using std::abs;
+ BOOST_ASSERT_MSG(first != last && std::next(first) != last, "At least two samples are required to compute the total variation.");
+ auto it = first;
+ if constexpr (std::is_unsigned<T>::value)
+ {
+ T tmp = *it;
+ double tv = 0;
+ while (++it != last)
+ {
+ if (*it > tmp)
+ {
+ tv += *it - tmp;
+ }
+ else
+ {
+ tv += tmp - *it;
+ }
+ tmp = *it;
+ }
+ return tv;
+ }
+ else if constexpr (std::is_integral<T>::value)
+ {
+ double tv = 0;
+ double tmp = *it;
+ while(++it != last)
+ {
+ double tmp2 = *it;
+ tv += abs(tmp2 - tmp);
+ tmp = *it;
+ }
+ return tv;
+ }
+ else
+ {
+ T tmp = *it;
+ T tv = 0;
+ while (++it != last)
+ {
+ tv += abs(*it - tmp);
+ tmp = *it;
+ }
+ return tv;
+ }
+}
+
+template<class Container>
+inline auto total_variation(Container const & v)
+{
+ return total_variation(v.cbegin(), v.cend());
+}
+
+
+template<class ForwardIterator>
+auto sup_norm(ForwardIterator first, ForwardIterator last)
+{
+ BOOST_ASSERT_MSG(first != last, "At least one value is required to compute the sup norm.");
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ using std::abs;
+ if constexpr (boost::is_complex<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_complex)
+ {
+ auto it = std::max_element(first, last, [](T a, T b) { return abs(b) > abs(a); });
+ return abs(*it);
+ }
+ else if constexpr (std::is_unsigned<T>::value)
+ {
+ return *std::max_element(first, last);
+ }
+ else
+ {
+ auto pair = std::minmax_element(first, last);
+ if (abs(*pair.first) > abs(*pair.second))
+ {
+ return abs(*pair.first);
+ }
+ else
+ {
+ return abs(*pair.second);
+ }
+ }
+}
+
+template<class Container>
+inline auto sup_norm(Container const & v)
+{
+ return sup_norm(v.cbegin(), v.cend());
+}
+
+template<class ForwardIterator>
+auto l1_norm(ForwardIterator first, ForwardIterator last)
+{
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ using std::abs;
+ if constexpr (std::is_unsigned<T>::value)
+ {
+ double l1 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ l1 += *it;
+ }
+ return l1;
+ }
+ else if constexpr (std::is_integral<T>::value)
+ {
+ double l1 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ double tmp = *it;
+ l1 += abs(tmp);
+ }
+ return l1;
+ }
+ else
+ {
+ decltype(abs(*first)) l1 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ l1 += abs(*it);
+ }
+ return l1;
+ }
+
+}
+
+template<class Container>
+inline auto l1_norm(Container const & v)
+{
+ return l1_norm(v.cbegin(), v.cend());
+}
+
+
+template<class ForwardIterator>
+auto l2_norm(ForwardIterator first, ForwardIterator last)
+{
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ using std::abs;
+ using std::norm;
+ using std::sqrt;
+ using std::is_floating_point;
+ if constexpr (boost::is_complex<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_complex)
+ {
+ typedef typename T::value_type Real;
+ Real l2 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ l2 += norm(*it);
+ }
+ Real result = sqrt(l2);
+ if (!isfinite(result))
+ {
+ Real a = sup_norm(first, last);
+ l2 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ l2 += norm(*it/a);
+ }
+ return a*sqrt(l2);
+ }
+ return result;
+ }
+ else if constexpr (is_floating_point<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_floating_point)
+ {
+ T l2 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ l2 += (*it)*(*it);
+ }
+ T result = sqrt(l2);
+ // Higham, Accuracy and Stability of Numerical Algorithms,
+ // Problem 27.5 presents a different algorithm to deal with overflow.
+ // The algorithm used here takes 3 passes *if* there is overflow.
+ // Higham's algorithm is 1 pass, but more requires operations than the no oveflow case.
+ // I'm operating under the assumption that overflow is rare since the dynamic range of floating point numbers is huge.
+ if (!isfinite(result))
+ {
+ T a = sup_norm(first, last);
+ l2 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ T tmp = *it/a;
+ l2 += tmp*tmp;
+ }
+ return a*sqrt(l2);
+ }
+ return result;
+ }
+ else
+ {
+ double l2 = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ double tmp = *it;
+ l2 += tmp*tmp;
+ }
+ return sqrt(l2);
+ }
+}
+
+template<class Container>
+inline auto l2_norm(Container const & v)
+{
+ return l2_norm(v.cbegin(), v.cend());
+}
+
+template<class ForwardIterator>
+size_t l0_pseudo_norm(ForwardIterator first, ForwardIterator last)
+{
+ using RealOrComplex = typename std::iterator_traits<ForwardIterator>::value_type;
+ size_t count = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ if (*it != RealOrComplex(0))
+ {
+ ++count;
+ }
+ }
+ return count;
+}
+
+template<class Container>
+inline size_t l0_pseudo_norm(Container const & v)
+{
+ return l0_pseudo_norm(v.cbegin(), v.cend());
+}
+
+template<class ForwardIterator>
+size_t hamming_distance(ForwardIterator first1, ForwardIterator last1, ForwardIterator first2)
+{
+ size_t count = 0;
+ auto it1 = first1;
+ auto it2 = first2;
+ while (it1 != last1)
+ {
+ if (*it1++ != *it2++)
+ {
+ ++count;
+ }
+ }
+ return count;
+}
+
+template<class Container>
+inline size_t hamming_distance(Container const & v, Container const & w)
+{
+ return hamming_distance(v.cbegin(), v.cend(), w.cbegin());
+}
+
+template<class ForwardIterator>
+auto lp_norm(ForwardIterator first, ForwardIterator last, unsigned p)
+{
+ using std::abs;
+ using std::pow;
+ using std::is_floating_point;
+ using std::isfinite;
+ using RealOrComplex = typename std::iterator_traits<ForwardIterator>::value_type;
+ if constexpr (boost::is_complex<RealOrComplex>::value ||
+ boost::multiprecision::number_category<RealOrComplex>::value == boost::multiprecision::number_kind_complex)
+ {
+ using std::norm;
+ using Real = typename RealOrComplex::value_type;
+ Real lp = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ lp += pow(abs(*it), p);
+ }
+
+ auto result = pow(lp, Real(1)/Real(p));
+ if (!isfinite(result))
+ {
+ auto a = boost::math::tools::sup_norm(first, last);
+ Real lp = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ lp += pow(abs(*it)/a, p);
+ }
+ result = a*pow(lp, Real(1)/Real(p));
+ }
+ return result;
+ }
+ else if constexpr (is_floating_point<RealOrComplex>::value ||
+ boost::multiprecision::number_category<RealOrComplex>::value == boost::multiprecision::number_kind_floating_point)
+ {
+ BOOST_ASSERT_MSG(p >= 0, "For p < 0, the lp norm is not a norm");
+ RealOrComplex lp = 0;
+
+ for (auto it = first; it != last; ++it)
+ {
+ lp += pow(abs(*it), p);
+ }
+
+ RealOrComplex result = pow(lp, RealOrComplex(1)/RealOrComplex(p));
+ if (!isfinite(result))
+ {
+ RealOrComplex a = boost::math::tools::sup_norm(first, last);
+ lp = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ lp += pow(abs(*it)/a, p);
+ }
+ result = a*pow(lp, RealOrComplex(1)/RealOrComplex(p));
+ }
+ return result;
+ }
+ else
+ {
+ double lp = 0;
+
+ for (auto it = first; it != last; ++it)
+ {
+ double tmp = *it;
+ lp += pow(abs(tmp), p);
+ }
+ double result = pow(lp, 1.0/double(p));
+ if (!isfinite(result))
+ {
+ double a = boost::math::tools::sup_norm(first, last);
+ lp = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ double tmp = *it;
+ lp += pow(abs(tmp)/a, p);
+ }
+ result = a*pow(lp, double(1)/double(p));
+ }
+ return result;
+ }
+}
+
+template<class Container>
+inline auto lp_norm(Container const & v, unsigned p)
+{
+ return lp_norm(v.cbegin(), v.cend(), p);
+}
+
+
+template<class ForwardIterator>
+auto lp_distance(ForwardIterator first1, ForwardIterator last1, ForwardIterator first2, unsigned p)
+{
+ using std::pow;
+ using std::abs;
+ using std::is_floating_point;
+ using std::isfinite;
+ using RealOrComplex = typename std::iterator_traits<ForwardIterator>::value_type;
+ auto it1 = first1;
+ auto it2 = first2;
+
+ if constexpr (boost::is_complex<RealOrComplex>::value ||
+ boost::multiprecision::number_category<RealOrComplex>::value == boost::multiprecision::number_kind_complex)
+ {
+ using Real = typename RealOrComplex::value_type;
+ using std::norm;
+ Real dist = 0;
+ while(it1 != last1)
+ {
+ auto tmp = *it1++ - *it2++;
+ dist += pow(abs(tmp), p);
+ }
+ return pow(dist, Real(1)/Real(p));
+ }
+ else if constexpr (is_floating_point<RealOrComplex>::value ||
+ boost::multiprecision::number_category<RealOrComplex>::value == boost::multiprecision::number_kind_floating_point)
+ {
+ RealOrComplex dist = 0;
+ while(it1 != last1)
+ {
+ auto tmp = *it1++ - *it2++;
+ dist += pow(abs(tmp), p);
+ }
+ return pow(dist, RealOrComplex(1)/RealOrComplex(p));
+ }
+ else
+ {
+ double dist = 0;
+ while(it1 != last1)
+ {
+ double tmp1 = *it1++;
+ double tmp2 = *it2++;
+ // Naively you'd expect the integer subtraction to be faster,
+ // but this can overflow or wraparound:
+ //double tmp = *it1++ - *it2++;
+ dist += pow(abs(tmp1 - tmp2), p);
+ }
+ return pow(dist, 1.0/double(p));
+ }
+}
+
+template<class Container>
+inline auto lp_distance(Container const & v, Container const & w, unsigned p)
+{
+ return lp_distance(v.cbegin(), v.cend(), w.cbegin(), p);
+}
+
+
+template<class ForwardIterator>
+auto l1_distance(ForwardIterator first1, ForwardIterator last1, ForwardIterator first2)
+{
+ using std::abs;
+ using std::is_floating_point;
+ using std::isfinite;
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ auto it1 = first1;
+ auto it2 = first2;
+ if constexpr (boost::is_complex<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_complex)
+ {
+ using Real = typename T::value_type;
+ Real sum = 0;
+ while (it1 != last1) {
+ sum += abs(*it1++ - *it2++);
+ }
+ return sum;
+ }
+ else if constexpr (is_floating_point<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_floating_point)
+ {
+ T sum = 0;
+ while (it1 != last1)
+ {
+ sum += abs(*it1++ - *it2++);
+ }
+ return sum;
+ }
+ else if constexpr (std::is_unsigned<T>::value)
+ {
+ double sum = 0;
+ while(it1 != last1)
+ {
+ T x1 = *it1++;
+ T x2 = *it2++;
+ if (x1 > x2)
+ {
+ sum += (x1 - x2);
+ }
+ else
+ {
+ sum += (x2 - x1);
+ }
+ }
+ return sum;
+ }
+ else if constexpr (std::is_integral<T>::value)
+ {
+ double sum = 0;
+ while(it1 != last1)
+ {
+ double x1 = *it1++;
+ double x2 = *it2++;
+ sum += abs(x1-x2);
+ }
+ return sum;
+ }
+ else
+ {
+ BOOST_ASSERT_MSG(false, "Could not recognize type.");
+ }
+
+}
+
+template<class Container>
+auto l1_distance(Container const & v, Container const & w)
+{
+ using std::size;
+ BOOST_ASSERT_MSG(size(v) == size(w),
+ "L1 distance requires both containers to have the same number of elements");
+ return l1_distance(v.cbegin(), v.cend(), w.begin());
+}
+
+template<class ForwardIterator>
+auto l2_distance(ForwardIterator first1, ForwardIterator last1, ForwardIterator first2)
+{
+ using std::abs;
+ using std::norm;
+ using std::sqrt;
+ using std::is_floating_point;
+ using std::isfinite;
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ auto it1 = first1;
+ auto it2 = first2;
+ if constexpr (boost::is_complex<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_complex)
+ {
+ using Real = typename T::value_type;
+ Real sum = 0;
+ while (it1 != last1) {
+ sum += norm(*it1++ - *it2++);
+ }
+ return sqrt(sum);
+ }
+ else if constexpr (is_floating_point<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_floating_point)
+ {
+ T sum = 0;
+ while (it1 != last1)
+ {
+ T tmp = *it1++ - *it2++;
+ sum += tmp*tmp;
+ }
+ return sqrt(sum);
+ }
+ else if constexpr (std::is_unsigned<T>::value)
+ {
+ double sum = 0;
+ while(it1 != last1)
+ {
+ T x1 = *it1++;
+ T x2 = *it2++;
+ if (x1 > x2)
+ {
+ double tmp = x1-x2;
+ sum += tmp*tmp;
+ }
+ else
+ {
+ double tmp = x2 - x1;
+ sum += tmp*tmp;
+ }
+ }
+ return sqrt(sum);
+ }
+ else
+ {
+ double sum = 0;
+ while(it1 != last1)
+ {
+ double x1 = *it1++;
+ double x2 = *it2++;
+ double tmp = x1-x2;
+ sum += tmp*tmp;
+ }
+ return sqrt(sum);
+ }
+}
+
+template<class Container>
+auto l2_distance(Container const & v, Container const & w)
+{
+ using std::size;
+ BOOST_ASSERT_MSG(size(v) == size(w),
+ "L2 distance requires both containers to have the same number of elements");
+ return l2_distance(v.cbegin(), v.cend(), w.begin());
+}
+
+template<class ForwardIterator>
+auto sup_distance(ForwardIterator first1, ForwardIterator last1, ForwardIterator first2)
+{
+ using std::abs;
+ using std::norm;
+ using std::sqrt;
+ using std::is_floating_point;
+ using std::isfinite;
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ auto it1 = first1;
+ auto it2 = first2;
+ if constexpr (boost::is_complex<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_complex)
+ {
+ using Real = typename T::value_type;
+ Real sup_sq = 0;
+ while (it1 != last1) {
+ Real tmp = norm(*it1++ - *it2++);
+ if (tmp > sup_sq) {
+ sup_sq = tmp;
+ }
+ }
+ return sqrt(sup_sq);
+ }
+ else if constexpr (is_floating_point<T>::value ||
+ boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_floating_point)
+ {
+ T sup = 0;
+ while (it1 != last1)
+ {
+ T tmp = *it1++ - *it2++;
+ if (sup < abs(tmp))
+ {
+ sup = abs(tmp);
+ }
+ }
+ return sup;
+ }
+ else // integral values:
+ {
+ double sup = 0;
+ while(it1 != last1)
+ {
+ T x1 = *it1++;
+ T x2 = *it2++;
+ double tmp;
+ if (x1 > x2)
+ {
+ tmp = x1-x2;
+ }
+ else
+ {
+ tmp = x2 - x1;
+ }
+ if (sup < tmp) {
+ sup = tmp;
+ }
+ }
+ return sup;
+ }
+}
+
+template<class Container>
+auto sup_distance(Container const & v, Container const & w)
+{
+ using std::size;
+ BOOST_ASSERT_MSG(size(v) == size(w),
+ "sup distance requires both containers to have the same number of elements");
+ return sup_distance(v.cbegin(), v.cend(), w.begin());
+}
+
+
+}
+#endif
diff --git a/boost/math/tools/numerical_differentiation.hpp b/boost/math/tools/numerical_differentiation.hpp
index 34fef0db87..52d2788895 100644
--- a/boost/math/tools/numerical_differentiation.hpp
+++ b/boost/math/tools/numerical_differentiation.hpp
@@ -2,266 +2,11 @@
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-
#ifndef BOOST_MATH_TOOLS_NUMERICAL_DIFFERENTIATION_HPP
#define BOOST_MATH_TOOLS_NUMERICAL_DIFFERENTIATION_HPP
+#include <boost/math/differentiation/finite_difference.hpp>
+#include <boost/config/header_deprecated.hpp>
-/*
- * Performs numerical differentiation by finite-differences.
- *
- * All numerical differentiation using finite-differences are ill-conditioned, and these routines are no exception.
- * A simple argument demonstrates that the error is unbounded as h->0.
- * Take the one sides finite difference formula f'(x) = (f(x+h)-f(x))/h.
- * The evaluation of f induces an error as well as the error from the finite-difference approximation, giving
- * |f'(x) - (f(x+h) -f(x))/h| < h|f''(x)|/2 + (|f(x)|+|f(x+h)|)eps/h =: g(h), where eps is the unit roundoff for the type.
- * It is reasonable to choose h in a way that minimizes the maximum error bound g(h).
- * The value of h that minimizes g is h = sqrt(2eps(|f(x)| + |f(x+h)|)/|f''(x)|), and for this value of h the error bound is
- * sqrt(2eps(|f(x+h) +f(x)||f''(x)|)).
- * In fact it is not necessary to compute the ratio (|f(x+h)| + |f(x)|)/|f''(x)|; the error bound of ~\sqrt{\epsilon} still holds if we set it to one.
- *
- *
- * For more details on this method of analysis, see
- *
- * http://www.uio.no/studier/emner/matnat/math/MAT-INF1100/h08/kompendiet/diffint.pdf
- * http://web.archive.org/web/20150420195907/http://www.uio.no/studier/emner/matnat/math/MAT-INF1100/h08/kompendiet/diffint.pdf
- *
- *
- * It can be shown on general grounds that when choosing the optimal h, the maximum error in f'(x) is ~(|f(x)|eps)^k/k+1|f^(k-1)(x)|^1/k+1.
- * From this we can see that full precision can be recovered in the limit k->infinity.
- *
- * References:
- *
- * 1) Fornberg, Bengt. "Generation of finite difference formulas on arbitrarily spaced grids." Mathematics of computation 51.184 (1988): 699-706.
- *
- *
- * The second algorithm, the complex step derivative, is not ill-conditioned.
- * However, it requires that your function can be evaluated at complex arguments.
- * The idea is that f(x+ih) = f(x) +ihf'(x) - h^2f''(x) + ... so f'(x) \approx Im[f(x+ih)]/h.
- * No subtractive cancellation occurs. The error is ~ eps|f'(x)| + eps^2|f'''(x)|/6; hard to beat that.
- *
- * References:
- *
- * 1) Squire, William, and George Trapp. "Using complex variables to estimate derivatives of real functions." Siam Review 40.1 (1998): 110-112.
- */
-
-#include <complex>
-#include <boost/math/special_functions/next.hpp>
-
-// Make sure everyone is informed that C++17 is required:
-namespace boost{ namespace math{ namespace tools {
-
-namespace detail {
- template<class Real>
- Real make_xph_representable(Real x, Real h)
- {
- using std::numeric_limits;
- // Redefine h so that x + h is representable. Not using this trick leads to large error.
- // The compiler flag -ffast-math evaporates these operations . . .
- Real temp = x + h;
- h = temp - x;
- // Handle the case x + h == x:
- if (h == 0)
- {
- h = boost::math::nextafter(x, (numeric_limits<Real>::max)()) - x;
- }
- return h;
- }
-}
-
-template<class F, class Real>
-Real complex_step_derivative(const F f, Real x)
-{
- // Is it really this easy? Yes.
- // Note that some authors recommend taking the stepsize h to be smaller than epsilon(), some recommending use of the min().
- // This idea was tested over a few billion test cases and found the make the error *much* worse.
- // Even 2eps and eps/2 made the error worse, which was surprising.
- using std::complex;
- using std::numeric_limits;
- constexpr const Real step = (numeric_limits<Real>::epsilon)();
- constexpr const Real inv_step = 1/(numeric_limits<Real>::epsilon)();
- return f(complex<Real>(x, step)).imag()*inv_step;
-}
-
-namespace detail {
-
- template <unsigned>
- struct fd_tag {};
-
- template<class F, class Real>
- Real finite_difference_derivative(const F f, Real x, Real* error, const fd_tag<1>&)
- {
- using std::sqrt;
- using std::pow;
- using std::abs;
- using std::numeric_limits;
-
- const Real eps = (numeric_limits<Real>::epsilon)();
- // Error bound ~eps^1/2
- // Note that this estimate of h differs from the best estimate by a factor of sqrt((|f(x)| + |f(x+h)|)/|f''(x)|).
- // Since this factor is invariant under the scaling f -> kf, then we are somewhat justified in approximating it by 1.
- // This approximation will get better as we move to higher orders of accuracy.
- Real h = 2 * sqrt(eps);
- h = detail::make_xph_representable(x, h);
-
- Real yh = f(x + h);
- Real y0 = f(x);
- Real diff = yh - y0;
- if (error)
- {
- Real ym = f(x - h);
- Real ypph = abs(yh - 2 * y0 + ym) / h;
- // h*|f''(x)|*0.5 + (|f(x+h)+|f(x)|)*eps/h
- *error = ypph / 2 + (abs(yh) + abs(y0))*eps / h;
- }
- return diff / h;
- }
-
- template<class F, class Real>
- Real finite_difference_derivative(const F f, Real x, Real* error, const fd_tag<2>&)
- {
- using std::sqrt;
- using std::pow;
- using std::abs;
- using std::numeric_limits;
-
- const Real eps = (numeric_limits<Real>::epsilon)();
- // Error bound ~eps^2/3
- // See the previous discussion to understand determination of h and the error bound.
- // Series[(f[x+h] - f[x-h])/(2*h), {h, 0, 4}]
- Real h = pow(3 * eps, static_cast<Real>(1) / static_cast<Real>(3));
- h = detail::make_xph_representable(x, h);
-
- Real yh = f(x + h);
- Real ymh = f(x - h);
- Real diff = yh - ymh;
- if (error)
- {
- Real yth = f(x + 2 * h);
- Real ymth = f(x - 2 * h);
- *error = eps * (abs(yh) + abs(ymh)) / (2 * h) + abs((yth - ymth) / 2 - diff) / (6 * h);
- }
-
- return diff / (2 * h);
- }
-
- template<class F, class Real>
- Real finite_difference_derivative(const F f, Real x, Real* error, const fd_tag<4>&)
- {
- using std::sqrt;
- using std::pow;
- using std::abs;
- using std::numeric_limits;
-
- const Real eps = (numeric_limits<Real>::epsilon)();
- // Error bound ~eps^4/5
- Real h = pow(11.25*eps, (Real)1 / (Real)5);
- h = detail::make_xph_representable(x, h);
- Real ymth = f(x - 2 * h);
- Real yth = f(x + 2 * h);
- Real yh = f(x + h);
- Real ymh = f(x - h);
- Real y2 = ymth - yth;
- Real y1 = yh - ymh;
- if (error)
- {
- // Mathematica code to extract the remainder:
- // Series[(f[x-2*h]+ 8*f[x+h] - 8*f[x-h] - f[x+2*h])/(12*h), {h, 0, 7}]
- Real y_three_h = f(x + 3 * h);
- Real y_m_three_h = f(x - 3 * h);
- // Error from fifth derivative:
- *error = abs((y_three_h - y_m_three_h) / 2 + 2 * (ymth - yth) + 5 * (yh - ymh) / 2) / (30 * h);
- // Error from function evaluation:
- *error += eps * (abs(yth) + abs(ymth) + 8 * (abs(ymh) + abs(yh))) / (12 * h);
- }
- return (y2 + 8 * y1) / (12 * h);
- }
-
- template<class F, class Real>
- Real finite_difference_derivative(const F f, Real x, Real* error, const fd_tag<6>&)
- {
- using std::sqrt;
- using std::pow;
- using std::abs;
- using std::numeric_limits;
-
- const Real eps = (numeric_limits<Real>::epsilon)();
- // Error bound ~eps^6/7
- // Error: h^6f^(7)(x)/140 + 5|f(x)|eps/h
- Real h = pow(eps / 168, (Real)1 / (Real)7);
- h = detail::make_xph_representable(x, h);
-
- Real yh = f(x + h);
- Real ymh = f(x - h);
- Real y1 = yh - ymh;
- Real y2 = f(x - 2 * h) - f(x + 2 * h);
- Real y3 = f(x + 3 * h) - f(x - 3 * h);
-
- if (error)
- {
- // Mathematica code to generate fd scheme for 7th derivative:
- // Sum[(-1)^i*Binomial[7, i]*(f[x+(3-i)*h] + f[x+(4-i)*h])/2, {i, 0, 7}]
- // Mathematica to demonstrate that this is a finite difference formula for 7th derivative:
- // Series[(f[x+4*h]-f[x-4*h] + 6*(f[x-3*h] - f[x+3*h]) + 14*(f[x-h] - f[x+h] + f[x+2*h] - f[x-2*h]))/2, {h, 0, 15}]
- Real y7 = (f(x + 4 * h) - f(x - 4 * h) - 6 * y3 - 14 * y1 - 14 * y2) / 2;
- *error = abs(y7) / (140 * h) + 5 * (abs(yh) + abs(ymh))*eps / h;
- }
- return (y3 + 9 * y2 + 45 * y1) / (60 * h);
- }
-
- template<class F, class Real>
- Real finite_difference_derivative(const F f, Real x, Real* error, const fd_tag<8>&)
- {
- using std::sqrt;
- using std::pow;
- using std::abs;
- using std::numeric_limits;
-
- const Real eps = (numeric_limits<Real>::epsilon)();
- // Error bound ~eps^8/9.
- // In double precision, we only expect to lose two digits of precision while using this formula, at the cost of 8 function evaluations.
- // Error: h^8|f^(9)(x)|/630 + 7|f(x)|eps/h assuming 7 unstabilized additions.
- // Mathematica code to get the error:
- // Series[(f[x+h]-f[x-h])*(4/5) + (1/5)*(f[x-2*h] - f[x+2*h]) + (4/105)*(f[x+3*h] - f[x-3*h]) + (1/280)*(f[x-4*h] - f[x+4*h]), {h, 0, 9}]
- // If we used Kahan summation, we could get the max error down to h^8|f^(9)(x)|/630 + |f(x)|eps/h.
- Real h = pow(551.25*eps, (Real)1 / (Real)9);
- h = detail::make_xph_representable(x, h);
-
- Real yh = f(x + h);
- Real ymh = f(x - h);
- Real y1 = yh - ymh;
- Real y2 = f(x - 2 * h) - f(x + 2 * h);
- Real y3 = f(x + 3 * h) - f(x - 3 * h);
- Real y4 = f(x - 4 * h) - f(x + 4 * h);
-
- Real tmp1 = 3 * y4 / 8 + 4 * y3;
- Real tmp2 = 21 * y2 + 84 * y1;
-
- if (error)
- {
- // Mathematica code to generate fd scheme for 7th derivative:
- // Sum[(-1)^i*Binomial[9, i]*(f[x+(4-i)*h] + f[x+(5-i)*h])/2, {i, 0, 9}]
- // Mathematica to demonstrate that this is a finite difference formula for 7th derivative:
- // Series[(f[x+5*h]-f[x- 5*h])/2 + 4*(f[x-4*h] - f[x+4*h]) + 27*(f[x+3*h] - f[x-3*h])/2 + 24*(f[x-2*h] - f[x+2*h]) + 21*(f[x+h] - f[x-h]), {h, 0, 15}]
- Real f9 = (f(x + 5 * h) - f(x - 5 * h)) / 2 + 4 * y4 + 27 * y3 / 2 + 24 * y2 + 21 * y1;
- *error = abs(f9) / (630 * h) + 7 * (abs(yh) + abs(ymh))*eps / h;
- }
- return (tmp1 + tmp2) / (105 * h);
- }
-
- template<class F, class Real, class tag>
- Real finite_difference_derivative(const F f, Real x, Real* error, const tag&)
- {
- // Always fails, but condition is template-arg-dependent so only evaluated if we get instantiated.
- BOOST_STATIC_ASSERT_MSG(sizeof(Real) == 0, "Finite difference not implemented for this order: try 1, 2, 4, 6 or 8");
- }
-
-}
-
-template<class F, class Real, size_t order=6>
-inline Real finite_difference_derivative(const F f, Real x, Real* error = nullptr)
-{
- return detail::finite_difference_derivative(f, x, error, detail::fd_tag<order>());
-}
+BOOST_HEADER_DEPRECATED("<boost/math/differentiation/finite_difference.hpp>");
-}}} // namespaces
#endif
diff --git a/boost/math/tools/polynomial.hpp b/boost/math/tools/polynomial.hpp
index f6c7ef11ee..357f1313fd 100644
--- a/boost/math/tools/polynomial.hpp
+++ b/boost/math/tools/polynomial.hpp
@@ -23,6 +23,8 @@
#include <boost/math/policies/error_handling.hpp>
#include <boost/math/special_functions/binomial.hpp>
#include <boost/core/enable_if.hpp>
+#include <boost/type_traits/is_convertible.hpp>
+#include <boost/math/tools/detail/is_const_iterable.hpp>
#include <vector>
#include <ostream>
@@ -197,7 +199,7 @@ division(polynomial<T> u, const polynomial<T>& v)
BOOST_ASSERT(u);
typedef typename polynomial<T>::size_type N;
-
+
N const m = u.size() - 1, n = v.size() - 1;
N k = m - n;
polynomial<T> q;
@@ -301,8 +303,15 @@ public:
normalize();
}
+#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
+ polynomial(std::vector<T>&& p) : m_data(std::move(p))
+ {
+ normalize();
+ }
+#endif
+
template <class U>
- explicit polynomial(const U& point)
+ explicit polynomial(const U& point, typename boost::enable_if<boost::is_convertible<U, T> >::type* = 0)
{
if (point != U(0))
m_data.push_back(point);
@@ -321,17 +330,24 @@ public:
template <class U>
polynomial(const polynomial<U>& p)
{
+ m_data.resize(p.size());
for(unsigned i = 0; i < p.size(); ++i)
{
- m_data.push_back(boost::math::tools::real_cast<T>(p[i]));
+ m_data[i] = boost::math::tools::real_cast<T>(p[i]);
}
}
-
+#ifdef BOOST_MATH_HAS_IS_CONST_ITERABLE
+ template <class Range>
+ explicit polynomial(const Range& r, typename boost::enable_if<boost::math::tools::detail::is_const_iterable<Range> >::type* = 0)
+ : polynomial(r.begin(), r.end())
+ {
+ }
+#endif
#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40500)
polynomial(std::initializer_list<T> l) : polynomial(std::begin(l), std::end(l))
{
}
-
+
polynomial&
operator=(std::initializer_list<T> l)
{
@@ -358,9 +374,15 @@ public:
{
return m_data[i];
}
+
T evaluate(T z) const
{
- return m_data.size() > 0 ? boost::math::tools::evaluate_polynomial(&m_data[0], z, m_data.size()) : 0;
+ return this->operator()(z);
+ }
+
+ T operator()(T z) const
+ {
+ return m_data.size() > 0 ? boost::math::tools::evaluate_polynomial(&m_data[0], z, m_data.size()) : T(0);
}
std::vector<T> chebyshev() const
{
@@ -377,8 +399,34 @@ public:
return m_data;
}
- // operators:
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
+ polynomial<T> prime() const
+ {
+ if (m_data.size() == 0)
+ {
+ return polynomial<T>({});
+ }
+
+ std::vector<T> p_data(m_data.size() - 1);
+ for (size_t i = 0; i < p_data.size(); ++i) {
+ p_data[i] = m_data[i+1]*static_cast<T>(i+1);
+ }
+ return polynomial<T>(std::move(p_data));
+ }
+
+ polynomial<T> integrate() const
+ {
+ std::vector<T> i_data(m_data.size() + 1);
+ // Choose integration constant such that P(0) = 0.
+ i_data[0] = T(0);
+ for (size_t i = 1; i < i_data.size(); ++i)
+ {
+ i_data[i] = m_data[i-1]/static_cast<T>(i);
+ }
+ return polynomial<T>(std::move(i_data));
+ }
+
+ // operators:
polynomial& operator =(polynomial&& p) BOOST_NOEXCEPT
{
m_data = std::move(p.m_data);
@@ -497,13 +545,13 @@ public:
normalize();
return *this;
}
-
+
// Convenient and efficient query for zero.
bool is_zero() const
{
return m_data.empty();
}
-
+
// Conversion to bool.
#ifdef BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS
typedef bool (polynomial::*unmentionable_type)() const;
@@ -524,13 +572,13 @@ public:
{
m_data.clear();
}
-
+
/** Remove zero coefficients 'from the top', that is for which there are no
* non-zero coefficients of higher degree. */
void normalize()
{
#ifndef BOOST_NO_CXX11_LAMBDAS
- m_data.erase(std::find_if(m_data.rbegin(), m_data.rend(), [](const T& x)->bool { return x != 0; }).base(), m_data.end());
+ m_data.erase(std::find_if(m_data.rbegin(), m_data.rend(), [](const T& x)->bool { return x != T(0); }).base(), m_data.end());
#else
using namespace boost::lambda;
m_data.erase(std::find_if(m_data.rbegin(), m_data.rend(), _1 != T(0)).base(), m_data.end());
@@ -834,6 +882,3 @@ inline std::basic_ostream<charT, traits>& operator << (std::basic_ostream<charT,
#include <boost/math/tools/polynomial_gcd.hpp>
#endif // BOOST_MATH_TOOLS_POLYNOMIAL_HPP
-
-
-
diff --git a/boost/math/tools/roots.hpp b/boost/math/tools/roots.hpp
index 81e8466138..e16294dc15 100644
--- a/boost/math/tools/roots.hpp
+++ b/boost/math/tools/roots.hpp
@@ -9,7 +9,10 @@
#ifdef _MSC_VER
#pragma once
#endif
+#include <boost/multiprecision/detail/number_base.hpp> // test for multiprecision types.
+#include <boost/type_traits/is_complex.hpp> // test for complex types
+#include <iostream>
#include <utility>
#include <boost/config/no_tr1/cmath.hpp>
#include <stdexcept>
@@ -65,7 +68,7 @@ inline void unpack_0(const Tuple& t, T& val) BOOST_MATH_NOEXCEPT(T)
{
using dummy::get;
// Rely on ADL to find the correct overload of get:
- val = get<0>(t);
+ val = get<0>(t);
}
template <class T, class U, class V>
@@ -268,11 +271,15 @@ T newton_raphson_iterate(F f, T guess, T min, T max, int digits, boost::uintmax_
if(fabs(delta * 2) > fabs(delta2))
{
// last two steps haven't converged.
- delta = (delta > 0) ? (result - min) / 2 : (result - max) / 2;
- if (fabs(delta) > fabs(result))
+ T shift = (delta > 0) ? (result - min) / 2 : (result - max) / 2;
+ if ((result != 0) && (fabs(shift) > fabs(result)))
+ {
delta = sign(delta) * result; // protect against huge jumps!
- // reset delta2 so we don't take this branch next time round:
- delta1 = delta;
+ }
+ else
+ delta = shift;
+ // reset delta1/2 so we don't take this branch next time round:
+ delta1 = 3 * delta;
delta2 = 3 * delta;
}
guess = result;
@@ -421,12 +428,12 @@ namespace detail{
{
// last two steps haven't converged.
delta = (delta > 0) ? (result - min) / 2 : (result - max) / 2;
- if (fabs(delta) > result)
+ if ((result != 0) && (fabs(delta) > result))
delta = sign(delta) * result; // protect against huge jumps!
// reset delta2 so that this branch will *not* be taken on the
// next iteration:
delta2 = delta * 3;
- delta1 = delta;
+ delta1 = delta * 3;
BOOST_MATH_INSTRUMENT_VARIABLE(delta);
}
guess = result;
@@ -436,7 +443,10 @@ namespace detail{
// check for out of bounds step:
if(result < min)
{
- T diff = ((fabs(min) < 1) && (fabs(result) > 1) && (tools::max_value<T>() / fabs(result) < fabs(min))) ? T(1000) : T(result / min);
+ T diff = ((fabs(min) < 1) && (fabs(result) > 1) && (tools::max_value<T>() / fabs(result) < fabs(min)))
+ ? T(1000)
+ : (fabs(min) < 1) && (fabs(tools::max_value<T>() * min) < fabs(result))
+ ? ((min < 0) != (result < 0)) ? -tools::max_value<T>() : tools::max_value<T>() : T(result / min);
if(fabs(diff) < 1)
diff = 1 / diff;
if(!out_of_bounds_sentry && (diff > 0) && (diff < 3))
@@ -514,9 +524,10 @@ namespace detail{
template <class T>
static T step(const T& x, const T& f0, const T& f1, const T& f2) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(T))
{
+ using std::fabs;
T ratio = f0 / f1;
T delta;
- if(ratio / x < 0.1)
+ if((x != 0) && (fabs(ratio / x) < 0.1))
{
delta = ratio + (f2 / (2 * f1)) * ratio * ratio;
// check second derivative doesn't over compensate:
@@ -559,10 +570,262 @@ inline T schroeder_iterate(F f, T guess, T min, T max, int digits) BOOST_NOEXCEP
return schroder_iterate(f, guess, min, max, digits, m);
}
+#ifndef BOOST_NO_CXX11_AUTO_DECLARATIONS
+/*
+ * Why do we set the default maximum number of iterations to the number of digits in the type?
+ * Because for double roots, the number of digits increases linearly with the number of iterations,
+ * so this default should recover full precision even in this somewhat pathological case.
+ * For isolated roots, the problem is so rapidly convergent that this doesn't matter at all.
+ */
+template<class Complex, class F>
+Complex complex_newton(F g, Complex guess, int max_iterations=std::numeric_limits<typename Complex::value_type>::digits)
+{
+ typedef typename Complex::value_type Real;
+ using std::norm;
+ using std::abs;
+ using std::max;
+ // z0, z1, and z2 cannot be the same, in case we immediately need to resort to Muller's Method:
+ Complex z0 = guess + Complex(1,0);
+ Complex z1 = guess + Complex(0,1);
+ Complex z2 = guess;
+
+ do {
+ auto pair = g(z2);
+ if (norm(pair.second) == 0)
+ {
+ // Muller's method. Notation follows Numerical Recipes, 9.5.2:
+ Complex q = (z2 - z1)/(z1 - z0);
+ auto P0 = g(z0);
+ auto P1 = g(z1);
+ Complex qp1 = static_cast<Complex>(1)+q;
+ Complex A = q*(pair.first - qp1*P1.first + q*P0.first);
+
+ Complex B = (static_cast<Complex>(2)*q+static_cast<Complex>(1))*pair.first - qp1*qp1*P1.first +q*q*P0.first;
+ Complex C = qp1*pair.first;
+ Complex rad = sqrt(B*B - static_cast<Complex>(4)*A*C);
+ Complex denom1 = B + rad;
+ Complex denom2 = B - rad;
+ Complex correction = (z1-z2)*static_cast<Complex>(2)*C;
+ if (norm(denom1) > norm(denom2))
+ {
+ correction /= denom1;
+ }
+ else
+ {
+ correction /= denom2;
+ }
+
+ z0 = z1;
+ z1 = z2;
+ z2 = z2 + correction;
+ }
+ else
+ {
+ z0 = z1;
+ z1 = z2;
+ z2 = z2 - (pair.first/pair.second);
+ }
+
+ // See: https://math.stackexchange.com/questions/3017766/constructing-newton-iteration-converging-to-non-root
+ // If f' is continuous, then convergence of x_n -> x* implies f(x*) = 0.
+ // This condition approximates this convergence condition by requiring three consecutive iterates to be clustered.
+ Real tol = max(abs(z2)*std::numeric_limits<Real>::epsilon(), std::numeric_limits<Real>::epsilon());
+ bool real_close = abs(z0.real() - z1.real()) < tol && abs(z0.real() - z2.real()) < tol && abs(z1.real() - z2.real()) < tol;
+ bool imag_close = abs(z0.imag() - z1.imag()) < tol && abs(z0.imag() - z2.imag()) < tol && abs(z1.imag() - z2.imag()) < tol;
+ if (real_close && imag_close)
+ {
+ return z2;
+ }
+
+ } while(max_iterations--);
+
+ // The idea is that if we can get abs(f) < eps, we should, but if we go through all these iterations
+ // and abs(f) < sqrt(eps), then roundoff error simply does not allow that we can evaluate f to < eps
+ // This is somewhat awkward as it isn't scale invariant, but using the Daubechies coefficient example code,
+ // I found this condition generates correct roots, whereas the scale invariant condition discussed here:
+ // https://scicomp.stackexchange.com/questions/30597/defining-a-condition-number-and-termination-criteria-for-newtons-method
+ // allows nonroots to be passed off as roots.
+ auto pair = g(z2);
+ if (abs(pair.first) < sqrt(std::numeric_limits<Real>::epsilon()))
+ {
+ return z2;
+ }
+
+ return {std::numeric_limits<Real>::quiet_NaN(),
+ std::numeric_limits<Real>::quiet_NaN()};
+}
+#endif
+
+
+#if !defined(BOOST_NO_CXX17_IF_CONSTEXPR)
+// https://stackoverflow.com/questions/48979861/numerically-stable-method-for-solving-quadratic-equations/50065711
+namespace detail
+{
+ template<class T>
+ inline T discriminant(T const & a, T const & b, T const & c)
+ {
+ T w = 4*a*c;
+ T e = std::fma(-c, 4*a, w);
+ T f = std::fma(b, b, -w);
+ return f + e;
+ }
+}
+
+template<class T>
+auto quadratic_roots(T const& a, T const& b, T const& c)
+{
+ using std::copysign;
+ using std::sqrt;
+ if constexpr (std::is_integral<T>::value)
+ {
+ // What I want is to write:
+ // return quadratic_roots(double(a), double(b), double(c));
+ // but that doesn't compile.
+ double nan = std::numeric_limits<double>::quiet_NaN();
+ if(a==0)
+ {
+ if (b==0 && c != 0)
+ {
+ return std::pair<double, double>(nan, nan);
+ }
+ else if (b==0 && c==0)
+ {
+ return std::pair<double, double>(0,0);
+ }
+ return std::pair<double, double>(-c/b, -c/b);
+ }
+ if (b==0)
+ {
+ double x0_sq = -double(c)/double(a);
+ if (x0_sq < 0) {
+ return std::pair<double, double>(nan, nan);
+ }
+ double x0 = sqrt(x0_sq);
+ return std::pair<double, double>(-x0,x0);
+ }
+ double discriminant = detail::discriminant(double(a), double(b), double(c));
+ if (discriminant < 0)
+ {
+ return std::pair<double, double>(nan, nan);
+ }
+ double q = -(b + copysign(sqrt(discriminant), double(b)))/T(2);
+ double x0 = q/a;
+ double x1 = c/q;
+ if (x0 < x1) {
+ return std::pair<double, double>(x0, x1);
+ }
+ return std::pair<double, double>(x1, x0);
+ }
+ else if constexpr (std::is_floating_point<T>::value)
+ {
+ T nan = std::numeric_limits<T>::quiet_NaN();
+ if(a==0)
+ {
+ if (b==0 && c != 0)
+ {
+ return std::pair<T, T>(nan, nan);
+ }
+ else if (b==0 && c==0)
+ {
+ return std::pair<T, T>(0,0);
+ }
+ return std::pair<T, T>(-c/b, -c/b);
+ }
+ if (b==0)
+ {
+ T x0_sq = -c/a;
+ if (x0_sq < 0) {
+ return std::pair<T, T>(nan, nan);
+ }
+ T x0 = sqrt(x0_sq);
+ return std::pair<T, T>(-x0,x0);
+ }
+ T discriminant = detail::discriminant(a, b, c);
+ // Is there a sane way to flush very small negative values to zero?
+ // If there is I don't know of it.
+ if (discriminant < 0)
+ {
+ return std::pair<T, T>(nan, nan);
+ }
+ T q = -(b + copysign(sqrt(discriminant), b))/T(2);
+ T x0 = q/a;
+ T x1 = c/q;
+ if (x0 < x1)
+ {
+ return std::pair<T, T>(x0, x1);
+ }
+ return std::pair<T, T>(x1, x0);
+ }
+ else if constexpr (boost::is_complex<T>::value || boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_complex)
+ {
+ typename T::value_type nan = std::numeric_limits<typename T::value_type>::quiet_NaN();
+ if(a.real()==0 && a.imag() ==0)
+ {
+ using std::norm;
+ if (b.real()==0 && b.imag() && norm(c) != 0)
+ {
+ return std::pair<T, T>({nan, nan}, {nan, nan});
+ }
+ else if (b.real()==0 && b.imag() && c.real() ==0 && c.imag() == 0)
+ {
+ return std::pair<T, T>({0,0},{0,0});
+ }
+ return std::pair<T, T>(-c/b, -c/b);
+ }
+ if (b.real()==0 && b.imag() == 0)
+ {
+ T x0_sq = -c/a;
+ T x0 = sqrt(x0_sq);
+ return std::pair<T, T>(-x0, x0);
+ }
+ // There's no fma for complex types:
+ T discriminant = b*b - T(4)*a*c;
+ T q = -(b + sqrt(discriminant))/T(2);
+ return std::pair<T, T>(q/a, c/q);
+ }
+ else // Most likely the type is a boost.multiprecision.
+ { //There is no fma for multiprecision, and in addition it doesn't seem to be useful, so revert to the naive computation.
+ T nan = std::numeric_limits<T>::quiet_NaN();
+ if(a==0)
+ {
+ if (b==0 && c != 0)
+ {
+ return std::pair<T, T>(nan, nan);
+ }
+ else if (b==0 && c==0)
+ {
+ return std::pair<T, T>(0,0);
+ }
+ return std::pair<T, T>(-c/b, -c/b);
+ }
+ if (b==0)
+ {
+ T x0_sq = -c/a;
+ if (x0_sq < 0) {
+ return std::pair<T, T>(nan, nan);
+ }
+ T x0 = sqrt(x0_sq);
+ return std::pair<T, T>(-x0,x0);
+ }
+ T discriminant = b*b - 4*a*c;
+ if (discriminant < 0)
+ {
+ return std::pair<T, T>(nan, nan);
+ }
+ T q = -(b + copysign(sqrt(discriminant), b))/T(2);
+ T x0 = q/a;
+ T x1 = c/q;
+ if (x0 < x1)
+ {
+ return std::pair<T, T>(x0, x1);
+ }
+ return std::pair<T, T>(x1, x0);
+ }
+}
+#endif
} // namespace tools
} // namespace math
} // namespace boost
#endif // BOOST_MATH_TOOLS_NEWTON_SOLVER_HPP
-
diff --git a/boost/math/tools/signal_statistics.hpp b/boost/math/tools/signal_statistics.hpp
new file mode 100644
index 0000000000..74f9dfd031
--- /dev/null
+++ b/boost/math/tools/signal_statistics.hpp
@@ -0,0 +1,346 @@
+// (C) Copyright Nick Thompson 2018.
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef BOOST_MATH_TOOLS_SIGNAL_STATISTICS_HPP
+#define BOOST_MATH_TOOLS_SIGNAL_STATISTICS_HPP
+
+#include <algorithm>
+#include <iterator>
+#include <boost/type_traits/is_complex.hpp>
+#include <boost/assert.hpp>
+#include <boost/multiprecision/detail/number_base.hpp>
+#include <boost/math/tools/roots.hpp>
+#include <boost/math/tools/univariate_statistics.hpp>
+
+
+namespace boost::math::tools {
+
+template<class ForwardIterator>
+auto absolute_gini_coefficient(ForwardIterator first, ForwardIterator last)
+{
+ using std::abs;
+ using RealOrComplex = typename std::iterator_traits<ForwardIterator>::value_type;
+ BOOST_ASSERT_MSG(first != last && std::next(first) != last, "Computation of the Gini coefficient requires at least two samples.");
+
+ std::sort(first, last, [](RealOrComplex a, RealOrComplex b) { return abs(b) > abs(a); });
+
+
+ decltype(abs(*first)) i = 1;
+ decltype(abs(*first)) num = 0;
+ decltype(abs(*first)) denom = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ decltype(abs(*first)) tmp = abs(*it);
+ num += tmp*i;
+ denom += tmp;
+ ++i;
+ }
+
+ // If the l1 norm is zero, all elements are zero, so every element is the same.
+ if (denom == 0)
+ {
+ decltype(abs(*first)) zero = 0;
+ return zero;
+ }
+ return ((2*num)/denom - i)/(i-1);
+}
+
+template<class RandomAccessContainer>
+inline auto absolute_gini_coefficient(RandomAccessContainer & v)
+{
+ return boost::math::tools::absolute_gini_coefficient(v.begin(), v.end());
+}
+
+template<class ForwardIterator>
+auto sample_absolute_gini_coefficient(ForwardIterator first, ForwardIterator last)
+{
+ size_t n = std::distance(first, last);
+ return n*boost::math::tools::absolute_gini_coefficient(first, last)/(n-1);
+}
+
+template<class RandomAccessContainer>
+inline auto sample_absolute_gini_coefficient(RandomAccessContainer & v)
+{
+ return boost::math::tools::sample_absolute_gini_coefficient(v.begin(), v.end());
+}
+
+
+// The Hoyer sparsity measure is defined in:
+// https://arxiv.org/pdf/0811.4706.pdf
+template<class ForwardIterator>
+auto hoyer_sparsity(const ForwardIterator first, const ForwardIterator last)
+{
+ using T = typename std::iterator_traits<ForwardIterator>::value_type;
+ using std::abs;
+ using std::sqrt;
+ BOOST_ASSERT_MSG(first != last && std::next(first) != last, "Computation of the Hoyer sparsity requires at least two samples.");
+
+ if constexpr (std::is_unsigned<T>::value)
+ {
+ T l1 = 0;
+ T l2 = 0;
+ size_t n = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ l1 += *it;
+ l2 += (*it)*(*it);
+ n += 1;
+ }
+
+ double rootn = sqrt(n);
+ return (rootn - l1/sqrt(l2) )/ (rootn - 1);
+ }
+ else {
+ decltype(abs(*first)) l1 = 0;
+ decltype(abs(*first)) l2 = 0;
+ // We wouldn't need to count the elements if it was a random access iterator,
+ // but our only constraint is that it's a forward iterator.
+ size_t n = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ decltype(abs(*first)) tmp = abs(*it);
+ l1 += tmp;
+ l2 += tmp*tmp;
+ n += 1;
+ }
+ if constexpr (std::is_integral<T>::value)
+ {
+ double rootn = sqrt(n);
+ return (rootn - l1/sqrt(l2) )/ (rootn - 1);
+ }
+ else
+ {
+ decltype(abs(*first)) rootn = sqrt(static_cast<decltype(abs(*first))>(n));
+ return (rootn - l1/sqrt(l2) )/ (rootn - 1);
+ }
+ }
+}
+
+template<class Container>
+inline auto hoyer_sparsity(Container const & v)
+{
+ return boost::math::tools::hoyer_sparsity(v.cbegin(), v.cend());
+}
+
+
+template<class Container>
+auto oracle_snr(Container const & signal, Container const & noisy_signal)
+{
+ using Real = typename Container::value_type;
+ BOOST_ASSERT_MSG(signal.size() == noisy_signal.size(),
+ "Signal and noisy_signal must be have the same number of elements.");
+ if constexpr (std::is_integral<Real>::value)
+ {
+ double numerator = 0;
+ double denominator = 0;
+ for (size_t i = 0; i < signal.size(); ++i)
+ {
+ numerator += signal[i]*signal[i];
+ denominator += (noisy_signal[i] - signal[i])*(noisy_signal[i] - signal[i]);
+ }
+ if (numerator == 0 && denominator == 0)
+ {
+ return std::numeric_limits<double>::quiet_NaN();
+ }
+ if (denominator == 0)
+ {
+ return std::numeric_limits<double>::infinity();
+ }
+ return numerator/denominator;
+ }
+ else if constexpr (boost::is_complex<Real>::value ||
+ boost::multiprecision::number_category<Real>::value == boost::multiprecision::number_kind_complex)
+
+ {
+ using std::norm;
+ typename Real::value_type numerator = 0;
+ typename Real::value_type denominator = 0;
+ for (size_t i = 0; i < signal.size(); ++i)
+ {
+ numerator += norm(signal[i]);
+ denominator += norm(noisy_signal[i] - signal[i]);
+ }
+ if (numerator == 0 && denominator == 0)
+ {
+ return std::numeric_limits<typename Real::value_type>::quiet_NaN();
+ }
+ if (denominator == 0)
+ {
+ return std::numeric_limits<typename Real::value_type>::infinity();
+ }
+
+ return numerator/denominator;
+ }
+ else
+ {
+ Real numerator = 0;
+ Real denominator = 0;
+ for (size_t i = 0; i < signal.size(); ++i)
+ {
+ numerator += signal[i]*signal[i];
+ denominator += (signal[i] - noisy_signal[i])*(signal[i] - noisy_signal[i]);
+ }
+ if (numerator == 0 && denominator == 0)
+ {
+ return std::numeric_limits<Real>::quiet_NaN();
+ }
+ if (denominator == 0)
+ {
+ return std::numeric_limits<Real>::infinity();
+ }
+
+ return numerator/denominator;
+ }
+}
+
+template<class Container>
+auto mean_invariant_oracle_snr(Container const & signal, Container const & noisy_signal)
+{
+ using Real = typename Container::value_type;
+ BOOST_ASSERT_MSG(signal.size() == noisy_signal.size(), "Signal and noisy signal must be have the same number of elements.");
+
+ Real mu = boost::math::tools::mean(signal);
+ Real numerator = 0;
+ Real denominator = 0;
+ for (size_t i = 0; i < signal.size(); ++i)
+ {
+ Real tmp = signal[i] - mu;
+ numerator += tmp*tmp;
+ denominator += (signal[i] - noisy_signal[i])*(signal[i] - noisy_signal[i]);
+ }
+ if (numerator == 0 && denominator == 0)
+ {
+ return std::numeric_limits<Real>::quiet_NaN();
+ }
+ if (denominator == 0)
+ {
+ return std::numeric_limits<Real>::infinity();
+ }
+
+ return numerator/denominator;
+
+}
+
+template<class Container>
+auto mean_invariant_oracle_snr_db(Container const & signal, Container const & noisy_signal)
+{
+ using std::log10;
+ return 10*log10(boost::math::tools::mean_invariant_oracle_snr(signal, noisy_signal));
+}
+
+
+// Follows the definition of SNR given in Mallat, A Wavelet Tour of Signal Processing, equation 11.16.
+template<class Container>
+auto oracle_snr_db(Container const & signal, Container const & noisy_signal)
+{
+ using std::log10;
+ return 10*log10(boost::math::tools::oracle_snr(signal, noisy_signal));
+}
+
+// A good reference on the M2M4 estimator:
+// 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.
+// A nice python implementation:
+// https://github.com/gnuradio/gnuradio/blob/master/gr-digital/examples/snr_estimators.py
+template<class ForwardIterator>
+auto m2m4_snr_estimator(ForwardIterator first, ForwardIterator last, decltype(*first) estimated_signal_kurtosis=1, decltype(*first) estimated_noise_kurtosis=3)
+{
+ BOOST_ASSERT_MSG(estimated_signal_kurtosis > 0, "The estimated signal kurtosis must be positive");
+ BOOST_ASSERT_MSG(estimated_noise_kurtosis > 0, "The estimated noise kurtosis must be positive.");
+ using Real = typename std::iterator_traits<ForwardIterator>::value_type;
+ using std::sqrt;
+ if constexpr (std::is_floating_point<Real>::value ||
+ boost::multiprecision::number_category<Real>::value == boost::multiprecision::number_kind_floating_point)
+ {
+ // If we first eliminate N, we obtain the quadratic equation:
+ // (ka+kw-6)S^2 + 2M2(3-kw)S + kw*M2^2 - M4 = 0 =: a*S^2 + bs*N + cs = 0
+ // If we first eliminate S, we obtain the quadratic equation:
+ // (ka+kw-6)N^2 + 2M2(3-ka)N + ka*M2^2 - M4 = 0 =: a*N^2 + bn*N + cn = 0
+ // I believe these equations are totally independent quadratics;
+ // if one has a complex solution it is not necessarily the case that the other must also.
+ // However, I can't prove that, so there is a chance that this does unnecessary work.
+ // Future improvements: There are algorithms which can solve quadratics much more effectively than the naive implementation found here.
+ // See: https://stackoverflow.com/questions/48979861/numerically-stable-method-for-solving-quadratic-equations/50065711#50065711
+ auto [M1, M2, M3, M4] = boost::math::tools::first_four_moments(first, last);
+ if (M4 == 0)
+ {
+ // The signal is constant. There is no noise:
+ return std::numeric_limits<Real>::infinity();
+ }
+ // Change to notation in Pauluzzi, equation 41:
+ auto kw = estimated_noise_kurtosis;
+ auto ka = estimated_signal_kurtosis;
+ // A common case, since it's the default:
+ Real a = (ka+kw-6);
+ Real bs = 2*M2*(3-kw);
+ Real cs = kw*M2*M2 - M4;
+ Real bn = 2*M2*(3-ka);
+ Real cn = ka*M2*M2 - M4;
+ auto [S0, S1] = boost::math::tools::quadratic_roots(a, bs, cs);
+ if (S1 > 0)
+ {
+ auto N = M2 - S1;
+ if (N > 0)
+ {
+ return S1/N;
+ }
+ if (S0 > 0)
+ {
+ N = M2 - S0;
+ if (N > 0)
+ {
+ return S0/N;
+ }
+ }
+ }
+ auto [N0, N1] = boost::math::tools::quadratic_roots(a, bn, cn);
+ if (N1 > 0)
+ {
+ auto S = M2 - N1;
+ if (S > 0)
+ {
+ return S/N1;
+ }
+ if (N0 > 0)
+ {
+ S = M2 - N0;
+ if (S > 0)
+ {
+ return S/N0;
+ }
+ }
+ }
+ // This happens distressingly often. It's a limitation of the method.
+ return std::numeric_limits<Real>::quiet_NaN();
+ }
+ else
+ {
+ BOOST_ASSERT_MSG(false, "The M2M4 estimator has not been implemented for this type.");
+ return std::numeric_limits<Real>::quiet_NaN();
+ }
+}
+
+template<class Container>
+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)
+{
+ return m2m4_snr_estimator(noisy_signal.cbegin(), noisy_signal.cend(), estimated_signal_kurtosis, estimated_noise_kurtosis);
+}
+
+template<class ForwardIterator>
+inline auto m2m4_snr_estimator_db(ForwardIterator first, ForwardIterator last, decltype(*first) estimated_signal_kurtosis=1, decltype(*first) estimated_noise_kurtosis=3)
+{
+ using std::log10;
+ return 10*log10(m2m4_snr_estimator(first, last, estimated_signal_kurtosis, estimated_noise_kurtosis));
+}
+
+
+template<class Container>
+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)
+{
+ using std::log10;
+ return 10*log10(m2m4_snr_estimator(noisy_signal, estimated_signal_kurtosis, estimated_noise_kurtosis));
+}
+
+}
+#endif
diff --git a/boost/math/tools/univariate_statistics.hpp b/boost/math/tools/univariate_statistics.hpp
new file mode 100644
index 0000000000..226fdf46d2
--- /dev/null
+++ b/boost/math/tools/univariate_statistics.hpp
@@ -0,0 +1,393 @@
+// (C) Copyright Nick Thompson 2018.
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef BOOST_MATH_TOOLS_UNIVARIATE_STATISTICS_HPP
+#define BOOST_MATH_TOOLS_UNIVARIATE_STATISTICS_HPP
+
+#include <algorithm>
+#include <iterator>
+#include <boost/type_traits/is_complex.hpp>
+#include <boost/assert.hpp>
+#include <boost/multiprecision/detail/number_base.hpp>
+
+
+namespace boost::math::tools {
+
+template<class ForwardIterator>
+auto mean(ForwardIterator first, ForwardIterator last)
+{
+ using Real = typename std::iterator_traits<ForwardIterator>::value_type;
+ BOOST_ASSERT_MSG(first != last, "At least one sample is required to compute the mean.");
+ if constexpr (std::is_integral<Real>::value)
+ {
+ double mu = 0;
+ double i = 1;
+ for(auto it = first; it != last; ++it) {
+ mu = mu + (*it - mu)/i;
+ i += 1;
+ }
+ return mu;
+ }
+ else
+ {
+ Real mu = 0;
+ Real i = 1;
+ for(auto it = first; it != last; ++it) {
+ mu = mu + (*it - mu)/i;
+ i += 1;
+ }
+ return mu;
+ }
+}
+
+template<class Container>
+inline auto mean(Container const & v)
+{
+ return mean(v.cbegin(), v.cend());
+}
+
+template<class ForwardIterator>
+auto variance(ForwardIterator first, ForwardIterator last)
+{
+ using Real = typename std::iterator_traits<ForwardIterator>::value_type;
+ BOOST_ASSERT_MSG(first != last, "At least one sample is required to compute mean and variance.");
+ // Higham, Accuracy and Stability, equation 1.6a and 1.6b:
+ if constexpr (std::is_integral<Real>::value)
+ {
+ double M = *first;
+ double Q = 0;
+ double k = 2;
+ for (auto it = std::next(first); it != last; ++it)
+ {
+ double tmp = *it - M;
+ Q = Q + ((k-1)*tmp*tmp)/k;
+ M = M + tmp/k;
+ k += 1;
+ }
+ return Q/(k-1);
+ }
+ else
+ {
+ Real M = *first;
+ Real Q = 0;
+ Real k = 2;
+ for (auto it = std::next(first); it != last; ++it)
+ {
+ Real tmp = (*it - M)/k;
+ Q += k*(k-1)*tmp*tmp;
+ M += tmp;
+ k += 1;
+ }
+ return Q/(k-1);
+ }
+}
+
+template<class Container>
+inline auto variance(Container const & v)
+{
+ return variance(v.cbegin(), v.cend());
+}
+
+template<class ForwardIterator>
+auto sample_variance(ForwardIterator first, ForwardIterator last)
+{
+ size_t n = std::distance(first, last);
+ BOOST_ASSERT_MSG(n > 1, "At least two samples are required to compute the sample variance.");
+ return n*variance(first, last)/(n-1);
+}
+
+template<class Container>
+inline auto sample_variance(Container const & v)
+{
+ return sample_variance(v.cbegin(), v.cend());
+}
+
+
+// Follows equation 1.5 of:
+// https://prod.sandia.gov/techlib-noauth/access-control.cgi/2008/086212.pdf
+template<class ForwardIterator>
+auto skewness(ForwardIterator first, ForwardIterator last)
+{
+ using Real = typename std::iterator_traits<ForwardIterator>::value_type;
+ BOOST_ASSERT_MSG(first != last, "At least one sample is required to compute skewness.");
+ if constexpr (std::is_integral<Real>::value)
+ {
+ double M1 = *first;
+ double M2 = 0;
+ double M3 = 0;
+ double n = 2;
+ for (auto it = std::next(first); it != last; ++it)
+ {
+ double delta21 = *it - M1;
+ double tmp = delta21/n;
+ M3 = M3 + tmp*((n-1)*(n-2)*delta21*tmp - 3*M2);
+ M2 = M2 + tmp*(n-1)*delta21;
+ M1 = M1 + tmp;
+ n += 1;
+ }
+
+ double var = M2/(n-1);
+ if (var == 0)
+ {
+ // The limit is technically undefined, but the interpretation here is clear:
+ // A constant dataset has no skewness.
+ return double(0);
+ }
+ double skew = M3/(M2*sqrt(var));
+ return skew;
+ }
+ else
+ {
+ Real M1 = *first;
+ Real M2 = 0;
+ Real M3 = 0;
+ Real n = 2;
+ for (auto it = std::next(first); it != last; ++it)
+ {
+ Real delta21 = *it - M1;
+ Real tmp = delta21/n;
+ M3 += tmp*((n-1)*(n-2)*delta21*tmp - 3*M2);
+ M2 += tmp*(n-1)*delta21;
+ M1 += tmp;
+ n += 1;
+ }
+
+ Real var = M2/(n-1);
+ if (var == 0)
+ {
+ // The limit is technically undefined, but the interpretation here is clear:
+ // A constant dataset has no skewness.
+ return Real(0);
+ }
+ Real skew = M3/(M2*sqrt(var));
+ return skew;
+ }
+}
+
+template<class Container>
+inline auto skewness(Container const & v)
+{
+ return skewness(v.cbegin(), v.cend());
+}
+
+// Follows equation 1.5/1.6 of:
+// https://prod.sandia.gov/techlib-noauth/access-control.cgi/2008/086212.pdf
+template<class ForwardIterator>
+auto first_four_moments(ForwardIterator first, ForwardIterator last)
+{
+ using Real = typename std::iterator_traits<ForwardIterator>::value_type;
+ BOOST_ASSERT_MSG(first != last, "At least one sample is required to compute the first four moments.");
+ if constexpr (std::is_integral<Real>::value)
+ {
+ double M1 = *first;
+ double M2 = 0;
+ double M3 = 0;
+ double M4 = 0;
+ double n = 2;
+ for (auto it = std::next(first); it != last; ++it)
+ {
+ double delta21 = *it - M1;
+ double tmp = delta21/n;
+ M4 = M4 + tmp*(tmp*tmp*delta21*((n-1)*(n*n-3*n+3)) + 6*tmp*M2 - 4*M3);
+ M3 = M3 + tmp*((n-1)*(n-2)*delta21*tmp - 3*M2);
+ M2 = M2 + tmp*(n-1)*delta21;
+ M1 = M1 + tmp;
+ n += 1;
+ }
+
+ return std::make_tuple(M1, M2/(n-1), M3/(n-1), M4/(n-1));
+ }
+ else
+ {
+ Real M1 = *first;
+ Real M2 = 0;
+ Real M3 = 0;
+ Real M4 = 0;
+ Real n = 2;
+ for (auto it = std::next(first); it != last; ++it)
+ {
+ Real delta21 = *it - M1;
+ Real tmp = delta21/n;
+ M4 = M4 + tmp*(tmp*tmp*delta21*((n-1)*(n*n-3*n+3)) + 6*tmp*M2 - 4*M3);
+ M3 = M3 + tmp*((n-1)*(n-2)*delta21*tmp - 3*M2);
+ M2 = M2 + tmp*(n-1)*delta21;
+ M1 = M1 + tmp;
+ n += 1;
+ }
+
+ return std::make_tuple(M1, M2/(n-1), M3/(n-1), M4/(n-1));
+ }
+}
+
+template<class Container>
+inline auto first_four_moments(Container const & v)
+{
+ return first_four_moments(v.cbegin(), v.cend());
+}
+
+
+// Follows equation 1.6 of:
+// https://prod.sandia.gov/techlib-noauth/access-control.cgi/2008/086212.pdf
+template<class ForwardIterator>
+auto kurtosis(ForwardIterator first, ForwardIterator last)
+{
+ auto [M1, M2, M3, M4] = first_four_moments(first, last);
+ if (M2 == 0)
+ {
+ return M2;
+ }
+ return M4/(M2*M2);
+}
+
+template<class Container>
+inline auto kurtosis(Container const & v)
+{
+ return kurtosis(v.cbegin(), v.cend());
+}
+
+template<class ForwardIterator>
+auto excess_kurtosis(ForwardIterator first, ForwardIterator last)
+{
+ return kurtosis(first, last) - 3;
+}
+
+template<class Container>
+inline auto excess_kurtosis(Container const & v)
+{
+ return excess_kurtosis(v.cbegin(), v.cend());
+}
+
+
+template<class RandomAccessIterator>
+auto median(RandomAccessIterator first, RandomAccessIterator last)
+{
+ size_t num_elems = std::distance(first, last);
+ BOOST_ASSERT_MSG(num_elems > 0, "The median of a zero length vector is undefined.");
+ if (num_elems & 1)
+ {
+ auto middle = first + (num_elems - 1)/2;
+ std::nth_element(first, middle, last);
+ return *middle;
+ }
+ else
+ {
+ auto middle = first + num_elems/2 - 1;
+ std::nth_element(first, middle, last);
+ std::nth_element(middle, middle+1, last);
+ return (*middle + *(middle+1))/2;
+ }
+}
+
+
+template<class RandomAccessContainer>
+inline auto median(RandomAccessContainer & v)
+{
+ return median(v.begin(), v.end());
+}
+
+template<class RandomAccessIterator>
+auto gini_coefficient(RandomAccessIterator first, RandomAccessIterator last)
+{
+ using Real = typename std::iterator_traits<RandomAccessIterator>::value_type;
+ BOOST_ASSERT_MSG(first != last && std::next(first) != last, "Computation of the Gini coefficient requires at least two samples.");
+
+ std::sort(first, last);
+ if constexpr (std::is_integral<Real>::value)
+ {
+ double i = 1;
+ double num = 0;
+ double denom = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ num += *it*i;
+ denom += *it;
+ ++i;
+ }
+
+ // If the l1 norm is zero, all elements are zero, so every element is the same.
+ if (denom == 0)
+ {
+ return double(0);
+ }
+
+ return ((2*num)/denom - i)/(i-1);
+ }
+ else
+ {
+ Real i = 1;
+ Real num = 0;
+ Real denom = 0;
+ for (auto it = first; it != last; ++it)
+ {
+ num += *it*i;
+ denom += *it;
+ ++i;
+ }
+
+ // If the l1 norm is zero, all elements are zero, so every element is the same.
+ if (denom == 0)
+ {
+ return Real(0);
+ }
+
+ return ((2*num)/denom - i)/(i-1);
+ }
+}
+
+template<class RandomAccessContainer>
+inline auto gini_coefficient(RandomAccessContainer & v)
+{
+ return gini_coefficient(v.begin(), v.end());
+}
+
+template<class RandomAccessIterator>
+inline auto sample_gini_coefficient(RandomAccessIterator first, RandomAccessIterator last)
+{
+ size_t n = std::distance(first, last);
+ return n*gini_coefficient(first, last)/(n-1);
+}
+
+template<class RandomAccessContainer>
+inline auto sample_gini_coefficient(RandomAccessContainer & v)
+{
+ return sample_gini_coefficient(v.begin(), v.end());
+}
+
+template<class RandomAccessIterator>
+auto median_absolute_deviation(RandomAccessIterator first, RandomAccessIterator last, typename std::iterator_traits<RandomAccessIterator>::value_type center=std::numeric_limits<typename std::iterator_traits<RandomAccessIterator>::value_type>::quiet_NaN())
+{
+ using std::abs;
+ using Real = typename std::iterator_traits<RandomAccessIterator>::value_type;
+ using std::isnan;
+ if (isnan(center))
+ {
+ center = boost::math::tools::median(first, last);
+ }
+ size_t num_elems = std::distance(first, last);
+ BOOST_ASSERT_MSG(num_elems > 0, "The median of a zero-length vector is undefined.");
+ auto comparator = [&center](Real a, Real b) { return abs(a-center) < abs(b-center);};
+ if (num_elems & 1)
+ {
+ auto middle = first + (num_elems - 1)/2;
+ std::nth_element(first, middle, last, comparator);
+ return abs(*middle);
+ }
+ else
+ {
+ auto middle = first + num_elems/2 - 1;
+ std::nth_element(first, middle, last, comparator);
+ std::nth_element(middle, middle+1, last, comparator);
+ return (abs(*middle) + abs(*(middle+1)))/abs(static_cast<Real>(2));
+ }
+}
+
+template<class RandomAccessContainer>
+inline auto median_absolute_deviation(RandomAccessContainer & v, typename RandomAccessContainer::value_type center=std::numeric_limits<typename RandomAccessContainer::value_type>::quiet_NaN())
+{
+ return median_absolute_deviation(v.begin(), v.end(), center);
+}
+
+}
+#endif