1use num_complex::Complex64;
15use crate::fft::{fft3d, ifft3d};
16use crate::kernels::dipole::dipole_kernel;
17use crate::utils::apply_mask_zero;
18use crate::Grid;
19
20#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
22#[derive(Clone, Debug)]
23pub struct TkdParams {
24 pub threshold: f64,
26}
27
28impl Default for TkdParams {
29 fn default() -> Self {
30 Self { threshold: 0.15 }
31 }
32}
33
34pub fn tkd(
49 local_field: &[f64],
50 mask: &[u8],
51 grid: &Grid,
52 bdir: (f64, f64, f64),
53 params: &TkdParams,
54) -> Vec<f64> {
55 let (nx, ny, nz) = grid.dims;
56 let n_total = grid.n_total();
57 let threshold = params.threshold;
58
59 let d = dipole_kernel(grid, bdir);
61
62 let inv_threshold = 1.0 / threshold;
65 let inv_d: Vec<f64> = d.iter().map(|&dval| {
66 if dval.abs() <= threshold {
67 if dval >= 0.0 { inv_threshold } else { -inv_threshold }
69 } else {
70 1.0 / dval
72 }
73 }).collect();
74
75 let mut field_complex: Vec<Complex64> = local_field.iter()
77 .map(|&x| Complex64::new(x, 0.0))
78 .collect();
79
80 fft3d(&mut field_complex, nx, ny, nz);
82
83 for i in 0..n_total {
85 field_complex[i] *= inv_d[i];
86 }
87
88 ifft3d(&mut field_complex, nx, ny, nz);
90
91 let mut chi: Vec<f64> = field_complex.iter()
93 .map(|c| c.re)
94 .collect();
95
96 apply_mask_zero(&mut chi, mask);
97
98 chi
99}
100
101pub fn tsvd(
106 local_field: &[f64],
107 mask: &[u8],
108 grid: &Grid,
109 bdir: (f64, f64, f64),
110 params: &TkdParams,
111) -> Vec<f64> {
112 let (nx, ny, nz) = grid.dims;
113 let n_total = grid.n_total();
114 let threshold = params.threshold;
115
116 let d = dipole_kernel(grid, bdir);
118
119 let inv_d: Vec<f64> = d.iter().map(|&dval| {
122 if dval.abs() <= threshold {
123 0.0 } else {
125 1.0 / dval
126 }
127 }).collect();
128
129 let mut field_complex: Vec<Complex64> = local_field.iter()
131 .map(|&x| Complex64::new(x, 0.0))
132 .collect();
133
134 fft3d(&mut field_complex, nx, ny, nz);
136
137 for i in 0..n_total {
139 field_complex[i] *= inv_d[i];
140 }
141
142 ifft3d(&mut field_complex, nx, ny, nz);
144
145 let mut chi: Vec<f64> = field_complex.iter()
147 .map(|c| c.re)
148 .collect();
149
150 apply_mask_zero(&mut chi, mask);
151
152 chi
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_tkd_zero_field() {
161 let n = 8;
163 let field = vec![0.0; n * n * n];
164 let mask = vec![1u8; n * n * n];
165 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
166
167 let chi = tkd(&field, &mask, &grid, (0.0, 0.0, 1.0), &TkdParams { threshold: 0.15 });
168
169 for val in chi.iter() {
170 assert!(val.abs() < 1e-10, "Zero field should give zero chi");
171 }
172 }
173
174 #[test]
175 fn test_tkd_mask() {
176 let n = 8;
178 let field = vec![1.0; n * n * n];
179 let mut mask = vec![1u8; n * n * n];
180 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
181
182 mask[0] = 0;
184 mask[1] = 0;
185
186 let chi = tkd(&field, &mask, &grid, (0.0, 0.0, 1.0), &TkdParams { threshold: 0.15 });
187
188 assert_eq!(chi[0], 0.0, "Outside mask should be 0");
189 assert_eq!(chi[1], 0.0, "Outside mask should be 0");
190 }
191
192 #[test]
193 fn test_tkd_finite() {
194 let n = 8;
196 let field: Vec<f64> = (0..n*n*n).map(|i| (i as f64) * 0.01).collect();
197 let mask = vec![1u8; n * n * n];
198 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
199
200 let chi = tkd(&field, &mask, &grid, (0.0, 0.0, 1.0), &TkdParams { threshold: 0.15 });
201
202 for (i, val) in chi.iter().enumerate() {
203 assert!(val.is_finite(), "Chi should be finite at index {}", i);
204 }
205 }
206
207 #[test]
208 fn test_tsvd_vs_tkd() {
209 let n = 16;
211 let field: Vec<f64> = (0..n*n*n).map(|i| ((i as f64) * 0.1).sin()).collect();
212 let mask = vec![1u8; n * n * n];
213 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
214
215 let chi_tkd = tkd(&field, &mask, &grid, (0.0, 0.0, 1.0), &TkdParams { threshold: 0.15 });
216 let chi_tsvd = tsvd(&field, &mask, &grid, (0.0, 0.0, 1.0), &TkdParams { threshold: 0.15 });
217
218 let diff: f64 = chi_tkd.iter().zip(chi_tsvd.iter())
220 .map(|(a, b)| (a - b).abs())
221 .sum();
222
223 assert!(diff > 1e-10, "TKD and TSVD should give different results");
224 }
225}