rgsl/types/
discrete_hankel.rs

1//
2// A rust binding for the GSL library by Guillaume Gomez (guillaume1.gomez@gmail.com)
3//
4
5/*!
6# Discrete Hankel Transforms
7
8This chapter describes functions for performing Discrete Hankel Transforms (DHTs).
9
10## Definitions
11
12The discrete Hankel transform acts on a vector of sampled data, where the samples are assumed to
13have been taken at points related to the zeroes of a Bessel function of fixed order; compare this to
14the case of the discrete Fourier transform, where samples are taken at points related to the zeroes
15of the sine or cosine function.
16
17Specifically, let f(t) be a function on the unit interval and j_(\nu,m) the m-th zero of the Bessel
18function J_\nu(x). Then the finite \nu-Hankel transform of f(t) is defined to be the set of numbers
19g_m given by,
20
21g_m = \int_0^1 t dt J_\nu(j_(\nu,m)t) f(t),
22
23so that,
24
25f(t) = \sum_{m=1}^\infty (2 J_\nu(j_(\nu,m)t) / J_(\nu+1)(j_(\nu,m))^2) g_m.
26
27Suppose that f is band-limited in the sense that g_m=0 for m > M. Then we have the following
28fundamental sampling theorem.
29
30g_m = (2 / j_(\nu,M)^2)
31      \sum_{k=1}^{M-1} f(j_(\nu,k)/j_(\nu,M))
32          (J_\nu(j_(\nu,m) j_(\nu,k) / j_(\nu,M)) / J_(\nu+1)(j_(\nu,k))^2).
33
34It is this discrete expression which defines the discrete Hankel transform. The kernel in the
35summation above defines the matrix of the \nu-Hankel transform of size M-1. The coefficients of this
36matrix, being dependent on \nu and M, must be precomputed and stored; the gsl_dht object
37encapsulates this data. The allocation function gsl_dht_alloc returns a gsl_dht object which must be
38properly initialized with gsl_dht_init before it can be used to perform transforms on data sample
39vectors, for fixed \nu and M, using the gsl_dht_apply function. The implementation allows a scaling
40of the fundamental interval, for convenience, so that one can assume the function is defined on the
41interval [0,X], rather than the unit interval.
42
43Notice that by assumption f(t) vanishes at the endpoints of the interval, consistent with the
44inversion formula and the sampling formula given above. Therefore, this transform corresponds to an
45orthogonal expansion in eigenfunctions of the Dirichlet problem for the Bessel differential
46equation.
47
48## References and Further Reading
49
50The algorithms used by these functions are described in the following papers,
51
52H. Fisk Johnson, Comp. Phys. Comm. 43, 181 (1987).
53D. Lemoine, J. Chem. Phys. 101, 3936 (1994).
54!*/
55
56use crate::Value;
57use ffi::FFI;
58
59ffi_wrapper!(DiscreteHankel, *mut sys::gsl_dht, gsl_dht_free);
60
61impl DiscreteHankel {
62    /// This function allocates a Discrete Hankel transform object of size `size`.
63    #[doc(alias = "gsl_dht_alloc")]
64    pub fn new(size: usize) -> Option<Self> {
65        let tmp = unsafe { sys::gsl_dht_alloc(size) };
66
67        if tmp.is_null() {
68            None
69        } else {
70            Some(Self::wrap(tmp))
71        }
72    }
73
74    /// This function allocates a Discrete Hankel transform object of size `size` and initializes it
75    /// for the given values of `nu` and `xmax`.
76    #[doc(alias = "gsl_dht_new")]
77    pub fn new_with_init(size: usize, nu: f64, xmax: f64) -> Option<Self> {
78        let tmp = unsafe { sys::gsl_dht_new(size, nu, xmax) };
79
80        if tmp.is_null() {
81            None
82        } else {
83            Some(Self::wrap(tmp))
84        }
85    }
86
87    /// This function initializes the transform `self` for the given values of `nu` and `xmax`.
88    #[doc(alias = "gsl_dht_init")]
89    pub fn init(&mut self, nu: f64, xmax: f64) -> Result<(), Value> {
90        let ret = unsafe { sys::gsl_dht_init(self.unwrap_unique(), nu, xmax) };
91        result_handler!(ret, ())
92    }
93
94    /// This function applies the transform t to the array f_in whose size is equal to the size of
95    /// the transform. The result is stored in the array `f_out` which must be of the same length.
96    ///
97    /// Applying this function to its output gives the original data multiplied by (1/j_(\nu,M))^2,
98    /// up to numerical errors.
99    #[doc(alias = "gsl_dht_apply")]
100    pub fn apply(&mut self, f_in: &[f64]) -> Result<Vec<f64>, Value> {
101        unsafe {
102            assert!(
103                (*self.unwrap_shared()).size == f_in.len() as _,
104                "f_in and f_out must have the same length as this struct"
105            );
106            let mut f_out: Vec<f64> = std::iter::repeat(0.).take(f_in.len()).collect();
107            let ret = sys::gsl_dht_apply(
108                self.unwrap_unique(),
109                f_in.as_ptr() as usize as *mut _,
110                f_out.as_mut_ptr(),
111            );
112            result_handler!(ret, f_out)
113        }
114    }
115
116    /// This function returns the value of the n-th sample point in the unit interval,
117    /// (j_{\nu,n+1}/j_{\nu,M}) X. These are the points where the function f(t) is assumed to be
118    /// sampled.
119    #[doc(alias = "gsl_dht_x_sample")]
120    pub fn x_sample(&self, n: i32) -> f64 {
121        unsafe { sys::gsl_dht_x_sample(self.unwrap_shared(), n) }
122    }
123
124    /// This function returns the value of the n-th sample point in “k-space”, j_{\nu,n+1}/X.
125    #[doc(alias = "gsl_dht_k_sample")]
126    pub fn k_sample(&self, n: i32) -> f64 {
127        unsafe { sys::gsl_dht_k_sample(self.unwrap_shared(), n) }
128    }
129}
130
131// The following tests have been made and tested against the following C code:
132//
133// ```ignore
134// #include <gsl/gsl_dht.h>
135//
136// int main() {
137//     gsl_dht *t = gsl_dht_alloc(3);
138//     printf("%d\n", gsl_dht_init(t, 3., 2.));
139//     printf("%f %f\n", gsl_dht_x_sample(t, 1), gsl_dht_k_sample(t, 1));
140//     double in[] = {100., 2., 3.};
141//     double out[] = {0., 0., 0.};
142//     gsl_dht_apply(t, in, out);
143//     printf("%f %f %f\n", out[0], out[1], out[2]);
144//     gsl_dht_free(t);
145//     return 0;
146// }
147// ```
148#[test]
149fn discrete_hankel() {
150    let mut d = DiscreteHankel::new(3).unwrap();
151    assert!(d.init(3., 2.).is_ok());
152    assert_eq!(
153        &format!("{:.4} {:.4}", d.x_sample(1), d.k_sample(1)),
154        "1.2033 4.8805"
155    );
156    let v = d.apply(&[100., 2., 3.]).unwrap();
157    assert_eq!(
158        &format!("{:.4} {:.4} {:.4}", v[0], v[1], v[2]),
159        "8.5259 13.9819 11.7320"
160    );
161}