1#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
210#[derive(Clone, Debug)]
211pub struct IlsqrParams {
212 pub tol: f64,
214 pub max_iter: usize,
219 pub pad_mm: f64,
224 pub precision: IlsqrPrecision,
227}
228
229#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
231#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
232pub enum IlsqrPrecision {
233 #[default]
235 Double,
236 Single,
239}
240
241impl Default for IlsqrParams {
242 fn default() -> Self {
243 Self {
244 tol: 1e-3,
245 max_iter: STI_DEFAULT_NITER,
246 pad_mm: STI_GUI_PAD_MM,
247 precision: IlsqrPrecision::Double,
248 }
249 }
250}
251
252impl IlsqrParams {
253 pub fn validate(&self) -> Result<(), String> {
255 if !(self.pad_mm.is_finite() && self.pad_mm >= 0.0) {
256 return Err(format!("iLSQR padding must be finite and >= 0 mm, got {}", self.pad_mm));
257 }
258 Ok(())
259 }
260}
261
262pub const STI_PAD_MM: f64 = 64.0;
264
265pub const STI_GUI_PAD_MM: f64 = 12.0;
269
270pub const STI_GUI_NITER: usize = 40;
273
274pub const STI_DEFAULT_NITER: usize = 100;
278
279pub const STI_DEFAULT_PAD_MM: f64 = 6.0;
283
284use std::cell::RefCell;
285use num_complex::{Complex32, Complex64};
286#[cfg(feature = "parallel")]
287use rayon::prelude::*;
288use crate::fft::{Box3, Fft3dWorkspace, Fft3dWorkspaceF32, FftOpts};
289use crate::Grid;
290
291pub fn lsqr<F, G>(
310 apply_a: F,
311 apply_at: G,
312 b: &[f64],
313 tol: f64,
314 max_iter: usize,
315) -> Vec<f64>
316where
317 F: Fn(&[f64]) -> Vec<f64>,
318 G: Fn(&[f64]) -> Vec<f64>,
319{
320 let mut u = b.to_vec();
322 let mut beta = norm(&u);
323
324 if beta > 0.0 {
325 scale_inplace(&mut u, 1.0 / beta);
326 }
327
328 let mut v = apply_at(&u);
329 let n = v.len();
330 let mut alpha = norm(&v);
331
332 if alpha > 0.0 {
333 scale_inplace(&mut v, 1.0 / alpha);
334 }
335
336 let mut w = v.clone();
337 let mut x = vec![0.0; n];
338
339 let mut phi_bar = beta;
340 let mut rho_bar = alpha;
341
342 let bnorm = beta;
343
344 if bnorm == 0.0 {
347 return x;
348 }
349
350 for _iter in 0..max_iter {
351 let mut u_new = apply_a(&v);
353 axpy(&mut u_new, -alpha, &u);
354 beta = norm(&u_new);
355
356 if beta > 0.0 {
357 scale_inplace(&mut u_new, 1.0 / beta);
358 }
359 u = u_new;
360
361 let mut v_new = apply_at(&u);
362 axpy(&mut v_new, -beta, &v);
363 alpha = norm(&v_new);
364
365 if alpha > 0.0 {
366 scale_inplace(&mut v_new, 1.0 / alpha);
367 }
368 v = v_new;
369
370 let rho = (rho_bar * rho_bar + beta * beta).sqrt();
372 let c = rho_bar / rho;
373 let s = beta / rho;
374 let theta = s * alpha;
375 rho_bar = -c * alpha;
376 let phi = c * phi_bar;
377 phi_bar *= s;
378
379 let t1 = phi / rho;
381 let t2 = -theta / rho;
382
383 for i in 0..n {
384 x[i] += t1 * w[i];
385 w[i] = v[i] + t2 * w[i];
386 }
387
388 let rel_residual = phi_bar / (bnorm + 1e-20);
390
391 if rel_residual < tol {
392 break;
393 }
394 }
395
396 x
397}
398
399fn norm(x: &[f64]) -> f64 {
400 x.iter().map(|&v| v * v).sum::<f64>().sqrt()
401}
402
403fn scale_inplace(x: &mut [f64], s: f64) {
404 for v in x.iter_mut() {
405 *v *= s;
406 }
407}
408
409fn axpy(y: &mut [f64], a: f64, x: &[f64]) {
410 for (yi, &xi) in y.iter_mut().zip(x.iter()) {
411 *yi += a * xi;
412 }
413}
414
415#[derive(Clone, Copy, Debug, PartialEq)]
421pub struct LsqrOutcome {
422 pub flag: u8,
424 pub iter: usize,
426 pub relres: f64,
428}
429
430const NORM_CHUNK: usize = 1 << 16;
432
433fn dense_parts(len: usize) -> Vec<usize> {
442 (0..len.div_ceil(NORM_CHUNK)).map(|c| NORM_CHUNK.min(len - c * NORM_CHUNK)).collect()
443}
444
445fn compact_parts(pos: impl Iterator<Item = usize>) -> Vec<usize> {
447 let mut parts: Vec<usize> = Vec::new();
448 let mut last = usize::MAX;
449 for g in pos {
450 let c = g / NORM_CHUNK;
451 if c == last {
452 *parts.last_mut().unwrap() += 1;
453 } else {
454 parts.push(1);
455 last = c;
456 }
457 }
458 parts
459}
460
461fn transform(ws: &mut Fft3dWorkspace, a: &mut [Complex64], inverse: bool, o: &FftOpts) {
463 if inverse { ws.ifft3d_with(a, o) } else { ws.fft3d_with(a, o) }
464}
465
466#[inline(always)]
468fn nsq(z: Complex64) -> f64 {
469 z.re * z.re + z.im * z.im
470}
471
472fn split_parts<'a, T>(mut x: &'a [T], parts: &[usize]) -> Vec<&'a [T]> {
473 parts.iter().map(|&len| { let (a, b) = x.split_at(len); x = b; a }).collect()
474}
475
476fn split_parts_mut<'a, T>(mut x: &'a mut [T], parts: &[usize]) -> Vec<&'a mut [T]> {
477 parts.iter().map(|&len| { let (a, b) = std::mem::take(&mut x).split_at_mut(len); x = b; a }).collect()
478}
479
480fn cnorm_parts(x: &[Complex64], parts: &[usize]) -> f64 {
481 let xs = split_parts(x, parts);
482 let sums: Vec<f64> = crate::maybe_par_iter!(xs).map(|c| c.iter().map(|&v| nsq(v)).sum::<f64>()).collect();
483 sums.iter().sum::<f64>().sqrt()
484}
485
486fn update_and_norm(
489 y: &mut [Complex64],
490 x: &[Complex64],
491 parts: &[usize],
492 f: impl Fn(&mut Complex64, Complex64) + Sync + Send,
493) -> f64 {
494 let mut ys = split_parts_mut(y, parts);
495 let xs = split_parts(x, parts);
496 let sums: Vec<f64> = crate::maybe_par_iter_mut!(ys)
497 .zip(crate::maybe_par_iter!(xs))
498 .map(|(yc, xc)| {
499 yc.iter_mut().zip(xc.iter()).for_each(|(a, &b)| f(a, b));
500 yc.iter().map(|&v| nsq(v)).sum::<f64>()
501 })
502 .collect();
503 sums.iter().sum::<f64>().sqrt()
504}
505
506#[allow(clippy::too_many_arguments)]
509fn update_d_x_and_norms(d: &mut [Complex64], x: &mut [Complex64], v: &[Complex64], parts: &[usize], thet: f64, rho: f64, phi: f64) -> (f64, f64) {
510 let mut ds = split_parts_mut(d, parts);
511 let mut xs = split_parts_mut(x, parts);
512 let vs = split_parts(v, parts);
513 let sums: Vec<(f64, f64)> = crate::maybe_par_iter_mut!(ds)
514 .zip(crate::maybe_par_iter_mut!(xs))
515 .zip(crate::maybe_par_iter!(vs))
516 .map(|((dc, xc), vc)| {
517 let (mut sd, mut sx) = (0.0f64, 0.0f64);
518 for ((di, xi), &vi) in dc.iter_mut().zip(xc.iter_mut()).zip(vc.iter()) {
519 *di = (vi - *di * thet) / rho;
520 *xi += *di * phi;
521 sd += nsq(*di);
522 sx += nsq(*xi);
523 }
524 (sd, sx)
525 })
526 .collect();
527 (sums.iter().map(|p| p.0).sum::<f64>().sqrt(), sums.iter().map(|p| p.1).sum::<f64>().sqrt())
528}
529
530fn div_inplace(x: &mut [Complex64], s: f64) {
531 crate::maybe_par_iter_mut!(x).for_each(|z| *z /= s);
532}
533
534pub fn lsqr_matlab<F, G>(
539 mut apply_a: F,
540 mut apply_ah: G,
541 b: &[Complex64],
542 tol: f64,
543 max_iter: usize,
544) -> (Vec<Complex64>, LsqrOutcome)
545where
546 F: FnMut(&[Complex64]) -> Vec<Complex64>,
547 G: FnMut(&[Complex64]) -> Vec<Complex64>,
548{
549 let nx = apply_ah(b).len();
550 lsqr_matlab_into(
551 |v, out| out.copy_from_slice(&apply_a(v)),
552 |u, out| out.copy_from_slice(&apply_ah(u)),
553 b.to_vec(),
554 (&dense_parts(b.len()), &dense_parts(nx)),
555 tol,
556 max_iter,
557 )
558}
559
560fn lsqr_matlab_into<F, G>(
567 mut apply_a: F,
568 mut apply_ah: G,
569 b: Vec<Complex64>,
570 parts: (&[usize], &[usize]),
571 tol: f64,
572 max_iter: usize,
573) -> (Vec<Complex64>, LsqrOutcome)
574where
575 F: FnMut(&[Complex64], &mut [Complex64]),
576 G: FnMut(&[Complex64], &mut [Complex64]),
577{
578 let zero = Complex64::new(0.0, 0.0);
579 let (up, vp) = parts;
580 let nx: usize = vp.iter().sum();
581 let n2b = cnorm_parts(&b, up);
582 let tolb = tol * n2b;
583 let mut u = b;
584 let mut beta = n2b;
585 let mut normr = beta;
586 if beta != 0.0 {
587 div_inplace(&mut u, beta);
588 }
589 let (mut c, mut s) = (1.0f64, 0.0f64);
590 let mut phibar = beta;
591 let mut v = vec![zero; nx];
592 apply_ah(&u, &mut v);
593 let mut x = vec![zero; nx];
594 let mut alpha = cnorm_parts(&v, vp);
595 if alpha != 0.0 {
596 div_inplace(&mut v, alpha);
597 }
598 let mut normar = alpha * beta;
599 let relres = |nr: f64| if n2b > 0.0 { nr / n2b } else { 0.0 };
600 if normar == 0.0 {
601 return (x, LsqrOutcome { flag: 0, iter: 0, relres: relres(normr) });
602 }
603 let mut un = vec![zero; u.len()];
604 let mut vt = vec![zero; nx];
605 let mut d = vec![zero; nx];
606 let mut normx = 0.0f64; let mut norma = 0.0f64;
608 let mut stag = 0usize;
609 let mut flag = 1u8;
610 let mut iter = max_iter;
611 for ii in 1..=max_iter {
612 apply_a(&v, &mut un);
613 beta = update_and_norm(&mut un, &u, up, |a, b| *a -= b * alpha);
614 if beta != 0.0 {
615 div_inplace(&mut un, beta);
616 }
617 std::mem::swap(&mut u, &mut un);
618 norma = (norma * norma + alpha * alpha + beta * beta).sqrt();
619 let thet = -s * alpha;
620 let rhot = c * alpha;
621 let rho = (rhot * rhot + beta * beta).sqrt();
622 c = rhot / rho;
623 s = -beta / rho;
624 let phi = c * phibar;
625 if phi == 0.0 {
626 stag = 1;
627 }
628 phibar *= s;
629 let converged = normar / (norma * normr) <= tol || normr <= tolb;
630 let breakdown = !phi.is_finite() || rho == 0.0 || !rho.is_finite();
631 let normx_prev = normx;
632 let fused = !converged && !breakdown && stag < 2;
635 let normd = if fused {
636 let (nd, nxn) = update_d_x_and_norms(&mut d, &mut x, &v, vp, thet, rho, phi);
637 normx = nxn;
638 nd
639 } else {
640 update_and_norm(&mut d, &v, vp, |di, vi| *di = (vi - *di * thet) / rho)
641 };
642 if phi.abs() * normd < f64::EPSILON * normx_prev {
643 stag += 1;
644 } else {
645 stag = 0;
646 }
647 if converged {
648 flag = 0;
649 iter = ii - 1;
650 break;
651 }
652 if stag >= 3 {
653 flag = 3;
654 iter = ii - 1;
655 break;
656 }
657 if breakdown {
658 flag = 4;
659 iter = ii - 1;
660 break;
661 }
662 if !fused {
663 normx = update_and_norm(&mut x, &d, vp, |xi, di| *xi += di * phi);
664 }
665 normr *= s.abs();
666 apply_ah(&u, &mut vt);
667 alpha = update_and_norm(&mut vt, &v, vp, |a, b| *a -= b * beta);
668 std::mem::swap(&mut v, &mut vt);
669 if (alpha == 0.0 || !alpha.is_finite()) && ii < max_iter {
670 flag = 4;
671 iter = ii;
672 break;
673 }
674 if alpha != 0.0 {
675 div_inplace(&mut v, alpha);
676 }
677 normar = alpha * (s * phi).abs();
678 }
679 if flag == 1 && (normar / (norma * normr) <= tol || normr <= tolb) {
680 flag = 0;
681 }
682 (x, LsqrOutcome { flag, iter, relres: relres(normr) })
683}
684
685fn sti_percentile_index(n: usize, p: f64) -> usize {
687 ((p * n as f64).round() as usize).clamp(1, n) - 1
688}
689
690#[cfg(test)]
692fn sti_percentile(sorted: &[f64], p: f64) -> f64 {
693 sorted[sti_percentile_index(sorted.len(), p)]
694}
695
696fn sti_percentiles(v: &mut [f64], p_lo: f64, p_hi: f64) -> (f64, f64) {
699 let (il, ih) = (sti_percentile_index(v.len(), p_lo), sti_percentile_index(v.len(), p_hi));
700 debug_assert!(il <= ih);
701 let hi = *v.select_nth_unstable_by(ih, |a, b| a.total_cmp(b)).1;
702 let lo = if il == ih { hi } else { *v[..ih].select_nth_unstable_by(il, |a, b| a.total_cmp(b)).1 };
703 (lo, hi)
704}
705
706fn sti_bounding_box(mask: &[u8], dims: (usize, usize, usize)) -> Option<([usize; 3], [usize; 3])> {
710 let (nx, ny, nz) = dims;
711 let n = [nx, ny, nz];
712 let mut lo = n;
713 let mut hi = [0usize; 3];
714 let mut any = false;
715 for k in 0..nz {
716 for j in 0..ny {
717 for i in 0..nx {
718 if mask[i + j * nx + k * nx * ny] != 0 {
719 any = true;
720 for (d, c) in [i, j, k].into_iter().enumerate() {
721 lo[d] = lo[d].min(c);
722 hi[d] = hi[d].max(c);
723 }
724 }
725 }
726 }
727 }
728 if !any {
729 return None;
730 }
731 for d in 0..3 {
732 if (hi[d] - lo[d] + 1) % 2 == 1 {
733 if hi[d] + 1 < n[d] {
734 hi[d] += 1;
735 } else if lo[d] > 0 {
736 lo[d] -= 1;
737 } else {
738 hi[d] -= 1;
739 }
740 }
741 }
742 Some((lo, hi))
743}
744
745fn sti_padded_dims(c: [usize; 3], vs: [f64; 3], pad_mm: f64) -> [usize; 3] {
747 [0, 1, 2].map(|d| 16 * ((c[d] as f64 + 2.0 * pad_mm / vs[d] + 1.0) / 16.0).ceil() as usize)
748}
749
750const NONE: u32 = u32::MAX;
752
753#[inline]
755fn circ_next(l: usize, axis: usize, n: [usize; 3]) -> usize {
756 let step = [1, n[0], n[0] * n[1]][axis];
757 if (l / step) % n[axis] + 1 == n[axis] { l - (n[axis] - 1) * step } else { l + step }
758}
759
760fn grad_masked(val: &(dyn Fn(usize) -> Complex64 + Sync), inside: &[usize], gw: &[Vec<f64>], n: [usize; 3], r: &mut [Complex64]) {
764 let nm = inside.len();
765 for (a, ra) in r.chunks_mut(nm).enumerate() {
766 let g = &gw[a];
767 crate::maybe_par_iter_mut!(ra).enumerate().for_each(|(i, o)| {
768 let l = inside[i];
769 *o = (val(circ_next(l, a, n)) - val(l)) * g[i];
770 });
771 }
772}
773
774fn grad_masked_adj(r: &[Complex64], mask_of: &[u32], gw: &[Vec<f64>], n: [usize; 3], bx: &Box3, acc: &mut [Complex64]) {
779 let (nxy, nt) = (n[0] * n[1], n[0] * n[1] * n[2]);
780 let nm = r.len() / 3;
781 let zero = Complex64::new(0.0, 0.0);
782 let q = |a: usize, l: usize| match mask_of[l] {
783 NONE => zero,
784 i => r[a * nm + i as usize] * gw[a][i as usize],
785 };
786 crate::maybe_par_chunks_mut!(acc, nxy).enumerate().filter(|(k, _)| bx[2].contains(k)).for_each(|(k, pa)| {
787 let uz = if k == 0 { nt } else { 0 }; for j in bx[1].clone() {
789 let uy = if j == 0 { nxy } else { 0 };
790 let row = k * nxy + j * n[0];
791 for i in bx[0].clone() {
792 let l = row + i;
793 let xp = if i == 0 { l + n[0] - 1 } else { l - 1 };
794 let mut o = zero;
795 o += q(0, xp) - q(0, l);
796 o += q(1, l + uy - n[0]) - q(1, l);
797 o += q(2, l + uz - nxy) - q(2, l);
798 pa[j * n[0] + i] = o;
799 }
800 }
801 });
802}
803
804pub fn ilsqr(
817 field: &[f64],
818 mask: &[u8],
819 grid: &Grid,
820 bdir: (f64, f64, f64),
821 params: &IlsqrParams,
822 progress: impl FnMut(usize, usize),
823) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
824 ilsqr_with(field, mask, grid, bdir, params, progress, &())
825}
826
827trait Tracer {
831 fn fft<R>(&self, f: impl FnOnce() -> R) -> R {
833 f()
834 }
835 fn stage(&self, _name: &'static str) {}
837 fn dims(&self, _n: [usize; 3]) {}
839 fn solve(&self, _out: &LsqrOutcome) {}
841}
842
843impl Tracer for () {}
844
845#[cfg(not(target_arch = "wasm32"))]
846pub use timing::{ilsqr_traced, IlsqrTrace};
847
848#[cfg(not(target_arch = "wasm32"))]
851mod timing {
852 use super::{ilsqr_with, IlsqrParams, LsqrOutcome, Tracer};
853 use crate::Grid;
854 use std::cell::{Cell, RefCell};
855 use std::time::Instant;
856
857 type Maps = (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>);
859
860 #[derive(Clone, Debug, Default)]
862 pub struct IlsqrTrace {
863 pub dims: [usize; 3],
865 pub stages: Vec<(&'static str, f64, usize, f64)>,
867 pub solves: Vec<LsqrOutcome>,
869 pub n_fft: usize,
871 pub fft_secs: f64,
872 }
873
874 struct Timer {
875 trace: RefCell<IlsqrTrace>,
876 n_fft: Cell<usize>,
877 fft_secs: Cell<f64>,
878 stage_start: Cell<(Instant, usize, f64)>,
880 }
881
882 impl Tracer for Timer {
883 fn fft<R>(&self, f: impl FnOnce() -> R) -> R {
884 let t = Instant::now();
885 let r = f();
886 self.n_fft.set(self.n_fft.get() + 1);
887 self.fft_secs.set(self.fft_secs.get() + t.elapsed().as_secs_f64());
888 r
889 }
890 fn stage(&self, name: &'static str) {
891 let (t0, n0, s0) = self.stage_start.get();
892 let (n, s) = (self.n_fft.get(), self.fft_secs.get());
893 self.trace.borrow_mut().stages.push((name, t0.elapsed().as_secs_f64(), n - n0, s - s0));
894 self.stage_start.set((Instant::now(), n, s));
895 }
896 fn dims(&self, n: [usize; 3]) {
897 self.trace.borrow_mut().dims = n;
898 }
899 fn solve(&self, out: &LsqrOutcome) {
900 self.trace.borrow_mut().solves.push(*out);
901 }
902 }
903
904 pub fn ilsqr_traced(
907 field: &[f64],
908 mask: &[u8],
909 grid: &Grid,
910 bdir: (f64, f64, f64),
911 params: &IlsqrParams,
912 progress: impl FnMut(usize, usize),
913 ) -> (Maps, IlsqrTrace) {
914 let timer = Timer {
915 trace: RefCell::default(),
916 n_fft: Cell::new(0),
917 fft_secs: Cell::new(0.0),
918 stage_start: Cell::new((Instant::now(), 0, 0.0)),
919 };
920 let out = ilsqr_with(field, mask, grid, bdir, params, progress, &timer);
921 let mut trace = timer.trace.into_inner();
922 trace.n_fft = timer.n_fft.get();
923 trace.fft_secs = timer.fft_secs.get();
924 (out, trace)
925 }
926}
927
928fn ilsqr_with(
930 field: &[f64],
931 mask: &[u8],
932 grid: &Grid,
933 bdir: (f64, f64, f64),
934 params: &IlsqrParams,
935 mut progress: impl FnMut(usize, usize),
936 tracer: &impl Tracer,
937) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
938 if let Err(e) = params.validate() {
939 panic!("{e}");
940 }
941 let (nx, ny, nz) = grid.dims;
942 let n_in = nx * ny * nz;
943 let vs = [grid.voxel_size.0, grid.voxel_size.1, grid.voxel_size.2];
944 let zeros = || vec![0.0; n_in];
945 let Some((lo, hi)) = sti_bounding_box(mask, grid.dims) else {
946 return (zeros(), zeros(), zeros(), zeros());
947 };
948 progress(1, 4);
949
950 let c = [0, 1, 2].map(|d| hi[d] - lo[d] + 1);
952 let n = sti_padded_dims(c, vs, params.pad_mm);
953 let pd = [0, 1, 2].map(|d| (n[d] - c[d]) / 2);
954 let nt = n[0] * n[1] * n[2];
955 let mut p = vec![0.0; nt];
956 let mut m = vec![0.0; nt];
957 for k in 0..c[2] {
958 for j in 0..c[1] {
959 for i in 0..c[0] {
960 let src = (i + lo[0]) + (j + lo[1]) * nx + (k + lo[2]) * nx * ny;
961 let dst = (i + pd[0]) + (j + pd[1]) * n[0] + (k + pd[2]) * n[0] * n[1];
962 p[dst] = field[src];
963 m[dst] = if mask[src] != 0 { 1.0 } else { 0.0 };
964 }
965 }
966 }
967 let inside: Vec<usize> = (0..nt).filter(|&l| m[l] > 0.0).collect();
968 let [kx, ky, kz] = [0, 1, 2].map(|d| sti_freqs(n[d], vs[d]));
969 let dk = sti_dipole_kernel(n, [&kx, &ky, &kz], bdir);
970 tracer.dims(n);
971 let ws = RefCell::new(Fft3dWorkspace::new(n[0], n[1], n[2]));
972 let fft = |a: &mut [Complex64]| tracer.fft(|| ws.borrow_mut().fft3d(a));
973 let ifft = |a: &mut [Complex64]| tracer.fft(|| ws.borrow_mut().ifft3d(a));
974 let crop_box: Box3 = [0, 1, 2].map(|d| pd[d]..pd[d] + c[d]);
976 let crop_box1: Box3 = [0, 1, 2].map(|d| pd[d]..(pd[d] + c[d] + 1).min(n[d]));
977 tracer.stage("crop/pad, dipole kernel, plans");
978 let mut p_k: Vec<Complex64> = crate::maybe_par_iter!(p).map(|&v| Complex64::new(v, 0.0)).collect();
979 fft(&mut p_k[..]);
980
981 let mut lap: Vec<Complex64> = p_k.clone();
983 crate::maybe_par_iter_mut!(lap).enumerate().for_each(|(l, z)| {
984 let (i, j, k) = (l % n[0], (l / n[0]) % n[1], l / (n[0] * n[1]));
985 let k2 = if l == 0 { 1e6 } else { kx[i] * kx[i] + ky[j] * ky[j] + kz[k] * kz[k] };
986 *z *= k2;
987 });
988 ifft(&mut lap[..]);
989 let mut w: Vec<f64> = crate::maybe_par_iter!(lap).map(|z| z.re.abs()).collect(); drop(lap);
991 let mut ranked: Vec<f64> = inside.iter().map(|&l| w[l]).collect();
992 let (t60, t999) = sti_percentiles(&mut ranked, 0.6, 0.999);
993 drop(ranked);
994 crate::maybe_par_iter_mut!(w).zip(crate::maybe_par_iter!(m)).for_each(|(v, &mv)| {
995 *v = if mv == 0.0 {
996 0.0
997 } else if t999 > t60 {
998 (1.0 - 0.8 * (*v - t60) / (t999 - t60)).clamp(0.2, 1.0)
999 } else if *v <= t60 {
1000 1.0
1001 } else {
1002 0.2
1003 };
1004 });
1005 tracer.stage("Laplacian weights (incl. F phi)");
1006
1007 let (x1r, out1) = match params.precision {
1009 IlsqrPrecision::Double => initial_solve(n, &p, &w, &dk, &crop_box, params.max_iter, tracer),
1010 IlsqrPrecision::Single => initial_solve_single(n, &p, &w, &dk, params.max_iter),
1011 };
1012 tracer.solve(&out1);
1013 tracer.stage("solve 1 (initial LSQR)");
1014 progress(2, 4);
1015
1016 let xfs = sti_fastqsm(&p_k, &m, &inside, &dk, n, &fft, &ifft);
1018 drop(p_k);
1019 tracer.stage("FastQSM");
1020 progress(3, 4);
1021
1022 let mut gw: Vec<Vec<f64>> = (0..3).map(|_| vec![0.0; nt]).collect();
1024 let mut pool = Vec::with_capacity(3 * inside.len());
1025 for (axis, a) in gw.iter_mut().enumerate() {
1026 let xfs = &xfs;
1028 crate::maybe_par_iter_mut!(a).enumerate().for_each(|(l, o)| *o = (xfs[circ_next(l, axis, n)] - xfs[l]).abs());
1029 pool.extend(inside.iter().map(|&l| a[l]));
1030 }
1031 let (t50, t70) = sti_percentiles(&mut pool, 0.5, 0.7);
1032 drop(pool);
1033 for a in gw.iter_mut() {
1034 crate::maybe_par_iter_mut!(a).zip(crate::maybe_par_iter!(m)).for_each(|(g, &mv)| {
1035 *g = if mv == 0.0 {
1036 0.0
1037 } else if t70 > t50 {
1038 ((t70 - *g) / (t70 - t50)).clamp(0.0, 1.0)
1039 } else if *g <= t50 {
1040 1.0
1041 } else {
1042 0.0
1043 };
1044 });
1045 }
1046 tracer.stage("gradient weights");
1047 progress(4, 4);
1048
1049 let sq = (nt as f64).sqrt();
1051 let (xsa, out2) = match params.precision {
1052 IlsqrPrecision::Double => artefact_solve(n, &x1r, &m, &inside, gw, &dk, &crop_box1, params.tol, params.max_iter, tracer),
1053 IlsqrPrecision::Single => artefact_solve_single(n, &x1r, &m, &gw, &dk, params.tol, params.max_iter),
1054 };
1055 tracer.solve(&out2);
1056
1057 let mut chi = zeros();
1059 let mut sa = zeros();
1060 let mut fs = zeros();
1061 let mut x0 = zeros();
1062 for k in 0..c[2] {
1063 for j in 0..c[1] {
1064 for i in 0..c[0] {
1065 let dst = (i + lo[0]) + (j + lo[1]) * nx + (k + lo[2]) * nx * ny;
1066 let src = (i + pd[0]) + (j + pd[1]) * n[0] + (k + pd[2]) * n[0] * n[1];
1067 if m[src] > 0.0 {
1068 let a = xsa[src] * sq;
1069 chi[dst] = x1r[src] - a;
1070 sa[dst] = a;
1071 fs[dst] = xfs[src];
1072 x0[dst] = x1r[src];
1073 }
1074 }
1075 }
1076 }
1077 tracer.stage("solve 2 (artefact LSQR, incl. setup) + output");
1078 (chi, sa, fs, x0)
1079}
1080
1081#[allow(clippy::too_many_arguments)]
1092fn initial_solve(
1093 n: [usize; 3],
1094 p: &[f64],
1095 w: &[f64],
1096 dk: &[f64],
1097 crop_box: &Box3,
1098 max_iter: usize,
1099 tracer: &impl Tracer,
1100) -> (Vec<f64>, LsqrOutcome) {
1101 let ws = RefCell::new(Fft3dWorkspace::new(n[0], n[1], n[2]));
1102 let tr = |a: &mut [Complex64], inverse: bool, o: &FftOpts| tracer.fft(|| transform(&mut ws.borrow_mut(), a, inverse, o));
1103 let apply1 = |v: &[Complex64], t: &mut [Complex64]| {
1104 let load = |l: usize, row: &mut [Complex64]| {
1105 row.iter_mut().zip(&v[l..]).zip(&dk[l..]).for_each(|((o, &z), &dv)| *o = z * dv);
1106 };
1107 tr(t, true, &FftOpts { needed: Some(crop_box), load: Some(&load), post: Some(w), ..Default::default() });
1108 tr(t, false, &FftOpts { support: Some(crop_box), post: Some(dk), ..Default::default() });
1109 };
1110 let mut b1: Vec<Complex64> = crate::maybe_par_iter!(p).zip(crate::maybe_par_iter!(w))
1111 .map(|(&pv, &wv)| Complex64::new(pv * wv, 0.0)).collect();
1112 tr(&mut b1, false, &FftOpts::default());
1113 crate::maybe_par_iter_mut!(b1).zip(crate::maybe_par_iter!(dk)).for_each(|(z, &dv)| *z *= dv);
1114 let dense = dense_parts(p.len());
1115 let (mut x1, out) = lsqr_matlab_into(apply1, apply1, b1, (&dense, &dense), 0.01, max_iter);
1116 tr(&mut x1, true, &FftOpts::default());
1117 (crate::maybe_par_iter!(x1).map(|z| z.re).collect(), out)
1118}
1119
1120#[allow(clippy::too_many_arguments)]
1132fn artefact_solve(
1133 n: [usize; 3],
1134 x1: &[f64],
1135 m: &[f64],
1136 inside: &[usize],
1137 gw: Vec<Vec<f64>>,
1138 dk: &[f64],
1139 crop_box1: &Box3,
1140 tol: f64,
1141 max_iter: usize,
1142 tracer: &impl Tracer,
1143) -> (Vec<f64>, LsqrOutcome) {
1144 let nt = dk.len();
1145 let sq = (nt as f64).sqrt();
1146 let cone: Vec<usize> = (0..nt).filter(|&l| dk[l].abs() < 0.1).collect();
1147 let nm = inside.len();
1148 assert!(nt < NONE as usize, "grid too large for 32-bit voxel indices");
1149 let index_of = |list: &[usize]| {
1150 let mut map = vec![NONE; nt];
1151 for (i, &l) in list.iter().enumerate() {
1152 map[l] = i as u32;
1153 }
1154 map
1155 };
1156 let cone_of = index_of(&cone);
1157 let mask_of = index_of(inside);
1158 let gwc: Vec<Vec<f64>> = gw.iter().map(|g| inside.iter().map(|&l| g[l]).collect()).collect();
1159 drop(gw);
1160 let u_parts = compact_parts((0..3).flat_map(|a| inside.iter().map(move |&l| a * nt + l)));
1161 let v_parts = compact_parts(cone.iter().copied());
1162 let zero = Complex64::new(0.0, 0.0);
1163 let ws = RefCell::new(Fft3dWorkspace::new(n[0], n[1], n[2]));
1164 let tr = |a: &mut [Complex64], inverse: bool, o: &FftOpts| tracer.fft(|| transform(&mut ws.borrow_mut(), a, inverse, o));
1165 let isq = 1.0 / sq;
1166 let t_cell = RefCell::new(vec![zero; nt]);
1167 let apply2 = |y: &[Complex64], out: &mut [Complex64]| {
1168 let mut t = t_cell.borrow_mut();
1169 let load = |l: usize, row: &mut [Complex64]| {
1172 row.iter_mut().zip(&cone_of[l..]).for_each(|(o, &ci)| *o = if ci == NONE { zero } else { y[ci as usize] });
1173 };
1174 tr(&mut t[..], true, &FftOpts { needed: Some(crop_box1), load: Some(&load), ..Default::default() });
1175 let t = &t[..];
1176 grad_masked(&|l| t[l] * sq, inside, &gwc, n, out);
1177 };
1178 let apply2h = |r: &[Complex64], out: &mut [Complex64]| {
1179 let mut acc = t_cell.borrow_mut();
1180 grad_masked_adj(r, &mask_of, &gwc, n, crop_box1, &mut acc);
1181 tr(&mut acc[..], false, &FftOpts { support: Some(crop_box1), ..Default::default() });
1182 let acc = &acc[..];
1183 crate::maybe_par_iter_mut!(out).zip(crate::maybe_par_iter!(cone)).for_each(|(o, &l)| *o = acc[l] * isq);
1184 };
1185 let mut b2 = vec![zero; 3 * nm];
1186 grad_masked(&|l| Complex64::new(x1[l] * m[l], 0.0), inside, &gwc, n, &mut b2);
1187 let (y, out) = lsqr_matlab_into(apply2, apply2h, b2, (&u_parts, &v_parts), tol, max_iter);
1188 drop(t_cell);
1189 let mut xsa = vec![zero; nt];
1190 for (&l, &z) in cone.iter().zip(&y) {
1191 xsa[l] = z;
1192 }
1193 drop(y);
1194 tr(&mut xsa, true, &FftOpts::default());
1195 (crate::maybe_par_iter!(xsa).map(|z| z.re).collect(), out)
1196}
1197
1198fn sti_fastqsm(
1201 p_k: &[Complex64],
1202 m: &[f64],
1203 inside: &[usize],
1204 dk: &[f64],
1205 n: [usize; 3],
1206 fft: &dyn Fn(&mut [Complex64]),
1207 ifft: &dyn Fn(&mut [Complex64]),
1208) -> Vec<f64> {
1209 let nt = p_k.len();
1210 let r = 3i64;
1213 let s = 7usize;
1214 let mut ball = vec![0.0f64; s * s * s];
1215 let mut cnt = 0.0;
1216 for (l, bv) in ball.iter_mut().enumerate() {
1217 let (i, j, k) = ((l % s) as i64 - r, ((l / s) % s) as i64 - r, (l / (s * s)) as i64 - r);
1218 let d2 = (i * i + j * j + k * k) as f64;
1219 if d2 > 0.0 && d2 <= (r as f64 + 1.0 / 6.0).powi(2) {
1220 *bv = 1.0;
1221 cnt += 1.0;
1222 }
1223 }
1224 ball.iter_mut().for_each(|v| *v /= cnt);
1225 let sp = s + 2;
1232 let at = |i: usize, j: usize, k: usize| i + j * s + k * s * sp;
1233 let mut padded = vec![f64::NEG_INFINITY; s * sp * s];
1234 for k in 0..s {
1235 for j in 0..s {
1236 for i in 0..s {
1237 padded[at(i, j + 1, k)] = ball[i + j * s + k * s * s];
1238 }
1239 }
1240 }
1241 let line = |a: &[f64], dil: bool| -> Vec<f64> {
1242 let mut o = vec![0.0; a.len()];
1243 for k in 0..s {
1244 for j in 0..sp {
1245 for i in 0..s {
1246 let mut v = a[at(i, j, k)];
1247 for jj in [j.wrapping_sub(1), j + 1] {
1248 if jj < sp {
1249 let q = a[at(i, jj, k)];
1250 v = if dil { v.max(q) } else { v.min(q) };
1251 }
1252 }
1253 o[at(i, j, k)] = v;
1254 }
1255 }
1256 }
1257 o
1258 };
1259 let closed_p = line(&line(&padded, true), false);
1260 let closed: Vec<f64> = (0..s * s * s).map(|l| closed_p[at(l % s, (l / s) % s + 1, l / (s * s))]).collect();
1261 debug_assert_eq!(closed.iter().filter(|&&v| v != 0.0).count(), 147);
1262 let mut kern = vec![Complex64::new(0.0, 0.0); nt];
1266 for (l, &bv) in closed.iter().enumerate() {
1267 if bv != 0.0 {
1268 let off = [(l % s) as i64 - r, ((l / s) % s) as i64 - r, (l / (s * s)) as i64 - r];
1269 let idx = [0, 1, 2].map(|d| off[d].rem_euclid(n[d] as i64) as usize);
1270 let sign = if (off[0] + off[1] + off[2]).rem_euclid(2) == 0 { 1.0 } else { -1.0 };
1271 kern[idx[0] + idx[1] * n[0] + idx[2] * n[0] * n[1]] = Complex64::new(sign * bv, 0.0);
1272 }
1273 }
1274 fft(&mut kern[..]);
1275 let kh: Vec<f64> = kern.iter().map(|z| z.re).collect();
1276 drop(kern);
1277 let mut wf: Vec<f64> = crate::maybe_par_iter!(dk).map(|&dv| dv.abs().powf(0.001)).collect();
1279 let mut zs = wf.clone();
1280 let (zlo, zhi) = sti_percentiles(&mut zs, 0.01, 0.3);
1281 drop(zs);
1282 crate::maybe_par_iter_mut!(wf).for_each(|v| *v = if zhi > zlo { ((*v - zlo) / (zhi - zlo)).clamp(0.0, 1.0) } else { 1.0 });
1283 let blend = |x: &mut [Complex64]| {
1285 let mut sm = x.to_vec();
1286 fft(&mut sm[..]);
1287 crate::maybe_par_iter_mut!(sm).zip(crate::maybe_par_iter!(kh)).for_each(|(v, &h)| *v *= h);
1288 ifft(&mut sm[..]);
1289 crate::maybe_par_iter_mut!(x).zip(crate::maybe_par_iter!(sm)).zip(crate::maybe_par_iter!(wf))
1290 .for_each(|((xv, &sv), &w)| *xv = *xv * w + sv * (1.0 - w));
1291 };
1292 let mut x: Vec<Complex64> = crate::maybe_par_iter!(p_k).zip(crate::maybe_par_iter!(dk)).map(|(&pv, &dv)| pv * sign0(dv)).collect();
1293 blend(&mut x[..]);
1294 ifft(&mut x[..]);
1295 crate::maybe_par_iter_mut!(x).zip(crate::maybe_par_iter!(m)).for_each(|(v, &mv)| *v *= mv);
1296 fft(&mut x[..]);
1297 blend(&mut x[..]);
1298 ifft(&mut x[..]);
1299 let mut xfs: Vec<f64> = crate::maybe_par_iter!(x).zip(crate::maybe_par_iter!(m)).map(|(v, &mv)| v.re * mv).collect();
1300 drop(x);
1301 let mut t: Vec<Complex64> = crate::maybe_par_iter!(p_k).zip(crate::maybe_par_iter!(dk)).map(|(&pv, &dv)| {
1303 let r = 1.0 / dv;
1304 pv * if r.abs() > 8.0 { 8.0f64.copysign(r) } else { r }
1305 }).collect();
1306 ifft(&mut t[..]);
1307 let (mut sxy, mut sxx) = (0.0, 0.0);
1308 for &l in inside {
1309 sxy += xfs[l] * t[l].re;
1310 sxx += xfs[l] * xfs[l];
1311 }
1312 let a = if sxx > 0.0 { sxy / sxx } else { 1.0 };
1313 crate::maybe_par_iter_mut!(xfs).for_each(|v| *v *= a);
1314 xfs
1315}
1316
1317fn cnorm32(x: &[Complex32]) -> f32 {
1324 let parts = dense_parts(x.len());
1325 let xs = split_parts(x, &parts);
1326 let sums: Vec<f64> = crate::maybe_par_iter!(xs)
1327 .map(|c| c.iter().map(|v| (v.re as f64) * (v.re as f64) + (v.im as f64) * (v.im as f64)).sum::<f64>())
1328 .collect();
1329 sums.iter().sum::<f64>().sqrt() as f32
1330}
1331
1332fn lsqr_matlab_f32<F, G>(mut apply_a: F, mut apply_ah: G, b: Vec<Complex32>, nx: usize, tol: f64, max_iter: usize) -> (Vec<Complex32>, LsqrOutcome)
1337where
1338 F: FnMut(&[Complex32], &mut [Complex32]),
1339 G: FnMut(&[Complex32], &mut [Complex32]),
1340{
1341 let zero = Complex32::new(0.0, 0.0);
1342 let tol = tol as f32;
1343 let n2b = cnorm32(&b);
1344 let tolb = tol * n2b;
1345 let mut u = b;
1346 let mut beta = n2b;
1347 let mut normr = beta;
1348 let scale = |x: &mut [Complex32], s: f32| crate::maybe_par_iter_mut!(x).for_each(|z| *z /= s);
1349 if beta != 0.0 {
1350 scale(&mut u, beta);
1351 }
1352 let (mut c, mut s) = (1.0f32, 0.0f32);
1353 let mut phibar = beta;
1354 let mut v = vec![zero; nx];
1355 apply_ah(&u, &mut v);
1356 let mut x = vec![zero; nx];
1357 let mut alpha = cnorm32(&v);
1358 if alpha != 0.0 {
1359 scale(&mut v, alpha);
1360 }
1361 let mut normar = alpha * beta;
1362 let relres = |nr: f32| if n2b > 0.0 { (nr / n2b) as f64 } else { 0.0 };
1363 if normar == 0.0 {
1364 return (x, LsqrOutcome { flag: 0, iter: 0, relres: relres(normr) });
1365 }
1366 let mut un = vec![zero; u.len()];
1367 let mut vt = vec![zero; nx];
1368 let mut d = vec![zero; nx];
1369 let mut normx = 0.0f32;
1370 let mut norma = 0.0f32;
1371 let mut stag = 0usize;
1372 let mut flag = 1u8;
1373 let mut iter = max_iter;
1374 let eps = f64::EPSILON as f32;
1375 for ii in 1..=max_iter {
1376 apply_a(&v, &mut un);
1377 crate::maybe_par_iter_mut!(un).zip(crate::maybe_par_iter!(u)).for_each(|(a, &b)| *a -= b * alpha);
1378 beta = cnorm32(&un);
1379 if beta != 0.0 {
1380 scale(&mut un, beta);
1381 }
1382 std::mem::swap(&mut u, &mut un);
1383 norma = (norma * norma + alpha * alpha + beta * beta).sqrt();
1384 let thet = -s * alpha;
1385 let rhot = c * alpha;
1386 let rho = (rhot * rhot + beta * beta).sqrt();
1387 c = rhot / rho;
1388 s = -beta / rho;
1389 let phi = c * phibar;
1390 if phi == 0.0 {
1391 stag = 1;
1392 }
1393 phibar *= s;
1394 let converged = normar / (norma * normr) <= tol || normr <= tolb;
1395 let breakdown = !phi.is_finite() || rho == 0.0 || !rho.is_finite();
1396 crate::maybe_par_iter_mut!(d).zip(crate::maybe_par_iter!(v)).for_each(|(di, &vi)| *di = (vi - *di * thet) / rho);
1397 if phi.abs() * cnorm32(&d) < eps * normx {
1398 stag += 1;
1399 } else {
1400 stag = 0;
1401 }
1402 if converged {
1403 flag = 0;
1404 iter = ii - 1;
1405 break;
1406 }
1407 if stag >= 3 {
1408 flag = 3;
1409 iter = ii - 1;
1410 break;
1411 }
1412 if breakdown {
1413 flag = 4;
1414 iter = ii - 1;
1415 break;
1416 }
1417 crate::maybe_par_iter_mut!(x).zip(crate::maybe_par_iter!(d)).for_each(|(xi, &di)| *xi += di * phi);
1418 normx = cnorm32(&x);
1419 normr *= s.abs();
1420 apply_ah(&u, &mut vt);
1421 crate::maybe_par_iter_mut!(vt).zip(crate::maybe_par_iter!(v)).for_each(|(a, &b)| *a -= b * beta);
1422 std::mem::swap(&mut v, &mut vt);
1423 alpha = cnorm32(&v);
1424 if (alpha == 0.0 || !alpha.is_finite()) && ii < max_iter {
1425 flag = 4;
1426 iter = ii;
1427 break;
1428 }
1429 if alpha != 0.0 {
1430 scale(&mut v, alpha);
1431 }
1432 normar = alpha * (s * phi).abs();
1433 }
1434 if flag == 1 && (normar / (norma * normr) <= tol || normr <= tolb) {
1435 flag = 0;
1436 }
1437 (x, LsqrOutcome { flag, iter, relres: relres(normr) })
1438}
1439
1440fn initial_solve_single(n: [usize; 3], p: &[f64], w: &[f64], dk: &[f64], max_iter: usize) -> (Vec<f64>, LsqrOutcome) {
1445 let nt = p.len();
1446 let mut b: Vec<Complex64> = p.iter().zip(w).map(|(&pv, &wv)| Complex64::new(wv * pv, 0.0)).collect();
1447 let mut ws64 = Fft3dWorkspace::new(n[0], n[1], n[2]);
1448 ws64.fft3d(&mut b);
1449 b.iter_mut().zip(dk).for_each(|(z, &dv)| *z *= dv);
1450 ws64.ifft3d(&mut b);
1451 drop(ws64);
1452 let b1: Vec<Complex32> = b.iter().map(|z| Complex32::new(z.re as f32, z.im as f32)).collect();
1453 drop(b);
1454 let dks: Vec<f32> = dk.iter().map(|&v| v as f32).collect();
1455 let wss: Vec<f32> = w.iter().map(|&v| v as f32).collect();
1456 let ws = RefCell::new(Fft3dWorkspaceF32::new(n[0], n[1], n[2]));
1457 let apply1 = |x: &[Complex32], out: &mut [Complex32]| {
1458 let mut ws = ws.borrow_mut();
1459 out.copy_from_slice(x);
1460 ws.fft3d(out);
1461 crate::maybe_par_iter_mut!(out).zip(crate::maybe_par_iter!(dks)).for_each(|(z, &dv)| *z *= dv);
1462 ws.ifft3d(out);
1463 crate::maybe_par_iter_mut!(out).zip(crate::maybe_par_iter!(wss)).for_each(|(z, &wv)| *z *= wv);
1464 ws.fft3d(out);
1465 crate::maybe_par_iter_mut!(out).zip(crate::maybe_par_iter!(dks)).for_each(|(z, &dv)| *z *= dv);
1466 ws.ifft3d(out);
1467 };
1468 let (x1, out) = lsqr_matlab_f32(apply1, apply1, b1, nt, 0.01, max_iter);
1469 (x1.iter().map(|z| z.re as f64).collect(), out)
1470}
1471
1472#[allow(clippy::too_many_arguments)]
1479fn artefact_solve_single(n: [usize; 3], x1: &[f64], m: &[f64], gw: &[Vec<f64>], dk: &[f64], tol: f64, max_iter: usize) -> (Vec<f64>, LsqrOutcome) {
1480 let nt = dk.len();
1481 let mic: Vec<f32> = dk.iter().map(|&v| if v.abs() < 0.1 { 1.0 } else { 0.0 }).collect();
1482 let gws: Vec<Vec<f32>> = gw.iter().map(|g| g.iter().map(|&v| v as f32).collect()).collect();
1483 let next = |l: usize, a: usize| circ_next(l, a, n);
1484 let prev = |l: usize, a: usize| {
1485 let step = [1, n[0], n[0] * n[1]][a];
1486 if (l / step).is_multiple_of(n[a]) { l + (n[a] - 1) * step } else { l - step }
1487 };
1488 let x1m: Vec<f64> = x1.iter().zip(m).map(|(&x, &mv)| x * mv).collect();
1490 let mut b2 = vec![Complex32::new(0.0, 0.0); 3 * nt];
1491 for (a, ba) in b2.chunks_mut(nt).enumerate() {
1492 let (g, x1m) = (&gw[a], &x1m);
1493 crate::maybe_par_iter_mut!(ba).enumerate().for_each(|(l, o)| *o = Complex32::new((g[l] * (x1m[next(l, a)] - x1m[l])) as f32, 0.0));
1494 }
1495 drop(x1m);
1496 let s = (nt as f64).sqrt() as f32;
1497 let ws = RefCell::new(Fft3dWorkspaceF32::new(n[0], n[1], n[2]));
1498 let t_cell = RefCell::new(vec![Complex32::new(0.0, 0.0); nt]);
1499 let apply2 = |y: &[Complex32], out: &mut [Complex32]| {
1500 let mut t = t_cell.borrow_mut();
1501 crate::maybe_par_iter_mut!(t).zip(crate::maybe_par_iter!(y)).zip(crate::maybe_par_iter!(mic)).for_each(|((o, &yv), &mv)| *o = yv * mv);
1502 ws.borrow_mut().ifft3d(&mut t);
1503 crate::maybe_par_iter_mut!(t).for_each(|z| *z *= s);
1504 let t = &t[..];
1505 for (a, oa) in out.chunks_mut(nt).enumerate() {
1506 let g = &gws[a];
1507 crate::maybe_par_iter_mut!(oa).enumerate().for_each(|(l, o)| *o = (t[next(l, a)] - t[l]) * g[l]);
1508 }
1509 };
1510 let apply2h = |r: &[Complex32], out: &mut [Complex32]| {
1511 crate::maybe_par_iter_mut!(out).enumerate().for_each(|(l, o)| {
1513 let mut u = Complex32::new(0.0, 0.0);
1514 for a in 0..3 {
1515 let (ra, g) = (&r[a * nt..(a + 1) * nt], &gws[a]);
1516 let lp = prev(l, a);
1517 u += ra[lp] * g[lp] - ra[l] * g[l];
1518 }
1519 *o = u;
1520 });
1521 ws.borrow_mut().fft3d(out);
1522 crate::maybe_par_iter_mut!(out).zip(crate::maybe_par_iter!(mic)).for_each(|(z, &mv)| *z = *z * mv / s);
1523 };
1524 let (y, out) = lsqr_matlab_f32(apply2, apply2h, b2, nt, tol, max_iter);
1525 drop(t_cell);
1526 let mut xsa: Vec<Complex64> = y.iter().zip(&mic).map(|(z, &mv)| Complex64::new(z.re as f64, z.im as f64) * mv as f64).collect();
1527 drop(y);
1528 Fft3dWorkspace::new(n[0], n[1], n[2]).ifft3d(&mut xsa);
1529 (xsa.iter().map(|z| z.re).collect(), out)
1530}
1531
1532fn sti_freqs(n: usize, vs: f64) -> Vec<f64> {
1543 let nv = n as f64 * vs;
1544 (0..n).map(|i| if 2 * i < n { i as f64 / nv } else { (i as f64 - n as f64) / nv }).collect()
1545}
1546
1547fn sti_dipole_kernel(n: [usize; 3], k: [&[f64]; 3], bdir: (f64, f64, f64)) -> Vec<f64> {
1552 let (mut hx, mut hy, mut hz) = bdir;
1553 let norm = (hx * hx + hy * hy + hz * hz).sqrt();
1554 if (norm - 1.0).abs() > 1e-12 {
1555 (hx, hy, hz) = (hx / norm, hy / norm, hz / norm);
1556 }
1557 let mut d = vec![0.0; n[0] * n[1] * n[2]];
1558 crate::maybe_par_iter_mut!(d).enumerate().for_each(|(l, o)| {
1559 let (i, j, kk) = (l % n[0], (l / n[0]) % n[1], l / (n[0] * n[1]));
1560 let (a, b, c) = (k[0][i], k[1][j], k[2][kk]);
1561 let k2 = a * a + b * b + c * c;
1562 if k2 != 0.0 {
1563 let kh = a * hx + b * hy + c * hz;
1564 *o = 1.0 / 3.0 - kh * kh / k2;
1565 }
1566 });
1567 d
1568}
1569
1570fn sign0(v: f64) -> f64 {
1571 if v > 0.0 { 1.0 } else if v < 0.0 { -1.0 } else { 0.0 }
1572}
1573
1574#[cfg(test)]
1575mod tests {
1576 use super::*;
1577 use crate::kernels::dipole::dipole_kernel;
1578
1579 #[test]
1580 fn test_lsqr_simple() {
1581 let n = 10;
1583 let diag: Vec<f64> = (1..=n).map(|i| i as f64).collect();
1584 let b: Vec<f64> = diag.iter().map(|&d| d * 2.0).collect(); let apply_a = |x: &[f64]| -> Vec<f64> {
1587 x.iter().zip(diag.iter()).map(|(&xi, &di)| xi * di).collect()
1588 };
1589
1590 let x = lsqr(apply_a, apply_a, &b, 1e-10, 100);
1591
1592 for (i, &xi) in x.iter().enumerate() {
1593 assert!((xi - 2.0).abs() < 1e-6, "x[{}] = {}, expected 2.0", i, xi);
1594 }
1595 }
1596
1597 fn c64(re: f64, im: f64) -> Complex64 { Complex64::new(re, im) }
1600
1601 type Op = Box<dyn Fn(&[Complex64]) -> Vec<Complex64>>;
1602
1603 fn dense(a: Vec<Complex64>, m: usize, n: usize) -> (Op, Op) {
1605 let a2 = a.clone();
1606 let fwd = move |x: &[Complex64]| (0..m).map(|i| (0..n).map(|j| a[i + j * m] * x[j]).sum()).collect();
1607 let adj = move |y: &[Complex64]| (0..n).map(|j| (0..m).map(|i| a2[i + j * m].conj() * y[i]).sum()).collect();
1608 (Box::new(fwd), Box::new(adj))
1609 }
1610
1611 #[test]
1612 fn test_lsqr_matlab_matches_matlab_lsqr() {
1613 let a: Vec<Complex64> = [1.0, 3.0, 5.0, 2.0, 4.0, 6.0].iter().map(|&v| c64(v, 0.0)).collect();
1616 let (f, g) = dense(a, 3, 2);
1617 let b = [c64(1.0, 0.0), c64(2.0, 0.0), c64(4.0, 0.0)];
1618 let (x, o) = lsqr_matlab(f, g, &b, 1e-8, 10);
1619 assert_eq!((o.flag, o.iter), (0, 2));
1620 assert!((o.relres - 0.0890870806374748).abs() < 1e-12);
1621 assert!((x[0].re - 0.666666666666665).abs() < 1e-12 && (x[1].re - 0.083333333333331).abs() < 1e-12);
1622
1623 let d: Vec<f64> = (0..8).map(|i| 10f64.powf(-3.0 * i as f64 / 7.0)).collect(); let mut a = vec![c64(0.0, 0.0); 64];
1626 for i in 0..8 { a[i + 8 * i] = c64(d[i], 0.0); }
1627 let (f, g) = dense(a, 8, 8);
1628 let b = vec![c64(1.0, 0.0); 8];
1629 let (x, o) = lsqr_matlab(f, g, &b, 1e-10, 5);
1630 assert_eq!((o.flag, o.iter), (1, 5));
1631 assert!((o.relres - 0.573674508817809).abs() < 1e-12);
1632 let want = [0.999999943834766, 2.68269578689076, 7.1969177940897, 19.2460743622976,
1633 59.9361124086567, 25.5070666956926, 9.67511787758301, 3.61516968204123];
1634 for (xi, wi) in x.iter().zip(want) { assert!((xi.re - wi).abs() < 1e-6 * wi, "{} vs {}", xi.re, wi); }
1636
1637 let a = vec![c64(1.0, 0.0), c64(2.0, 0.0), c64(0.0, 0.5), c64(0.0, 1.0), c64(-1.0, 0.0), c64(3.0, 0.0)];
1639 let (f, g) = dense(a, 3, 2);
1640 let b = [c64(1.0, 0.0), c64(0.0, 1.0), c64(2.0, 0.0)];
1641 let (x, o) = lsqr_matlab(f, g, &b, 1e-12, 20);
1642 assert_eq!((o.flag, o.iter), (0, 2));
1643 assert!((o.relres - 0.449991346403468).abs() < 1e-12);
1644 assert!((x[0] - c64(0.448598130841121, 0.186915887850467)).norm() < 1e-12);
1645 assert!((x[1] - c64(0.635514018691589, -0.168224299065421)).norm() < 1e-12);
1646 }
1647
1648 #[test]
1649 fn test_lsqr_matlab_zero_rhs() {
1650 let (x, o) = lsqr_matlab(|v: &[Complex64]| v.to_vec(), |v: &[Complex64]| v.to_vec(), &[c64(0.0, 0.0); 4], 1e-6, 10);
1651 assert_eq!((o.flag, o.iter), (0, 0));
1652 assert!(x.iter().all(|z| z.norm() == 0.0));
1653 }
1654
1655 #[test]
1656 fn test_sti_bounding_box() {
1657 type Case = ((usize, usize, usize), [usize; 3], [usize; 3], [[usize; 2]; 3]);
1659 let cases: [Case; 5] = [
1660 ((20, 20, 20), [4, 5, 6], [10, 11, 12], [[4, 11], [5, 12], [6, 13]]),
1661 ((20, 20, 20), [1, 1, 1], [5, 6, 7], [[1, 6], [1, 6], [1, 8]]),
1662 ((20, 20, 20), [14, 15, 16], [20, 20, 20], [[13, 20], [15, 20], [15, 20]]),
1663 ((21, 21, 21), [1, 2, 3], [21, 21, 21], [[1, 20], [2, 21], [2, 21]]),
1664 ((20, 20, 20), [7, 7, 7], [7, 8, 7], [[7, 8], [7, 8], [7, 8]]),
1665 ];
1666 for (dims, lo1, hi1, want) in cases {
1667 let mut m = vec![0u8; dims.0 * dims.1 * dims.2];
1668 for k in lo1[2] - 1..hi1[2] { for j in lo1[1] - 1..hi1[1] { for i in lo1[0] - 1..hi1[0] {
1669 m[i + j * dims.0 + k * dims.0 * dims.1] = 1;
1670 } } }
1671 let (lo, hi) = sti_bounding_box(&m, dims).unwrap();
1672 for d in 0..3 { assert_eq!([lo[d] + 1, hi[d] + 1], want[d], "dims {:?} box {:?}..{:?}", dims, lo1, hi1); }
1673 }
1674 assert!(sti_bounding_box(&[0u8; 8], (2, 2, 2)).is_none());
1675 }
1676
1677 #[test]
1678 fn test_sti_dipole_kernel_rounds_as_sti() {
1679 let n = [48; 3];
1683 let k = sti_freqs(48, 1.0);
1684 let d = sti_dipole_kernel(n, [&k, &k, &k], (0.0, 0.0, 1.0));
1685 let at = |i: usize, j: usize, l: usize| d[i + 48 * (j + 48 * l)];
1686 assert_eq!(at(5, 5, 5), -5.551115123125783e-17);
1687 assert_eq!(at(7, 7, 7), -5.551115123125783e-17);
1688 assert_eq!(at(7, 23, 17), 0.0);
1689 assert_eq!(at(17, 17, 17), 0.0);
1690 assert_eq!(at(0, 0, 0), 0.0);
1691 assert_eq!(k[47], -1.0 / 48.0);
1692 let usual = dipole_kernel(&Grid::new(48, 48, 48, 1.0, 1.0, 1.0), (0.0, 0.0, 1.0));
1694 assert!(d.iter().zip(&usual).all(|(a, b)| (a - b).abs() < 1e-15));
1695 }
1696
1697 #[test]
1698 fn test_sti_padded_dims() {
1699 type Case = ([usize; 3], [f64; 3], [f64; 3], [usize; 3]);
1701 let cases: [Case; 6] = [
1702 ([32, 30, 8], [0.8, 0.8, 3.0], [64.0; 3], [208, 192, 64]),
1703 ([48, 44, 12], [0.8, 0.8, 3.0], [0.0; 3], [64, 48, 16]),
1704 ([32, 34, 32], [1.3; 3], [10.0; 3], [64, 64, 64]),
1705 ([32, 30, 18], [1.3, 0.7, 1.1], [10.0, 3.0, 7.0], [64, 48, 32]),
1706 ([40, 42, 10], [1.05, 2.2, 3.3], [20.0; 3], [80, 64, 32]),
1707 ([46, 42, 10], [0.5, 0.5, 2.0], [64.0; 3], [304, 304, 80]),
1708 ];
1709 for (c, vs, pad, want) in cases {
1711 assert_eq!([0, 1, 2].map(|d| sti_padded_dims(c, vs, pad[d])[d]), want);
1712 }
1713 }
1714
1715 #[test]
1716 fn test_sti_percentile_is_rounded_index() {
1717 let v: Vec<f64> = (1..=10).map(|i| i as f64).collect();
1718 assert_eq!(sti_percentile(&v, 0.25), 3.0); assert_eq!(sti_percentile(&v, 0.999), 10.0);
1720 assert_eq!(sti_percentile(&v, 0.0), 1.0);
1721 }
1722
1723 #[test]
1726 fn test_grad_masked_and_adjoint() {
1727 let n = [6, 5, 4];
1728 let nt = 120;
1729 let inside: Vec<usize> = (0..nt).filter(|&l| (l * 7) % 5 != 0).collect();
1730 let nm = inside.len();
1731 let mut mask_of = vec![NONE; nt];
1732 for (i, &l) in inside.iter().enumerate() { mask_of[l] = i as u32; }
1733 let u: Vec<Complex64> = (0..nt).map(|i| c64((i as f64 * 0.37).sin(), (i as f64 * 0.11).cos())).collect();
1734 let r: Vec<Complex64> = (0..3 * nm).map(|i| c64((i as f64 * 0.23).cos(), (i as f64 * 0.71).sin())).collect();
1735 let gw: Vec<Vec<f64>> = (0..3).map(|a| (0..nm).map(|i| ((i * 7 + a * 3) % 11) as f64 / 10.0).collect()).collect();
1736 let mut g = vec![c64(0.0, 0.0); 3 * nm];
1737 grad_masked(&|l| u[l], &inside, &gw, n, &mut g);
1738 for a in 0..3 {
1739 let (s, len) = ([1, 6, 30][a], n[a]);
1740 for (i, &l) in inside.iter().enumerate() {
1741 let c = (l / s) % len;
1742 let nb = if c + 1 == len { l - (len - 1) * s } else { l + s };
1743 assert_eq!(g[a * nm + i], (u[nb] - u[l]) * gw[a][i]);
1744 }
1745 }
1746 let mut h = vec![c64(9.0, 9.0); nt];
1747 grad_masked_adj(&r, &mask_of, &gw, n, &[0..6, 0..5, 0..4], &mut h);
1748 let lhs: Complex64 = g.iter().zip(&r).map(|(a, b)| a.conj() * b).sum();
1749 let rhs: Complex64 = u.iter().zip(&h).map(|(a, b)| a.conj() * b).sum();
1750 assert!((lhs - rhs).norm() < 1e-12);
1751 }
1752
1753 #[test]
1756 fn test_lsqr_compact_equals_dense() {
1757 let nt = 3 * NORM_CHUNK + 1234;
1758 let supp: Vec<usize> = (0..nt).filter(|&l| (l / 1000) % 3 != 1 && l % 7 != 3).collect();
1759 let w: Vec<f64> = (0..nt).map(|l| 0.5 + ((l * 13) % 17) as f64 / 7.0).collect();
1760 let b: Vec<Complex64> = (0..nt).map(|l| if supp.binary_search(&l).is_ok() { c64((l as f64 * 0.01).sin(), (l as f64 * 0.003).cos()) } else { c64(0.0, 0.0) }).collect();
1761 let dense = |v: &[Complex64]| -> Vec<Complex64> { v.iter().zip(&w).map(|(&z, &wv)| z * wv).collect() };
1763 let (xd, od) = lsqr_matlab(dense, dense, &b, 1e-14, 15);
1764 let wc: Vec<f64> = supp.iter().map(|&l| w[l]).collect();
1765 let bc: Vec<Complex64> = supp.iter().map(|&l| b[l]).collect();
1766 let parts = compact_parts(supp.iter().copied());
1767 let op = |v: &[Complex64], out: &mut [Complex64]| out.iter_mut().zip(v).zip(&wc).for_each(|((o, &z), &wv)| *o = z * wv);
1768 let (xc, oc) = lsqr_matlab_into(op, op, bc, (&parts, &parts), 1e-14, 15);
1769 assert_eq!(od, oc);
1770 for (i, &l) in supp.iter().enumerate() { assert_eq!(xd[l], xc[i]); }
1771 }
1772
1773 fn sphere_phantom(n: usize, vs: (f64, f64, f64), bdir: (f64, f64, f64)) -> (Vec<f64>, Vec<u8>, Vec<f64>) {
1775 let nt = n * n * n;
1776 let c = (n as f64 - 1.0) / 2.0;
1777 let mut chi = vec![0.0; nt];
1778 let mut mask = vec![0u8; nt];
1779 for k in 0..n { for j in 0..n { for i in 0..n {
1780 let (x, y, z) = (i as f64 - c, j as f64 - c, k as f64 - c);
1781 let r2 = x * x + y * y + z * z;
1782 let l = i + j * n + k * n * n;
1783 if r2 < (0.4 * n as f64).powi(2) { mask[l] = 1; }
1784 if (x - 2.0).powi(2) + y * y + z * z < 9.0 { chi[l] = 0.1; }
1785 if (x + 3.0).powi(2) + (y - 1.0).powi(2) + z * z < 4.0 { chi[l] = -0.05; }
1786 } } }
1787 let m2 = 2 * n;
1788 let g2 = Grid::new(m2, m2, m2, vs.0, vs.1, vs.2);
1789 let d = dipole_kernel(&g2, bdir);
1790 let mut big = vec![Complex64::new(0.0, 0.0); m2 * m2 * m2];
1791 for k in 0..n { for j in 0..n { for i in 0..n { big[i + j * m2 + k * m2 * m2].re = chi[i + j * n + k * n * n]; } } }
1792 let mut ws = Fft3dWorkspace::new(m2, m2, m2);
1793 ws.fft3d(&mut big);
1794 for (z, &dv) in big.iter_mut().zip(&d) { *z *= dv; }
1795 ws.ifft3d(&mut big);
1796 let mut field = vec![0.0; nt];
1797 for k in 0..n { for j in 0..n { for i in 0..n {
1798 let l = i + j * n + k * n * n;
1799 if mask[l] != 0 { field[l] = big[i + j * m2 + k * m2 * m2].re; }
1800 } } }
1801 (field, mask, chi)
1802 }
1803
1804 fn corr_in(a: &[f64], b: &[f64], m: &[u8]) -> f64 {
1805 let idx: Vec<usize> = (0..a.len()).filter(|&i| m[i] != 0).collect();
1806 let n = idx.len() as f64;
1807 let (ma, mb) = (idx.iter().map(|&i| a[i]).sum::<f64>() / n, idx.iter().map(|&i| b[i]).sum::<f64>() / n);
1808 let (mut sab, mut saa, mut sbb) = (0.0, 0.0, 0.0);
1809 for &i in &idx { sab += (a[i] - ma) * (b[i] - mb); saa += (a[i] - ma).powi(2); sbb += (b[i] - mb).powi(2); }
1810 sab / (saa * sbb).sqrt()
1811 }
1812
1813 #[test]
1814 fn test_ilsqr_sti_recovers_spheres() {
1815 let n = 16;
1816 let bdir = (0.1, -0.2, 0.95f64.sqrt());
1817 let (field, mask, chi) = sphere_phantom(n, (1.0, 1.0, 1.0), bdir);
1818 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
1819 let mut steps = Vec::new();
1820 let params = IlsqrParams { max_iter: 40, pad_mm: 4.0, ..IlsqrParams::default() };
1821 let (x, xsa, xfs, x1) = ilsqr(&field, &mask, &grid, bdir, ¶ms, |s, t| steps.push((s, t)));
1822 assert_eq!(steps, vec![(1, 4), (2, 4), (3, 4), (4, 4)]);
1823 for v in [&x, &xsa, &xfs, &x1] {
1824 assert!(v.iter().all(|z| z.is_finite()));
1825 assert!(v.iter().zip(&mask).all(|(&z, &m)| m != 0 || z == 0.0), "output must be masked");
1826 }
1827 let (r, r1) = (corr_in(&x, &chi, &mask), corr_in(&x1, &chi, &mask));
1828 println!("corr with truth: chi {:.4}, initial LSQR {:.4}", r, r1);
1829 assert!(r > 0.9 && r1 > 0.8, "corr with truth {} / {}", r, r1);
1830 for i in 0..x.len() { assert!((x[i] - (x1[i] - xsa[i])).abs() < 1e-12); }
1832 let f2: Vec<f64> = field.iter().map(|v| 3.0 * v).collect();
1834 let (y, _, _, _) = ilsqr(&f2, &mask, &grid, bdir, ¶ms, |_, _| {});
1835 let x3: Vec<f64> = x.iter().map(|v| 3.0 * v).collect();
1836 let worst = y.iter().zip(&x3).map(|(a, b)| (a - b).abs()).fold(0.0, f64::max);
1837 println!("scaling: max|chi(3f) - 3 chi(f)| = {:.2e}", worst);
1838 assert!(corr_in(&y, &x3, &mask) > 0.9999 && worst < 0.01 * 0.3, "not linear: {}", worst);
1839 }
1840
1841 #[test]
1842 fn test_ilsqr_sti_empty_mask_and_zero_field() {
1843 let grid = Grid::new(8, 8, 8, 1.0, 1.0, 1.0);
1844 let (x, _, _, _) = ilsqr(&[1.0; 512], &[0u8; 512], &grid, (0.0, 0.0, 1.0), &IlsqrParams::default(), |_, _| {});
1845 assert!(x.iter().all(|&v| v == 0.0));
1846 let mut mask = vec![0u8; 512];
1847 for k in 2..6 { for j in 2..6 { for i in 2..6 { mask[i + 8 * j + 64 * k] = 1; } } }
1848 let p4 = IlsqrParams { pad_mm: 4.0, ..IlsqrParams::default() };
1849 let (x, _, _, _) = ilsqr(&[0.0; 512], &mask, &grid, (0.0, 0.0, 1.0), &p4, |_, _| {});
1850 assert!(x.iter().all(|&v| v == 0.0));
1851 }
1852
1853 #[test]
1854 fn test_ilsqr_params_defaults() {
1855 let p = IlsqrParams::default();
1856 assert_eq!((p.tol, p.max_iter, p.pad_mm), (1e-3, STI_DEFAULT_NITER, STI_GUI_PAD_MM));
1858 assert_eq!((STI_DEFAULT_NITER, STI_GUI_NITER, STI_GUI_PAD_MM), (100, 40, 12.0));
1859 assert_eq!((STI_PAD_MM, STI_DEFAULT_PAD_MM), (64.0, 6.0));
1860 assert!(p.validate().is_ok());
1861 }
1862
1863 #[test]
1864 fn test_ilsqr_pad_sets_the_grid() {
1865 let n = 8;
1867 let bdir = (0.0, 0.0, 1.0);
1868 let (field, _, _) = sphere_phantom(n, (2.0, 2.0, 2.0), bdir);
1869 let mask = vec![1u8; n * n * n];
1870 let run = |vs: (f64, f64, f64), p: &IlsqrParams| {
1871 let grid = Grid::new(n, n, n, vs.0, vs.1, vs.2);
1872 let ((x, _, _, _), tr) = ilsqr_traced(&field, &mask, &grid, bdir, p, |_, _| {});
1873 (tr.dims, x)
1874 };
1875 let iso = (2.0, 2.0, 2.0);
1876 let with_pad = |pad_mm: f64| IlsqrParams { max_iter: 2, pad_mm, ..IlsqrParams::default() };
1877 let (d_default, x_default) = run(iso, &IlsqrParams { max_iter: 2, ..IlsqrParams::default() });
1878 let (d12, x12) = run(iso, &with_pad(STI_GUI_PAD_MM));
1879 assert_eq!((d_default, d12), ([32; 3], [32; 3]));
1880 assert!(x_default.iter().zip(&x12).all(|(a, b)| a.to_bits() == b.to_bits()));
1882 assert_eq!(run(iso, &with_pad(STI_DEFAULT_PAD_MM)).0, [16; 3]);
1883 let (d16, x16) = run(iso, &with_pad(16.0));
1884 assert_eq!(d16, [32; 3]);
1885 let (d64, x64) = run(iso, &with_pad(STI_PAD_MM));
1886 assert_eq!(d64, [80; 3]);
1887 assert!(x16.iter().zip(&x64).any(|(a, b)| a != b), "padding should change chi");
1888 assert_eq!(run(iso, &with_pad(0.0)).0, [16; 3]);
1889 assert_eq!(run((2.0, 2.0, 4.0), &with_pad(STI_PAD_MM)).0, [80, 80, 48]);
1891 }
1892
1893 #[test]
1894 fn test_ilsqr_pad_validation() {
1895 for bad in [-1.0, f64::NAN, f64::INFINITY] {
1896 let p = IlsqrParams { pad_mm: bad, ..IlsqrParams::default() };
1897 assert!(p.validate().is_err(), "pad {bad} accepted");
1898 }
1899 assert!(IlsqrParams { pad_mm: 0.0, ..IlsqrParams::default() }.validate().is_ok());
1900 }
1901
1902 #[test]
1903 #[should_panic(expected = "iLSQR padding must be finite")]
1904 fn test_ilsqr_rejects_negative_pad() {
1905 let grid = Grid::new(8, 8, 8, 1.0, 1.0, 1.0);
1906 let p = IlsqrParams { pad_mm: -1.0, ..IlsqrParams::default() };
1907 ilsqr(&[0.0; 512], &[1u8; 512], &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1908 }
1909
1910}