scirs2_fft/
hartley.rs

1//! Hartley Transform implementation
2//!
3//! The Hartley transform is a real-valued alternative to the Fourier transform.
4//! It is related to the FFT by: H(f) = Re(FFT(f)) - Im(FFT(f))
5
6use scirs2_core::ndarray::{Array1, ArrayBase, Data, Dimension};
7use scirs2_core::numeric::Complex64;
8
9use crate::error::{FFTError, FFTResult};
10use crate::fft::fft;
11
12/// Compute the Discrete Hartley Transform (DHT) of a real-valued sequence.
13///
14/// The Hartley transform is defined as:
15/// H\[k\] = sum_{n=0}^{N-1} x\[n\] * cas(2*pi*k*n/N)
16///
17/// where cas(x) = cos(x) + sin(x) = sqrt(2) * cos(x - pi/4)
18///
19/// # Arguments
20///
21/// * `x` - Input array (can be complex, but imaginary part is ignored)
22///
23/// # Returns
24///
25/// The Hartley transform of the input array.
26///
27/// # Example
28///
29/// ```
30/// use scirs2_core::ndarray::array;
31/// use scirs2_fft::hartley::dht;
32///
33/// let x = array![1.0, 2.0, 3.0, 4.0];
34/// let h = dht(&x).unwrap();
35/// println!("Hartley transform: {:?}", h);
36/// ```
37#[allow(dead_code)]
38pub fn dht<S, D>(x: &ArrayBase<S, D>) -> FFTResult<Array1<f64>>
39where
40    S: Data<Elem = f64>,
41    D: Dimension,
42{
43    // Flatten input to 1D for processing
44    let x_flat = x.iter().cloned().collect::<Vec<f64>>();
45    let n = x_flat.len();
46
47    if n == 0 {
48        return Err(FFTError::ValueError("empty array".to_string()));
49    }
50
51    // Convert to complex array
52    let mut x_complex = Vec::new();
53    for &val in x_flat.iter() {
54        x_complex.push(Complex64::new(val, 0.0));
55    }
56
57    // Compute FFT
58    let fft_result = fft(&x_complex, None)?;
59
60    // Compute Hartley transform: H[k] = Re(F[k]) - Im(F[k])
61    let mut hartley = Array1::zeros(n);
62    for i in 0..n {
63        hartley[i] = fft_result[i].re - fft_result[i].im;
64    }
65
66    Ok(hartley)
67}
68
69/// Compute the inverse Discrete Hartley Transform (IDHT).
70///
71/// The inverse Hartley transform has the same form as the forward transform,
72/// but with a scaling factor of 1/N.
73///
74/// # Arguments
75///
76/// * `h` - Input Hartley-transformed array
77///
78/// # Returns
79///
80/// The inverse Hartley transform of the input array.
81///
82/// # Example
83///
84/// ```
85/// use scirs2_core::ndarray::array;
86/// use scirs2_fft::hartley::{dht, idht};
87///
88/// let x = array![1.0, 2.0, 3.0, 4.0];
89/// let h = dht(&x).unwrap();
90/// let x_recovered = idht(&h).unwrap();
91/// assert!((x_recovered[0] - 1.0).abs() < 1e-10);
92/// ```
93#[allow(dead_code)]
94pub fn idht<S>(h: &ArrayBase<S, scirs2_core::ndarray::Ix1>) -> FFTResult<Array1<f64>>
95where
96    S: Data<Elem = f64>,
97{
98    let n = h.len();
99
100    if n == 0 {
101        return Err(FFTError::ValueError("empty array".to_string()));
102    }
103
104    // The Hartley transform is self-inverse up to a scaling factor
105    let mut result = dht(h)?;
106
107    // Apply scaling factor
108    let scale = 1.0 / n as f64;
109    result.map_inplace(|x| *x *= scale);
110
111    Ok(result)
112}
113
114/// Compute the 2D Discrete Hartley Transform.
115///
116/// # Arguments
117///
118/// * `x` - Input 2D array
119/// * `axes` - Axes along which to compute the transform (default: (0, 1))
120///
121/// # Returns
122///
123/// The 2D Hartley transform of the input array.
124///
125/// # Example
126///
127/// ```
128/// use scirs2_core::ndarray::array;
129/// use scirs2_fft::hartley::dht2;
130///
131/// let x = array![[1.0, 2.0], [3.0, 4.0]];
132/// let h = dht2(&x, None).unwrap();
133/// println!("2D Hartley transform: {:?}", h);
134/// ```
135#[allow(dead_code)]
136pub fn dht2<S>(
137    x: &ArrayBase<S, scirs2_core::ndarray::Ix2>,
138    axes: Option<(usize, usize)>,
139) -> FFTResult<scirs2_core::ndarray::Array2<f64>>
140where
141    S: Data<Elem = f64>,
142{
143    let axes = axes.unwrap_or((0, 1));
144    let shape = x.shape();
145
146    if axes.0 >= 2 || axes.1 >= 2 {
147        return Err(FFTError::ValueError(format!(
148            "Axes out of bounds: {axes:?}"
149        )));
150    }
151
152    // Apply 1D Hartley transform along first axis
153    let mut result = scirs2_core::ndarray::Array2::zeros((shape[0], shape[1]));
154
155    if axes.0 == 0 {
156        // Transform along rows
157        for j in 0..shape[1] {
158            let column = x.slice(scirs2_core::ndarray::s![.., j]);
159            let transformed = dht(&column)?;
160            for i in 0..shape[0] {
161                result[[i, j]] = transformed[i];
162            }
163        }
164    } else {
165        // Transform along columns
166        for i in 0..shape[0] {
167            let row = x.slice(scirs2_core::ndarray::s![i, ..]);
168            let transformed = dht(&row)?;
169            for j in 0..shape[1] {
170                result[[i, j]] = transformed[j];
171            }
172        }
173    }
174
175    // Apply 1D Hartley transform along second axis
176    let mut final_result = scirs2_core::ndarray::Array2::zeros((shape[0], shape[1]));
177
178    if axes.1 == 1 {
179        // Transform along columns
180        for i in 0..shape[0] {
181            let row = result.slice(scirs2_core::ndarray::s![i, ..]);
182            let transformed = dht(&row)?;
183            for j in 0..shape[1] {
184                final_result[[i, j]] = transformed[j];
185            }
186        }
187    } else {
188        // Transform along rows
189        for j in 0..shape[1] {
190            let column = result.slice(scirs2_core::ndarray::s![.., j]);
191            let transformed = dht(&column)?;
192            for i in 0..shape[0] {
193                final_result[[i, j]] = transformed[i];
194            }
195        }
196    }
197
198    Ok(final_result)
199}
200
201/// Fast Hartley Transform using FFT
202///
203/// This is an optimized version that uses FFT directly for better performance.
204#[allow(dead_code)]
205pub fn fht<S, D>(x: &ArrayBase<S, D>) -> FFTResult<Array1<f64>>
206where
207    S: Data<Elem = f64>,
208    D: Dimension,
209{
210    // This is an alias for dht, but could be optimized further in the future
211    dht(x)
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use scirs2_core::ndarray::array;
218
219    #[test]
220    fn test_hartley_transform() {
221        let x = array![1.0, 2.0, 3.0, 4.0];
222        let h = dht(&x).unwrap();
223
224        // Test inverse
225        let x_recovered = idht(&h).unwrap();
226        for i in 0..x.len() {
227            assert!(
228                (x[i] - x_recovered[i]).abs() < 1e-10,
229                "Failed at index {}: expected {}, got {}",
230                i,
231                x[i],
232                x_recovered[i]
233            );
234        }
235    }
236
237    #[test]
238    fn test_hartley_properties() {
239        // Test that the Hartley transform of a real signal is real
240        let x = array![1.0, 2.0, 3.0, 4.0, 5.0];
241        let h = dht(&x).unwrap();
242
243        // All values should be real (they already are by construction)
244        for &val in h.iter() {
245            assert!(val.is_finite());
246        }
247    }
248
249    #[test]
250    fn test_2d_hartley() {
251        let x = array![[1.0, 2.0], [3.0, 4.0]];
252        let h = dht2(&x, None).unwrap();
253
254        // Test that the result has the same shape
255        assert_eq!(h.shape(), x.shape());
256    }
257
258    #[test]
259    fn test_empty_input() {
260        let x: Array1<f64> = array![];
261        let result = dht(&x);
262        assert!(result.is_err());
263    }
264}