Gemmi C++ API
Loading...
Searching...
No Matches
bessel.hpp
Go to the documentation of this file.
1// Functions derived from modified Bessel functions I1(x) and I0(x).
2//
3// Crystallographic codes (including Refmac and cctbx) often use polynomial
4// approximation of I0 and I1 from p. 378 of Abramowitz and Stegun.
5// Gemmi uses approximation based on polynomial coefficients from bessel_i0
6// and bessel_i1(float) from Boost.Math:
7// https://www.boost.org/doc/libs/1_76_0/libs/math/doc/html/math_toolkit/bessel/mbessel.html
8// This approximation was derived in 2017 by John Maddock,
9// building on the work of Pavel Holoborodko:
10// https://www.advanpix.com/2015/11/11/rational-approximations-for-the-modified-bessel-function-of-the-first-kind-i0-computations-double-precision/
11// The efficiency is similar to that of scitbx.math.bessel_i1_over_i0.
12// Using std::cyl_bessel_if was not considered, because it requires C++17,
13
14#ifndef GEMMI_BESSEL_HPP_
15#define GEMMI_BESSEL_HPP_
16
17#include <cmath>
18
19namespace gemmi {
20
21template<int N>
22inline double evaluate_polynomial(const double(&poly)[N], double x) {
23 static_assert(N > 1, "");
24 double result = poly[N-1];
25 for (int i = N-2; i >= 0; --i)
26 result = result * x + poly[i];
27 return result;
28}
29
30template<class Dummy>
31struct BesselTables_
32{
33 static const double P1[8];
34 static const double Q1[9];
35 static const double P2[5];
36 static const double Q2[5];
37 static const double Q3[3];
38};
39template<class Dummy> const double BesselTables_<Dummy>::P1[8] = {
40 8.333333221e-02,
41 6.944453712e-03,
42 3.472097211e-04,
43 1.158047174e-05,
44 2.739745142e-07,
45 5.135884609e-09,
46 5.262251502e-11,
47 1.331933703e-12
48};
49template<class Dummy> const double BesselTables_<Dummy>::Q1[9] = {
50 1.00000003928615375e+00,
51 2.49999576572179639e-01,
52 2.77785268558399407e-02,
53 1.73560257755821695e-03,
54 6.96166518788906424e-05,
55 1.89645733877137904e-06,
56 4.29455004657565361e-08,
57 3.90565476357034480e-10,
58 1.48095934745267240e-11
59};
60template<class Dummy> const double BesselTables_<Dummy>::P2[5] = {
61 3.98942115977513013e-01,
62 -1.49581264836620262e-01,
63 -4.76475741878486795e-02,
64 -2.65157315524784407e-02,
65 -1.47148600683672014e-01
66};
67template<class Dummy> const double BesselTables_<Dummy>::Q2[5] = {
68 3.98942651588301770e-01,
69 4.98327234176892844e-02,
70 2.91866904423115499e-02,
71 1.35614940793742178e-02,
72 1.31409251787866793e-01
73};
74template<class Dummy> const double BesselTables_<Dummy>::Q3[3] = {
75 3.98942391532752700e-01,
76 4.98455950638200020e-02,
77 2.94835666900682535e-02
78};
79
80
81inline double bessel_i1_over_i0(double x) {
82 using B = BesselTables_<void>;
83 if (x < 0)
84 return -bessel_i1_over_i0(-x);
85 if (x < 7.75) {
86 double a = x * x / 4;
87 double bessel0 = a * evaluate_polynomial(B::Q1, a) + 1;
88 double R[3] = { 1, 0.5f, evaluate_polynomial(B::P1, a) };
89 double bessel1 = x * evaluate_polynomial(R, a) / 2;
90 return bessel1 / bessel0;
91 }
92 double p = evaluate_polynomial(B::P2, 1 / x);
93 double q = x < 50 ? evaluate_polynomial(B::Q2, 1 / x)
94 : evaluate_polynomial(B::Q3, 1 / x);
95 return p / q;
96}
97
98// Simplified function from Boost.Math.
99// Similar to std::cyl_bessel_i(0, x), but much faster, less exact and doesn't
100// throw out_of_range on negative argument. Relative error < 5.02e-08.
101inline double bessel_i0(double x) {
102 using B = BesselTables_<void>;
103 x = std::fabs(x);
104 if (x < 7.75) {
105 double a = x * x / 4;
106 return a * evaluate_polynomial(B::Q1, a) + 1;
107 }
108 if (x < 50)
109 return std::exp(x) * evaluate_polynomial(B::Q2, 1 / x) / std::sqrt(x);
110 double ex = std::exp(x / 2);
111 return ex * evaluate_polynomial(B::Q3, 1 / x) / std::sqrt(x) * ex;
112}
113
114// Relative error < 4e-08.
115inline double log_bessel_i0(double x) {
116 using B = BesselTables_<void>;
117 x = std::fabs(x);
118 if (x < 7.75) {
119 double a = x * x / 4;
120 return std::log1p(a * evaluate_polynomial(B::Q1, a));
121 }
122 double q = x < 50 ? evaluate_polynomial(B::Q2, 1 / x)
123 : evaluate_polynomial(B::Q3, 1 / x);
124 return x + std::log(q / std::sqrt(x));
125}
126
127} // namespace gemmi
128#endif
double evaluate_polynomial(const double(&poly)[N], double x)
Definition bessel.hpp:22
double bessel_i0(double x)
Definition bessel.hpp:101
double bessel_i1_over_i0(double x)
Definition bessel.hpp:81
double log_bessel_i0(double x)
Definition bessel.hpp:115