1use crate::error::{check_len, InputError};
212use crate::fft::Fft3dWorkspace;
213use crate::inversion::medi::{par_fill, par_update};
214use crate::inversion::{IlsqrParams, STI_DEFAULT_NITER, STI_GUI_PAD_MM};
215use crate::Grid;
216use num_complex::Complex64;
217#[cfg(feature = "parallel")]
218use rayon::prelude::*;
219
220use super::chi_sep_medi::toolbox_default_dr;
221use super::ChiSepMaps;
222
223pub const NNLS_TOL: f64 = 0.01;
225pub const NNLS_ITER_MAX: usize = 10;
228const BALL_RADIUS_MM: f64 = 2.5;
230const TKD_THRESHOLD: f64 = 0.125;
232const TUKEY_RATIO: f64 = 0.5;
234
235pub fn chisep_toolbox_ilsqr() -> IlsqrParams {
240 IlsqrParams { max_iter: STI_DEFAULT_NITER, pad_mm: STI_GUI_PAD_MM, ..IlsqrParams::default() }
241}
242
243#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
246#[derive(Clone, Debug)]
247pub struct ChiSepIlsqrParams {
248 pub cf: f64,
251 pub dr_pos: Option<f64>,
254 pub dr_neg: Option<f64>,
257 pub pad: [usize; 3],
260 pub cg_max_iter_lsqr: usize,
263 pub cg_tol_lsqr: f64,
265 pub cg_max_iter_sa: usize,
267 pub cg_tol_sa: f64,
269 pub d2_thr: f64,
271 pub prc_w_i: (f64, f64),
274 pub prc_w_g: (f64, f64),
277 pub qsm_ilsqr: IlsqrParams,
284}
285
286impl Default for ChiSepIlsqrParams {
287 fn default() -> Self {
288 Self {
289 cf: 123.2e6,
290 dr_pos: None,
291 dr_neg: None,
292 pad: [16; 3],
293 cg_max_iter_lsqr: 50,
294 cg_tol_lsqr: 0.02,
295 cg_max_iter_sa: 50,
296 cg_tol_sa: 0.02,
297 d2_thr: 0.11,
298 prc_w_i: (60.0, 99.8),
299 prc_w_g: (50.0, 70.0),
300 qsm_ilsqr: chisep_toolbox_ilsqr(),
301 }
302 }
303}
304
305impl ChiSepIlsqrParams {
306 pub fn dr(&self) -> (f64, f64) {
309 let default = toolbox_default_dr(self.cf);
310 (self.dr_pos.unwrap_or(default), self.dr_neg.unwrap_or(default))
311 }
312
313 pub fn validate(&self) -> Result<(), InputError> {
315 let pos = |name: &str, v: f64| {
316 if v.is_finite() && v > 0.0 {
317 Ok(())
318 } else {
319 Err(InputError::param(name, v, "finite and > 0"))
320 }
321 };
322 pos("χ-separation iLSQR cf", self.cf)?;
323 let (dp, dn) = self.dr();
324 pos("χ-separation iLSQR dr_pos", dp)?;
325 pos("χ-separation iLSQR dr_neg", dn)?;
326 pos("χ-separation iLSQR d2_thr", self.d2_thr)?;
327 for (name, v) in [("χ-separation iLSQR cg_tol_lsqr", self.cg_tol_lsqr), ("χ-separation iLSQR cg_tol_sa", self.cg_tol_sa)] {
328 if !(v.is_finite() && v > 0.0 && v < 1.0) {
329 return Err(InputError::param(name, v, "in (0, 1)"));
330 }
331 }
332 for (name, (lo, hi)) in [("χ-separation iLSQR prc_w_i", self.prc_w_i), ("χ-separation iLSQR prc_w_g", self.prc_w_g)] {
333 if !(lo.is_finite() && hi.is_finite() && (0.0..=100.0).contains(&lo) && (0.0..=100.0).contains(&hi) && lo < hi) {
334 return Err(InputError::param(name, format!("({lo}, {hi})"), "0 ≤ min < max ≤ 100"));
335 }
336 }
337 Ok(())
338 }
339}
340
341#[derive(Clone, Copy)]
345pub struct ChiSepIlsqrInputs<'a> {
346 pub local_field: &'a [f64],
348 pub r2prime: &'a [f64],
350 pub qsm: &'a [f64],
353 pub mask: &'a [u8],
355}
356
357pub fn chi_sep_ilsqr(
368 inputs: &ChiSepIlsqrInputs,
369 grid: &Grid,
370 bdir: (f64, f64, f64),
371 params: &ChiSepIlsqrParams,
372 mut progress: impl FnMut(usize, usize),
373) -> Result<ChiSepMaps, InputError> {
374 params.validate()?;
375 let n0 = grid.n_total();
376 for (name, len) in [
377 ("χ-separation iLSQR local_field", inputs.local_field.len()),
378 ("χ-separation iLSQR r2prime", inputs.r2prime.len()),
379 ("χ-separation iLSQR qsm", inputs.qsm.len()),
380 ("χ-separation iLSQR mask", inputs.mask.len()),
381 ] {
382 check_len(name, len, n0)?;
383 }
384 if !inputs.mask.iter().any(|&m| m != 0) {
385 return Err(InputError("χ-separation iLSQR mask is empty".into()));
386 }
387 let padded: usize = (0..3).map(|a| [grid.dims.0, grid.dims.1, grid.dims.2][a] + 2 * params.pad[a]).product();
388 if padded > u32::MAX as usize {
389 return Err(InputError(format!("χ-separation iLSQR padded grid has {padded} voxels, more than 2^32")));
390 }
391 let (pb, mut ws, r) = Problem::new(inputs, grid, bdir, params);
392 let n = pb.n;
393
394 let (drp, drn) = (pb.drp, pb.drn);
396 let mut x0 = vec![0.0; 2 * n];
397 {
398 let q = pb.pad_input(inputs.qsm);
399 let (a, b) = x0.split_at_mut(n);
400 let m = &pb.m;
401 par_fill(a, |i| ((r[i] + drn * q[i]) / (drp + drn)).max(0.0) * f64::from(m[i]));
402 par_fill(b, |i| ((r[i] - drp * q[i]) / (drp + drn)).max(0.0) * f64::from(m[i]));
403 }
404 drop(r);
405 let mut x = pb.step1(&mut ws, Start::Owned(x0), params.cg_tol_lsqr, params.cg_max_iter_lsqr);
406 pb.step2(&mut ws, &mut x, params);
407 let mut cur = pb.on_mask(&x, false);
409 drop(x);
410
411 let ni = pb.inside.len();
413 let max_it = NNLS_ITER_MAX + 1;
414 let mut res = f64::INFINITY;
415 let mut res_min = f64::INFINITY;
416 let mut best = cur.clone();
417 let mut it = 1;
418 while res > NNLS_TOL && it <= max_it {
419 let start = |x: &mut [f64]| pb.padded_from_mask(&cur, x);
421 let mut x = pb.step1(&mut ws, Start::Fill(&start), params.cg_tol_lsqr, 1);
422 pb.step2(&mut ws, &mut x, params);
423 let new = pb.on_mask(&x, true);
425 drop(x);
426 let sums = |f: &(dyn Fn(usize) -> f64 + Sync)| pb.inside.sum(f).sqrt();
427 let (np, nn) = (&new[..ni], &new[ni..]);
428 let (cp, cn) = (&cur[..ni], &cur[ni..]);
429 res = (sums(&|j| (np[j] - cp[j]) * (np[j] - cp[j])) + sums(&|j| (nn[j] - cn[j]) * (nn[j] - cn[j])))
430 / (sums(&|j| np[j] * np[j]) + sums(&|j| nn[j] * nn[j]));
431 cur = new;
432 if res < res_min {
433 res_min = res;
434 best.copy_from_slice(&cur);
435 }
436 progress(it, max_it);
437 it += 1;
438 }
439 drop(cur);
440
441 let (chi_pos, chi_neg) = pb.crop_from_mask(&best);
442 let chi_total = chi_pos.iter().zip(&chi_neg).map(|(p, q)| p + q).collect();
443 Ok((chi_pos, chi_neg, chi_total))
444}
445
446const CHUNK: usize = 1 << 15;
448
449struct Support {
454 idx: Vec<u32>,
455 bounds: Vec<usize>,
456}
457
458impl Support {
459 fn new(n: usize, keep: impl Fn(usize) -> bool) -> Self {
461 let mut idx = Vec::new();
462 let mut bounds = Vec::with_capacity(n.div_ceil(CHUNK) + 1);
463 for l in 0..n {
464 if l.is_multiple_of(CHUNK) {
465 bounds.push(idx.len());
466 }
467 if keep(l) {
468 idx.push(l as u32);
469 }
470 }
471 bounds.push(idx.len());
472 idx.shrink_to_fit();
473 Self { idx, bounds }
474 }
475
476 fn len(&self) -> usize {
477 self.idx.len()
478 }
479
480 fn sum<T>(&self, f: impl Fn(usize) -> T + Sync) -> T
482 where
483 T: Copy + Send + Sync + std::ops::Add<Output = T> + Default,
484 {
485 chunked_sum(self.bounds.len() - 1, |c| self.bounds[c]..self.bounds[c + 1], f)
486 }
487
488 fn walk(&self, c: usize, n: usize, mut f: impl FnMut(usize, Option<usize>)) {
491 let (mut j, end) = (self.bounds[c], self.bounds[c + 1]);
492 for l in c * CHUNK..((c + 1) * CHUNK).min(n) {
493 if j < end && self.idx[j] as usize == l {
494 f(l, Some(j));
495 j += 1;
496 } else {
497 f(l, None);
498 }
499 }
500 }
501}
502
503fn par_chunked<T: Send>(a: &mut [T], f: impl Fn(usize, &mut [T]) + Sync + Send) {
505 #[cfg(feature = "parallel")]
506 a.par_chunks_mut(CHUNK).enumerate().for_each(|(c, s)| f(c, s));
507 #[cfg(not(feature = "parallel"))]
508 a.chunks_mut(CHUNK).enumerate().for_each(|(c, s)| f(c, s));
509}
510
511fn par_chunked2<A: Send, B: Send>(a: &mut [A], b: &mut [B], f: impl Fn(usize, &mut [A], &mut [B]) + Sync + Send) {
513 #[cfg(feature = "parallel")]
514 a.par_chunks_mut(CHUNK).zip(b.par_chunks_mut(CHUNK)).enumerate().for_each(|(c, (s, t))| f(c, s, t));
515 #[cfg(not(feature = "parallel"))]
516 a.chunks_mut(CHUNK).zip(b.chunks_mut(CHUNK)).enumerate().for_each(|(c, (s, t))| f(c, s, t));
517}
518
519fn par_rows<T: Send>(a: &mut [T], (nx, ny, _): (usize, usize, usize), f: impl Fn(usize, usize, usize, &mut [T]) + Sync + Send) {
522 #[cfg(feature = "parallel")]
523 a.par_chunks_mut(nx).with_min_len(64).enumerate().for_each(|(r, row)| f(r % ny, r / ny, r * nx, row));
524 #[cfg(not(feature = "parallel"))]
525 a.chunks_mut(nx).enumerate().for_each(|(r, row)| f(r % ny, r / ny, r * nx, row));
526}
527
528struct Kernel {
532 k: [Vec<f64>; 3],
534 b0: (f64, f64, f64),
535}
536
537impl Kernel {
538 fn new(dims: (usize, usize, usize), vs: [f64; 3], b0: (f64, f64, f64)) -> Self {
539 let axis = |n: usize, v: f64| {
540 let mut t = vec![0.0; n];
541 for i in 0..n {
542 t[(i + n / 2) % n] = (-(n as f64) / 2.0 + i as f64) / (n as f64 * v);
543 }
544 t
545 };
546 Self { k: [axis(dims.0, vs[0]), axis(dims.1, vs[1]), axis(dims.2, vs[2])], b0 }
547 }
548
549 #[inline]
551 fn at(&self, i: usize, j: usize, k: usize) -> f64 {
552 let (x, y, z) = (self.k[0][i], self.k[1][j], self.k[2][k]);
553 let s = x * self.b0.0 + y * self.b0.1 + z * self.b0.2;
554 let den = x * x + y * y + z * z;
555 let v = 1.0 / 3.0 - (s * s) / den;
556 if v.is_nan() {
557 0.0
558 } else {
559 v
560 }
561 }
562}
563
564struct BoxVol {
566 lo: [usize; 3],
567 len: [usize; 3],
568 data: Vec<f64>,
569}
570
571impl BoxVol {
572 fn new(a: &[f64], (nx, ny, _): (usize, usize, usize), lo: [usize; 3], hi: [usize; 3]) -> Self {
573 let len = [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]];
574 let mut data = Vec::with_capacity(len[0] * len[1] * len[2]);
575 for k in lo[2]..hi[2] {
576 for j in lo[1]..hi[1] {
577 let s = lo[0] + nx * (j + ny * k);
578 data.extend_from_slice(&a[s..s + len[0]]);
579 }
580 }
581 Self { lo, len, data }
582 }
583
584 #[inline]
585 fn get(&self, p: [usize; 3]) -> f64 {
586 let q = [p[0].wrapping_sub(self.lo[0]), p[1].wrapping_sub(self.lo[1]), p[2].wrapping_sub(self.lo[2])];
587 if q[0] < self.len[0] && q[1] < self.len[1] && q[2] < self.len[2] {
588 self.data[q[0] + self.len[0] * (q[1] + self.len[1] * q[2])]
589 } else {
590 0.0
591 }
592 }
593}
594
595struct Problem {
598 dims: (usize, usize, usize),
599 n: usize,
600 dims0: (usize, usize, usize),
602 pad: [usize; 3],
603 vs: [f64; 3],
604 m: Vec<u8>,
606 inside: Support,
608 drp: f64,
610 drn: f64,
611 d: Kernel,
613 w_i: Vec<f64>,
616 b1: Vec<f64>,
618 xm: BoxVol,
622 rm: BoxVol,
623 wg_thr: [[(f64, f64); 3]; 2],
624 cone: Support,
626}
627
628struct Workspace {
630 fft: Fft3dWorkspace,
631 c: Vec<Complex64>,
632}
633
634impl Problem {
635 fn new(inputs: &ChiSepIlsqrInputs, grid: &Grid, bdir: (f64, f64, f64), params: &ChiSepIlsqrParams) -> (Self, Workspace, Vec<f64>) {
637 let dims0 = grid.dims;
638 let pad = params.pad;
639 let dims = (dims0.0 + 2 * pad[0], dims0.1 + 2 * pad[1], dims0.2 + 2 * pad[2]);
640 let n = dims.0 * dims.1 * dims.2;
641 let vs = [grid.vsx(), grid.vsy(), grid.vsz()];
642 let mut pb = Self {
643 dims,
644 n,
645 dims0,
646 pad,
647 vs,
648 m: Vec::new(),
649 inside: Support { idx: Vec::new(), bounds: Vec::new() },
650 drp: 0.0,
651 drn: 0.0,
652 d: Kernel::new(dims, vs, bdir),
653 w_i: Vec::new(),
654 b1: Vec::new(),
655 xm: BoxVol { lo: [0; 3], len: [0; 3], data: Vec::new() },
656 rm: BoxVol { lo: [0; 3], len: [0; 3], data: Vec::new() },
657 wg_thr: [[(0.0, 0.0); 3]; 2],
658 cone: Support { idx: Vec::new(), bounds: Vec::new() },
659 };
660 let mut ws = Workspace { fft: Fft3dWorkspace::new(dims.0, dims.1, dims.2), c: vec![Complex64::new(0.0, 0.0); n] };
661 let cf = params.cf;
662 let mask: Vec<u8> = inputs.mask.iter().map(|&v| u8::from(v != 0)).collect();
663 pb.m = pb.pad_input(&mask);
664 drop(mask);
665 pb.inside = Support::new(n, |l| pb.m[l] != 0);
666 let mut r = pb.pad_input(inputs.r2prime);
667 par_update(&mut r, |_, v| *v = (*v / cf * 1e6).max(0.0));
668 let f = pb.pad_input(inputs.local_field);
669 let (dp, dn) = params.dr();
670 pb.drp = dp / cf * 1e6;
671 pb.drn = dn / cf * 1e6;
672
673 let mut lap = del2(&f, dims, [vs[1], vs[0], vs[2]]);
675 par_update(&mut lap, |_, v| *v = v.abs());
676 let (lo, hi) = prc_on(&pb.inside, |l| lap[l], params.prc_w_i);
677 pb.w_i = pb.inside.idx.iter().map(|&l| clamp01((hi - lap[l as usize]) / (hi - lo))).collect();
678 drop(lap);
679 let mut b1 = vec![0.0; 2 * n];
680 {
681 let (bp, bn) = b1.split_at_mut(n);
682 pb.load_w(&mut ws.c, |l, w| w * r[l]);
684 pb.dc_c(&mut ws);
685 pb.dc_out(&mut ws, bp);
686 pb.load_w(&mut ws.c, |l, w| 2.0 * std::f64::consts::PI * w * f[l]);
688 pb.dc_c(&mut ws);
689 pb.af_c(&mut ws);
690 let c = &ws.c;
691 par_fill(bn, |i| c[i].re);
692 let (drp, drn) = (pb.drp, pb.drn);
693 let (w_i, inside) = (&pb.w_i, &pb.inside);
694 par_chunked2(bp, bn, |ch, sp, sn| {
695 let o = ch * CHUNK;
696 inside.walk(ch, n, |l, j| {
697 let w = j.map_or(0.0, |j| w_i[j]);
698 let (t, af) = (sp[l - o], sn[l - o]);
699 sp[l - o] = dp_mul(drp, w * t) + af;
700 sn[l - o] = dp_mul(drn, w * t) - af;
701 });
702 });
703 }
704 pb.b1 = b1;
705
706 let mut xm = pb.fast_qsm(&mut ws, f, vs);
708 let mut rm = pb.tukey_lpf(&mut ws, &r);
709 let m = &pb.m;
710 par_update(&mut xm, |i, v| *v *= f64::from(m[i]));
711 par_update(&mut rm, |i, v| *v *= f64::from(m[i]));
712 let (mut lo, mut hi) = ([usize::MAX; 3], [0; 3]);
713 for &l in &pb.inside.idx {
714 let l = l as usize;
715 let p = [l % dims.0, (l / dims.0) % dims.1, l / (dims.0 * dims.1)];
716 for a in 0..3 {
717 lo[a] = lo[a].min(p[a]);
718 hi[a] = hi[a].max(p[a] + 1);
719 }
720 }
721 pb.xm = BoxVol::new(&xm, dims, lo, hi);
722 drop(xm);
723 pb.rm = BoxVol::new(&rm, dims, lo, hi);
724 drop(rm);
725 for s in 0..2 {
726 for c in 0..3 {
727 let at = |l: usize| {
728 let p = [l % dims.0, (l / dims.0) % dims.1, l / (dims.0 * dims.1)];
729 pb.g_pm(s, c, p)
730 };
731 pb.wg_thr[s][c] = prc_on(&pb.inside, at, params.prc_w_g);
732 }
733 }
734 let (d, (nx, ny, _)) = (&pb.d, dims);
735 pb.cone = Support::new(n, |l| d.at(l % nx, (l / nx) % ny, l / (nx * ny)).abs() < params.d2_thr);
736 (pb, ws, r)
737 }
738
739 fn pad_input<T: Copy + Default>(&self, a: &[T]) -> Vec<T> {
741 let (nx, ny, nz) = self.dims;
742 let (mx, my, _) = self.dims0;
743 let [px, py, pz] = self.pad;
744 let mut out = vec![T::default(); nx * ny * nz];
745 for k in 0..self.dims0.2 {
746 for j in 0..my {
747 let src = mx * (j + my * k);
748 let dst = px + nx * ((j + py) + ny * (k + pz));
749 out[dst..dst + mx].copy_from_slice(&a[src..src + mx]);
750 }
751 }
752 out
753 }
754
755 fn on_mask(&self, x: &[f64], clamp: bool) -> Vec<f64> {
757 let ni = self.inside.len();
758 let mut out = vec![0.0; 2 * ni];
759 let (n, idx) = (self.n, &self.inside.idx);
760 par_fill(&mut out, |j| {
761 let v = if j < ni { x[idx[j] as usize] } else { x[n + idx[j - ni] as usize] };
762 if clamp {
763 v * f64::from(u8::from(v > 0.0))
764 } else {
765 v
766 }
767 });
768 out
769 }
770
771 fn padded_from_mask(&self, v: &[f64], x: &mut [f64]) {
773 let (n, ni) = (self.n, self.inside.len());
774 let (a, b) = x.split_at_mut(n);
775 let inside = &self.inside;
776 par_chunked2(a, b, |ch, sa, sb| {
777 let o = ch * CHUNK;
778 inside.walk(ch, n, |l, j| {
779 (sa[l - o], sb[l - o]) = j.map_or((0.0, 0.0), |j| (v[j], v[ni + j]));
780 });
781 });
782 }
783
784 fn crop_from_mask(&self, v: &[f64]) -> (Vec<f64>, Vec<f64>) {
786 let (nx, ny, _) = self.dims;
787 let (mx, my, mz) = self.dims0;
788 let [px, py, pz] = self.pad;
789 let ni = self.inside.len();
790 let (mut p, mut q) = (vec![0.0; mx * my * mz], vec![0.0; mx * my * mz]);
791 for (j, &l) in self.inside.idx.iter().enumerate() {
792 let l = l as usize;
793 let (i, jj, k) = (l % nx, (l / nx) % ny, l / (nx * ny));
794 let o = (i - px) + mx * ((jj - py) + my * (k - pz));
795 p[o] = v[j];
796 q[o] = -v[ni + j];
797 }
798 (p, q)
799 }
800
801 fn zero_ring(&self, a: &mut [f64]) {
803 let (nx, ny, _) = self.dims;
804 let (mx, my, mz) = self.dims0;
805 let [px, py, pz] = self.pad;
806 par_update(a, |l, v| {
807 let (i, j, k) = (l % nx, (l / nx) % ny, l / (nx * ny));
808 if i < px || i >= px + mx || j < py || j >= py + my || k < pz || k >= pz + mz {
809 *v = 0.0;
810 }
811 });
812 }
813
814 fn load_w(&self, c: &mut [Complex64], f: impl Fn(usize, f64) -> f64 + Sync + Send) {
817 let (n, w_i, inside) = (self.n, &self.w_i, &self.inside);
818 par_chunked(c, |ch, s| {
819 let o = ch * CHUNK;
820 inside.walk(ch, n, |l, j| s[l - o] = Complex64::new(f(l, j.map_or(0.0, |j| w_i[j])), 0.0));
821 });
822 }
823
824 fn kmul(&self, c: &mut [Complex64]) {
826 let d = &self.d;
827 par_rows(c, self.dims, |j, k, _, row| {
828 for (i, v) in row.iter_mut().enumerate() {
829 *v *= d.at(i, j, k);
830 }
831 });
832 }
833
834 fn dc_raw(&self, ws: &mut Workspace) {
836 ws.fft.fft3d(&mut ws.c);
837 self.kmul(&mut ws.c);
838 ws.fft.ifft3d(&mut ws.c);
839 }
840
841 fn dc_c(&self, ws: &mut Workspace) {
843 self.dc_raw(ws);
844 par_update(&mut ws.c, |_, v| *v = Complex64::new(v.re, 0.0));
845 }
846
847 fn dc_out(&self, ws: &mut Workspace, out: &mut [f64]) {
849 self.dc_raw(ws);
850 let c = &ws.c;
851 par_fill(out, |i| c[i].re);
852 }
853
854 fn af_c(&self, ws: &mut Workspace) {
856 self.dc_raw(ws);
857 let (n, w_i, inside) = (self.n, &self.w_i, &self.inside);
858 par_chunked(&mut ws.c, |ch, s| {
859 let o = ch * CHUNK;
860 inside.walk(ch, n, |l, j| {
861 let w = j.map_or(0.0, |j| 2.0 * std::f64::consts::PI * w_i[j]);
862 s[l - o] = Complex64::new(s[l - o].re * w, 0.0);
863 });
864 });
865 self.dc_c(ws);
866 }
867
868 fn a1(&self, ws: &mut Workspace, x: &[f64], out: &mut [f64]) {
870 let n = self.n;
871 let (xp, xn) = x.split_at(n);
872 let (drp, drn) = (self.drp, self.drn);
873 let (op, on) = out.split_at_mut(n);
874 self.load_w(&mut ws.c, |l, w| w * (drp * xp[l]) + w * (drn * xn[l]));
877 self.dc_c(ws);
878 self.dc_out(ws, op);
879 par_fill(&mut ws.c, |i| Complex64::new(xp[i] - xn[i], 0.0));
881 self.af_c(ws);
882 self.af_c(ws);
883 let (c, w_i, inside) = (&ws.c, &self.w_i, &self.inside);
884 par_chunked2(op, on, |ch, sp, sn| {
885 let o = ch * CHUNK;
886 inside.walk(ch, n, |l, j| {
887 let w = j.map_or(0.0, |j| w_i[j]);
888 let (s, t) = (sp[l - o], c[l].re);
889 sp[l - o] = dp_mul(drp, w * s) + t;
890 sn[l - o] = dp_mul(drn, w * s) - t;
891 });
892 });
893 }
894
895 fn step1(&self, ws: &mut Workspace, start: Start<f64>, tol: f64, maxit: usize) -> Vec<f64> {
897 let o = pcg(|v, o| self.a1(ws, v, o), &self.b1, tol, maxit, start, 2 * self.n, None);
898 let mut x = o.x;
899 let (a, b) = x.split_at_mut(self.n);
900 self.zero_ring(a);
901 self.zero_ring(b);
902 x
903 }
904
905 fn cone_ifft(&self, ws: &mut Workspace, y: &[Complex64], out: &mut [f64]) {
907 let (n, cone) = (self.n, &self.cone);
908 par_chunked(&mut ws.c, |ch, s| {
909 let o = ch * CHUNK;
910 cone.walk(ch, n, |l, j| s[l - o] = j.map_or(Complex64::new(0.0, 0.0), |j| y[j]));
911 });
912 ws.fft.ifft3d(&mut ws.c);
913 let c = &ws.c;
914 par_fill(out, |i| c[i].re);
915 }
916
917 #[inline]
920 fn g_pm(&self, s: usize, c: usize, p: [usize; 3]) -> f64 {
921 let len = [self.dims.0, self.dims.1, self.dims.2];
922 let (gx, gr) = if p[c] + 1 < len[c] {
923 let mut q = p;
924 q[c] += 1;
925 ((self.xm.get(q) - self.xm.get(p)) / self.vs[c], (self.rm.get(q) - self.rm.get(p)) / self.vs[c])
926 } else {
927 (0.0, 0.0)
928 };
929 let (drp, drn) = (self.drp, self.drn);
930 if s == 0 {
931 ((gr + drn * gx) / (drp + drn)).abs()
932 } else {
933 ((gr - drp * gx) / (drp + drn)).abs()
934 }
935 }
936
937 #[inline]
939 fn wg2(&self, s: usize, c: usize, p: [usize; 3], l: usize) -> f64 {
940 if self.m[l] == 0 {
941 return 0.0;
942 }
943 let (lo, hi) = self.wg_thr[s][c];
944 let w = clamp01((hi - self.g_pm(s, c, p)) / (hi - lo));
945 w * w
946 }
947
948 fn cone_fft_reg(&self, ws: &mut Workspace, s: usize, x: &[f64], out: &mut [Complex64]) {
950 let dims = self.dims;
951 let len = [dims.0, dims.1, dims.2];
952 let stride = [1, dims.0, dims.0 * dims.1];
953 let gw = |c: usize, p: [usize; 3], l: usize| -> f64 {
955 let g = if p[c] + 1 < len[c] { x[l + stride[c]] - x[l] } else { 0.0 };
956 g * self.wg2(s, c, p, l)
957 };
958 par_rows(&mut ws.c, dims, |j, k, l0, row| {
960 for (i, v) in row.iter_mut().enumerate() {
961 let (p, l) = ([i, j, k], l0 + i);
962 let mut acc = 0.0;
963 for c in 0..3 {
964 let (pos, st) = (p[c], stride[c]);
965 let mut q = p;
966 q[c] = pos.wrapping_sub(1);
967 let t = if pos == 0 {
968 -gw(c, p, l)
969 } else if pos == len[c] - 1 {
970 gw(c, q, l - st)
971 } else {
972 gw(c, q, l - st) - gw(c, p, l)
973 };
974 acc = if c == 0 { t } else { acc + t };
975 }
976 *v = Complex64::new(acc, 0.0);
977 }
978 });
979 ws.fft.fft3d(&mut ws.c);
980 let (idx, c) = (&self.cone.idx, &ws.c);
981 par_fill(out, |j| c[idx[j] as usize]);
982 }
983
984 fn step2(&self, ws: &mut Workspace, x: &mut [f64], params: &ChiSepIlsqrParams) {
986 let n = self.n;
987 let mut t = vec![0.0; n];
988 for (s, xs) in x.chunks_mut(n).enumerate() {
989 let mut b = vec![Complex64::new(0.0, 0.0); self.cone.len()];
990 self.cone_fft_reg(ws, s, xs, &mut b);
991 let y = pcg(
992 |v: &[Complex64], o: &mut [Complex64]| {
993 self.cone_ifft(ws, v, &mut t);
994 self.cone_fft_reg(ws, s, &t, o);
995 },
996 &b,
997 params.cg_tol_sa,
998 params.cg_max_iter_sa,
999 Start::Zero,
1000 n,
1001 Some(&self.cone),
1002 );
1003 drop(b);
1004 self.cone_ifft(ws, &y.x, &mut t);
1005 let m = &self.m;
1006 par_update(xs, |i, v| *v = (*v - t[i] * f64::from(m[i])) * f64::from(m[i]));
1007 }
1008 }
1009
1010 fn fast_qsm(&self, ws: &mut Workspace, f: Vec<f64>, vs: [f64; 3]) -> Vec<f64> {
1012 let n = self.n;
1013 let dims = self.dims;
1014 let d = &self.d;
1015 let m = &self.m;
1016 let mut w = vec![0.0; n];
1018 par_rows(&mut w, dims, |j, k, _, row| {
1019 for (i, v) in row.iter_mut().enumerate() {
1020 *v = d.at(i, j, k).abs().powf(0.001);
1021 }
1022 });
1023 let (a, b) = {
1024 let mut t = w.clone();
1025 (prctile(&mut t, 1.0), prctile(&mut t, 30.0))
1026 };
1027 par_update(&mut w, |_, v| *v = clamp01((*v - a) / (b - a)));
1028 let mut fk: Vec<Complex64> = f.iter().map(|&v| Complex64::new(v, 0.0)).collect();
1030 drop(f);
1031 ws.fft.fft3d(&mut fk);
1032 {
1033 let fk = &fk;
1034 par_rows(&mut ws.c, dims, |j, k, l0, row| {
1035 for (i, v) in row.iter_mut().enumerate() {
1036 let di = d.at(i, j, k);
1037 let s = if di < 0.0 { -1.0 } else { 1.0 };
1038 let dt = if di.abs() < TKD_THRESHOLD { TKD_THRESHOLD * s } else { di };
1039 *v = fk[l0 + i] / dt;
1040 }
1041 });
1042 }
1043 ws.fft.ifft3d(&mut ws.c);
1044 let mut x_tkd = vec![0.0; n];
1045 {
1046 let c = &ws.c;
1047 par_fill(&mut x_tkd, |i| c[i].re * f64::from(m[i]));
1048 }
1049 let kk = {
1051 let mut ball = pv_ball(dims, BALL_RADIUS_MM, vs);
1052 circshift(&mut ball, dims, true);
1053 let mut kk: Vec<Complex64> = ball.iter().map(|&v| Complex64::new(v, 0.0)).collect();
1054 drop(ball);
1055 ws.fft.fft3d(&mut kk);
1056 kk
1057 };
1058 let lpf = |fft: &mut Fft3dWorkspace, z: &[Complex64], out: &mut [Complex64], kk: &[Complex64]| {
1060 out.copy_from_slice(z);
1061 fft.fft3d(out);
1062 par_update(out, |i, v| *v *= kk[i]);
1063 fft.ifft3d(out);
1064 };
1065 par_rows(&mut fk, dims, |j, k, _, row| {
1067 for (i, v) in row.iter_mut().enumerate() {
1068 *v *= sign0(d.at(i, j, k));
1069 }
1070 });
1071 circshift(&mut fk, dims, false);
1072 let k1c = fk;
1073 lpf(&mut ws.fft, &k1c, &mut ws.c, &kk);
1074 par_update(&mut ws.c, |i, v| *v = k1c[i] * w[i] + *v * (1.0 - w[i]));
1075 let mut l2 = k1c;
1076 circshift(&mut ws.c, dims, true);
1077 ws.fft.ifft3d(&mut ws.c);
1078 par_update(&mut ws.c, |i, v| *v = Complex64::new(v.re * f64::from(m[i]), 0.0));
1080 ws.fft.fft3d(&mut ws.c);
1081 lpf(&mut ws.fft, &ws.c, &mut l2, &kk);
1082 drop(kk);
1083 {
1084 let km = &ws.c;
1085 par_update(&mut l2, |i, v| *v = km[i] * w[i] + *v * (1.0 - w[i]));
1086 }
1087 drop(w);
1088 ws.fft.ifft3d(&mut l2);
1089 let mut x3 = vec![0.0; n];
1090 par_fill(&mut x3, |i| l2[i].re * f64::from(m[i]));
1091 drop(l2);
1092 let nf = n as f64;
1094 let mx = det_sum(n, |i| x3[i]) / nf;
1095 let my = det_sum(n, |i| x_tkd[i]) / nf;
1096 let sxy = det_sum(n, |i| (x3[i] - mx) * (x_tkd[i] - my));
1097 let sxx = det_sum(n, |i| (x3[i] - mx) * (x3[i] - mx));
1098 let c2 = sxy / sxx;
1099 let c1 = my - c2 * mx;
1100 par_update(&mut x3, |_, v| *v = c2 * *v + c1);
1101 x3
1102 }
1103
1104 fn tukey_lpf(&self, ws: &mut Workspace, r: &[f64]) -> Vec<f64> {
1107 let (nx, ny, nz) = self.dims;
1108 let (wx, wy, wz) = (tukeywin(nx, TUKEY_RATIO), tukeywin(ny, TUKEY_RATIO), tukeywin(nz, TUKEY_RATIO));
1109 par_fill(&mut ws.c, |i| Complex64::new(r[i], 0.0));
1110 ws.fft.fft3d(&mut ws.c);
1111 circshift(&mut ws.c, self.dims, false);
1112 par_rows(&mut ws.c, self.dims, |j, k, _, row| {
1113 for (i, v) in row.iter_mut().enumerate() {
1114 *v *= (wx[i] * wy[j]) * wz[k];
1115 }
1116 });
1117 circshift(&mut ws.c, self.dims, true);
1118 ws.fft.ifft3d(&mut ws.c);
1119 let c = &ws.c;
1120 let mut out = vec![0.0; nx * ny * nz];
1121 par_fill(&mut out, |i| c[i].re);
1122 out
1123 }
1124}
1125
1126#[inline]
1128fn dp_mul(d: f64, v: f64) -> f64 {
1129 d * v
1130}
1131
1132#[allow(clippy::manual_clamp)]
1134fn clamp01(v: f64) -> f64 {
1135 v.max(0.0).min(1.0)
1136}
1137
1138fn sign0(v: f64) -> f64 {
1140 if v > 0.0 {
1141 1.0
1142 } else if v < 0.0 {
1143 -1.0
1144 } else {
1145 0.0
1146 }
1147}
1148
1149fn prc_on(inside: &Support, v: impl Fn(usize) -> f64, p: (f64, f64)) -> (f64, f64) {
1152 let mut sel: Vec<f64> = inside.idx.iter().map(|&l| v(l as usize)).collect();
1153 let lo = prctile(&mut sel, p.0);
1154 let hi = prctile(&mut sel, p.1);
1155 (lo, hi)
1156}
1157
1158fn prctile(x: &mut [f64], p: f64) -> f64 {
1162 let n = x.len();
1163 let r = (p / 100.0) * n as f64;
1164 let k = (r + 0.5).floor();
1165 let rr = r - k;
1166 let k1 = (k as i64).max(1) as usize; let kp1 = ((k as i64 + 1) as usize).min(n);
1168 let xk = *x.select_nth_unstable_by(k1 - 1, f64::total_cmp).1;
1169 let xkp1 = if kp1 == k1 { xk } else { x[k1..].iter().copied().fold(f64::INFINITY, f64::min) };
1170 if rr == -0.5 || xk == xkp1 {
1171 return xk;
1172 }
1173 (0.5 + rr) * xkp1 + (0.5 - rr) * xk
1174}
1175
1176fn tukeywin(n: usize, ratio: f64) -> Vec<f64> {
1178 if n == 1 {
1179 return vec![1.0];
1180 }
1181 let n1 = (n - 1) as f64;
1182 let t: Vec<f64> = (0..n).map(|k| if k + 1 == n { 1.0 } else { k as f64 / n1 }).collect();
1184 let per = ratio / 2.0;
1185 let tl = (per * n1).floor() as usize + 1;
1186 let th = n - tl + 1;
1187 let pi_per = std::f64::consts::PI / per;
1188 (0..n)
1189 .map(|k| {
1190 if k < tl {
1191 (1.0 + (pi_per * (t[k] - per)).cos()) / 2.0
1192 } else if k + 1 >= th {
1193 (1.0 + (pi_per * (t[k] - 1.0 + per)).cos()) / 2.0
1194 } else {
1195 1.0
1196 }
1197 })
1198 .collect()
1199}
1200
1201fn circshift<T: Copy + Send>(a: &mut [T], (nx, ny, nz): (usize, usize, usize), forward: bool) {
1205 let s = |n: usize| if forward { n / 2 } else { n.div_ceil(2) };
1206 let (sx, sy, sz) = (s(nx), s(ny), s(nz));
1207 par_rows(a, (nx, ny, nz), |_, _, _, row| row.rotate_right(sx));
1208 #[cfg(feature = "parallel")]
1209 a.par_chunks_mut(nx * ny).for_each(|p| p.rotate_right(sy * nx));
1210 #[cfg(not(feature = "parallel"))]
1211 a.chunks_mut(nx * ny).for_each(|p| p.rotate_right(sy * nx));
1212 a.rotate_right(sz * nx * ny);
1213}
1214
1215fn del2(f: &[f64], (nx, ny, nz): (usize, usize, usize), h: [f64; 3]) -> Vec<f64> {
1220 let n = [nx, ny, nz];
1221 let stride = [1, nx, nx * ny];
1222 let mut v = vec![0.0; f.len()];
1223 for ax in 0..3 {
1224 let len = n[ax];
1225 let hs: Vec<f64> = (0..len - 1).map(|i| h[ax] * (i + 2) as f64 - h[ax] * (i + 1) as f64).collect();
1226 let st = stride[ax];
1227 let g_at = |p0: usize, t: usize| -> f64 {
1228 let p = p0 + t * st;
1230 ((f[p + st] - f[p]) / hs[t] - (f[p] - f[p - st]) / hs[t - 1]) / (hs[t] + hs[t - 1])
1231 };
1232 par_update(&mut v, |l, out| {
1233 let pos = (l / st) % len;
1234 let p0 = l - pos * st;
1235 let g = if len > 3 {
1236 if pos == 0 {
1237 g_at(p0, 1) * (hs[0] + hs[1]) / hs[1] - g_at(p0, 2) * hs[0] / hs[1]
1238 } else if pos == len - 1 {
1239 -g_at(p0, len - 3) * hs[len - 2] / hs[len - 3] + g_at(p0, len - 2) * (hs[len - 2] + hs[len - 3]) / hs[len - 3]
1240 } else {
1241 g_at(p0, pos)
1242 }
1243 } else if len == 3 {
1244 g_at(p0, 1)
1245 } else {
1246 0.0
1247 };
1248 *out += g;
1249 });
1250 }
1251 par_update(&mut v, |_, x| *x /= 3.0);
1252 v
1253}
1254
1255#[cfg(test)]
1259fn grad_adjoint_unit(g: &[Vec<f64>; 3], (nx, ny, nz): (usize, usize, usize), out: &mut [f64]) {
1260 let n = [nx, ny, nz];
1261 let stride = [1, nx, nx * ny];
1262 par_fill(out, |l| {
1263 let mut acc = 0.0;
1264 for ax in 0..3 {
1265 let (len, st) = (n[ax], stride[ax]);
1266 let pos = (l / st) % len;
1267 let gg = &g[ax];
1268 let t = if pos == 0 {
1269 -gg[l]
1270 } else if pos == len - 1 {
1271 gg[l - st]
1272 } else {
1273 gg[l - st] - gg[l]
1274 };
1275 acc = if ax == 0 { t } else { acc + t };
1276 }
1277 acc
1278 });
1279}
1280
1281fn pv_ball((nx, ny, nz): (usize, usize, usize), r: f64, vs: [f64; 3]) -> Vec<f64> {
1286 let coord = |n: usize, v: f64| -> Vec<f64> { (0..n).map(|i| (-(n as f64) / 2.0 + i as f64) * v).collect() };
1287 let (cx, cy, cz) = (coord(nx, vs[0]), coord(ny, vs[1]), coord(nz, vs[2]));
1288 let h = ((vs[0] / 2.0).powi(2) + (vs[1] / 2.0).powi(2) + (vs[2] / 2.0).powi(2)).sqrt();
1289 let sub: Vec<f64> = (0..20).map(|k| if k < 10 { -0.475 + k as f64 * 0.05 } else { 0.475 - (19 - k) as f64 * 0.05 }).collect();
1291 let mut s = vec![0.0; nx * ny * nz];
1292 let r2 = r * r;
1293 for k in 0..nz {
1294 for j in 0..ny {
1295 for i in 0..nx {
1296 let (x, y, z) = (cx[i], cy[j], cz[k]);
1297 let d = (x * x + y * y + z * z).sqrt();
1298 let l = i + nx * (j + ny * k);
1299 if d <= r - h {
1300 s[l] = 1.0;
1301 } else if d < r + h {
1302 let mut cnt = 0usize;
1303 for &c in &sub {
1304 let zz = z + c * vs[2];
1305 for &b in &sub {
1306 let yy = y + b * vs[1];
1307 for &a in &sub {
1308 let xx = x + a * vs[0];
1309 if xx * xx + yy * yy + zz * zz <= r2 {
1310 cnt += 1;
1311 }
1312 }
1313 }
1314 }
1315 s[l] = cnt as f64 / 8000.0;
1316 }
1317 }
1318 }
1319 }
1320 let tot: f64 = s.iter().sum();
1321 s.iter_mut().for_each(|v| *v /= tot);
1322 s
1323}
1324
1325fn det_sum<T>(n: usize, f: impl Fn(usize) -> T + Sync) -> T
1327where
1328 T: Copy + Send + Sync + std::ops::Add<Output = T> + Default,
1329{
1330 chunked_sum(n.div_ceil(CHUNK), |c| c * CHUNK..((c + 1) * CHUNK).min(n), f)
1331}
1332
1333fn chunked_sum<T>(chunks: usize, range: impl Fn(usize) -> std::ops::Range<usize> + Sync, f: impl Fn(usize) -> T + Sync) -> T
1335where
1336 T: Copy + Send + Sync + std::ops::Add<Output = T> + Default,
1337{
1338 let part = |c: usize| {
1339 let mut acc = T::default();
1340 for i in range(c) {
1341 acc = acc + f(i);
1342 }
1343 acc
1344 };
1345 #[cfg(feature = "parallel")]
1346 let parts: Vec<T> = (0..chunks).into_par_iter().map(part).collect();
1347 #[cfg(not(feature = "parallel"))]
1348 let parts: Vec<T> = (0..chunks).map(part).collect();
1349 parts.into_iter().fold(T::default(), |a, b| a + b)
1350}
1351
1352trait PcgElem:
1354 Copy + Send + Sync + Default + std::ops::Add<Output = Self> + std::ops::Sub<Output = Self> + std::ops::Mul<Output = Self> + std::ops::Div<Output = Self>
1355{
1356 fn one() -> Self;
1357 fn cmul(self, o: Self) -> Self;
1359 fn abs2(self) -> f64;
1360 fn abs(self) -> f64;
1361 fn re(self) -> f64;
1362 fn is_inf(self) -> bool;
1363 fn is_zero(self) -> bool;
1364}
1365
1366impl PcgElem for f64 {
1367 fn one() -> Self {
1368 1.0
1369 }
1370 fn cmul(self, o: Self) -> Self {
1371 self * o
1372 }
1373 fn abs2(self) -> f64 {
1374 self * self
1375 }
1376 fn abs(self) -> f64 {
1377 f64::abs(self)
1378 }
1379 fn re(self) -> f64 {
1380 self
1381 }
1382 fn is_inf(self) -> bool {
1383 self.is_infinite()
1384 }
1385 fn is_zero(self) -> bool {
1386 self == 0.0
1387 }
1388}
1389
1390impl PcgElem for Complex64 {
1391 fn one() -> Self {
1392 Complex64::new(1.0, 0.0)
1393 }
1394 fn cmul(self, o: Self) -> Self {
1395 self.conj() * o
1396 }
1397 fn abs2(self) -> f64 {
1398 self.norm_sqr()
1399 }
1400 fn abs(self) -> f64 {
1401 self.norm()
1402 }
1403 fn re(self) -> f64 {
1404 self.re
1405 }
1406 fn is_inf(self) -> bool {
1407 self.re.is_infinite() || self.im.is_infinite()
1408 }
1409 fn is_zero(self) -> bool {
1410 self.re == 0.0 && self.im == 0.0
1411 }
1412}
1413
1414struct PcgOut<T> {
1416 x: Vec<T>,
1417 #[allow(dead_code)]
1418 flag: u8,
1419 #[allow(dead_code)]
1420 iter: usize,
1421}
1422
1423enum Start<'a, T> {
1425 Zero,
1426 Owned(Vec<T>),
1427 Fill(&'a (dyn Fn(&mut [T]) + 'a)),
1429}
1430
1431#[derive(Clone, Copy, PartialEq)]
1434enum XMin {
1435 Start,
1436 X,
1437 Saved,
1438}
1439
1440fn psum<T>(on: Option<&Support>, n: usize, f: impl Fn(usize) -> T + Sync) -> T
1442where
1443 T: Copy + Send + Sync + std::ops::Add<Output = T> + Default,
1444{
1445 match on {
1446 Some(s) => s.sum(f),
1447 None => det_sum(n, f),
1448 }
1449}
1450
1451fn pcg<T: PcgElem>(
1460 mut a: impl FnMut(&[T], &mut [T]),
1461 b: &[T],
1462 tol: f64,
1463 maxit: usize,
1464 start: Start<T>,
1465 n_logical: usize,
1466 on: Option<&Support>,
1467) -> PcgOut<T> {
1468 let n = b.len();
1469 let norm = |v: &[T]| psum(on, n, |i| v[i].abs2()).sqrt();
1470 let dot = |u: &[T], v: &[T]| psum(on, n, |i| u[i].cmul(v[i]));
1471 let n2b = norm(b);
1472 if n2b == 0.0 {
1473 return PcgOut { x: vec![T::default(); n], flag: 0, iter: 0 };
1474 }
1475 let (mut x, mut xmin, fill) = match start {
1476 Start::Zero => (vec![T::default(); n], XMin::X, None),
1477 Start::Owned(v) => (v, XMin::X, None),
1478 Start::Fill(f) => {
1479 let mut x = vec![T::default(); n];
1480 f(&mut x);
1481 (x, XMin::Start, Some(f))
1482 }
1483 };
1484 let mut saved: Vec<T> = Vec::new();
1485 let mut flag = 1u8;
1486 let mut imin = 0;
1487 let tolb = tol * n2b;
1488 let mut q = vec![T::default(); n];
1489 a(&x, &mut q);
1490 let mut r: Vec<T> = (0..n).map(|i| b[i] - q[i]).collect();
1491 let mut normr_act = norm(&r);
1492 if normr_act <= tolb {
1493 return PcgOut { x, flag: 0, iter: 0 };
1494 }
1495 let mut normrmin = normr_act;
1496 let mut rho = T::one();
1497 let mut stag = 0;
1498 let mut moresteps = 0;
1499 let maxmsteps = ((n_logical / 50) as i64).min(5).min(n_logical as i64 - maxit as i64);
1500 let maxstagsteps = 3;
1501 let mut p = vec![T::default(); n];
1502 let mut ii = 0;
1503 let mut iter = 0;
1504 for it in 1..=maxit {
1505 ii = it;
1506 let rho1 = rho;
1507 rho = dot(&r, &r);
1508 if rho.is_zero() || rho.is_inf() {
1509 flag = 4;
1510 break;
1511 }
1512 if it == 1 {
1513 p.copy_from_slice(&r);
1514 } else {
1515 let beta = rho / rho1;
1516 if beta.is_zero() || beta.is_inf() {
1517 flag = 4;
1518 break;
1519 }
1520 par_update(&mut p, |i, v| *v = r[i] + beta * *v);
1521 }
1522 a(&p, &mut q);
1523 let pq = dot(&p, &q);
1524 if pq.re() <= 0.0 || pq.is_inf() {
1525 flag = 4;
1526 break;
1527 }
1528 let alpha = rho / pq;
1529 if alpha.is_inf() {
1530 flag = 4;
1531 break;
1532 }
1533 if norm(&p) * alpha.abs() < f64::EPSILON * norm(&x) {
1534 stag += 1;
1535 } else {
1536 stag = 0;
1537 }
1538 if xmin == XMin::X {
1539 if saved.is_empty() {
1540 saved = x.clone();
1541 } else {
1542 saved.copy_from_slice(&x);
1543 }
1544 xmin = XMin::Saved;
1545 }
1546 par_update(&mut x, |i, v| *v = *v + alpha * p[i]);
1547 par_update(&mut r, |i, v| *v = *v - alpha * q[i]);
1548 let normr = norm(&r);
1549 normr_act = normr;
1550 if normr <= tolb || stag >= maxstagsteps || moresteps > 0 {
1551 a(&x, &mut q);
1552 par_fill(&mut r, |i| b[i] - q[i]);
1553 normr_act = norm(&r);
1554 if normr_act <= tolb {
1555 flag = 0;
1556 iter = it;
1557 break;
1558 }
1559 if stag >= maxstagsteps && moresteps == 0 {
1560 stag = 0;
1561 }
1562 moresteps += 1;
1563 if moresteps >= maxmsteps {
1564 flag = 3;
1565 iter = it;
1566 break;
1567 }
1568 }
1569 if normr_act < normrmin {
1570 normrmin = normr_act;
1571 xmin = XMin::X;
1572 imin = it;
1573 }
1574 if stag >= maxstagsteps {
1575 flag = 3;
1576 break;
1577 }
1578 }
1579 if flag != 0 {
1580 if xmin == XMin::X {
1581 iter = imin;
1582 } else {
1583 if xmin == XMin::Start {
1585 (fill.expect("a start to rebuild"))(&mut p);
1586 }
1587 let xm: &[T] = if xmin == XMin::Saved { &saved } else { &p };
1588 a(xm, &mut q);
1589 let r_comp = psum(on, n, |i| (b[i] - q[i]).abs2()).sqrt();
1590 if r_comp <= normr_act {
1591 x = if xmin == XMin::Saved { saved } else { p };
1592 iter = imin;
1593 } else {
1594 iter = ii;
1595 }
1596 }
1597 }
1598 PcgOut { x, flag, iter }
1599}
1600
1601#[cfg(test)]
1602mod tests {
1603 use super::*;
1604
1605 #[test]
1606 fn defaults_are_the_toolbox_values() {
1607 let p = ChiSepIlsqrParams::default();
1608 assert_eq!((p.dr_pos, p.dr_neg, p.pad), (None, None, [16; 3]));
1609 assert_eq!((p.cg_max_iter_lsqr, p.cg_tol_lsqr, p.cg_max_iter_sa, p.cg_tol_sa), (50, 0.02, 50, 0.02));
1610 assert_eq!((p.d2_thr, p.prc_w_i, p.prc_w_g), (0.11, (60.0, 99.8), (50.0, 70.0)));
1611 assert_eq!((NNLS_TOL, NNLS_ITER_MAX), (0.01, 10));
1612 let (dp, dn) = ChiSepIlsqrParams { cf: 127.74e6, ..p.clone() }.dr();
1613 assert_eq!((dp, dn), (141.7241379310345, 141.7241379310345));
1614 assert_eq!(p.qsm_ilsqr.max_iter, STI_DEFAULT_NITER);
1615 }
1616
1617 #[test]
1618 fn param_errors_name_the_parameter() {
1619 let bad = [
1620 ChiSepIlsqrParams { cf: 0.0, ..Default::default() },
1621 ChiSepIlsqrParams { dr_pos: Some(-1.0), ..Default::default() },
1622 ChiSepIlsqrParams { dr_neg: Some(f64::NAN), ..Default::default() },
1623 ChiSepIlsqrParams { d2_thr: 0.0, ..Default::default() },
1624 ChiSepIlsqrParams { cg_tol_lsqr: 1.0, ..Default::default() },
1625 ChiSepIlsqrParams { cg_tol_sa: 0.0, ..Default::default() },
1626 ChiSepIlsqrParams { prc_w_i: (99.8, 60.0), ..Default::default() },
1627 ChiSepIlsqrParams { prc_w_g: (50.0, 101.0), ..Default::default() },
1628 ];
1629 let names = ["cf", "dr_pos", "dr_neg", "d2_thr", "cg_tol_lsqr", "cg_tol_sa", "prc_w_i", "prc_w_g"];
1630 for (p, name) in bad.iter().zip(names) {
1631 let e = p.validate().unwrap_err().to_string();
1632 assert!(e.starts_with(&format!("χ-separation iLSQR {name} = ")), "{e}");
1633 }
1634 assert!(ChiSepIlsqrParams::default().validate().is_ok());
1635 }
1636
1637 #[test]
1638 fn wrong_buffer_length_and_empty_mask_are_input_errors() {
1639 let nt = 8 * 8 * 6;
1640 let v = vec![0.0; nt];
1641 let short = vec![0.0; nt - 1];
1642 let m = vec![1u8; nt];
1643 let grid = Grid::new(8, 8, 6, 1.0, 1.0, 1.0);
1644 let inputs = ChiSepIlsqrInputs { local_field: &v, r2prime: &short, qsm: &v, mask: &m };
1645 let e = chi_sep_ilsqr(&inputs, &grid, (0.0, 0.0, 1.0), &ChiSepIlsqrParams::default(), |_, _| {}).unwrap_err();
1646 assert_eq!(e.to_string(), format!("χ-separation iLSQR r2prime has {} values, expected {nt}", nt - 1));
1647 let z = vec![0u8; nt];
1648 let inputs = ChiSepIlsqrInputs { local_field: &v, r2prime: &v, qsm: &v, mask: &z };
1649 assert!(chi_sep_ilsqr(&inputs, &grid, (0.0, 0.0, 1.0), &ChiSepIlsqrParams::default(), |_, _| {}).is_err());
1650 }
1651
1652 #[test]
1654 fn matlab_helpers() {
1655 let mut x: Vec<f64> = vec![5.0, 1.0, 4.0, 2.0, 3.0];
1656 assert_eq!(prctile(&mut x.clone(), 1.0), 1.0);
1658 assert_eq!(prctile(&mut x.clone(), 30.0), 2.0);
1659 assert_eq!(prctile(&mut x.clone(), 60.0), 3.5);
1660 assert_eq!(prctile(&mut x, 99.8), 5.0);
1661 let w = tukeywin(9, 0.5);
1663 let want = [0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.0];
1664 assert!(w.iter().zip(want).all(|(a, b)| (a - b).abs() < 1e-15), "{w:?}");
1665 let dims = (6, 4, 5);
1667 let f: Vec<f64> = (0..120).map(|l| ((l % 6) as f64).powi(2)).collect();
1668 assert!(del2(&f, dims, [1.0; 3]).iter().all(|v| (v - 2.0 / 6.0).abs() < 1e-13));
1669 let a: Vec<usize> = (0..7 * 4 * 3).collect();
1671 let mut b = a.clone();
1672 circshift(&mut b, (7, 4, 3), false);
1673 circshift(&mut b, (7, 4, 3), true);
1674 assert_eq!(b, a);
1675 let mut b = vec![0, 1, 2, 3, 4];
1676 circshift(&mut b, (5, 1, 1), true);
1677 assert_eq!(b, vec![3, 4, 0, 1, 2]);
1678 for dims in [(7, 4, 3), (6, 5, 4), (1, 3, 2), (5, 1, 6)] {
1680 for forward in [true, false] {
1681 let a: Vec<usize> = (0..dims.0 * dims.1 * dims.2).collect();
1682 let mut b = a.clone();
1683 circshift(&mut b, dims, forward);
1684 assert_eq!(b, circshift_ref(&a, dims, forward), "{dims:?} {forward}");
1685 }
1686 }
1687 }
1688
1689 #[test]
1692 fn pcg_follows_matlab() {
1693 let m = [[4.0, 1.0, 0.0], [1.0, 3.0, 1.0], [0.0, 1.0, 2.0]];
1694 let apply = |x: &[f64], o: &mut [f64]| {
1695 for i in 0..3 {
1696 o[i] = (0..3).map(|j| m[i][j] * x[j]).sum();
1697 }
1698 };
1699 let b = [1.0, 2.0, 3.0];
1700 let out = pcg(apply, &b, 1e-12, 10, Start::Zero, 3, None);
1701 let mut ax = [0.0; 3];
1702 apply(&out.x, &mut ax);
1703 assert!(ax.iter().zip(&b).all(|(a, b)| (a - b).abs() < 1e-10), "{:?}", out.x);
1704 assert_eq!(out.flag, 0);
1705 let bc = [Complex64::new(1.0, 0.5), Complex64::new(-1.0, 0.0)];
1707 let out = pcg(|x: &[Complex64], o: &mut [Complex64]| { o[0] = x[0] * 2.0; o[1] = x[1] * 3.0; }, &bc, 1e-12, 5, Start::Zero, 2, None);
1708 assert!((out.x[0] - Complex64::new(0.5, 0.25)).norm() < 1e-14 && (out.x[1] - Complex64::new(-1.0 / 3.0, 0.0)).norm() < 1e-14);
1709 }
1710
1711 fn circshift_ref<T: Copy>(a: &[T], (nx, ny, nz): (usize, usize, usize), forward: bool) -> Vec<T> {
1713 let s = |n: usize| if forward { n / 2 } else { n.div_ceil(2) };
1714 let (sx, sy, sz) = (s(nx), s(ny), s(nz));
1715 let mut out = a.to_vec();
1716 for k in 0..nz {
1717 for j in 0..ny {
1718 for i in 0..nx {
1719 out[(i + sx) % nx + nx * ((j + sy) % ny + ny * ((k + sz) % nz))] = a[i + nx * (j + ny * k)];
1720 }
1721 }
1722 }
1723 out
1724 }
1725
1726 #[test]
1727 fn kernel_is_the_toolbox_kernel_bit_for_bit() {
1728 let h = (0.1f64, -0.4, 0.9);
1729 let norm = (h.0 * h.0 + h.1 * h.1 + h.2 * h.2).sqrt();
1730 for (dims, vs, b0) in [((8, 6, 4), [1.0; 3], (0.0, 0.0, 1.0)), ((7, 5, 9), [0.8, 0.9, 1.5], (h.0 / norm, h.1 / norm, h.2 / norm))] {
1731 let want = super::super::chi_sep_medi::toolbox_dipole_kernel(dims, vs, b0);
1732 let kernel = Kernel::new(dims, vs, b0);
1733 for l in 0..want.len() {
1734 let (i, j, k) = (l % dims.0, (l / dims.0) % dims.1, l / (dims.0 * dims.1));
1735 assert_eq!(kernel.at(i, j, k).to_bits(), want[l].to_bits(), "{dims:?} at {l}");
1736 }
1737 }
1738 }
1739
1740 #[test]
1743 fn sums_on_a_support_are_the_full_sums() {
1744 let n = 3 * CHUNK + 1234;
1745 let keep = |l: usize| (l / CHUNK != 1) && (l.is_multiple_of(7) || l % 11 == 3);
1746 let sup = Support::new(n, keep);
1747 let full: Vec<f64> = (0..n).map(|l| if keep(l) { ((l as f64) * 0.618).sin() * 1e3 } else { 0.0 }).collect();
1748 let on: Vec<f64> = sup.idx.iter().map(|&l| full[l as usize]).collect();
1749 assert_eq!(sup.bounds[1], sup.bounds[2]);
1750 assert_eq!(det_sum(n, |i| full[i] * full[i]).to_bits(), sup.sum(|j| on[j] * on[j]).to_bits());
1751 assert_eq!(det_sum(n, |i| full[i]).to_bits(), sup.sum(|j| on[j]).to_bits());
1752 let mut seen = 0;
1753 for c in 0..n.div_ceil(CHUNK) {
1754 sup.walk(c, n, |l, j| {
1755 assert_eq!(j.is_some(), keep(l));
1756 if let Some(j) = j {
1757 assert_eq!(sup.idx[j] as usize, l);
1758 }
1759 seen += 1;
1760 });
1761 }
1762 assert_eq!(seen, n);
1763 }
1764
1765 #[test]
1768 fn pcg_support_and_start_do_not_change_the_result() {
1769 let n = 2 * CHUNK + 77;
1770 let keep = |l: usize| l % 5 != 1;
1771 let sup = Support::new(n, keep);
1772 let ns = sup.len();
1773 let op_on = |x: &[f64], o: &mut [f64]| {
1775 for j in 0..x.len() {
1776 let prev = if j > 0 { x[j - 1] } else { 0.0 };
1777 let next = if j + 1 < x.len() { x[j + 1] } else { 0.0 };
1778 o[j] = 3.0 * x[j] + 0.5 * prev + 0.5 * next;
1779 }
1780 };
1781 let gather = |x: &[f64]| -> Vec<f64> { sup.idx.iter().map(|&l| x[l as usize]).collect() };
1782 let scatter = |v: &[f64], x: &mut [f64]| {
1783 x.fill(0.0);
1784 sup.idx.iter().zip(v).for_each(|(&l, &v)| x[l as usize] = v);
1785 };
1786 let op_full = |x: &[f64], o: &mut [f64]| {
1787 let mut t = vec![0.0; ns];
1788 op_on(&gather(x), &mut t);
1789 scatter(&t, o);
1790 };
1791 let b_on: Vec<f64> = (0..ns).map(|j| ((j as f64) * 0.37).cos()).collect();
1792 let mut b = vec![0.0; n];
1793 scatter(&b_on, &mut b);
1794 let x0_on: Vec<f64> = (0..ns).map(|j| 0.1 * ((j as f64) * 1.3).sin()).collect();
1795 let mut x0 = vec![0.0; n];
1796 scatter(&x0_on, &mut x0);
1797 for maxit in [1, 3, 30] {
1798 let full = pcg(op_full, &b, 1e-10, maxit, Start::Owned(x0.clone()), n, None).x;
1799 let fill = |x: &mut [f64]| scatter(&x0_on, x);
1800 let rebuilt = pcg(op_full, &b, 1e-10, maxit, Start::Fill(&fill), n, None).x;
1801 let on = pcg(op_on, &b_on, 1e-10, maxit, Start::Owned(x0_on.clone()), n, Some(&sup)).x;
1802 assert!(full.iter().zip(&rebuilt).all(|(a, b)| a.to_bits() == b.to_bits()), "maxit {maxit}");
1803 assert!(gather(&full).iter().zip(&on).all(|(a, b)| a.to_bits() == b.to_bits()), "maxit {maxit}");
1804 }
1805 }
1806
1807 fn small_phantom() -> (Grid, Vec<f64>, Vec<f64>, Vec<f64>, Vec<u8>) {
1809 let (nx, ny, nz) = (20, 18, 14);
1810 let nt = nx * ny * nz;
1811 let mut mask = vec![0u8; nt];
1812 let mut chi = vec![0.0; nt];
1813 let mut r2p = vec![0.0; nt];
1814 for k in 0..nz {
1815 for j in 0..ny {
1816 for i in 0..nx {
1817 let l = i + nx * (j + ny * k);
1818 let (x, y, z) = (i as f64 - 10.0, j as f64 - 9.0, k as f64 - 7.0);
1819 if (x / 8.0).powi(2) + (y / 7.0).powi(2) + (z / 5.5).powi(2) <= 1.0 {
1820 mask[l] = 1;
1821 let (p, q) = (if x > 1.0 { 0.05 } else { 0.0 }, if x < -1.0 { 0.03 } else { 0.0 });
1822 chi[l] = p - q;
1823 r2p[l] = 141.0 * (p + q) + 2.0;
1824 }
1825 }
1826 }
1827 }
1828 let grid = Grid::new(nx, ny, nz, 1.0, 1.0, 1.0);
1829 let d = crate::kernels::dipole::dipole_kernel(&grid, (0.0, 0.0, 1.0));
1830 let mut buf: Vec<Complex64> = chi.iter().map(|&v| Complex64::new(v, 0.0)).collect();
1831 crate::fft::fft3d(&mut buf, nx, ny, nz);
1832 buf.iter_mut().zip(&d).for_each(|(b, &dv)| *b *= dv);
1833 crate::fft::ifft3d(&mut buf, nx, ny, nz);
1834 let field: Vec<f64> = buf.iter().zip(&mask).map(|(b, &m)| b.re * m as f64).collect();
1835 (grid, field, r2p, chi, mask)
1836 }
1837
1838 #[test]
1841 fn fused_regulariser_is_the_full_volume_one() {
1842 use crate::inversion::medi::fgrad;
1843 let (grid, field, r2p, chi, mask) = small_phantom();
1844 let inputs = ChiSepIlsqrInputs { local_field: &field, r2prime: &r2p, qsm: &chi, mask: &mask };
1845 let params = ChiSepIlsqrParams { pad: [4; 3], ..Default::default() };
1846 let (pb, mut ws, _) = Problem::new(&inputs, &grid, (0.0, 0.0, 1.0), ¶ms);
1847 let (dims, n) = (pb.dims, pb.n);
1848 let at = |l: usize| [l % dims.0, (l / dims.0) % dims.1, l / (dims.0 * dims.1)];
1849 let full = |b: &BoxVol| (0..n).map(|l| b.get(at(l))).collect::<Vec<f64>>();
1850 let (xm, rm) = (full(&pb.xm), full(&pb.rm));
1851 let mut gx = [vec![0.0; n], vec![0.0; n], vec![0.0; n]];
1853 let mut gr = [vec![0.0; n], vec![0.0; n], vec![0.0; n]];
1854 fgrad(&xm, dims, pb.vs, &mut gx);
1855 fgrad(&rm, dims, pb.vs, &mut gr);
1856 let (drp, drn) = (pb.drp, pb.drn);
1857 let inside: Vec<usize> = (0..n).filter(|&l| pb.m[l] != 0).collect();
1858 let mut wg2 = [[vec![], vec![], vec![]], [vec![], vec![], vec![]]];
1859 for c in 0..3 {
1860 let gp: Vec<f64> = (0..n).map(|i| ((gr[c][i] + drn * gx[c][i]) / (drp + drn)).abs()).collect();
1861 let gn: Vec<f64> = (0..n).map(|i| ((gr[c][i] - drp * gx[c][i]) / (drp + drn)).abs()).collect();
1862 for (s, g) in [gp, gn].into_iter().enumerate() {
1863 let mut sel: Vec<f64> = inside.iter().map(|&i| g[i]).collect();
1864 let (lo, hi) = (prctile(&mut sel, params.prc_w_g.0), prctile(&mut sel, params.prc_w_g.1));
1865 wg2[s][c] = (0..n).map(|i| clamp01((hi - g[i]) / (hi - lo)) * f64::from(pb.m[i])).map(|v| v * v).collect();
1866 for l in 0..n {
1867 assert_eq!(pb.wg2(s, c, at(l), l).to_bits(), wg2[s][c][l].to_bits(), "W_G² {s} {c} at {l}");
1868 }
1869 }
1870 }
1871 let x: Vec<f64> = (0..n).map(|l| ((l as f64) * 0.71).sin() * 0.01).collect();
1872 for s in 0..2 {
1873 let mut g = [vec![0.0; n], vec![0.0; n], vec![0.0; n]];
1874 fgrad(&x, dims, [1.0; 3], &mut g);
1875 for c in 0..3 {
1876 g[c].iter_mut().zip(&wg2[s][c]).for_each(|(v, w)| *v *= w);
1877 }
1878 let mut t = vec![0.0; n];
1879 grad_adjoint_unit(&g, dims, &mut t);
1880 let mut want: Vec<Complex64> = t.iter().map(|&v| Complex64::new(v, 0.0)).collect();
1881 ws.fft.fft3d(&mut want);
1882 let mut got = vec![Complex64::new(0.0, 0.0); pb.cone.len()];
1883 pb.cone_fft_reg(&mut ws, s, &x, &mut got);
1884 for (j, &l) in pb.cone.idx.iter().enumerate() {
1885 let w = want[l as usize];
1886 assert_eq!((got[j].re.to_bits(), got[j].im.to_bits()), (w.re.to_bits(), w.im.to_bits()), "source {s} at {l}");
1887 }
1888 }
1889 }
1890
1891 #[test]
1892 fn sources_have_their_signs() {
1893 let (grid, field, r2p, chi, mask) = small_phantom();
1894 let nt = mask.len();
1895 let inputs = ChiSepIlsqrInputs { local_field: &field, r2prime: &r2p, qsm: &chi, mask: &mask };
1896 let mut iters = 0;
1897 let (p, q, t) = chi_sep_ilsqr(&inputs, &grid, (0.0, 0.0, 1.0), &ChiSepIlsqrParams { pad: [4; 3], ..Default::default() }, |i, _| iters = i).unwrap();
1898 assert!((1..=NNLS_ITER_MAX + 1).contains(&iters));
1899 for l in 0..nt {
1900 assert!(p[l] >= 0.0 && q[l] <= 0.0 && t[l] == p[l] + q[l]);
1901 if mask[l] == 0 {
1902 assert!(p[l] == 0.0 && q[l] == 0.0);
1903 }
1904 }
1905 assert!(p.iter().any(|&v| v > 0.01) && q.iter().any(|&v| v < -0.01));
1906 }
1907}