1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
31use scirs2_core::numeric::{Float, NumAssign, One, Zero};
32use scirs2_core::random::prelude::*;
33use scirs2_core::random::{Distribution, Normal, Uniform};
34use std::fmt::Debug;
35use std::iter::Sum;
36
37use crate::decomposition::qr;
38use crate::error::{LinalgError, LinalgResult};
39
40#[derive(Debug, Clone)]
50pub struct SparseSign {
51 pub rows: usize,
53 pub cols: usize,
55 pub sparsity: usize,
57 row_indices: Vec<usize>,
59 signs: Vec<f64>,
61 scale: f64,
63}
64
65impl SparseSign {
66 pub fn new(
75 rows: usize,
76 cols: usize,
77 sparsity: Option<usize>,
78 seed: Option<u64>,
79 ) -> LinalgResult<Self> {
80 if rows == 0 || cols == 0 {
81 return Err(LinalgError::ShapeError(
82 "SparseSign: rows and cols must be positive".to_string(),
83 ));
84 }
85
86 let s = sparsity.unwrap_or_else(|| {
87 let log2r = (rows as f64).log2().ceil() as usize;
88 log2r.max(1).min(rows)
89 });
90
91 if s > rows {
92 return Err(LinalgError::ShapeError(format!(
93 "SparseSign: sparsity {s} cannot exceed rows {rows}"
94 )));
95 }
96
97 let mut rng = {
98 let s = seed.unwrap_or_else(|| {
99 let mut tr = scirs2_core::random::rng();
100 scirs2_core::random::RngExt::random::<u64>(&mut tr)
101 });
102 scirs2_core::random::seeded_rng(s)
103 };
104
105 let mut row_indices = vec![0usize; cols * s];
106 let mut signs = vec![0.0f64; cols * s];
107
108 let uniform_sign = Uniform::new(0u8, 2).map_err(|e| {
109 LinalgError::ComputationError(format!("Failed to create uniform distribution: {e}"))
110 })?;
111
112 for col in 0..cols {
113 let mut pool: Vec<usize> = (0..rows).collect();
115 for k in 0..s {
116 let j = (Uniform::new(k, rows)
117 .map_err(|e| {
118 LinalgError::ComputationError(format!("Failed to sample index: {e}"))
119 })?
120 .sample(&mut rng)) as usize;
121 pool.swap(k, j);
122 let row = pool[k];
123 row_indices[col * s + k] = row;
124 let sign_bit = uniform_sign.sample(&mut rng);
125 signs[col * s + k] = if sign_bit == 0 { 1.0 } else { -1.0 };
126 }
127 }
128
129 let scale = 1.0 / (s as f64).sqrt();
130
131 Ok(Self {
132 rows,
133 cols,
134 sparsity: s,
135 row_indices,
136 signs,
137 scale,
138 })
139 }
140
141 pub fn apply(&self, x: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
147 if x.len() != self.cols {
148 return Err(LinalgError::DimensionError(format!(
149 "SparseSign::apply: x has length {} but cols={}",
150 x.len(),
151 self.cols
152 )));
153 }
154
155 let mut y = Array1::zeros(self.rows);
156 let s = self.sparsity;
157
158 for col in 0..self.cols {
159 let xval = x[col];
160 for k in 0..s {
161 let row = self.row_indices[col * s + k];
162 let sign = self.signs[col * s + k];
163 y[row] += self.scale * sign * xval;
164 }
165 }
166
167 Ok(y)
168 }
169
170 pub fn apply_matrix(&self, a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
174 let (nrows_a, ncols_a) = a.dim();
175 if nrows_a != self.cols {
176 return Err(LinalgError::DimensionError(format!(
177 "SparseSign::apply_matrix: A has {} rows but cols={}",
178 nrows_a, self.cols
179 )));
180 }
181
182 let mut y = Array2::zeros((self.rows, ncols_a));
183 let s = self.sparsity;
184
185 for col in 0..self.cols {
186 for k in 0..s {
187 let row = self.row_indices[col * s + k];
188 let sign = self.signs[col * s + k];
189 let coeff = self.scale * sign;
190 for j in 0..ncols_a {
191 y[[row, j]] += coeff * a[[col, j]];
192 }
193 }
194 }
195
196 Ok(y)
197 }
198
199 pub fn apply_transpose(&self, x: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
201 if x.len() != self.rows {
202 return Err(LinalgError::DimensionError(format!(
203 "SparseSign::apply_transpose: x has length {} but rows={}",
204 x.len(),
205 self.rows
206 )));
207 }
208
209 let mut y = Array1::zeros(self.cols);
210 let s = self.sparsity;
211
212 for col in 0..self.cols {
213 let mut val = 0.0f64;
214 for k in 0..s {
215 let row = self.row_indices[col * s + k];
216 let sign = self.signs[col * s + k];
217 val += sign * x[row];
218 }
219 y[col] = self.scale * val;
220 }
221
222 Ok(y)
223 }
224}
225
226#[derive(Debug, Clone)]
241pub struct SubsampledRandomizedHadamard {
242 pub n: usize,
244 pub k: usize,
246 diag_signs: Vec<f64>,
248 sampled_rows: Vec<usize>,
250 scale: f64,
252}
253
254impl SubsampledRandomizedHadamard {
255 pub fn new(n: usize, k: usize, seed: Option<u64>) -> LinalgResult<Self> {
263 if n == 0 {
264 return Err(LinalgError::ShapeError(
265 "SRHT: n must be positive".to_string(),
266 ));
267 }
268 if k == 0 || k > n {
269 return Err(LinalgError::ShapeError(format!(
270 "SRHT: k must satisfy 1 ≤ k ≤ n={n}, got k={k}"
271 )));
272 }
273
274 let n_padded = n.next_power_of_two();
276
277 let mut rng = {
278 let s = seed.unwrap_or_else(|| {
279 let mut tr = scirs2_core::random::rng();
280 scirs2_core::random::RngExt::random::<u64>(&mut tr)
281 });
282 scirs2_core::random::seeded_rng(s)
283 };
284
285 let uniform_bit = Uniform::new(0u8, 2).map_err(|e| {
287 LinalgError::ComputationError(format!("Failed to create uniform distribution: {e}"))
288 })?;
289 let diag_signs: Vec<f64> = (0..n_padded)
290 .map(|_| {
291 if uniform_bit.sample(&mut rng) == 0 {
292 1.0
293 } else {
294 -1.0
295 }
296 })
297 .collect();
298
299 let mut pool: Vec<usize> = (0..n_padded).collect();
301 let k_actual = k.min(n_padded);
302 for i in 0..k_actual {
303 let j_range = Uniform::new(i, n_padded).map_err(|e| {
304 LinalgError::ComputationError(format!("Failed to sample row index: {e}"))
305 })?;
306 let j = j_range.sample(&mut rng);
307 pool.swap(i, j);
308 }
309 let sampled_rows: Vec<usize> = pool[..k_actual].to_vec();
310
311 let scale = (n_padded as f64 / k_actual as f64).sqrt();
312
313 Ok(Self {
314 n: n_padded,
315 k: k_actual,
316 diag_signs,
317 sampled_rows,
318 scale,
319 })
320 }
321
322 pub fn apply(&self, x: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
326 if x.len() > self.n {
327 return Err(LinalgError::DimensionError(format!(
328 "SRHT::apply: x has length {} but n={}",
329 x.len(),
330 self.n
331 )));
332 }
333
334 let mut buf = vec![0.0f64; self.n];
336 for i in 0..x.len() {
337 buf[i] = self.diag_signs[i] * x[i];
338 }
339
340 Self::fwht(&mut buf);
342
343 let scale = self.scale / (self.n as f64).sqrt();
345 let y: Array1<f64> = self.sampled_rows.iter().map(|&r| scale * buf[r]).collect();
346
347 Ok(y)
348 }
349
350 pub fn apply_matrix(&self, a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
354 let (nrows, ncols) = a.dim();
355 if nrows > self.n {
356 return Err(LinalgError::DimensionError(format!(
357 "SRHT::apply_matrix: A has {} rows but n={}",
358 nrows, self.n
359 )));
360 }
361
362 let scale = self.scale / (self.n as f64).sqrt();
363 let mut result = Array2::zeros((self.k, ncols));
364
365 for j in 0..ncols {
366 let mut buf = vec![0.0f64; self.n];
368 for i in 0..nrows {
369 buf[i] = self.diag_signs[i] * a[[i, j]];
370 }
371 Self::fwht(&mut buf);
373 for (ki, &row) in self.sampled_rows.iter().enumerate() {
375 result[[ki, j]] = scale * buf[row];
376 }
377 }
378
379 Ok(result)
380 }
381
382 fn fwht(data: &mut [f64]) {
386 let n = data.len();
387 let mut h = 1;
388 while h < n {
389 let mut i = 0;
390 while i < n {
391 for j in i..(i + h) {
392 let u = data[j];
393 let v = data[j + h];
394 data[j] = u + v;
395 data[j + h] = u - v;
396 }
397 i += 2 * h;
398 }
399 h *= 2;
400 }
401 }
402}
403
404#[derive(Debug, Clone)]
410pub struct RandomizedPrecondResult {
411 pub solution: Array1<f64>,
413 pub relative_residual: f64,
415 pub iterations: usize,
417 pub converged: bool,
419 pub preconditioned_cond_estimate: Option<f64>,
421}
422
423#[derive(Debug, Clone)]
439pub struct BlendenpikPreconditioner {
440 pub r_factor: Array2<f64>,
442 pub n: usize,
444 pub sketch_dim: usize,
446 pub cond_estimate: f64,
448}
449
450impl BlendenpikPreconditioner {
451 pub fn new(
460 a: &ArrayView2<f64>,
461 oversampling: Option<f64>,
462 use_srht: bool,
463 seed: Option<u64>,
464 ) -> LinalgResult<Self> {
465 let (m, n) = a.dim();
466 if m < n {
467 return Err(LinalgError::ShapeError(format!(
468 "Blendenpik requires m ≥ n, got {}×{}",
469 m, n
470 )));
471 }
472
473 let alpha = oversampling.unwrap_or(2.0).max(1.0);
474 let sketch_dim = ((alpha * n as f64) as usize).max(n + 1).min(m);
475
476 let sketch_a = if use_srht {
478 let srht = SubsampledRandomizedHadamard::new(m, sketch_dim, seed)?;
479 srht.apply_matrix(a)?
480 } else {
481 let sparse = SparseSign::new(sketch_dim, m, None, seed)?;
482 sparse.apply_matrix(a)?
483 };
484
485 let (_, r_factor) = qr(&sketch_a.view(), None)?;
487
488 let r_n = r_factor.nrows().min(n);
490 let r_square = r_factor
491 .slice(scirs2_core::ndarray::s![..r_n, ..n])
492 .to_owned();
493
494 let diag_abs: Vec<f64> = (0..r_n).map(|i| r_square[[i, i]].abs()).collect();
496 let r_max = diag_abs.iter().cloned().fold(0.0_f64, f64::max);
497 let r_min = diag_abs
498 .iter()
499 .cloned()
500 .fold(f64::INFINITY, f64::min)
501 .max(1e-300);
502 let cond_estimate = if r_max > 0.0 { r_max / r_min } else { 1e16 };
503
504 Ok(Self {
505 r_factor: r_square,
506 n: r_n,
507 sketch_dim,
508 cond_estimate,
509 })
510 }
511
512 pub fn apply_r_inverse(&self, v: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
514 let n = self.n;
515 if v.len() != n {
516 return Err(LinalgError::DimensionError(format!(
517 "BlendenpikPreconditioner::apply_r_inverse: v has {} elements but n={}",
518 v.len(),
519 n
520 )));
521 }
522
523 let mut x = Array1::zeros(n);
525 for i in (0..n).rev() {
526 let mut sum = v[i];
527 for j in (i + 1)..n {
528 sum -= self.r_factor[[i, j]] * x[j];
529 }
530 let diag = self.r_factor[[i, i]];
531 if diag.abs() < 1e-300 {
532 return Err(LinalgError::SingularMatrixError(format!(
533 "Blendenpik R is singular at diagonal index {i}"
534 )));
535 }
536 x[i] = sum / diag;
537 }
538
539 Ok(x)
540 }
541
542 pub fn apply_r(&self, x: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
544 let n = self.n;
545 if x.len() != n {
546 return Err(LinalgError::DimensionError(format!(
547 "BlendenpikPreconditioner::apply_r: x has {} elements but n={}",
548 x.len(),
549 n
550 )));
551 }
552
553 let mut y = Array1::zeros(n);
554 for i in 0..n {
555 let mut val = 0.0f64;
556 for j in i..n {
557 val += self.r_factor[[i, j]] * x[j];
558 }
559 y[i] = val;
560 }
561
562 Ok(y)
563 }
564
565 pub fn solve(
576 &self,
577 a: &ArrayView2<f64>,
578 b: &ArrayView1<f64>,
579 max_iter: Option<usize>,
580 tol: Option<f64>,
581 ) -> LinalgResult<RandomizedPrecondResult> {
582 let (m, n_a) = a.dim();
583 if m != b.len() {
584 return Err(LinalgError::DimensionError(format!(
585 "BlendenpikPreconditioner::solve: A has {} rows but b has {} elements",
586 m,
587 b.len()
588 )));
589 }
590
591 let max_it = max_iter.unwrap_or(200);
592 let tolerance = tol.unwrap_or(1e-10);
593
594 let n = self.n.min(n_a);
597
598 let mut atb = Array1::zeros(n);
600 for i in 0..n {
601 let mut val = 0.0f64;
602 for k in 0..m {
603 val += a[[k, i]] * b[k];
604 }
605 atb[i] = val;
606 }
607
608 let mut y = Array1::zeros(n);
610
611 let mut g = self.apply_r_transpose_inverse(&atb.view())?;
614
615 let mut p = g.clone();
617 let mut rr = dot_product(&g.view(), &g.view());
618
619 let mut converged = false;
620 let mut iterations = 0;
621
622 for iter in 0..max_it {
623 iterations = iter + 1;
624
625 let rp = self.apply_r_inverse(&p.view())?;
627 let mut arp = Array1::zeros(m);
629 for i in 0..m {
630 let mut val = 0.0f64;
631 for j in 0..n.min(n_a) {
632 val += a[[i, j]] * rp[j];
633 }
634 arp[i] = val;
635 }
636 let mut aarp = Array1::zeros(n);
638 for i in 0..n {
639 let mut val = 0.0f64;
640 for k in 0..m {
641 val += a[[k, i]] * arp[k];
642 }
643 aarp[i] = val;
644 }
645 let q = self.apply_r_transpose_inverse(&aarp.view())?;
647
648 let pq = dot_product(&p.view(), &q.view());
649 if pq.abs() < 1e-300 {
650 break;
651 }
652
653 let alpha = rr / pq;
654 y = y + alpha * &p;
655 g = g - alpha * &q;
656
657 let rr_new = dot_product(&g.view(), &g.view());
658 let beta = rr_new / rr.max(1e-300);
659 rr = rr_new;
660
661 if rr.sqrt() < tolerance {
662 converged = true;
663 break;
664 }
665
666 p = &g + beta * &p;
667 }
668
669 let solution = self.apply_r_inverse(&y.view())?;
671
672 let mut ax = Array1::zeros(m);
674 for i in 0..m {
675 let mut val = 0.0f64;
676 for j in 0..n.min(n_a) {
677 val += a[[i, j]] * solution[j];
678 }
679 ax[i] = val;
680 }
681 let residual = (&ax - b).mapv(|v: f64| v * v).sum().sqrt();
682 let b_norm = b.mapv(|v: f64| v * v).sum().sqrt().max(1e-300);
683 let relative_residual = residual / b_norm;
684
685 Ok(RandomizedPrecondResult {
686 solution,
687 relative_residual,
688 iterations,
689 converged,
690 preconditioned_cond_estimate: Some(self.cond_estimate),
691 })
692 }
693
694 fn apply_r_transpose_inverse(&self, v: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
696 let n = self.n;
697 if v.len() != n {
698 return Err(LinalgError::DimensionError(format!(
699 "apply_r_transpose_inverse: v has {} elements but n={}",
700 v.len(),
701 n
702 )));
703 }
704
705 let mut x = Array1::zeros(n);
706 for i in 0..n {
708 let mut sum = v[i];
709 for j in 0..i {
710 sum -= self.r_factor[[j, i]] * x[j];
711 }
712 let diag = self.r_factor[[i, i]];
713 if diag.abs() < 1e-300 {
714 return Err(LinalgError::SingularMatrixError(format!(
715 "Blendenpik R^T is singular at index {i}"
716 )));
717 }
718 x[i] = sum / diag;
719 }
720
721 Ok(x)
722 }
723}
724
725#[derive(Debug, Clone)]
743pub struct LSRNPreconditioner {
744 pub v_factor: Array2<f64>,
746 pub sigma_inv: Array1<f64>,
748 pub n: usize,
750 pub rank: usize,
752 pub sketch_dim: usize,
754}
755
756impl LSRNPreconditioner {
757 pub fn new(
766 a: &ArrayView2<f64>,
767 oversampling: Option<f64>,
768 rcond: Option<f64>,
769 seed: Option<u64>,
770 ) -> LinalgResult<Self> {
771 let (m, n) = a.dim();
772 if m < n {
773 return Err(LinalgError::ShapeError(format!(
774 "LSRN requires m ≥ n, got {}×{}",
775 m, n
776 )));
777 }
778
779 let alpha = oversampling.unwrap_or(2.0).max(1.0);
780 let k = ((alpha * n as f64) as usize).max(n + 1).min(m);
781 let threshold = rcond.unwrap_or(1e-12);
782
783 let s_matrix = gaussian_random_matrix(k, m, seed)?;
785
786 let mut b_sketch = Array2::zeros((k, n));
788 let scale = 1.0 / (k as f64).sqrt();
789 for i in 0..k {
790 for j in 0..n {
791 let mut val = 0.0f64;
792 for l in 0..m {
793 val += s_matrix[[i, l]] * a[[l, j]];
794 }
795 b_sketch[[i, j]] = scale * val;
796 }
797 }
798
799 let (_, sigma, vt) = crate::decomposition::svd(&b_sketch.view(), true, None)?;
801
802 let sigma_max = sigma[0].max(1e-300);
803 let tol = threshold * sigma_max;
804
805 let rank = sigma.iter().filter(|&&s| s > tol).count().max(1);
807 let rank = rank.min(n);
808
809 let v_factor = vt
812 .slice(scirs2_core::ndarray::s![..rank, ..])
813 .t()
814 .to_owned();
815 let sigma_inv: Array1<f64> =
816 sigma
817 .slice(scirs2_core::ndarray::s![..rank])
818 .mapv(|s| if s > tol { 1.0 / s } else { 0.0 });
819
820 Ok(Self {
821 v_factor,
822 sigma_inv,
823 n,
824 rank,
825 sketch_dim: k,
826 })
827 }
828
829 pub fn apply_n(&self, x: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
831 if x.len() != self.rank {
832 return Err(LinalgError::DimensionError(format!(
833 "LSRN::apply_n: x has {} elements but rank={}",
834 x.len(),
835 self.rank
836 )));
837 }
838
839 let mut scaled = Array1::zeros(self.rank);
841 for i in 0..self.rank {
842 scaled[i] = self.sigma_inv[i] * x[i];
843 }
844
845 let mut result = Array1::zeros(self.n);
846 for i in 0..self.n {
847 let mut val = 0.0f64;
848 for j in 0..self.rank {
849 val += self.v_factor[[i, j]] * scaled[j];
850 }
851 result[i] = val;
852 }
853
854 Ok(result)
855 }
856
857 pub fn apply_n_transpose(&self, x: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
859 if x.len() != self.n {
860 return Err(LinalgError::DimensionError(format!(
861 "LSRN::apply_n_transpose: x has {} elements but n={}",
862 x.len(),
863 self.n
864 )));
865 }
866
867 let mut vt_x = Array1::zeros(self.rank);
869 for i in 0..self.rank {
870 let mut val = 0.0f64;
871 for j in 0..self.n {
872 val += self.v_factor[[j, i]] * x[j];
873 }
874 vt_x[i] = self.sigma_inv[i] * val;
875 }
876
877 Ok(vt_x)
878 }
879
880 pub fn solve(
884 &self,
885 a: &ArrayView2<f64>,
886 b: &ArrayView1<f64>,
887 max_iter: Option<usize>,
888 tol: Option<f64>,
889 ) -> LinalgResult<RandomizedPrecondResult> {
890 let (m, n) = a.dim();
891 if b.len() != m {
892 return Err(LinalgError::DimensionError(format!(
893 "LSRN::solve: A has {} rows but b has {} elements",
894 m,
895 b.len()
896 )));
897 }
898
899 let max_it = max_iter.unwrap_or(300);
900 let tolerance = tol.unwrap_or(1e-10);
901 let rank = self.rank;
902
903 let mut atb = Array1::zeros(n);
905 for i in 0..n {
906 let mut val = 0.0f64;
907 for k in 0..m {
908 val += a[[k, i]] * b[k];
909 }
910 atb[i] = val;
911 }
912
913 let nt_atb = self.apply_n_transpose(&atb.view())?;
915
916 let mut y = Array1::zeros(rank);
918 let mut g = nt_atb.clone();
919 let mut p = g.clone();
920 let mut rr = dot_product(&g.view(), &g.view());
921
922 let mut converged = false;
923 let mut iterations = 0;
924
925 for iter in 0..max_it {
926 iterations = iter + 1;
927
928 let np = self.apply_n(&p.view())?;
930 let mut anp = Array1::zeros(m);
932 for i in 0..m {
933 let mut val = 0.0f64;
934 for j in 0..n.min(self.n) {
935 val += a[[i, j]] * np[j];
936 }
937 anp[i] = val;
938 }
939 let mut at_anp = Array1::zeros(n);
941 for i in 0..n {
942 let mut val = 0.0f64;
943 for k in 0..m {
944 val += a[[k, i]] * anp[k];
945 }
946 at_anp[i] = val;
947 }
948 let q = self.apply_n_transpose(&at_anp.view())?;
950
951 let pq = dot_product(&p.view(), &q.view());
952 if pq.abs() < 1e-300 {
953 break;
954 }
955
956 let alpha = rr / pq;
957 y = y + alpha * &p;
958 g = g - alpha * &q;
959
960 let rr_new = dot_product(&g.view(), &g.view());
961 let beta = rr_new / rr.max(1e-300);
962 rr = rr_new;
963
964 if rr.sqrt() < tolerance {
965 converged = true;
966 break;
967 }
968
969 p = &g + beta * &p;
970 }
971
972 let solution = self.apply_n(&y.view())?;
974
975 let mut ax = Array1::zeros(m);
977 for i in 0..m {
978 let mut val = 0.0f64;
979 for j in 0..n.min(self.n) {
980 val += a[[i, j]] * solution[j];
981 }
982 ax[i] = val;
983 }
984 let residual = (&ax - b).mapv(|v: f64| v * v).sum().sqrt();
985 let b_norm = b.mapv(|v: f64| v * v).sum().sqrt().max(1e-300);
986 let relative_residual = residual / b_norm;
987
988 Ok(RandomizedPrecondResult {
989 solution,
990 relative_residual,
991 iterations,
992 converged,
993 preconditioned_cond_estimate: None,
994 })
995 }
996}
997
998#[derive(Debug, Clone)]
1013pub struct IterativeRefinementLS {
1014 pub max_refinement_steps: usize,
1016 pub inner_tol: f64,
1018 pub outer_tol: f64,
1020 pub use_blendenpik: bool,
1022 pub oversampling: f64,
1024 pub seed: Option<u64>,
1026}
1027
1028impl IterativeRefinementLS {
1029 pub fn new() -> Self {
1031 Self {
1032 max_refinement_steps: 3,
1033 inner_tol: 1e-8,
1034 outer_tol: 1e-12,
1035 use_blendenpik: true,
1036 oversampling: 2.0,
1037 seed: None,
1038 }
1039 }
1040
1041 pub fn with_max_steps(mut self, steps: usize) -> Self {
1043 self.max_refinement_steps = steps;
1044 self
1045 }
1046
1047 pub fn with_inner_tol(mut self, tol: f64) -> Self {
1049 self.inner_tol = tol;
1050 self
1051 }
1052
1053 pub fn with_outer_tol(mut self, tol: f64) -> Self {
1055 self.outer_tol = tol;
1056 self
1057 }
1058
1059 pub fn with_lsrn(mut self) -> Self {
1061 self.use_blendenpik = false;
1062 self
1063 }
1064
1065 pub fn with_oversampling(mut self, alpha: f64) -> Self {
1067 self.oversampling = alpha;
1068 self
1069 }
1070
1071 pub fn with_seed(mut self, seed: u64) -> Self {
1073 self.seed = Some(seed);
1074 self
1075 }
1076
1077 pub fn solve(
1079 &self,
1080 a: &ArrayView2<f64>,
1081 b: &ArrayView1<f64>,
1082 ) -> LinalgResult<RandomizedPrecondResult> {
1083 let (m, n) = a.dim();
1084 if b.len() != m {
1085 return Err(LinalgError::DimensionError(format!(
1086 "IterativeRefinementLS: A has {} rows but b has {} elements",
1087 m,
1088 b.len()
1089 )));
1090 }
1091
1092 let mut result = if self.use_blendenpik {
1094 let precond =
1095 BlendenpikPreconditioner::new(a, Some(self.oversampling), true, self.seed)?;
1096 precond.solve(a, b, Some(200), Some(self.inner_tol))?
1097 } else {
1098 let precond = LSRNPreconditioner::new(a, Some(self.oversampling), None, self.seed)?;
1099 precond.solve(a, b, Some(300), Some(self.inner_tol))?
1100 };
1101
1102 let mut x = result.solution.clone();
1103 let mut total_iters = result.iterations;
1104
1105 for step in 0..self.max_refinement_steps {
1107 let rel_res = result.relative_residual;
1108 if rel_res < self.outer_tol {
1109 result.converged = true;
1110 break;
1111 }
1112
1113 let mut r = Array1::zeros(m);
1115 for i in 0..m {
1116 let mut ax_i = 0.0f64;
1117 for j in 0..n {
1118 ax_i += a[[i, j]] * x[j];
1119 }
1120 r[i] = b[i] - ax_i;
1121 }
1122
1123 let correction_result = if self.use_blendenpik {
1125 let new_seed = self.seed.map(|s| s + step as u64 + 1);
1127 let precond =
1128 BlendenpikPreconditioner::new(a, Some(self.oversampling), true, new_seed)?;
1129 precond.solve(a, &r.view(), Some(100), Some(self.inner_tol * 0.1))?
1130 } else {
1131 let new_seed = self.seed.map(|s| s + step as u64 + 1);
1132 let precond = LSRNPreconditioner::new(a, Some(self.oversampling), None, new_seed)?;
1133 precond.solve(a, &r.view(), Some(150), Some(self.inner_tol * 0.1))?
1134 };
1135
1136 x += &correction_result.solution;
1137 total_iters += correction_result.iterations;
1138
1139 let mut ax = Array1::zeros(m);
1141 for i in 0..m {
1142 let mut val = 0.0f64;
1143 for j in 0..n {
1144 val += a[[i, j]] * x[j];
1145 }
1146 ax[i] = val;
1147 }
1148 let res_norm = (&ax - b).mapv(|v: f64| v * v).sum().sqrt();
1149 let b_norm = b.mapv(|v: f64| v * v).sum().sqrt().max(1e-300);
1150 result.relative_residual = res_norm / b_norm;
1151 }
1152
1153 result.solution = x;
1154 result.iterations = total_iters;
1155
1156 Ok(result)
1157 }
1158}
1159
1160fn dot_product(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> f64 {
1166 a.iter().zip(b.iter()).map(|(ai, bi)| ai * bi).sum()
1167}
1168
1169fn gaussian_random_matrix(
1171 rows: usize,
1172 cols: usize,
1173 seed: Option<u64>,
1174) -> LinalgResult<Array2<f64>> {
1175 let mut rng = {
1176 let seed_val = seed.unwrap_or_else(|| {
1177 let mut tr = scirs2_core::random::rng();
1178 scirs2_core::random::RngExt::random::<u64>(&mut tr)
1179 });
1180 scirs2_core::random::seeded_rng(seed_val)
1181 };
1182
1183 let normal = Normal::new(0.0, 1.0).map_err(|e| {
1184 LinalgError::ComputationError(format!("Failed to create Normal distribution: {e}"))
1185 })?;
1186
1187 let mut matrix = Array2::zeros((rows, cols));
1188 for i in 0..rows {
1189 for j in 0..cols {
1190 matrix[[i, j]] = normal.sample(&mut rng);
1191 }
1192 }
1193
1194 Ok(matrix)
1195}
1196
1197#[cfg(test)]
1202mod tests {
1203 use super::*;
1204 use scirs2_core::ndarray::array;
1205
1206 #[test]
1207 fn test_sparse_sign_dimensions() {
1208 let ss = SparseSign::new(20, 10, Some(3), Some(42)).expect("SparseSign::new failed");
1209 assert_eq!(ss.rows, 20);
1210 assert_eq!(ss.cols, 10);
1211 assert_eq!(ss.sparsity, 3);
1212
1213 let x = Array1::ones(10);
1214 let y = ss.apply(&x.view()).expect("apply failed");
1215 assert_eq!(y.len(), 20);
1216 }
1217
1218 #[test]
1219 fn test_sparse_sign_linearity() {
1220 let ss = SparseSign::new(30, 15, Some(4), Some(7)).expect("SparseSign::new failed");
1221 let x1 = Array1::from_vec((0..15).map(|i| i as f64).collect());
1222 let x2 = Array1::from_vec((0..15).map(|i| (i + 1) as f64).collect());
1223
1224 let y1 = ss.apply(&x1.view()).expect("apply x1 failed");
1225 let y2 = ss.apply(&x2.view()).expect("apply x2 failed");
1226 let y12 = ss.apply(&(&x1 + &x2).view()).expect("apply x1+x2 failed");
1227
1228 for i in 0..30 {
1229 assert!((y12[i] - y1[i] - y2[i]).abs() < 1e-12);
1230 }
1231 }
1232
1233 #[test]
1234 fn test_srht_dimensions() {
1235 let srht = SubsampledRandomizedHadamard::new(64, 16, Some(99)).expect("SRHT::new failed");
1236 let x = Array1::ones(64);
1237 let y = srht.apply(&x.view()).expect("SRHT apply failed");
1238 assert_eq!(y.len(), 16);
1239 }
1240
1241 #[test]
1242 fn test_blendenpik_overdetermined() {
1243 let m = 20;
1245 let n = 5;
1246 let mut a = Array2::zeros((m, n));
1247 for i in 0..m {
1248 for j in 0..n {
1249 a[[i, j]] = ((i + j + 1) as f64).cos() + if i == j { 2.0 } else { 0.0 };
1250 }
1251 }
1252 let x_true = array![1.0, -1.0, 2.0, 0.5, -0.5];
1253 let mut b = Array1::zeros(m);
1254 for i in 0..m {
1255 for j in 0..n {
1256 b[i] += a[[i, j]] * x_true[j];
1257 }
1258 }
1259
1260 let precond = BlendenpikPreconditioner::new(&a.view(), Some(2.0), false, Some(1))
1261 .expect("Blendenpik::new failed");
1262 let result = precond
1263 .solve(&a.view(), &b.view(), Some(100), Some(1e-8))
1264 .expect("Blendenpik::solve failed");
1265
1266 assert!(
1267 result.relative_residual < 1e-4,
1268 "Blendenpik residual too large: {}",
1269 result.relative_residual
1270 );
1271 }
1272
1273 #[test]
1274 fn test_lsrn_overdetermined() {
1275 let m = 30;
1276 let n = 6;
1277 let mut a = Array2::zeros((m, n));
1278 for i in 0..m {
1279 for j in 0..n {
1280 a[[i, j]] = 0.5 * ((i * n + j) as f64 / (m * n) as f64);
1281 if i == j {
1282 a[[i, j]] += 3.0;
1283 }
1284 }
1285 }
1286 let x_true = array![1.0, 2.0, -1.0, 0.5, -2.0, 0.0];
1287 let mut b = Array1::zeros(m);
1288 for i in 0..m {
1289 for j in 0..n {
1290 b[i] += a[[i, j]] * x_true[j];
1291 }
1292 }
1293
1294 let precond = LSRNPreconditioner::new(&a.view(), Some(2.0), Some(1e-12), Some(42))
1295 .expect("LSRN::new failed");
1296 let result = precond
1297 .solve(&a.view(), &b.view(), Some(200), Some(1e-8))
1298 .expect("LSRN::solve failed");
1299
1300 assert!(
1301 result.relative_residual < 1e-3,
1302 "LSRN residual too large: {}",
1303 result.relative_residual
1304 );
1305 }
1306
1307 #[test]
1308 fn test_iterative_refinement_ls() {
1309 let m = 25;
1310 let n = 5;
1311 let mut a = Array2::zeros((m, n));
1312 for i in 0..m {
1313 for j in 0..n {
1314 a[[i, j]] = (i as f64 * 0.1 + j as f64 * 0.3 + 1.0).sin();
1315 if i == j && j < n {
1316 a[[i, j]] += 2.0;
1317 }
1318 }
1319 }
1320 let x_true = Array1::from_vec(vec![1.0, -1.0, 0.5, -0.5, 2.0]);
1321 let mut b = Array1::zeros(m);
1322 for i in 0..m {
1323 for j in 0..n {
1324 b[i] += a[[i, j]] * x_true[j];
1325 }
1326 }
1327
1328 let solver = IterativeRefinementLS::new()
1329 .with_max_steps(2)
1330 .with_outer_tol(1e-6)
1331 .with_seed(123);
1332
1333 let result = solver
1334 .solve(&a.view(), &b.view())
1335 .expect("IterativeRefinementLS::solve failed");
1336
1337 assert!(
1338 result.relative_residual < 1e-3,
1339 "IterativeRefinementLS residual too large: {}",
1340 result.relative_residual
1341 );
1342 }
1343}