1use num_complex::Complex64;
73use crate::fft::Fft3dWorkspace;
74use crate::kernels::dipole::dipole_kernel;
75use crate::kernels::laplacian::laplacian_kernel;
76use crate::utils::apply_mask_zero;
77use crate::utils::gradient::{bdiv_inplace, fgrad_inplace};
78use crate::Grid;
79
80#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
82#[derive(Clone, Debug)]
83pub struct RtsParams {
84 pub delta: f64,
86 pub mu: f64,
88 pub rho: f64,
90 pub tol: f64,
92 pub max_iter: usize,
94 pub lsmr_iter: usize,
96 pub tau: f64,
98 pub gamma: f64,
100 pub pad: [i64; 3],
104}
105
106impl Default for RtsParams {
107 fn default() -> Self {
108 Self {
109 delta: 0.15,
110 mu: 1e5,
111 rho: 10.0,
112 tol: 1e-2,
113 max_iter: 20,
114 lsmr_iter: 4,
115 tau: 0.7,
116 gamma: 5.0,
117 pad: crate::utils::padding::QSMJL_PAD,
118 }
119 }
120}
121
122fn half_norm(v: &[Complex64], nx: usize) -> f64 {
125 let half = nx / 2;
126 v.iter()
127 .enumerate()
128 .filter(|(i, _)| i % nx <= half)
129 .map(|(_, c)| c.norm_sqr())
130 .sum::<f64>()
131 .sqrt()
132}
133
134fn lsmr_diag(d: &[f64], b: &[Complex64], nx: usize, maxit: usize) -> Vec<Complex64> {
138 let n = b.len();
139 let zero = Complex64::new(0.0, 0.0);
140 let mut x = vec![zero; n];
141
142 let mut u = b.to_vec();
143 let mut beta = half_norm(&u, nx);
144 if beta > 0.0 {
145 u.iter_mut().for_each(|c| *c /= beta);
146 }
147 let mut v: Vec<Complex64> = (0..n).map(|i| u[i] * d[i]).collect();
148 let mut alpha = half_norm(&v, nx);
149 if alpha * beta == 0.0 {
150 return x;
151 }
152 if alpha > 0.0 {
153 v.iter_mut().for_each(|c| *c /= alpha);
154 }
155
156 let mut zetabar = alpha * beta;
157 let mut alphabar = alpha;
158 let mut rho = 1.0f64;
159 let mut rhobar = 1.0f64;
160 let mut cbar = 1.0f64;
161 let mut sbar = 0.0f64;
162
163 let mut hbar = vec![zero; n];
164 let mut h = v.clone();
165
166 let mut betadd = beta;
167 let mut betad = 0.0f64;
168 let mut rhodold = 1.0f64;
169 let mut tautildeold = 0.0f64;
170 let mut thetatilde = 0.0f64;
171 let mut zeta = 0.0f64;
172 let mut dsum = 0.0f64;
173
174 let mut norm_a2 = alpha * alpha;
175 let mut maxrbar = 0.0f64;
176 let mut minrbar = f64::MAX;
177 let normb = beta;
178 let ctol = 1e-8; let mut i = 0;
181 while i < maxit {
182 i += 1;
183
184 for k in 0..n {
186 u[k] = v[k] * d[k] - u[k] * alpha;
187 }
188 beta = half_norm(&u, nx);
189 if beta > 0.0 {
190 u.iter_mut().for_each(|c| *c /= beta);
191 for k in 0..n {
192 v[k] = u[k] * d[k] - v[k] * beta;
193 }
194 alpha = half_norm(&v, nx);
195 if alpha > 0.0 {
196 v.iter_mut().for_each(|c| *c /= alpha);
197 }
198 }
199
200 let alphahat = alphabar.hypot(0.0);
202 let chat = alphabar / alphahat;
203 let shat = 0.0 / alphahat;
204
205 let rhoold = rho;
206 rho = alphahat.hypot(beta);
207 let c = alphahat / rho;
208 let s = beta / rho;
209 let thetanew = s * alpha;
210 alphabar = c * alpha;
211
212 let rhobarold = rhobar;
213 let zetaold = zeta;
214 let thetabar = sbar * rho;
215 let rhotmp = cbar * rho;
216 rhobar = rhotmp.hypot(thetanew);
217 cbar = rhotmp / rhobar;
218 sbar = thetanew / rhobar;
219 zeta = cbar * zetabar;
220 zetabar *= -sbar;
221
222 let delta = -thetabar * rho / (rhoold * rhobarold);
223 let sigma = zeta / (rho * rhobar);
224 let nu = -thetanew / rho;
225 let mut normx2 = 0.0;
226 for k in 0..n {
227 hbar[k] = hbar[k] * delta + h[k];
228 x[k] += hbar[k] * sigma;
229 h[k] = h[k] * nu + v[k];
230 if k % nx <= nx / 2 {
231 normx2 += x[k].norm_sqr();
232 }
233 }
234 let normx = normx2.sqrt();
235
236 let betaacute = chat * betadd;
237 let betacheck = -shat * betadd;
238 let betahat = c * betaacute;
239 betadd = -s * betaacute;
240
241 let thetatildeold = thetatilde;
242 let rhotildeold = rhodold.hypot(thetabar);
243 let ctildeold = rhodold / rhotildeold;
244 let stildeold = thetabar / rhotildeold;
245 thetatilde = stildeold * rhobar;
246 rhodold = ctildeold * rhobar;
247 betad = -stildeold * betad + ctildeold * betahat;
248
249 tautildeold = (zetaold - thetatildeold * tautildeold) / rhotildeold;
250 let taud = (zeta - thetatilde * tautildeold) / rhodold;
251
252 dsum += betacheck * betacheck;
253 let normr = (dsum + (betad - taud).powi(2) + betadd * betadd).sqrt();
254
255 norm_a2 += beta * beta;
256 let norm_a = norm_a2.sqrt();
257 norm_a2 += alpha * alpha;
258
259 maxrbar = maxrbar.max(rhobarold);
260 if i > 1 {
261 minrbar = minrbar.min(rhobarold);
262 }
263 let cond_a = maxrbar.max(rhotmp) / minrbar.min(rhotmp);
264
265 let norm_ar = zetabar.abs();
266 let test1 = normr / normb;
267 let test2 = norm_ar / (norm_a * normr);
268 let test3 = 1.0 / cond_a;
269 let t1 = test1 / (1.0 + norm_a * normx / normb);
270 let eps_r = 0.0; if test1 <= eps_r
273 || test2 <= 0.0
274 || test3 <= ctol
275 || 1.0 + t1 <= 1.0
276 || 1.0 + test2 <= 1.0
277 || 1.0 + test3 <= 1.0
278 || i >= maxit
279 {
280 break;
281 }
282 }
283 x
284}
285
286pub fn rts(
299 local_field: &[f64],
300 mask: &[u8],
301 grid: &Grid,
302 bdir: (f64, f64, f64),
303 params: &RtsParams,
304 progress: impl FnMut(usize, usize),
305) -> Vec<f64> {
306 crate::utils::padding::with_qsmjl_padding(local_field, mask, grid, params.pad, |f, m, g| {
307 rts_unpadded(f, m, g, bdir, params, progress)
308 })
309}
310
311fn rts_unpadded(
313 local_field: &[f64],
314 mask: &[u8],
315 grid: &Grid,
316 bdir: (f64, f64, f64),
317 params: &RtsParams,
318 mut progress: impl FnMut(usize, usize),
319) -> Vec<f64> {
320 let (nx, ny, nz) = grid.dims;
321 let n = grid.n_total();
322 let zero = Complex64::new(0.0, 0.0);
323
324 let mut fft_ws = Fft3dWorkspace::new(nx, ny, nz);
325 let d = dipole_kernel(grid, bdir);
326
327 let mut work: Vec<Complex64> = laplacian_kernel(grid, true)
329 .iter()
330 .map(|&v| Complex64::new(v, 0.0))
331 .collect();
332 fft_ws.fft3d(&mut work);
333 let mut l: Vec<f64> = work.iter().map(|c| c.re).collect();
334 l[0] = 0.0;
337
338 let m: Vec<f64> = d.iter().map(|&dv| if dv.abs() > params.delta { params.mu } else { 0.0 }).collect();
340
341 for i in 0..n {
343 work[i] = Complex64::new(local_field[i], 0.0);
344 }
345 fft_ws.fft3d(&mut work);
346 let mut xhat = lsmr_diag(&d, &work, nx, params.lsmr_iter);
347 crate::fft::c2r_spectrum(&mut xhat, nx, ny, nz);
349 fft_ws.ifft3d(&mut xhat);
350 let mut x: Vec<f64> = (0..n).map(|i| if mask[i] != 0 { xhat[i].re } else { 0.0 }).collect();
351
352 let mut rho = params.rho;
354 for i in 0..n {
355 work[i] = Complex64::new(x[i], 0.0);
356 }
357 fft_ws.fft3d(&mut work);
358 let mut f_hat = vec![zero; n];
359 let mut i_a = vec![0.0f64; n];
360 for i in 0..n {
361 let a = rho * l[i] + m[i];
362 if a != 0.0 {
363 let ia = 1.0 / a;
364 f_hat[i] = work[i] * (ia * m[i]);
365 i_a[i] = rho * ia;
366 }
367 }
368
369 let mut px = vec![0.0f64; n];
370 let mut py = vec![0.0f64; n];
371 let mut pz = vec![0.0f64; n];
372 let mut vx = vec![0.0f64; n];
374 let mut vy = vec![0.0f64; n];
375 let mut vz = vec![0.0f64; n];
376 fgrad_inplace(&mut vx, &mut vy, &mut vz, &x, grid);
377 let mut dx = vec![0.0f64; n];
378 let mut dy = vec![0.0f64; n];
379 let mut dz = vec![0.0f64; n];
380 let mut div = vec![0.0f64; n];
381 let mut x0 = vec![0.0f64; n];
382 let mut nr = f64::MAX;
383
384 for it in 1..=params.max_iter {
385 progress(it, params.max_iter);
386 std::mem::swap(&mut x, &mut x0);
387
388 bdiv_inplace(&mut div, &vx, &vy, &vz, grid);
390 for i in 0..n {
391 work[i] = Complex64::new(-div[i], 0.0);
392 }
393 fft_ws.fft3d(&mut work);
394 for i in 0..n {
395 work[i] = work[i] * i_a[i] + f_hat[i];
396 }
397 crate::fft::c2r_spectrum(&mut work, nx, ny, nz);
398 fft_ws.ifft3d(&mut work);
399 for i in 0..n {
400 x[i] = work[i].re;
401 }
402
403 let (mut ndx2, mut nx2) = (0.0, 0.0);
405 for i in 0..n {
406 let (a, b) = (x[i], x0[i]);
407 ndx2 += (a - b) * (a - b);
408 nx2 += a * a;
409 }
410 if ndx2.sqrt() < params.tol * nx2.sqrt() || it == params.max_iter {
411 progress(it, it);
412 break;
413 }
414
415 fgrad_inplace(&mut dx, &mut dy, &mut dz, &x, grid);
417 let thr = 1.0 / rho;
418 let update = |p: &mut [f64], v: &mut [f64], dd: &mut [f64]| {
419 for i in 0..n {
420 let di = dd[i];
421 let pi = p[i] + di;
422 let y = if pi.abs() <= thr { 0.0 } else { (pi.abs() - thr).copysign(pi) };
423 p[i] = pi - y;
424 v[i] = y - pi + y;
425 dd[i] = di - y;
426 }
427 };
428 update(&mut px, &mut vx, &mut dx);
429 update(&mut py, &mut vy, &mut dy);
430 update(&mut pz, &mut vz, &mut dz);
431
432 let nr0 = nr;
434 nr = (0..n).map(|i| (dx[i] * dx[i] + dy[i] * dy[i] + dz[i] * dz[i]).sqrt()).sum();
435 if nr > params.tau * nr0 {
436 let rho0 = rho;
437 rho *= params.gamma;
438 for i in 0..n {
439 let iaprev = i_a[i];
440 if iaprev != 0.0 {
441 let ia = 1.0 / (rho * l[i] + m[i]);
442 f_hat[i] *= ia * rho0 / iaprev;
443 i_a[i] = rho * ia;
444 }
445 }
446 let ig = 1.0 / params.gamma;
447 px.iter_mut().for_each(|v| *v *= ig);
448 py.iter_mut().for_each(|v| *v *= ig);
449 pz.iter_mut().for_each(|v| *v *= ig);
450 }
451 }
452
453 apply_mask_zero(&mut x, mask);
454 x
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460
461 #[test]
462 fn test_rts_zero_field() {
463 let n = 8;
464 let field = vec![0.0; n * n * n];
465 let mask = vec![1u8; n * n * n];
466 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
467 let params = RtsParams { max_iter: 5, lsmr_iter: 2, ..Default::default() };
468
469 let chi = rts(&field, &mask, &grid, (0.0, 0.0, 1.0), ¶ms, |_, _| {});
470
471 for &val in chi.iter() {
472 assert!(val.abs() < 1e-6, "Zero field should give near-zero chi");
473 }
474 }
475
476 #[test]
477 fn test_rts_finite() {
478 let n = 8;
479 let field: Vec<f64> = (0..n*n*n).map(|i| (i as f64) * 0.001).collect();
480 let mask = vec![1u8; n * n * n];
481 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
482 let params = RtsParams { max_iter: 5, lsmr_iter: 2, ..Default::default() };
483
484 let chi = rts(&field, &mask, &grid, (0.0, 0.0, 1.0), ¶ms, |_, _| {});
485
486 for (i, &val) in chi.iter().enumerate() {
487 assert!(val.is_finite(), "Chi should be finite at index {}", i);
488 }
489 }
490
491 #[test]
492 fn test_rts_mask() {
493 let n = 8;
494 let field: Vec<f64> = (0..n*n*n).map(|i| (i as f64) * 0.001).collect();
495 let mut mask = vec![1u8; n * n * n];
496 mask[0] = 0;
498 mask[10] = 0;
499 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
500 let params = RtsParams { max_iter: 5, lsmr_iter: 2, ..Default::default() };
501
502 let chi = rts(&field, &mask, &grid, (0.0, 0.0, 1.0), ¶ms, |_, _| {});
503
504 assert_eq!(chi[0], 0.0, "Masked voxel should be zero");
505 assert_eq!(chi[10], 0.0, "Masked voxel should be zero");
506 }
507
508 #[cfg(feature = "parallel")]
510 #[test]
511 fn test_rts_parallel_matches_sequential() {
512 let n = 16;
513 let field: Vec<f64> = (0..n*n*n).map(|i| ((i as f64) * 0.7).sin() * 0.01).collect();
514 let mask = vec![1u8; n * n * n];
515 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
516 let params = RtsParams { tol: 1e-4, ..Default::default() };
517
518 let pool_1 = rayon::ThreadPoolBuilder::new().num_threads(1).build().unwrap();
520 let chi_seq = pool_1.install(|| {
521 rts(&field, &mask, &grid, (0.0, 0.0, 1.0), ¶ms, |_, _| {})
522 });
523
524 let chi_par = rts(&field, &mask, &grid, (0.0, 0.0, 1.0), ¶ms, |_, _| {});
526
527 for (i, (s, p)) in chi_seq.iter().zip(chi_par.iter()).enumerate() {
529 assert!(
530 (s - p).abs() < 1e-10,
531 "RTS mismatch at voxel {}: seq={} par={} diff={}",
532 i, s, p, (s - p).abs()
533 );
534 }
535 }
536}