1use scirs2_core::ndarray::{Array1, ArrayBase, Data, Dimension};
7use scirs2_core::numeric::Complex64;
8
9use crate::error::{FFTError, FFTResult};
10use crate::fft::fft;
11
12#[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 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 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 let fft_result = fft(&x_complex, None)?;
59
60 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#[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 let mut result = dht(h)?;
106
107 let scale = 1.0 / n as f64;
109 result.map_inplace(|x| *x *= scale);
110
111 Ok(result)
112}
113
114#[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 let mut result = scirs2_core::ndarray::Array2::zeros((shape[0], shape[1]));
154
155 if axes.0 == 0 {
156 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 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 let mut final_result = scirs2_core::ndarray::Array2::zeros((shape[0], shape[1]));
177
178 if axes.1 == 1 {
179 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 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#[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 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 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 let x = array![1.0, 2.0, 3.0, 4.0, 5.0];
241 let h = dht(&x).unwrap();
242
243 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 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}