Skip to content

Commit 712d6cf

Browse files
authored
Merge pull request #17 from jorenham/gamma
2 parents 8ed393e + a53a8a6 commit 712d6cf

File tree

3 files changed

+253
-37
lines changed

3 files changed

+253
-37
lines changed

src/ffi.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,19 @@ unsafe extern "C" {
4040

4141
// boost/math/special_functions/gamma.hpp
4242
pub fn math_tgamma(x: f64) -> f64;
43-
pub fn math_lgamma(x: f64) -> f64;
44-
pub fn math_gamma_p(a: f64, x: f64) -> f64;
43+
pub fn math_tgamma1pm1(x: f64) -> f64;
44+
pub fn math_tgamma_(a: f64, x: f64) -> f64;
45+
pub fn math_tgamma_lower(a: f64, x: f64) -> f64;
46+
pub fn math_tgamma_ratio(a: f64, b: f64) -> f64;
47+
pub fn math_tgamma_delta_ratio(x: f64, delta: f64) -> f64;
48+
pub fn math_lgamma(x: f64, sign: *mut c_int) -> f64;
4549
pub fn math_gamma_q(a: f64, x: f64) -> f64;
50+
pub fn math_gamma_q_inv(a: f64, q: f64) -> f64;
51+
pub fn math_gamma_q_inva(x: f64, q: f64) -> f64;
52+
pub fn math_gamma_p(a: f64, x: f64) -> f64;
53+
pub fn math_gamma_p_inv(a: f64, p: f64) -> f64;
54+
pub fn math_gamma_p_inva(x: f64, p: f64) -> f64;
55+
pub fn math_gamma_p_derivative(a: f64, x: f64) -> f64;
4656

4757
// boost/math/special_functions/jacobi.hpp
4858
pub fn math_jacobi(n: c_uint, alpha: f64, beta: f64, x: f64) -> f64;
Lines changed: 227 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,272 @@
11
//! boost/math/special_functions/gamma.hpp
22
//!
3-
//! # TODO:
4-
//! - `tgamma1pm1`
5-
//! - `tgamma_lower`
6-
//! - `tgamma_delta_ratio`
7-
//! - `tgamma_ratio`
8-
//! - `gamma_p_derivative`
9-
//! - `gamma_p_inv`
10-
//! - `gamma_p_inva`
11-
//! - `gamma_q_inv`
12-
//! - `gamma_q_inva`
3+
//! Note that we deviate from the original "tgamma" names by dropping the "t" prefix.
134
145
use crate::ffi;
6+
use core::ffi::c_int;
157

168
/// Gamma function *Γ(x)*
179
///
10+
/// Corresponds to `boost::math::tgamma(x)` in C++.
1811
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/tgamma.html>
19-
pub fn tgamma(x: f64) -> f64 {
12+
pub fn gamma(x: f64) -> f64 {
2013
unsafe { ffi::math_tgamma(x) }
2114
}
2215

23-
/// Log-Gamma function *ln |Γ(x)|*
16+
/// Accurate evaluation of `tgamma(x + 1) - 1` for very small `x`
2417
///
25-
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/lgamma.html>
26-
pub fn lgamma(x: f64) -> f64 {
27-
unsafe { ffi::math_lgamma(x) }
18+
/// Internally the implementation does not make use of the addition and subtraction implied by the
19+
/// definition, leading to accurate results even for very small `x`.
20+
///
21+
/// See [`gamma`] for the gamma function itself.
22+
///
23+
/// Corresponds to `boost::math::tgamma1pm1(x)` in C++.
24+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/tgamma.html>
25+
pub fn gamma1pm1(x: f64) -> f64 {
26+
unsafe { ffi::math_tgamma1pm1(x) }
2827
}
2928

30-
/// Incomplete gamma function *P(a,x) = γ(a,x) / Γ(a)*
29+
/// Upper incomplete gamma function *Γ(a,x)*
30+
///
31+
/// See also:
32+
/// - [`gamma`]: Gamma function *Γ(x)*
33+
/// - [`gamma_lower`]: Lower incomplete gamma function *γ(a,x)*
34+
/// - [`gamma_q`]: Normalized upper incomplete gamma function *Q(a,x) = Γ(a,x) / Γ(a)*
3135
///
36+
/// Corresponds to `boost::math::tgamma(a, x)` in C++.
3237
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma.html>
33-
pub fn gamma_p(a: f64, x: f64) -> f64 {
34-
unsafe { ffi::math_gamma_p(a, x) }
38+
pub fn gamma_upper(a: f64, x: f64) -> f64 {
39+
unsafe { ffi::math_tgamma_(a, x) }
3540
}
3641

37-
/// Incomplete gamma function *Q(a,x) = Γ(a,x) / Γ(a)*
42+
/// Lower incomplete gamma function *γ(a,x)*
43+
///
44+
/// See also:
45+
/// - [`gamma`]: Gamma function *Γ(x)*
46+
/// - [`gamma_upper`]: Upper incomplete gamma function *Γ(a,x)*
47+
/// - [`gamma_p`]: Normalized lower incomplete gamma function *P(a,x) = γ(a,x) / Γ(a)*
3848
///
49+
/// Corresponds to `boost::math::tgamma_lower(a, x)` in C++.
50+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma.html>
51+
pub fn gamma_lower(a: f64, x: f64) -> f64 {
52+
unsafe { ffi::math_tgamma_lower(a, x) }
53+
}
54+
55+
/// Ratio of two gamma functions *Γ(a) / Γ(b)*
56+
///
57+
/// See [`gamma`] for the gamma function itself.
58+
///
59+
/// Corresponds to `boost::math::tgamma_ratio(a, b)` in C++.
60+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/gamma_ratios.html>
61+
pub fn gamma_ratio(a: f64, b: f64) -> f64 {
62+
unsafe { ffi::math_tgamma_ratio(a, b) }
63+
}
64+
65+
/// Ratio of two gamma functions *Γ(x) / Γ(x + δ)*
66+
///
67+
/// See [`gamma`] for the gamma function itself.
68+
///
69+
/// Corresponds to `boost::math::tgamma_delta_ratio(x, delta)` in C++.
70+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/gamma_ratios.html>
71+
pub fn gamma_delta_ratio(x: f64, delta: f64) -> f64 {
72+
unsafe { ffi::math_tgamma_delta_ratio(x, delta) }
73+
}
74+
75+
/// Natural logarithm of the absolute value of the gamma function
76+
///
77+
/// The integer part of the tuple indicates the sign of the gamma function.
78+
///
79+
/// See [`gamma`] for the gamma function itself.
80+
///
81+
/// Corresponds to `boost::math::lgamma(x, *sign)` in C++.
82+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/lgamma.html>
83+
pub fn lgamma(x: f64) -> (f64, i32) {
84+
let mut sign: c_int = 0;
85+
let out = unsafe { ffi::math_lgamma(x, &mut sign) };
86+
assert_ne!(sign, 0);
87+
(out, sign)
88+
}
89+
90+
/// Normalized upper incomplete gamma function *Q(a,x)*
91+
///
92+
/// *Q(a,x) = Γ(a,x) / Γ(a)*
93+
///
94+
/// See also:
95+
/// - [`gamma`]: Gamma function *Γ(x)*
96+
/// - [`gamma_upper`]: Upper incomplete gamma function *Γ(a,x)*
97+
/// - [`gamma_p`]: Normalized lower incomplete gamma function *P(a,x)*
98+
///
99+
/// Corresponds to `boost::math::gamma_q(a, x)` in C++.
39100
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma.html>
40101
pub fn gamma_q(a: f64, x: f64) -> f64 {
41102
unsafe { ffi::math_gamma_q(a, x) }
42103
}
43104

105+
/// Inverse of [`gamma_q`] w.r.t. `x`
106+
///
107+
/// Corresponds to `boost::math::gamma_q_inv(a, p)` in C++.
108+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma_inv.html>
109+
pub fn gamma_q_inv(a: f64, q: f64) -> f64 {
110+
unsafe { ffi::math_gamma_q_inv(a, q) }
111+
}
112+
113+
/// Inverse of [`gamma_q`] w.r.t. `a`
114+
///
115+
/// Corresponds to `boost::math::gamma_q_inva(x, p)` in C++.
116+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma_inv.html>
117+
pub fn gamma_q_inva(x: f64, q: f64) -> f64 {
118+
unsafe { ffi::math_gamma_q_inva(x, q) }
119+
}
120+
121+
/// Normalized lower incomplete gamma function *P(a,x)*
122+
///
123+
/// *P(a,x) = γ(a,x) / Γ(a)*
124+
///
125+
/// See also:
126+
/// - [`gamma`]: Gamma function *Γ(x)*
127+
/// - [`gamma_lower`]: Lower incomplete gamma function *γ(a,x)*
128+
/// - [`gamma_q`]: Normalized upper incomplete gamma function *Q(a,x)*
129+
///
130+
/// Corresponds to `boost::math::gamma_p(a, x)` in C++.
131+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma.html>
132+
pub fn gamma_p(a: f64, x: f64) -> f64 {
133+
unsafe { ffi::math_gamma_p(a, x) }
134+
}
135+
136+
/// Inverse of [`gamma_p`] w.r.t. `x`
137+
///
138+
/// Corresponds to `boost::math::gamma_p_inv(a, p)` in C++.
139+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma_inv.html>
140+
pub fn gamma_p_inv(a: f64, p: f64) -> f64 {
141+
unsafe { ffi::math_gamma_p_inv(a, p) }
142+
}
143+
144+
/// Inverse of [`gamma_p`] w.r.t. `a`
145+
///
146+
/// Corresponds to `boost::math::gamma_p_inva(x, p)` in C++.
147+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/igamma_inv.html>
148+
pub fn gamma_p_inva(x: f64, p: f64) -> f64 {
149+
unsafe { ffi::math_gamma_p_inva(x, p) }
150+
}
151+
152+
/// Derivative of the normalized lower incomplete gamma function
153+
///
154+
/// *P'(a,x) = e<sup>-x</sup> x<sup>a-1</sup> / Γ(a)*
155+
///
156+
/// Note that the derivative of the function [`gamma_q`] can be obtained by negating the result of
157+
/// this function, i.e., *Q'(a,x) = -P'(a,x)*.
158+
///
159+
/// Also note that this is the probability distribution function of the standard gamma distribution.
160+
///
161+
/// See [`gamma_p`] for the normalized lower incomplete gamma function.
162+
///
163+
/// Corresponds to `boost::math::gamma_p_derivative(a, x)` in C++.
164+
/// <https://boost.org/doc/libs/latest/libs/math/doc/html/math_toolkit/sf_gamma/gamma_derivatives.html>
165+
pub fn gamma_p_derivative(a: f64, x: f64) -> f64 {
166+
unsafe { ffi::math_gamma_p_derivative(a, x) }
167+
}
168+
44169
#[cfg(test)]
45170
mod tests {
46171
use super::*;
47172

48-
const ATOL: f64 = f64::EPSILON;
173+
const RTOL: f64 = f64::EPSILON;
174+
const SQRT_PI: f64 = 1.772_453_850_905_516; // √π
175+
176+
#[test]
177+
fn test_gamma() {
178+
assert!(gamma(0.0).is_infinite());
179+
assert_relative_eq!(gamma(-0.5), -2.0 * SQRT_PI, epsilon = RTOL);
180+
assert_relative_eq!(gamma(0.5), SQRT_PI, epsilon = RTOL);
181+
assert_relative_eq!(gamma(1.0), 1.0, epsilon = RTOL);
182+
assert_relative_eq!(gamma(2.0), 1.0, epsilon = RTOL);
183+
assert_relative_eq!(gamma(3.0), 2.0, epsilon = RTOL);
184+
}
49185

50186
#[test]
51-
fn test_tgamma() {
52-
assert_abs_diff_eq!(tgamma(1.0), 1.0, epsilon = ATOL);
53-
assert_abs_diff_eq!(tgamma(0.5), core::f64::consts::PI.sqrt(), epsilon = ATOL);
54-
assert_abs_diff_eq!(tgamma(5.0), 24.0, epsilon = ATOL);
187+
fn test_gamma1pm1() {
188+
assert_relative_eq!(gamma1pm1(-0.5), SQRT_PI - 1.0, epsilon = RTOL);
189+
assert_relative_eq!(gamma1pm1(0.0), 0.0, epsilon = RTOL);
190+
assert_relative_eq!(gamma1pm1(1.0), 0.0, epsilon = RTOL);
191+
assert_relative_eq!(gamma1pm1(2.0), 1.0, epsilon = RTOL);
192+
// results from Wolfram Alpha
193+
assert_abs_diff_eq!(
194+
gamma1pm1(-1e-14),
195+
5.772_156_649_015_427_5e-15,
196+
epsilon = 1e-30
197+
);
198+
assert_abs_diff_eq!(
199+
gamma1pm1(1e-14),
200+
-5.772_156_649_015_229_5e-15,
201+
epsilon = 1e-30
202+
);
203+
}
204+
205+
#[test]
206+
fn test_gamma_upper() {
207+
assert!(gamma_upper(4.2, 0.5).is_finite());
208+
}
209+
210+
#[test]
211+
fn test_gamma_lower() {
212+
assert!(gamma_lower(4.2, 0.5).is_finite());
213+
}
214+
215+
#[test]
216+
fn test_gamma_ratio() {
217+
assert_relative_eq!(gamma_ratio(4.0, 6.0), 0.05, epsilon = RTOL);
218+
}
219+
220+
#[test]
221+
fn test_gamma_delta_ratio() {
222+
assert_relative_eq!(gamma_delta_ratio(4.0, 2.0), 0.05, epsilon = RTOL);
55223
}
56224

57225
#[test]
58226
fn test_lgamma() {
59-
assert_abs_diff_eq!(lgamma(1.0), 0.0, epsilon = ATOL);
227+
let (val, sign) = lgamma(0.5);
228+
assert!(val.is_finite());
229+
assert!(val > 0.0);
230+
assert_eq!(sign, 1);
231+
232+
let (val, sign) = lgamma(-0.5);
233+
assert!(val.is_finite());
234+
assert!(val > 0.0);
235+
assert_eq!(sign, -1);
236+
}
237+
238+
#[test]
239+
fn test_gamma_q() {
240+
assert!(gamma_q(4.2, 0.5).is_finite());
241+
}
242+
243+
#[test]
244+
fn test_gamma_q_inv() {
245+
assert!(gamma_q_inv(4.2, 0.5).is_finite());
246+
}
247+
248+
#[test]
249+
fn test_gamma_q_inva() {
250+
assert!(gamma_q_inva(4.2, 0.5).is_finite());
60251
}
61252

62253
#[test]
63254
fn test_gamma_p() {
64-
assert_abs_diff_eq!(gamma_p(2.0, 0.0), 0.0, epsilon = ATOL);
255+
assert!(gamma_p(4.2, 0.5).is_finite());
65256
}
257+
66258
#[test]
67-
fn test_gamma_q() {
68-
assert_abs_diff_eq!(gamma_q(2.0, 0.0), 1.0, epsilon = ATOL);
259+
fn test_gamma_p_inv() {
260+
assert!(gamma_p_inv(4.2, 0.5).is_finite());
261+
}
262+
263+
#[test]
264+
fn test_gamma_p_inva() {
265+
assert!(gamma_p_inva(4.2, 0.5).is_finite());
69266
}
70267

71268
#[test]
72-
fn test_gamma_p_q_relation() {
73-
assert_abs_diff_eq!(gamma_p(2.0, 1.0) + gamma_q(2.0, 1.0), 1.0, epsilon = ATOL);
269+
fn test_gamma_p_derivative() {
270+
assert!(gamma_p_derivative(4.2, 0.5).is_finite());
74271
}
75272
}

wrapper.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
// Simple C++ wrappers for the Boost Math functions
1+
// Simple C++ wrappers for the Boost Math functions for https://github.com/jorenham/boost-rust
22

3-
// Boost static config, see
4-
// https://www.boost.org/doc/libs/latest/boost/math/tools/user.hpp
53
#ifndef BOOST_MATH_TOOLS_USER_HPP
4+
// https://www.boost.org/doc/libs/latest/boost/math/tools/user.hpp
65
#define BOOST_MATH_TOOLS_USER_HPP
76
#define BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
87
#define BOOST_MATH_DOMAIN_ERROR_POLICY errno_on_error
@@ -65,9 +64,19 @@ double math_rising_factorial(double x, int n) { return rising_factorial(x, n); }
6564

6665
// boost/math/special_functions/gamma.hpp
6766
double math_tgamma(double x) { return tgamma(x); }
68-
double math_lgamma(double x) { return lgamma(x); }
69-
double math_gamma_p(double a, double x) { return gamma_p(a, x); }
67+
double math_tgamma1pm1(double x) { return tgamma1pm1(x); }
68+
double math_tgamma_(double a, double x) { return tgamma(a, x); }
69+
double math_tgamma_lower(double a, double x) { return tgamma_lower(a, x); }
70+
double math_tgamma_ratio(double a, double b) { return tgamma_ratio(a, b); }
71+
double math_tgamma_delta_ratio(double x, double delta) { return tgamma_delta_ratio(x, delta); }
72+
double math_lgamma(double x, int* sign) { return lgamma(x, sign); }
7073
double math_gamma_q(double a, double x) { return gamma_q(a, x); }
74+
double math_gamma_q_inv(double a, double q) { return gamma_q_inv(a, q); }
75+
double math_gamma_q_inva(double x, double q) { return gamma_q_inva(x, q); }
76+
double math_gamma_p(double a, double x) { return gamma_p(a, x); }
77+
double math_gamma_p_inv(double a, double p) { return gamma_p_inv(a, p); }
78+
double math_gamma_p_inva(double x, double p) { return gamma_p_inva(x, p); }
79+
double math_gamma_p_derivative(double a, double x) { return gamma_p_derivative(a, x); }
7180

7281
// boost/math/special_functions/jacobi.hpp
7382
double math_jacobi(unsigned n, double alpha, double beta, double x) {

0 commit comments

Comments
 (0)