condition_numbers.hpp 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // (C) Copyright Nick Thompson 2019.
  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_CONDITION_NUMBERS_HPP
  6. #define BOOST_MATH_TOOLS_CONDITION_NUMBERS_HPP
  7. #include <cmath>
  8. #include <boost/math/differentiation/finite_difference.hpp>
  9. namespace boost::math::tools {
  10. template<class Real, bool kahan=true>
  11. class summation_condition_number {
  12. public:
  13. summation_condition_number(Real const x = 0)
  14. {
  15. using std::abs;
  16. m_l1 = abs(x);
  17. m_sum = x;
  18. m_c = 0;
  19. }
  20. void operator+=(Real const & x)
  21. {
  22. using std::abs;
  23. // No need to Kahan the l1 calc; it's well conditioned:
  24. m_l1 += abs(x);
  25. if constexpr(kahan)
  26. {
  27. Real y = x - m_c;
  28. Real t = m_sum + y;
  29. m_c = (t-m_sum) -y;
  30. m_sum = t;
  31. }
  32. else
  33. {
  34. m_sum += x;
  35. }
  36. }
  37. inline void operator-=(Real const & x)
  38. {
  39. this->operator+=(-x);
  40. }
  41. // Is operator*= relevant? Presumably everything gets rescaled,
  42. // (m_sum -> k*m_sum, m_l1->k*m_l1, m_c->k*m_c),
  43. // but is this sensible? More important is it useful?
  44. // In addition, it might change the condition number.
  45. [[nodiscard]] Real operator()() const
  46. {
  47. using std::abs;
  48. if (m_sum == Real(0) && m_l1 != Real(0))
  49. {
  50. return std::numeric_limits<Real>::infinity();
  51. }
  52. return m_l1/abs(m_sum);
  53. }
  54. [[nodiscard]] Real sum() const
  55. {
  56. // Higham, 1993, "The Accuracy of Floating Point Summation":
  57. // "In [17] and [18], Kahan describes a variation of compensated summation in which the final sum is also corrected
  58. // thus s=s+e is appended to the algorithm above)."
  59. return m_sum + m_c;
  60. }
  61. [[nodiscard]] Real l1_norm() const
  62. {
  63. return m_l1;
  64. }
  65. private:
  66. Real m_l1;
  67. Real m_sum;
  68. Real m_c;
  69. };
  70. template<class F, class Real>
  71. auto evaluation_condition_number(F const & f, Real const & x)
  72. {
  73. using std::abs;
  74. using std::isnan;
  75. using std::sqrt;
  76. using boost::math::differentiation::finite_difference_derivative;
  77. Real fx = f(x);
  78. if (isnan(fx))
  79. {
  80. return std::numeric_limits<Real>::quiet_NaN();
  81. }
  82. bool caught_exception = false;
  83. Real fp;
  84. try
  85. {
  86. fp = finite_difference_derivative(f, x);
  87. }
  88. catch(...)
  89. {
  90. caught_exception = true;
  91. }
  92. if (isnan(fp) || caught_exception)
  93. {
  94. // Check if the right derivative exists:
  95. fp = finite_difference_derivative<decltype(f), Real, 1>(f, x);
  96. if (isnan(fp))
  97. {
  98. // Check if a left derivative exists:
  99. const Real eps = (std::numeric_limits<Real>::epsilon)();
  100. Real h = - 2 * sqrt(eps);
  101. h = boost::math::differentiation::detail::make_xph_representable(x, h);
  102. Real yh = f(x + h);
  103. Real y0 = f(x);
  104. Real diff = yh - y0;
  105. fp = diff / h;
  106. if (isnan(fp))
  107. {
  108. return std::numeric_limits<Real>::quiet_NaN();
  109. }
  110. }
  111. }
  112. if (fx == 0)
  113. {
  114. if (x==0 || fp==0)
  115. {
  116. return std::numeric_limits<Real>::quiet_NaN();
  117. }
  118. return std::numeric_limits<Real>::infinity();
  119. }
  120. return abs(x*fp/fx);
  121. }
  122. }
  123. #endif