1pub fn dot(x: &[f64], y: &[f64]) -> f64 {
15 x.iter().zip(y.iter()).map(|(a, b)| a * b).sum()
16}
17
18pub fn axpy(alpha: f64, x: &[f64], y: &[f64]) -> Vec<f64> {
20 y.iter()
21 .zip(x.iter())
22 .map(|(yi, xi)| yi + alpha * xi)
23 .collect()
24}
25
26pub fn norm2(x: &[f64]) -> f64 {
28 dot(x, x).sqrt()
29}
30
31pub fn scale_vec(x: &[f64], s: f64) -> Vec<f64> {
33 x.iter().map(|v| v * s).collect()
34}
35
36pub struct SparseTriplet {
42 pub rows: Vec<usize>,
44 pub cols: Vec<usize>,
46 pub vals: Vec<f64>,
48}
49
50impl SparseTriplet {
51 pub fn new() -> Self {
53 Self {
54 rows: Vec::new(),
55 cols: Vec::new(),
56 vals: Vec::new(),
57 }
58 }
59
60 pub fn add(&mut self, row: usize, col: usize, val: f64) {
62 self.rows.push(row);
63 self.cols.push(col);
64 self.vals.push(val);
65 }
66
67 pub fn to_csr(&self, n_rows: usize, n_cols: usize) -> CsrMatrix {
69 let mut order: Vec<usize> = (0..self.rows.len()).collect();
71 order.sort_by_key(|&i| (self.rows[i], self.cols[i]));
72
73 let mut entries: Vec<(usize, usize, f64)> = Vec::new();
75 for &i in &order {
76 let r = self.rows[i];
77 let c = self.cols[i];
78 let v = self.vals[i];
79 if let Some(last) = entries.last_mut()
80 && last.0 == r
81 && last.1 == c
82 {
83 last.2 += v;
84 continue;
85 }
86 entries.push((r, c, v));
87 }
88
89 let nnz = entries.len();
91 let mut row_ptr = vec![0usize; n_rows + 1];
92 let mut col_idx = Vec::with_capacity(nnz);
93 let mut values = Vec::with_capacity(nnz);
94
95 for &(r, c, v) in &entries {
96 row_ptr[r + 1] += 1;
97 col_idx.push(c);
98 values.push(v);
99 }
100 for i in 0..n_rows {
101 row_ptr[i + 1] += row_ptr[i];
102 }
103
104 CsrMatrix {
105 n_rows,
106 n_cols,
107 row_ptr,
108 col_idx,
109 values,
110 }
111 }
112}
113
114impl Default for SparseTriplet {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120pub struct CsrMatrix {
126 pub n_rows: usize,
128 pub n_cols: usize,
130 pub row_ptr: Vec<usize>,
132 pub col_idx: Vec<usize>,
134 pub values: Vec<f64>,
136}
137
138impl CsrMatrix {
139 pub fn new(n_rows: usize, n_cols: usize) -> Self {
141 Self {
142 n_rows,
143 n_cols,
144 row_ptr: vec![0; n_rows + 1],
145 col_idx: Vec::new(),
146 values: Vec::new(),
147 }
148 }
149
150 pub fn from_dense(m: &[Vec<f64>]) -> Self {
152 let n_rows = m.len();
153 let n_cols = if n_rows > 0 { m[0].len() } else { 0 };
154 let mut row_ptr = vec![0usize; n_rows + 1];
155 let mut col_idx = Vec::new();
156 let mut values = Vec::new();
157 for (r, row) in m.iter().enumerate() {
158 for (c, &v) in row.iter().enumerate() {
159 if v != 0.0 {
160 col_idx.push(c);
161 values.push(v);
162 }
163 }
164 row_ptr[r + 1] = col_idx.len();
165 }
166 Self {
167 n_rows,
168 n_cols,
169 row_ptr,
170 col_idx,
171 values,
172 }
173 }
174
175 pub fn nnz(&self) -> usize {
177 self.values.len()
178 }
179
180 pub fn spmv(&self, x: &[f64]) -> Vec<f64> {
182 let mut y = vec![0.0f64; self.n_rows];
183 for (r, yr) in y.iter_mut().enumerate() {
184 let start = self.row_ptr[r];
185 let end = self.row_ptr[r + 1];
186 let mut sum = 0.0;
187 for k in start..end {
188 sum += self.values[k] * x[self.col_idx[k]];
189 }
190 *yr = sum;
191 }
192 y
193 }
194
195 pub fn get(&self, row: usize, col: usize) -> f64 {
197 let start = self.row_ptr[row];
198 let end = self.row_ptr[row + 1];
199 for k in start..end {
200 if self.col_idx[k] == col {
201 return self.values[k];
202 }
203 }
204 0.0
205 }
206
207 pub fn transpose(&self) -> CsrMatrix {
209 let mut row_ptr = vec![0usize; self.n_cols + 1];
211 for &c in &self.col_idx {
212 row_ptr[c + 1] += 1;
213 }
214 for i in 0..self.n_cols {
215 row_ptr[i + 1] += row_ptr[i];
216 }
217
218 let nnz = self.values.len();
219 let mut col_idx = vec![0usize; nnz];
220 let mut values = vec![0.0f64; nnz];
221 let mut pos = row_ptr[..self.n_cols].to_vec();
222
223 for r in 0..self.n_rows {
224 let start = self.row_ptr[r];
225 let end = self.row_ptr[r + 1];
226 for k in start..end {
227 let c = self.col_idx[k];
228 let dest = pos[c];
229 col_idx[dest] = r;
230 values[dest] = self.values[k];
231 pos[c] += 1;
232 }
233 }
234
235 CsrMatrix {
236 n_rows: self.n_cols,
237 n_cols: self.n_rows,
238 row_ptr,
239 col_idx,
240 values,
241 }
242 }
243
244 pub fn add(&self, other: &CsrMatrix) -> CsrMatrix {
246 assert_eq!(self.n_rows, other.n_rows);
247 assert_eq!(self.n_cols, other.n_cols);
248 let mut trip = SparseTriplet::new();
250 for r in 0..self.n_rows {
251 for k in self.row_ptr[r]..self.row_ptr[r + 1] {
252 trip.add(r, self.col_idx[k], self.values[k]);
253 }
254 for k in other.row_ptr[r]..other.row_ptr[r + 1] {
255 trip.add(r, other.col_idx[k], other.values[k]);
256 }
257 }
258 trip.to_csr(self.n_rows, self.n_cols)
259 }
260
261 pub fn scale(&self, s: f64) -> CsrMatrix {
263 CsrMatrix {
264 n_rows: self.n_rows,
265 n_cols: self.n_cols,
266 row_ptr: self.row_ptr.clone(),
267 col_idx: self.col_idx.clone(),
268 values: self.values.iter().map(|v| v * s).collect(),
269 }
270 }
271}
272
273pub struct EllMatrix {
279 pub n_rows: usize,
281 pub n_cols: usize,
283 pub max_nnz_per_row: usize,
285 pub col_idx: Vec<usize>,
287 pub values: Vec<f64>,
289}
290
291impl EllMatrix {
292 pub fn from_csr(csr: &CsrMatrix) -> Self {
294 let n_rows = csr.n_rows;
295 let n_cols = csr.n_cols;
296 let max_nnz_per_row = (0..n_rows)
297 .map(|r| csr.row_ptr[r + 1] - csr.row_ptr[r])
298 .max()
299 .unwrap_or(0);
300
301 let size = n_rows * max_nnz_per_row;
302 let mut col_idx = vec![0usize; size];
303 let mut values = vec![0.0f64; size];
304
305 for r in 0..n_rows {
306 let start = csr.row_ptr[r];
307 let end = csr.row_ptr[r + 1];
308 for (j, k) in (start..end).enumerate() {
309 col_idx[r * max_nnz_per_row + j] = csr.col_idx[k];
310 values[r * max_nnz_per_row + j] = csr.values[k];
311 }
312 }
313
314 Self {
315 n_rows,
316 n_cols,
317 max_nnz_per_row,
318 col_idx,
319 values,
320 }
321 }
322
323 pub fn spmv(&self, x: &[f64]) -> Vec<f64> {
325 let mut y = vec![0.0f64; self.n_rows];
326 for (r, yr) in y.iter_mut().enumerate() {
327 let mut sum = 0.0;
328 for j in 0..self.max_nnz_per_row {
329 let v = self.values[r * self.max_nnz_per_row + j];
330 if v != 0.0 {
331 let c = self.col_idx[r * self.max_nnz_per_row + j];
332 sum += v * x[c];
333 }
334 }
335 *yr = sum;
336 }
337 y
338 }
339}
340
341pub struct HybridMatrix {
347 pub ell: EllMatrix,
349 pub coo_row: Vec<usize>,
351 pub coo_col: Vec<usize>,
353 pub coo_val: Vec<f64>,
355}
356
357impl HybridMatrix {
358 pub fn spmv(&self, x: &[f64]) -> Vec<f64> {
360 let mut y = self.ell.spmv(x);
361 for k in 0..self.coo_val.len() {
362 y[self.coo_row[k]] += self.coo_val[k] * x[self.coo_col[k]];
363 }
364 y
365 }
366}
367
368pub struct BlockCsrMatrix {
374 pub block_size: usize,
376 pub n_block_rows: usize,
378 pub n_block_cols: usize,
380 pub row_ptr: Vec<usize>,
382 pub col_idx: Vec<usize>,
384 pub blocks: Vec<Vec<f64>>,
386}
387
388impl BlockCsrMatrix {
389 pub fn spmv_block(&self, x: &[f64]) -> Vec<f64> {
391 let bs = self.block_size;
392 let n = self.n_block_rows * bs;
393 let mut y = vec![0.0f64; n];
394 for br in 0..self.n_block_rows {
395 let row_start = self.row_ptr[br];
396 let row_end = self.row_ptr[br + 1];
397 for k in row_start..row_end {
398 let bc = self.col_idx[k];
399 let blk = &self.blocks[k];
400 for i in 0..bs {
402 let mut s = 0.0;
403 for j in 0..bs {
404 s += blk[i * bs + j] * x[bc * bs + j];
405 }
406 y[br * bs + i] += s;
407 }
408 }
409 }
410 y
411 }
412
413 pub fn to_csr(&self) -> CsrMatrix {
415 let bs = self.block_size;
416 let n_rows = self.n_block_rows * bs;
417 let n_cols = self.n_block_cols * bs;
418 let mut trip = SparseTriplet::new();
419 for br in 0..self.n_block_rows {
420 for k in self.row_ptr[br]..self.row_ptr[br + 1] {
421 let bc = self.col_idx[k];
422 let blk = &self.blocks[k];
423 for i in 0..bs {
424 for j in 0..bs {
425 let v = blk[i * bs + j];
426 if v != 0.0 {
427 trip.add(br * bs + i, bc * bs + j, v);
428 }
429 }
430 }
431 }
432 }
433 trip.to_csr(n_rows, n_cols)
434 }
435}
436
437pub fn cg_solve(
445 a: &CsrMatrix,
446 b: &[f64],
447 x0: &[f64],
448 max_iter: usize,
449 tol: f64,
450) -> (Vec<f64>, usize) {
451 let n = b.len();
452 let mut x = x0.to_vec();
453 let ax = a.spmv(&x);
455 let mut r: Vec<f64> = (0..n).map(|i| b[i] - ax[i]).collect();
456 let mut p = r.clone();
457 let mut rs_old = dot(&r, &r);
458
459 for iter in 0..max_iter {
460 if rs_old.sqrt() < tol {
461 return (x, iter);
462 }
463 let ap = a.spmv(&p);
464 let alpha = rs_old / dot(&p, &ap);
465 x = axpy(alpha, &p, &x);
466 r = axpy(-alpha, &ap, &r);
467 let rs_new = dot(&r, &r);
468 let beta = rs_new / rs_old;
469 p = axpy(beta, &p, &r);
470 rs_old = rs_new;
471 }
472 (x, max_iter)
473}
474
475pub fn bicgstab_solve(
479 a: &CsrMatrix,
480 b: &[f64],
481 x0: &[f64],
482 max_iter: usize,
483 tol: f64,
484) -> (Vec<f64>, usize) {
485 let n = b.len();
486 let mut x = x0.to_vec();
487 let ax = a.spmv(&x);
488 let mut r: Vec<f64> = (0..n).map(|i| b[i] - ax[i]).collect();
489 let r_hat = r.clone();
490 let mut rho = 1.0_f64;
491 let mut alpha_s = 1.0_f64;
492 let mut omega = 1.0_f64;
493 let mut v = vec![0.0f64; n];
494 let mut p = vec![0.0f64; n];
495
496 for iter in 0..max_iter {
497 if norm2(&r) < tol {
498 return (x, iter);
499 }
500 let rho_new = dot(&r_hat, &r);
501 let beta = (rho_new / rho) * (alpha_s / omega);
502 p = axpy(beta, &p, &axpy(-beta * omega, &v, &r));
503 v = a.spmv(&p);
504 let denom = dot(&r_hat, &v);
505 if denom.abs() < 1e-300 {
506 return (x, iter);
507 }
508 alpha_s = rho_new / denom;
509 let s: Vec<f64> = axpy(-alpha_s, &v, &r);
510 if norm2(&s) < tol {
511 x = axpy(alpha_s, &p, &x);
512 return (x, iter + 1);
513 }
514 let t = a.spmv(&s);
515 let tt = dot(&t, &t);
516 omega = if tt.abs() < 1e-300 {
517 0.0
518 } else {
519 dot(&t, &s) / tt
520 };
521 x = axpy(omega, &s, &axpy(alpha_s, &p, &x));
522 r = axpy(-omega, &t, &s);
523 rho = rho_new;
524 }
525 (x, max_iter)
526}
527
528pub fn jacobi_preconditioned_cg(
532 a: &CsrMatrix,
533 b: &[f64],
534 max_iter: usize,
535 tol: f64,
536) -> (Vec<f64>, usize) {
537 let n = b.len();
538 let mut m_inv = vec![1.0f64; n];
540 for (r, m) in m_inv.iter_mut().enumerate() {
541 let d = a.get(r, r);
542 if d.abs() > 1e-300 {
543 *m = 1.0 / d;
544 }
545 }
546
547 let mut x = vec![0.0f64; n];
548 let ax = a.spmv(&x);
549 let mut r: Vec<f64> = b
550 .iter()
551 .zip(ax.iter())
552 .map(|(&bi, &axi)| bi - axi)
553 .collect();
554 let z: Vec<f64> = (0..n).map(|i| m_inv[i] * r[i]).collect();
556 let mut p = z.clone();
557 let mut rz_old = dot(&r, &z);
558
559 for iter in 0..max_iter {
560 if norm2(&r) < tol {
561 return (x, iter);
562 }
563 let ap = a.spmv(&p);
564 let alpha = rz_old / dot(&p, &ap);
565 x = axpy(alpha, &p, &x);
566 r = axpy(-alpha, &ap, &r);
567 let z_new: Vec<f64> = (0..n).map(|i| m_inv[i] * r[i]).collect();
568 let rz_new = dot(&r, &z_new);
569 let beta = rz_new / rz_old;
570 p = axpy(beta, &p, &z_new);
571 rz_old = rz_new;
572 }
573 (x, max_iter)
574}
575
576pub fn simulate_spmv_throughput(n: usize, nnz: usize) -> f64 {
584 let _ = n; let bytes_transferred = (nnz * 24) as f64;
588 let bandwidth_gb_s = 100.0_f64; let time_s = bytes_transferred / (bandwidth_gb_s * 1e9);
590 let flops = 2.0 * nnz as f64; flops / time_s / 1e9 }
593
594pub fn optimal_ell_row_width(nnz_distribution: &[usize]) -> usize {
598 if nnz_distribution.is_empty() {
599 return 0;
600 }
601 let mut sorted = nnz_distribution.to_vec();
602 sorted.sort_unstable();
603 let idx = (sorted.len() * 3) / 4; sorted[idx]
605}
606
607pub fn spmv_segmented(a: &CsrMatrix, x: &[f64]) -> Vec<f64> {
615 let mut y = vec![0.0_f64; a.n_rows];
616 for (r, yr) in y.iter_mut().enumerate() {
617 let start = a.row_ptr[r];
618 let end = a.row_ptr[r + 1];
619 let mut acc = 0.0_f64;
620 for k in start..end {
621 acc += a.values[k] * x[a.col_idx[k]];
622 }
623 *yr = acc;
624 }
625 y
626}
627
628pub fn assemble_1d_laplacian(n: usize) -> CsrMatrix {
634 let mut trip = SparseTriplet::new();
635 for i in 0..n {
636 trip.add(i, i, 2.0);
637 if i > 0 {
638 trip.add(i, i - 1, -1.0);
639 }
640 if i + 1 < n {
641 trip.add(i, i + 1, -1.0);
642 }
643 }
644 trip.to_csr(n, n)
645}
646
647pub fn csr_to_ell(csr: &CsrMatrix) -> EllMatrix {
653 EllMatrix::from_csr(csr)
654}
655
656pub fn extract_diagonal(a: &CsrMatrix) -> Vec<f64> {
662 let n = a.n_rows.min(a.n_cols);
663 let mut diag = vec![0.0_f64; n];
664 for (r, d) in diag.iter_mut().enumerate() {
665 *d = a.get(r, r);
666 }
667 diag
668}
669
670pub fn compute_nnz_per_row(a: &CsrMatrix) -> Vec<usize> {
676 (0..a.n_rows)
677 .map(|r| a.row_ptr[r + 1] - a.row_ptr[r])
678 .collect()
679}
680
681pub fn frobenius_norm(a: &CsrMatrix) -> f64 {
687 let sum_sq: f64 = a.values.iter().map(|v| v * v).sum();
688 sum_sq.sqrt()
689}
690
691pub fn sparse_lower_triangular_solve(l: &CsrMatrix, b: &[f64]) -> Vec<f64> {
700 let n = b.len();
701 let mut x = vec![0.0_f64; n];
702 for i in 0..n {
703 let mut sum = b[i];
704 let start = l.row_ptr[i];
705 let end = l.row_ptr[i + 1];
706 let mut diag = 1.0_f64;
707 for k in start..end {
708 let c = l.col_idx[k];
709 if c < i {
710 sum -= l.values[k] * x[c];
711 } else if c == i {
712 diag = l.values[k];
713 }
714 }
715 x[i] = sum / diag;
716 }
717 x
718}
719
720pub fn sparse_upper_triangular_solve(u: &CsrMatrix, b: &[f64]) -> Vec<f64> {
724 let n = b.len();
725 let mut x = vec![0.0_f64; n];
726 for i in (0..n).rev() {
727 let mut sum = b[i];
728 let start = u.row_ptr[i];
729 let end = u.row_ptr[i + 1];
730 let mut diag = 1.0_f64;
731 for k in start..end {
732 let c = u.col_idx[k];
733 if c > i {
734 sum -= u.values[k] * x[c];
735 } else if c == i {
736 diag = u.values[k];
737 }
738 }
739 x[i] = sum / diag;
740 }
741 x
742}
743
744#[cfg(test)]
749mod tests {
750 use super::*;
751
752 #[test]
753 fn test_csr_from_dense_nnz() {
754 let m = vec![
755 vec![1.0, 0.0, 2.0],
756 vec![0.0, 3.0, 0.0],
757 vec![4.0, 5.0, 6.0],
758 ];
759 let csr = CsrMatrix::from_dense(&m);
760 assert_eq!(csr.n_rows, 3);
761 assert_eq!(csr.n_cols, 3);
762 assert_eq!(csr.nnz(), 6);
763 }
764
765 #[test]
766 fn test_csr_spmv_identity() {
767 let m = vec![
769 vec![1.0, 0.0, 0.0],
770 vec![0.0, 1.0, 0.0],
771 vec![0.0, 0.0, 1.0],
772 ];
773 let csr = CsrMatrix::from_dense(&m);
774 let x = vec![3.0, 7.0, -2.0];
775 let y = csr.spmv(&x);
776 assert_eq!(y, x);
777 }
778
779 #[test]
780 fn test_csr_spmv_known_3x3() {
781 let m = vec![
783 vec![2.0, 1.0, 0.0],
784 vec![1.0, 3.0, 1.0],
785 vec![0.0, 1.0, 2.0],
786 ];
787 let csr = CsrMatrix::from_dense(&m);
788 let x = vec![1.0, 2.0, 3.0];
789 let y = csr.spmv(&x);
790 assert!((y[0] - 4.0).abs() < 1e-12);
792 assert!((y[1] - 10.0).abs() < 1e-12);
793 assert!((y[2] - 8.0).abs() < 1e-12);
794 }
795
796 #[test]
797 fn test_cg_solve_diagonal_spd() {
798 let m = vec![
800 vec![1.0, 0.0, 0.0, 0.0],
801 vec![0.0, 2.0, 0.0, 0.0],
802 vec![0.0, 0.0, 3.0, 0.0],
803 vec![0.0, 0.0, 0.0, 4.0],
804 ];
805 let a = CsrMatrix::from_dense(&m);
806 let b = vec![1.0, 2.0, 3.0, 4.0];
807 let x0 = vec![0.0; 4];
808 let (x, _iters) = cg_solve(&a, &b, &x0, 100, 1e-12);
809 for v in &x {
810 assert!((v - 1.0).abs() < 1e-10, "x value {v} not close to 1.0");
811 }
812 }
813
814 #[test]
815 fn test_sparse_triplet_to_csr_duplicate_sum() {
816 let mut trip = SparseTriplet::new();
817 trip.add(0, 0, 1.0);
818 trip.add(0, 0, 2.0); trip.add(1, 1, 5.0);
820 let csr = trip.to_csr(2, 2);
821 assert!((csr.get(0, 0) - 3.0).abs() < 1e-12);
822 assert!((csr.get(1, 1) - 5.0).abs() < 1e-12);
823 assert_eq!(csr.nnz(), 2);
824 }
825
826 #[test]
827 fn test_ell_spmv_matches_csr() {
828 let m = vec![
829 vec![2.0, 1.0, 0.0],
830 vec![1.0, 3.0, 1.0],
831 vec![0.0, 1.0, 2.0],
832 ];
833 let csr = CsrMatrix::from_dense(&m);
834 let ell = EllMatrix::from_csr(&csr);
835 let x = vec![1.0, -1.0, 2.0];
836 let y_csr = csr.spmv(&x);
837 let y_ell = ell.spmv(&x);
838 for (a, b) in y_csr.iter().zip(y_ell.iter()) {
839 assert!((a - b).abs() < 1e-12, "ELL mismatch: {a} vs {b}");
840 }
841 }
842
843 #[test]
844 fn test_csr_transpose() {
845 let m = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
847 let csr = CsrMatrix::from_dense(&m);
848 let at = csr.transpose();
849 assert_eq!(at.n_rows, 3);
850 assert_eq!(at.n_cols, 2);
851 assert!((at.get(0, 0) - 1.0).abs() < 1e-12);
852 assert!((at.get(0, 1) - 4.0).abs() < 1e-12);
853 assert!((at.get(1, 0) - 2.0).abs() < 1e-12);
854 assert!((at.get(1, 1) - 5.0).abs() < 1e-12);
855 assert!((at.get(2, 0) - 3.0).abs() < 1e-12);
856 assert!((at.get(2, 1) - 6.0).abs() < 1e-12);
857 }
858
859 #[test]
862 fn test_spmv_segmented_identity() {
863 let m = vec![
864 vec![1.0, 0.0, 0.0],
865 vec![0.0, 1.0, 0.0],
866 vec![0.0, 0.0, 1.0],
867 ];
868 let csr = CsrMatrix::from_dense(&m);
869 let x = vec![3.0, 7.0, -2.0];
870 let y = spmv_segmented(&csr, &x);
871 for (a, b) in y.iter().zip(x.iter()) {
872 assert!((a - b).abs() < 1e-12);
873 }
874 }
875
876 #[test]
877 fn test_spmv_segmented_matches_csr() {
878 let m = vec![
879 vec![2.0, 1.0, 0.0],
880 vec![1.0, 3.0, 1.0],
881 vec![0.0, 1.0, 2.0],
882 ];
883 let csr = CsrMatrix::from_dense(&m);
884 let x = vec![1.0, -1.0, 2.0];
885 let y_std = csr.spmv(&x);
886 let y_seg = spmv_segmented(&csr, &x);
887 for (a, b) in y_std.iter().zip(y_seg.iter()) {
888 assert!((a - b).abs() < 1e-12);
889 }
890 }
891
892 #[test]
895 fn test_assemble_1d_laplacian_3x3() {
896 let l = assemble_1d_laplacian(3);
897 assert!((l.get(0, 0) - 2.0).abs() < 1e-12);
899 assert!((l.get(0, 1) - (-1.0)).abs() < 1e-12);
900 assert!((l.get(0, 2)).abs() < 1e-12);
901 assert!((l.get(1, 0) - (-1.0)).abs() < 1e-12);
902 assert!((l.get(1, 1) - 2.0).abs() < 1e-12);
903 assert!((l.get(1, 2) - (-1.0)).abs() < 1e-12);
904 assert!((l.get(2, 2) - 2.0).abs() < 1e-12);
905 }
906
907 #[test]
908 fn test_assemble_1d_laplacian_spd() {
909 let n = 5;
911 let l = assemble_1d_laplacian(n);
912 for i in 0..n {
914 for j in 0..n {
915 assert!((l.get(i, j) - l.get(j, i)).abs() < 1e-12);
916 }
917 }
918 let b = vec![1.0; n];
920 let x0 = vec![0.0; n];
921 let (x, iters) = cg_solve(&l, &b, &x0, 200, 1e-10);
922 assert!(iters < 200);
923 let ax = l.spmv(&x);
925 for i in 0..n {
926 assert!((ax[i] - b[i]).abs() < 1e-8);
927 }
928 }
929
930 #[test]
933 fn test_csr_to_ell_spmv() {
934 let m = vec![
935 vec![5.0, 0.0, 1.0, 0.0],
936 vec![0.0, 3.0, 0.0, 2.0],
937 vec![1.0, 0.0, 4.0, 0.0],
938 vec![0.0, 0.0, 0.0, 6.0],
939 ];
940 let csr = CsrMatrix::from_dense(&m);
941 let ell = csr_to_ell(&csr);
942 let x = vec![1.0, 2.0, 3.0, 4.0];
943 let y_csr = csr.spmv(&x);
944 let y_ell = ell.spmv(&x);
945 for (a, b) in y_csr.iter().zip(y_ell.iter()) {
946 assert!((a - b).abs() < 1e-12, "mismatch: {a} vs {b}");
947 }
948 }
949
950 #[test]
951 fn test_csr_to_ell_max_nnz() {
952 let m = vec![
953 vec![1.0, 0.0, 0.0],
954 vec![1.0, 2.0, 3.0], vec![0.0, 0.0, 1.0],
956 ];
957 let csr = CsrMatrix::from_dense(&m);
958 let ell = csr_to_ell(&csr);
959 assert_eq!(ell.max_nnz_per_row, 3);
960 }
961
962 #[test]
965 fn test_block_csr_spmv_2x2() {
966 let bcsr = BlockCsrMatrix {
968 block_size: 2,
969 n_block_rows: 1,
970 n_block_cols: 1,
971 row_ptr: vec![0, 1],
972 col_idx: vec![0],
973 blocks: vec![vec![1.0, 2.0, 3.0, 4.0]],
974 };
975 let x = vec![1.0, 1.0];
976 let y = bcsr.spmv_block(&x);
977 assert!((y[0] - 3.0).abs() < 1e-12); assert!((y[1] - 7.0).abs() < 1e-12); }
980
981 #[test]
982 fn test_block_csr_to_csr_roundtrip() {
983 let bcsr = BlockCsrMatrix {
984 block_size: 2,
985 n_block_rows: 2,
986 n_block_cols: 2,
987 row_ptr: vec![0, 1, 2],
988 col_idx: vec![0, 1],
989 blocks: vec![vec![1.0, 2.0, 3.0, 4.0], vec![5.0, 6.0, 7.0, 8.0]],
990 };
991 let csr = bcsr.to_csr();
992 let x = vec![1.0, 1.0, 1.0, 1.0];
993 let y_block = bcsr.spmv_block(&x);
994 let y_csr = csr.spmv(&x);
995 for (a, b) in y_block.iter().zip(y_csr.iter()) {
996 assert!((a - b).abs() < 1e-12);
997 }
998 }
999
1000 #[test]
1003 fn test_lower_tri_solve_identity() {
1004 let m = vec![
1005 vec![1.0, 0.0, 0.0],
1006 vec![0.0, 1.0, 0.0],
1007 vec![0.0, 0.0, 1.0],
1008 ];
1009 let l = CsrMatrix::from_dense(&m);
1010 let b = vec![3.0, 7.0, -2.0];
1011 let x = sparse_lower_triangular_solve(&l, &b);
1012 for (a, bv) in x.iter().zip(b.iter()) {
1013 assert!((a - bv).abs() < 1e-12);
1014 }
1015 }
1016
1017 #[test]
1018 fn test_lower_tri_solve_3x3() {
1019 let m = vec![
1021 vec![2.0, 0.0, 0.0],
1022 vec![1.0, 3.0, 0.0],
1023 vec![4.0, 2.0, 5.0],
1024 ];
1025 let l = CsrMatrix::from_dense(&m);
1026 let b = vec![4.0, 7.0, 26.0];
1027 let x = sparse_lower_triangular_solve(&l, &b);
1028 assert!((x[0] - 2.0).abs() < 1e-10);
1032 assert!((x[1] - 5.0 / 3.0).abs() < 1e-10);
1033 let expected_x2 = (26.0 - 8.0 - 10.0 / 3.0) / 5.0;
1034 assert!((x[2] - expected_x2).abs() < 1e-10);
1035 }
1036
1037 #[test]
1038 fn test_lower_tri_solve_verify_lx_eq_b() {
1039 let m = vec![
1040 vec![3.0, 0.0, 0.0, 0.0],
1041 vec![1.0, 2.0, 0.0, 0.0],
1042 vec![0.0, 4.0, 5.0, 0.0],
1043 vec![2.0, 0.0, 1.0, 6.0],
1044 ];
1045 let l = CsrMatrix::from_dense(&m);
1046 let b = vec![9.0, 8.0, 22.0, 29.0];
1047 let x = sparse_lower_triangular_solve(&l, &b);
1048 let lx = l.spmv(&x);
1050 for i in 0..4 {
1051 assert!(
1052 (lx[i] - b[i]).abs() < 1e-10,
1053 "row {i}: {} vs {}",
1054 lx[i],
1055 b[i]
1056 );
1057 }
1058 }
1059
1060 #[test]
1063 fn test_upper_tri_solve_identity() {
1064 let m = vec![
1065 vec![1.0, 0.0, 0.0],
1066 vec![0.0, 1.0, 0.0],
1067 vec![0.0, 0.0, 1.0],
1068 ];
1069 let u = CsrMatrix::from_dense(&m);
1070 let b = vec![3.0, 7.0, -2.0];
1071 let x = sparse_upper_triangular_solve(&u, &b);
1072 for (a, bv) in x.iter().zip(b.iter()) {
1073 assert!((a - bv).abs() < 1e-12);
1074 }
1075 }
1076
1077 #[test]
1078 fn test_upper_tri_solve_verify_ux_eq_b() {
1079 let m = vec![
1081 vec![2.0, 1.0, 3.0],
1082 vec![0.0, 4.0, 2.0],
1083 vec![0.0, 0.0, 5.0],
1084 ];
1085 let u = CsrMatrix::from_dense(&m);
1086 let b = vec![13.0, 14.0, 10.0];
1087 let x = sparse_upper_triangular_solve(&u, &b);
1088 let ux = u.spmv(&x);
1089 for i in 0..3 {
1090 assert!(
1091 (ux[i] - b[i]).abs() < 1e-10,
1092 "row {i}: {} vs {}",
1093 ux[i],
1094 b[i]
1095 );
1096 }
1097 }
1098
1099 #[test]
1102 fn test_csr_add() {
1103 let m1 = vec![vec![1.0, 0.0], vec![0.0, 2.0]];
1104 let m2 = vec![vec![0.0, 3.0], vec![4.0, 0.0]];
1105 let a = CsrMatrix::from_dense(&m1);
1106 let b = CsrMatrix::from_dense(&m2);
1107 let c = a.add(&b);
1108 assert!((c.get(0, 0) - 1.0).abs() < 1e-12);
1109 assert!((c.get(0, 1) - 3.0).abs() < 1e-12);
1110 assert!((c.get(1, 0) - 4.0).abs() < 1e-12);
1111 assert!((c.get(1, 1) - 2.0).abs() < 1e-12);
1112 }
1113
1114 #[test]
1115 fn test_csr_scale() {
1116 let m = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
1117 let a = CsrMatrix::from_dense(&m);
1118 let b = a.scale(2.0);
1119 assert!((b.get(0, 0) - 2.0).abs() < 1e-12);
1120 assert!((b.get(0, 1) - 4.0).abs() < 1e-12);
1121 assert!((b.get(1, 0) - 6.0).abs() < 1e-12);
1122 assert!((b.get(1, 1) - 8.0).abs() < 1e-12);
1123 }
1124
1125 #[test]
1128 fn test_bicgstab_diagonal() {
1129 let m = vec![
1130 vec![2.0, 0.0, 0.0],
1131 vec![0.0, 3.0, 0.0],
1132 vec![0.0, 0.0, 4.0],
1133 ];
1134 let a = CsrMatrix::from_dense(&m);
1135 let b = vec![4.0, 9.0, 16.0];
1136 let x0 = vec![0.0; 3];
1137 let (x, _iters) = bicgstab_solve(&a, &b, &x0, 100, 1e-10);
1138 assert!((x[0] - 2.0).abs() < 1e-8);
1139 assert!((x[1] - 3.0).abs() < 1e-8);
1140 assert!((x[2] - 4.0).abs() < 1e-8);
1141 }
1142
1143 #[test]
1144 fn test_bicgstab_nonsymmetric() {
1145 let m = vec![
1147 vec![4.0, 1.0, 0.0],
1148 vec![0.0, 3.0, 1.0],
1149 vec![0.0, 0.0, 5.0],
1150 ];
1151 let a = CsrMatrix::from_dense(&m);
1152 let b = vec![5.0, 4.0, 5.0];
1153 let x0 = vec![0.0; 3];
1154 let (x, _iters) = bicgstab_solve(&a, &b, &x0, 200, 1e-10);
1155 let ax = a.spmv(&x);
1157 for i in 0..3 {
1158 assert!(
1159 (ax[i] - b[i]).abs() < 1e-6,
1160 "row {i}: {} vs {}",
1161 ax[i],
1162 b[i]
1163 );
1164 }
1165 }
1166
1167 #[test]
1170 fn test_jacobi_pcg_laplacian() {
1171 let n = 8;
1172 let a = assemble_1d_laplacian(n);
1173 let b: Vec<f64> = (0..n).map(|i| (i as f64 + 1.0).sin()).collect();
1174 let (x, iters) = jacobi_preconditioned_cg(&a, &b, 500, 1e-10);
1175 assert!(iters < 500, "PCG should converge, used {iters} iterations");
1176 let ax = a.spmv(&x);
1177 for i in 0..n {
1178 assert!(
1179 (ax[i] - b[i]).abs() < 1e-6,
1180 "row {i}: {} vs {}",
1181 ax[i],
1182 b[i]
1183 );
1184 }
1185 }
1186
1187 #[test]
1190 fn test_simulate_spmv_throughput() {
1191 let gflops = simulate_spmv_throughput(100, 1000);
1192 assert!(gflops > 0.0);
1193 let gflops2 = simulate_spmv_throughput(1000, 10000);
1195 assert!((gflops2 - gflops).abs() < 1.0); }
1197
1198 #[test]
1199 fn test_optimal_ell_row_width_empty() {
1200 assert_eq!(optimal_ell_row_width(&[]), 0);
1201 }
1202
1203 #[test]
1204 fn test_optimal_ell_row_width_uniform() {
1205 let dist = vec![5; 10];
1207 assert_eq!(optimal_ell_row_width(&dist), 5);
1208 }
1209
1210 #[test]
1211 fn test_optimal_ell_row_width_varied() {
1212 let dist = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1213 let w = optimal_ell_row_width(&dist);
1214 assert_eq!(w, 8);
1216 }
1217
1218 #[test]
1221 fn test_sparse_triplet_default() {
1222 let t = SparseTriplet::default();
1223 assert!(t.rows.is_empty());
1224 assert!(t.cols.is_empty());
1225 assert!(t.vals.is_empty());
1226 }
1227
1228 #[test]
1231 fn test_dot_product() {
1232 let x = vec![1.0, 2.0, 3.0];
1233 let y = vec![4.0, 5.0, 6.0];
1234 assert!((dot(&x, &y) - 32.0).abs() < 1e-12);
1235 }
1236
1237 #[test]
1238 fn test_axpy() {
1239 let x = vec![1.0, 2.0, 3.0];
1240 let y = vec![10.0, 20.0, 30.0];
1241 let z = axpy(2.0, &x, &y);
1242 assert_eq!(z, vec![12.0, 24.0, 36.0]);
1243 }
1244
1245 #[test]
1246 fn test_norm2() {
1247 let x = vec![3.0, 4.0];
1248 assert!((norm2(&x) - 5.0).abs() < 1e-12);
1249 }
1250
1251 #[test]
1252 fn test_scale_vec() {
1253 let x = vec![1.0, 2.0, 3.0];
1254 let s = scale_vec(&x, 3.0);
1255 assert_eq!(s, vec![3.0, 6.0, 9.0]);
1256 }
1257
1258 #[test]
1261 fn test_hybrid_spmv() {
1262 let m = vec![
1263 vec![1.0, 2.0, 0.0],
1264 vec![0.0, 3.0, 0.0],
1265 vec![0.0, 0.0, 4.0],
1266 ];
1267 let csr = CsrMatrix::from_dense(&m);
1268 let ell = EllMatrix::from_csr(&csr);
1269 let hybrid = HybridMatrix {
1270 ell,
1271 coo_row: vec![],
1272 coo_col: vec![],
1273 coo_val: vec![],
1274 };
1275 let x = vec![1.0, 1.0, 1.0];
1276 let y = hybrid.spmv(&x);
1277 assert!((y[0] - 3.0).abs() < 1e-12);
1278 assert!((y[1] - 3.0).abs() < 1e-12);
1279 assert!((y[2] - 4.0).abs() < 1e-12);
1280 }
1281
1282 #[test]
1283 fn test_hybrid_spmv_with_coo() {
1284 let m = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
1286 let csr = CsrMatrix::from_dense(&m);
1287 let ell = EllMatrix::from_csr(&csr);
1288 let hybrid = HybridMatrix {
1289 ell,
1290 coo_row: vec![0],
1291 coo_col: vec![1],
1292 coo_val: vec![5.0],
1293 };
1294 let x = vec![1.0, 2.0];
1295 let y = hybrid.spmv(&x);
1296 assert!((y[0] - 11.0).abs() < 1e-12); assert!((y[1] - 2.0).abs() < 1e-12);
1298 }
1299
1300 #[test]
1303 fn test_csr_empty() {
1304 let csr = CsrMatrix::new(3, 3);
1305 assert_eq!(csr.nnz(), 0);
1306 let y = csr.spmv(&[1.0, 2.0, 3.0]);
1307 assert_eq!(y, vec![0.0, 0.0, 0.0]);
1308 }
1309
1310 #[test]
1313 fn test_extract_diagonal() {
1314 let m = vec![
1315 vec![5.0, 1.0, 0.0],
1316 vec![0.0, 3.0, 2.0],
1317 vec![0.0, 0.0, 7.0],
1318 ];
1319 let csr = CsrMatrix::from_dense(&m);
1320 let diag = extract_diagonal(&csr);
1321 assert!((diag[0] - 5.0).abs() < 1e-12);
1322 assert!((diag[1] - 3.0).abs() < 1e-12);
1323 assert!((diag[2] - 7.0).abs() < 1e-12);
1324 }
1325
1326 #[test]
1327 fn test_extract_diagonal_missing() {
1328 let m = vec![vec![0.0, 1.0], vec![1.0, 0.0]];
1330 let csr = CsrMatrix::from_dense(&m);
1331 let diag = extract_diagonal(&csr);
1332 assert!((diag[0]).abs() < 1e-12);
1333 assert!((diag[1]).abs() < 1e-12);
1334 }
1335
1336 #[test]
1339 fn test_compute_nnz_per_row() {
1340 let m = vec![
1341 vec![1.0, 2.0, 0.0],
1342 vec![0.0, 3.0, 0.0],
1343 vec![4.0, 5.0, 6.0],
1344 ];
1345 let csr = CsrMatrix::from_dense(&m);
1346 let nnz = compute_nnz_per_row(&csr);
1347 assert_eq!(nnz, vec![2, 1, 3]);
1348 }
1349
1350 #[test]
1353 fn test_frobenius_norm() {
1354 let m = vec![
1356 vec![1.0, 0.0, 0.0],
1357 vec![0.0, 1.0, 0.0],
1358 vec![0.0, 0.0, 1.0],
1359 ];
1360 let csr = CsrMatrix::from_dense(&m);
1361 let f = frobenius_norm(&csr);
1362 assert!((f - 3.0_f64.sqrt()).abs() < 1e-12);
1363 }
1364
1365 #[test]
1366 fn test_frobenius_norm_known() {
1367 let m = vec![vec![3.0, 4.0], vec![0.0, 0.0]];
1368 let csr = CsrMatrix::from_dense(&m);
1369 let f = frobenius_norm(&csr);
1370 assert!((f - 5.0).abs() < 1e-12);
1372 }
1373}