1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
35use std::collections::HashMap;
36use std::fmt::Debug;
37use std::ops::{Add, Mul, Sub};
38
39use scirs2_core::numeric::{Float, NumAssign, NumCast, Zero};
40
41use crate::error::{LinalgError, LinalgResult};
42
43#[derive(Debug, Clone)]
48pub struct SparseMatrixView<T> {
49 pub rows: usize,
51 pub cols: usize,
53 pub indptr: Vec<usize>,
55 pub indices: Vec<usize>,
57 pub data: Vec<T>,
59}
60
61impl<T> SparseMatrixView<T>
62where
63 T: Clone + Copy + Debug + Zero,
64{
65 pub fn new(
78 data: Vec<T>,
79 indptr: Vec<usize>,
80 indices: Vec<usize>,
81 shape: (usize, usize),
82 ) -> LinalgResult<Self> {
83 let (rows, cols) = shape;
84
85 if indptr.len() != rows + 1 {
87 return Err(LinalgError::DimensionError(format!(
88 "Row pointer array length must be rows + 1, got {} for {} rows",
89 indptr.len(),
90 rows
91 )));
92 }
93
94 if data.len() != indices.len() {
95 return Err(LinalgError::DimensionError(format!(
96 "Data and indices must have the same length, got {} and {}",
97 data.len(),
98 indices.len()
99 )));
100 }
101
102 for i in 1..indptr.len() {
104 if indptr[i] < indptr[i - 1] {
105 return Err(LinalgError::ValueError(
106 "Row pointer array must be monotonically increasing".to_string(),
107 ));
108 }
109 }
110
111 if indptr[rows] != data.len() {
113 return Err(LinalgError::ValueError(format!(
114 "Last row pointer entry must match data length, got {} and {}",
115 indptr[rows],
116 data.len()
117 )));
118 }
119
120 if let Some(&max_index) = indices.iter().max() {
122 if max_index >= cols {
123 return Err(LinalgError::ValueError(format!(
124 "Column index out of bounds: {max_index} for a matrix with {cols} columns"
125 )));
126 }
127 }
128
129 Ok(SparseMatrixView {
130 rows,
131 cols,
132 indptr,
133 indices,
134 data,
135 })
136 }
137
138 pub fn shape(&self) -> (usize, usize) {
140 (self.rows, self.cols)
141 }
142
143 pub fn nnz(&self) -> usize {
145 self.data.len()
146 }
147
148 pub fn nrows(&self) -> usize {
150 self.rows
151 }
152
153 pub fn ncols(&self) -> usize {
155 self.cols
156 }
157
158 pub fn is_empty(&self) -> bool {
160 self.nnz() == 0
161 }
162
163 pub fn to_dense(&self) -> Array2<T> {
165 let mut dense = Array2::zeros((self.rows, self.cols));
166
167 for row in 0..self.rows {
168 for j in self.indptr[row]..self.indptr[row + 1] {
169 let col = self.indices[j];
170 dense[[row, col]] = self.data[j];
171 }
172 }
173
174 dense
175 }
176}
177
178#[allow(dead_code)]
189pub fn sparse_from_ndarray<T, F>(
190 array: &ArrayView2<T>,
191 threshold: F,
192) -> LinalgResult<SparseMatrixView<T>>
193where
194 T: Clone + Copy + Debug + PartialOrd + Zero + NumCast,
195 F: Float + NumCast,
196{
197 let shape = array.dim();
198 let (rows, cols) = (shape.0, shape.1);
199
200 let mut data = Vec::new();
201 let mut indices = Vec::new();
202 let mut indptr = vec![0; rows + 1];
203
204 let threshold_abs: F = threshold.abs();
205
206 for (i, row) in array.axis_iter(Axis(0)).enumerate() {
208 for (j, &val) in row.iter().enumerate() {
209 let val_abs: F = if let Some(v) = NumCast::from(val) {
210 let v_typed: F = v;
211 v_typed.abs()
212 } else {
213 F::zero()
214 };
215 if val_abs > threshold_abs {
216 data.push(val);
217 indices.push(j);
218 indptr[i + 1] += 1;
219 }
220 }
221 }
222
223 for i in 1..=rows {
225 indptr[i] += indptr[i - 1];
226 }
227
228 SparseMatrixView::new(data, indptr, indices, (rows, cols))
229}
230
231#[allow(dead_code)]
242pub fn sparse_dense_matmul<T>(
243 sparse: &SparseMatrixView<T>,
244 dense: &ArrayView2<T>,
245) -> LinalgResult<Array2<T>>
246where
247 T: Clone + Copy + Debug + Zero + Add<Output = T> + Mul<Output = T>,
248{
249 if sparse.cols != dense.dim().0 {
251 return Err(LinalgError::DimensionError(format!(
252 "Matrix dimensions incompatible for multiplication: {}x{} and {}x{}",
253 sparse.rows,
254 sparse.cols,
255 dense.dim().0,
256 dense.dim().1
257 )));
258 }
259
260 let result_rows = sparse.rows;
262 let result_cols = dense.dim().1;
263 let mut result = Array2::zeros((result_rows, result_cols));
264
265 for i in 0..sparse.rows {
267 for j in 0..result_cols {
268 let mut sum = T::zero();
269
270 for k in sparse.indptr[i]..sparse.indptr[i + 1] {
272 let col = sparse.indices[k];
273 sum = sum + sparse.data[k] * dense[[col, j]];
274 }
275
276 result[[i, j]] = sum;
277 }
278 }
279
280 Ok(result)
281}
282
283#[allow(dead_code)]
294pub fn dense_sparse_matmul<T>(
295 dense: &ArrayView2<T>,
296 sparse: &SparseMatrixView<T>,
297) -> LinalgResult<Array2<T>>
298where
299 T: Clone + Copy + Debug + Zero + Add<Output = T> + Mul<Output = T>,
300{
301 if dense.dim().1 != sparse.rows {
303 return Err(LinalgError::DimensionError(format!(
304 "Matrix dimensions incompatible for multiplication: {}x{} and {}x{}",
305 dense.dim().0,
306 dense.dim().1,
307 sparse.rows,
308 sparse.cols
309 )));
310 }
311
312 let result_rows = dense.dim().0;
314 let result_cols = sparse.cols;
315 let mut result = Array2::zeros((result_rows, result_cols));
316
317 let mut col_sums: HashMap<usize, Vec<T>> = HashMap::new();
319
320 for i in 0..sparse.rows {
322 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
323 let col = sparse.indices[j];
324 let val = sparse.data[j];
325
326 col_sums
327 .entry(col)
328 .or_insert_with(|| vec![T::zero(); result_rows]);
329
330 for (k, dense_row) in dense.axis_iter(Axis(0)).enumerate() {
332 col_sums.get_mut(&col).expect("Operation failed")[k] =
333 col_sums[&col][k] + dense_row[i] * val;
334 }
335 }
336 }
337
338 for (col, sums) in col_sums.iter() {
340 for (row, &sum) in sums.iter().enumerate() {
341 result[[row, *col]] = sum;
342 }
343 }
344
345 Ok(result)
346}
347
348#[allow(dead_code)]
359pub fn sparse_dense_matvec<T>(
360 sparse: &SparseMatrixView<T>,
361 vector: &ArrayView1<T>,
362) -> LinalgResult<Array1<T>>
363where
364 T: Clone + Copy + Debug + Zero + Add<Output = T> + Mul<Output = T>,
365{
366 if sparse.cols != vector.len() {
368 return Err(LinalgError::DimensionError(format!(
369 "Matrix-vector dimensions incompatible: {}x{} and {}",
370 sparse.rows,
371 sparse.cols,
372 vector.len()
373 )));
374 }
375
376 let mut result = Array1::zeros(sparse.rows);
378
379 for i in 0..sparse.rows {
381 let mut sum = T::zero();
382
383 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
384 let col = sparse.indices[j];
385 sum = sum + sparse.data[j] * vector[col];
386 }
387
388 result[i] = sum;
389 }
390
391 Ok(result)
392}
393
394#[allow(dead_code)]
405pub fn dense_sparse_matvec<T>(
406 dense: &ArrayView2<T>,
407 sparse: &SparseMatrixView<T>,
408) -> LinalgResult<Array1<T>>
409where
410 T: Clone + Copy + Debug + Zero + Add<Output = T> + Mul<Output = T>,
411{
412 if sparse.rows != 1 {
414 return Err(LinalgError::DimensionError(format!(
415 "Expected a sparse vector (single row), got {} rows",
416 sparse.rows
417 )));
418 }
419
420 if dense.dim().1 != sparse.cols {
422 return Err(LinalgError::DimensionError(format!(
423 "Matrix-vector dimensions incompatible: {}x{} and {}",
424 dense.dim().0,
425 dense.dim().1,
426 sparse.cols
427 )));
428 }
429
430 let mut result = Array1::zeros(dense.dim().0);
432
433 for j in sparse.indptr[0]..sparse.indptr[1] {
435 let col = sparse.indices[j];
436 let val = sparse.data[j];
437
438 for (i, row_val) in result.iter_mut().enumerate() {
439 *row_val = *row_val + dense[[i, col]] * val;
440 }
441 }
442
443 Ok(result)
444}
445
446#[allow(dead_code)]
457pub fn sparse_dense_add<T>(
458 sparse: &SparseMatrixView<T>,
459 dense: &ArrayView2<T>,
460) -> LinalgResult<Array2<T>>
461where
462 T: Clone + Copy + Debug + Zero + Add<Output = T>,
463{
464 if sparse.shape() != dense.dim() {
466 return Err(LinalgError::DimensionError(format!(
467 "Matrix dimensions incompatible for addition: {}x{} and {}x{}",
468 sparse.rows,
469 sparse.cols,
470 dense.dim().0,
471 dense.dim().1
472 )));
473 }
474
475 let mut result = dense.to_owned();
477
478 for i in 0..sparse.rows {
480 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
481 let col = sparse.indices[j];
482 result[[i, col]] = result[[i, col]] + sparse.data[j];
483 }
484 }
485
486 Ok(result)
487}
488
489#[allow(dead_code)]
500pub fn sparse_dense_sub<T>(
501 sparse: &SparseMatrixView<T>,
502 dense: &ArrayView2<T>,
503) -> LinalgResult<Array2<T>>
504where
505 T: Clone + Copy + Debug + Zero + Sub<Output = T> + Neg<Output = T>,
506{
507 if sparse.shape() != dense.dim() {
509 return Err(LinalgError::DimensionError(format!(
510 "Matrix dimensions incompatible for subtraction: {}x{} and {}x{}",
511 sparse.rows,
512 sparse.cols,
513 dense.dim().0,
514 dense.dim().1
515 )));
516 }
517
518 let mut result = dense.mapv(|x| -x);
520
521 for i in 0..sparse.rows {
523 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
524 let col = sparse.indices[j];
525 result[[i, col]] = result[[i, col]] + sparse.data[j];
526 }
527 }
528
529 Ok(result)
530}
531
532#[allow(dead_code)]
543pub fn sparse_dense_elementwise_mul<T>(
544 sparse: &SparseMatrixView<T>,
545 dense: &ArrayView2<T>,
546) -> LinalgResult<SparseMatrixView<T>>
547where
548 T: Clone + Copy + Debug + Zero + Mul<Output = T> + PartialEq,
549{
550 if sparse.shape() != dense.dim() {
552 return Err(LinalgError::DimensionError(format!(
553 "Matrix dimensions incompatible for element-wise multiplication: {}x{} and {}x{}",
554 sparse.rows,
555 sparse.cols,
556 dense.dim().0,
557 dense.dim().1
558 )));
559 }
560
561 let mut data = Vec::new();
563 let mut indices = Vec::new();
564 let mut indptr = vec![0; sparse.rows + 1];
565
566 for i in 0..sparse.rows {
568 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
569 let col = sparse.indices[j];
570 let val = sparse.data[j] * dense[[i, col]];
571
572 if val != T::zero() {
574 data.push(val);
575 indices.push(col);
576 indptr[i + 1] += 1;
577 }
578 }
579 }
580
581 for i in 1..=sparse.rows {
583 indptr[i] += indptr[i - 1];
584 }
585
586 SparseMatrixView::new(data, indptr, indices, sparse.shape())
587}
588
589#[allow(dead_code)]
599pub fn sparse_transpose<T>(sparse: &SparseMatrixView<T>) -> LinalgResult<SparseMatrixView<T>>
600where
601 T: Clone + Copy + Debug + Zero,
602{
603 let mut col_counts = vec![0; sparse.cols];
605 for &col in &sparse.indices {
606 col_counts[col] += 1;
607 }
608
609 let mut col_ptrs = vec![0; sparse.cols + 1];
611 for i in 0..sparse.cols {
612 col_ptrs[i + 1] = col_ptrs[i] + col_counts[i];
613 }
614
615 let nnz = sparse.nnz();
617 let mut indices_t = vec![0; nnz];
618 let mut data_t = vec![T::zero(); nnz];
619 let mut col_offsets = vec![0; sparse.cols];
620
621 for row in 0..sparse.rows {
622 for j in sparse.indptr[row]..sparse.indptr[row + 1] {
623 let col = sparse.indices[j];
624 let dest = col_ptrs[col] + col_offsets[col];
625
626 indices_t[dest] = row;
627 data_t[dest] = sparse.data[j];
628 col_offsets[col] += 1;
629 }
630 }
631
632 SparseMatrixView::new(data_t, col_ptrs, indices_t, (sparse.cols, sparse.rows))
633}
634
635pub mod advanced {
637 use super::*;
638
639 pub fn adaptive_sparse_dense_solve<T>(
641 sparse: &SparseMatrixView<T>,
642 rhs: &ArrayView1<T>,
643 tolerance: T,
644 max_iterations: usize,
645 ) -> LinalgResult<Array1<T>>
646 where
647 T: Float
648 + Clone
649 + Copy
650 + Debug
651 + Zero
652 + Add<Output = T>
653 + Sub<Output = T>
654 + Mul<Output = T>
655 + PartialOrd
656 + std::iter::Sum
657 + scirs2_core::ndarray::ScalarOperand
658 + NumAssign
659 + Send
660 + Sync,
661 {
662 let sparsity_ratio = sparse.nnz() as f64 / (sparse.nrows() * sparse.ncols()) as f64;
664 let avg_nnz_per_row = sparse.nnz() as f64 / sparse.nrows() as f64;
665
666 if sparsity_ratio < 0.1 && avg_nnz_per_row < 50.0 {
667 sparse_conjugate_gradient(sparse, rhs, tolerance, max_iterations)
669 } else if sparsity_ratio < 0.3 {
670 sparse_preconditioned_cg(sparse, rhs, tolerance, max_iterations)
672 } else {
673 let dense = sparse.to_dense();
675 crate::solve::solve(&dense.view(), rhs, None)
676 }
677 }
678
679 pub fn sparse_conjugate_gradient<T>(
681 a: &SparseMatrixView<T>,
682 b: &ArrayView1<T>,
683 tolerance: T,
684 max_iterations: usize,
685 ) -> LinalgResult<Array1<T>>
686 where
687 T: Float
688 + Clone
689 + Copy
690 + Debug
691 + Zero
692 + Add<Output = T>
693 + Sub<Output = T>
694 + Mul<Output = T>
695 + PartialOrd
696 + scirs2_core::ndarray::ScalarOperand,
697 {
698 let n = a.nrows();
699 if a.ncols() != n {
700 return Err(LinalgError::DimensionError(
701 "Matrix must be square for conjugate gradient".to_string(),
702 ));
703 }
704
705 let mut x = Array1::zeros(n);
707
708 let mut r = b.to_owned();
710 let mut p = r.clone();
711
712 let mut rsold = r.dot(&r);
713 let b_norm = b.dot(b).sqrt();
714
715 if b_norm < T::epsilon() {
716 return Ok(x);
717 }
718
719 for _ in 0..max_iterations {
720 let ap = sparse_dense_matvec(a, &p.view())?;
722
723 let pap = p.dot(&ap);
725 if pap <= T::zero() {
726 return Err(LinalgError::ComputationError(
727 "Matrix is not positive definite".to_string(),
728 ));
729 }
730
731 let alpha = rsold / pap;
732
733 x = x + &p * alpha;
735 r = r - &ap * alpha;
736
737 let rsnew = r.dot(&r);
738
739 if rsnew.sqrt() < tolerance * b_norm {
741 return Ok(x);
742 }
743
744 let beta = rsnew / rsold;
746 p = &r + &p * beta;
747 rsold = rsnew;
748 }
749
750 Ok(x)
752 }
753
754 pub fn sparse_preconditioned_cg<T>(
756 a: &SparseMatrixView<T>,
757 b: &ArrayView1<T>,
758 tolerance: T,
759 max_iterations: usize,
760 ) -> LinalgResult<Array1<T>>
761 where
762 T: Float
763 + Clone
764 + Copy
765 + Debug
766 + Zero
767 + Add<Output = T>
768 + Sub<Output = T>
769 + Mul<Output = T>
770 + PartialOrd
771 + scirs2_core::ndarray::ScalarOperand,
772 {
773 let mut diag = Array1::zeros(a.nrows());
775 for i in 0..a.nrows() {
776 for j in a.indptr[i]..a.indptr[i + 1] {
777 if a.indices[j] == i {
778 diag[i] = a.data[j];
779 break;
780 }
781 }
782 if diag[i].abs() < T::epsilon() {
784 diag[i] = T::one();
785 }
786 }
787
788 let m_inv = diag.mapv(|x| T::one() / x);
790
791 let n = a.nrows();
792 let mut x = Array1::zeros(n);
793 let mut r = b.to_owned();
794 let mut z = &r * &m_inv; let mut p = z.clone();
796
797 let mut rzold = r.dot(&z);
798 let b_norm = b.dot(b).sqrt();
799
800 for _ in 0..max_iterations {
801 let ap = sparse_dense_matvec(a, &p.view())?;
802 let pap = p.dot(&ap);
803
804 if pap <= T::zero() {
805 return Err(LinalgError::ComputationError(
806 "Matrix is not positive definite".to_string(),
807 ));
808 }
809
810 let alpha = rzold / pap;
811
812 x = x + &p * alpha;
813 r = r - &ap * alpha;
814
815 let r_norm = r.dot(&r).sqrt();
817 if r_norm < tolerance * b_norm {
818 return Ok(x);
819 }
820
821 z = &r * &m_inv;
823 let rznew = r.dot(&z);
824 let beta = rznew / rzold;
825
826 p = &z + &p * beta;
827 rzold = rznew;
828 }
829
830 Ok(x)
831 }
832
833 #[derive(Debug, Clone)]
835 pub struct SparseMatrixStats {
836 pub sparsity_ratio: f64,
837 pub avg_nnz_per_row: f64,
838 pub max_nnz_per_row: usize,
839 pub bandwidth: usize,
840 pub is_symmetric: bool,
841 pub is_diagonal_dominant: bool,
842 }
843
844 pub fn analyze_sparse_structure<T>(sparse: &SparseMatrixView<T>) -> SparseMatrixStats
846 where
847 T: Float + Clone + Copy + Debug + PartialOrd,
848 {
849 let total_elements = sparse.nrows() * sparse.ncols();
850 let sparsity_ratio = sparse.nnz() as f64 / total_elements as f64;
851 let avg_nnz_per_row = sparse.nnz() as f64 / sparse.nrows() as f64;
852
853 let mut max_nnz_per_row = 0;
855 for i in 0..sparse.nrows() {
856 let row_nnz = sparse.indptr[i + 1] - sparse.indptr[i];
857 max_nnz_per_row = max_nnz_per_row.max(row_nnz);
858 }
859
860 let mut bandwidth = 0;
862 for i in 0..sparse.nrows() {
863 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
864 let col = sparse.indices[j];
865 let distance = i.abs_diff(col);
866 bandwidth = bandwidth.max(distance);
867 }
868 }
869
870 let is_symmetric = if sparse.nrows() == sparse.ncols() {
872 check_sparse_symmetry(sparse)
873 } else {
874 false
875 };
876
877 let is_diagonal_dominant = check_diagonal_dominance(sparse);
879
880 SparseMatrixStats {
881 sparsity_ratio,
882 avg_nnz_per_row,
883 max_nnz_per_row,
884 bandwidth,
885 is_symmetric,
886 is_diagonal_dominant,
887 }
888 }
889
890 fn check_sparse_symmetry<T>(sparse: &SparseMatrixView<T>) -> bool
892 where
893 T: Float + Clone + Copy + Debug + PartialOrd,
894 {
895 let mut elements = HashMap::new();
897
898 for i in 0..sparse.nrows() {
899 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
900 let col = sparse.indices[j];
901 elements.insert((i, col), sparse.data[j]);
902 }
903 }
904
905 for i in 0..sparse.nrows() {
907 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
908 let col = sparse.indices[j];
909 let val_ij = sparse.data[j];
910
911 if let Some(&val_ji) = elements.get(&(col, i)) {
912 let diff = (val_ij - val_ji).abs();
913 let tolerance = T::epsilon() * T::from(100.0).expect("Operation failed");
914 if diff > tolerance {
915 return false;
916 }
917 } else if val_ij.abs() > T::epsilon() * T::from(100.0).expect("Operation failed") {
918 return false;
920 }
921 }
922 }
923
924 true
925 }
926
927 fn check_diagonal_dominance<T>(sparse: &SparseMatrixView<T>) -> bool
929 where
930 T: Float + Clone + Copy + Debug + PartialOrd,
931 {
932 for i in 0..sparse.nrows() {
933 let mut diag_val = T::zero();
934 let mut off_diag_sum = T::zero();
935
936 for j in sparse.indptr[i]..sparse.indptr[i + 1] {
937 let col = sparse.indices[j];
938 let val = sparse.data[j].abs();
939
940 if col == i {
941 diag_val = val;
942 } else {
943 off_diag_sum = off_diag_sum + val;
944 }
945 }
946
947 if diag_val <= off_diag_sum {
948 return false;
949 }
950 }
951
952 true
953 }
954}
955
956pub trait SparseLinalg<T> {
960 fn shape(&self) -> (usize, usize);
962
963 fn nnz(&self) -> usize;
965
966 fn to_csr(&self) -> LinalgResult<SparseMatrixView<T>>;
968
969 fn matvec(&self, x: &ArrayView1<T>) -> LinalgResult<Array1<T>>;
971
972 fn solve(&self, b: &ArrayView1<T>) -> LinalgResult<Array1<T>>;
974
975 fn stats(&self) -> advanced::SparseMatrixStats;
977}
978
979impl<T> SparseLinalg<T> for SparseMatrixView<T>
981where
982 T: Float
983 + Clone
984 + Copy
985 + Debug
986 + Zero
987 + Add<Output = T>
988 + Sub<Output = T>
989 + Mul<Output = T>
990 + PartialOrd
991 + std::iter::Sum
992 + scirs2_core::ndarray::ScalarOperand
993 + NumAssign
994 + Send
995 + Sync,
996{
997 fn shape(&self) -> (usize, usize) {
998 (self.rows, self.cols)
999 }
1000
1001 fn nnz(&self) -> usize {
1002 self.data.len()
1003 }
1004
1005 fn to_csr(&self) -> LinalgResult<SparseMatrixView<T>> {
1006 Ok(self.clone())
1007 }
1008
1009 fn matvec(&self, x: &ArrayView1<T>) -> LinalgResult<Array1<T>> {
1010 sparse_dense_matvec(self, x)
1011 }
1012
1013 fn solve(&self, b: &ArrayView1<T>) -> LinalgResult<Array1<T>> {
1014 advanced::adaptive_sparse_dense_solve(
1015 self,
1016 b,
1017 T::epsilon() * T::from(1000.0).expect("Operation failed"),
1018 1000,
1019 )
1020 }
1021
1022 fn stats(&self) -> advanced::SparseMatrixStats {
1023 advanced::analyze_sparse_structure(self)
1024 }
1025}
1026
1027pub mod utils {
1029 use super::*;
1030
1031 pub fn auto_solve<T>(
1033 matrix: &ArrayView2<T>,
1034 rhs: &ArrayView1<T>,
1035 sparsity_threshold: f64,
1036 ) -> LinalgResult<Array1<T>>
1037 where
1038 T: Float
1039 + Clone
1040 + Copy
1041 + Debug
1042 + Zero
1043 + Add<Output = T>
1044 + Sub<Output = T>
1045 + Mul<Output = T>
1046 + PartialOrd
1047 + NumCast
1048 + NumAssign
1049 + std::iter::Sum
1050 + scirs2_core::ndarray::ScalarOperand
1051 + Send
1052 + Sync,
1053 {
1054 let total_elements = matrix.len();
1056 let mut zero_count = 0;
1057 let threshold = T::epsilon() * T::from(1000.0).expect("Operation failed");
1058
1059 for &val in matrix.iter() {
1060 if val.abs() < threshold {
1061 zero_count += 1;
1062 }
1063 }
1064
1065 let sparsity_ratio = zero_count as f64 / total_elements as f64;
1066
1067 if sparsity_ratio > sparsity_threshold {
1068 let sparse = sparse_from_ndarray(matrix, threshold)?;
1070 sparse.solve(rhs)
1071 } else {
1072 crate::solve::solve(matrix, rhs, None)
1074 }
1075 }
1076
1077 pub fn convert_sparse_format<T>(
1079 sparse: &SparseMatrixView<T>,
1080 format: &str,
1081 ) -> LinalgResult<SparseMatrixView<T>>
1082 where
1083 T: Clone + Copy + Debug + Zero,
1084 {
1085 match format {
1086 "csr" => Ok(sparse.clone()),
1087 "csc" => sparse_transpose(sparse), _ => Err(LinalgError::ValueError(format!(
1089 "Unsupported sparse format: {format}"
1090 ))),
1091 }
1092 }
1093
1094 pub fn estimate_memory_usage<T>(shape: (usize, usize), nnz: usize) -> (usize, usize) {
1096 let (rows, cols) = shape;
1097 let elementsize = std::mem::size_of::<T>();
1098
1099 let dense_memory = rows * cols * elementsize;
1101
1102 let sparse_memory = nnz * elementsize + nnz * std::mem::size_of::<usize>() + (rows + 1) * std::mem::size_of::<usize>(); (dense_memory, sparse_memory)
1108 }
1109}
1110
1111use std::ops::Neg;
1113
1114pub mod sparse_eigen;
1116
1117#[cfg(test)]
1118mod tests {
1119 use super::*;
1120 use approx::assert_abs_diff_eq;
1121 use scirs2_core::ndarray::array;
1122
1123 #[test]
1124 fn test_sparse_from_ndarray() {
1125 let dense = array![[1.0, 0.0, 2.0], [0.0, 0.0, 3.0], [4.0, 5.0, 0.0]];
1126
1127 let sparse = sparse_from_ndarray(&dense.view(), 1e-10).expect("Operation failed");
1128
1129 assert_eq!(sparse.shape(), (3, 3));
1131 assert_eq!(sparse.nnz(), 5);
1132
1133 let dense_again = sparse.to_dense();
1135 for i in 0..3 {
1136 for j in 0..3 {
1137 assert_abs_diff_eq!(dense[[i, j]], dense_again[[i, j]], epsilon = 1e-10);
1138 }
1139 }
1140 }
1141
1142 #[test]
1143 fn test_sparse_dense_matmul() {
1144 let dense_a = array![[1.0, 0.0, 2.0], [0.0, 0.0, 3.0], [4.0, 5.0, 0.0]];
1145
1146 let dense_b = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
1147
1148 let sparse_a = sparse_from_ndarray(&dense_a.view(), 1e-10).expect("Operation failed");
1149
1150 let result = sparse_dense_matmul(&sparse_a, &dense_b.view()).expect("Operation failed");
1152
1153 let expected = array![[11.0, 14.0], [15.0, 18.0], [19.0, 28.0]];
1155
1156 for i in 0..3 {
1158 for j in 0..2 {
1159 assert_abs_diff_eq!(result[[i, j]], expected[[i, j]], epsilon = 1e-10);
1160 }
1161 }
1162 }
1163
1164 #[test]
1165 fn test_dense_sparse_matmul() {
1166 let dense_a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
1167
1168 let dense_b = array![[1.0, 0.0, 2.0], [0.0, 0.0, 3.0], [4.0, 5.0, 0.0]];
1169
1170 let sparse_b = sparse_from_ndarray(&dense_b.view(), 1e-10).expect("Operation failed");
1171
1172 let result = dense_sparse_matmul(&dense_a.view(), &sparse_b).expect("Operation failed");
1174
1175 let expected = dense_a.dot(&dense_b);
1177
1178 for i in 0..2 {
1180 for j in 0..3 {
1181 assert_abs_diff_eq!(result[[i, j]], expected[[i, j]], epsilon = 1e-10);
1182 }
1183 }
1184 }
1185
1186 #[test]
1187 fn test_sparse_dense_matvec() {
1188 let dense_a = array![[1.0, 0.0, 2.0], [0.0, 0.0, 3.0], [4.0, 5.0, 0.0]];
1189
1190 let vec_b = array![1.0, 2.0, 3.0];
1191
1192 let sparse_a = sparse_from_ndarray(&dense_a.view(), 1e-10).expect("Operation failed");
1193
1194 let result = sparse_dense_matvec(&sparse_a, &vec_b.view()).expect("Operation failed");
1196
1197 let expected = array![7.0, 9.0, 14.0];
1199
1200 for i in 0..3 {
1202 assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-10);
1203 }
1204 }
1205
1206 #[test]
1207 fn test_sparse_dense_add() {
1208 let dense_a = array![[1.0, 0.0, 2.0], [0.0, 0.0, 3.0], [4.0, 5.0, 0.0]];
1209
1210 let dense_b = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
1211
1212 let sparse_a = sparse_from_ndarray(&dense_a.view(), 1e-10).expect("Operation failed");
1213
1214 let result = sparse_dense_add(&sparse_a, &dense_b.view()).expect("Operation failed");
1216
1217 let expected = array![[2.0, 2.0, 5.0], [4.0, 5.0, 9.0], [11.0, 13.0, 9.0]];
1219
1220 for i in 0..3 {
1222 for j in 0..3 {
1223 assert_abs_diff_eq!(result[[i, j]], expected[[i, j]], epsilon = 1e-10);
1224 }
1225 }
1226 }
1227
1228 #[test]
1229 fn test_sparse_dense_elementwise_mul() {
1230 let dense_a = array![[1.0, 0.0, 2.0], [0.0, 0.0, 3.0], [4.0, 5.0, 0.0]];
1231
1232 let dense_b = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
1233
1234 let sparse_a = sparse_from_ndarray(&dense_a.view(), 1e-10).expect("Operation failed");
1235
1236 let result =
1238 sparse_dense_elementwise_mul(&sparse_a, &dense_b.view()).expect("Operation failed");
1239
1240 let result_dense = result.to_dense();
1242
1243 let expected = array![[1.0, 0.0, 6.0], [0.0, 0.0, 18.0], [28.0, 40.0, 0.0]];
1245
1246 for i in 0..3 {
1248 for j in 0..3 {
1249 assert_abs_diff_eq!(result_dense[[i, j]], expected[[i, j]], epsilon = 1e-10);
1250 }
1251 }
1252 }
1253
1254 #[test]
1255 fn test_sparse_transpose() {
1256 let dense_a = array![[1.0, 0.0, 2.0], [0.0, 0.0, 3.0], [4.0, 5.0, 0.0]];
1257
1258 let sparse_a = sparse_from_ndarray(&dense_a.view(), 1e-10).expect("Operation failed");
1259
1260 let transposed = sparse_transpose(&sparse_a).expect("Operation failed");
1262
1263 let transposed_dense = transposed.to_dense();
1265
1266 let expected = array![[1.0, 0.0, 4.0], [0.0, 0.0, 5.0], [2.0, 3.0, 0.0]];
1268
1269 for i in 0..3 {
1271 for j in 0..3 {
1272 assert_abs_diff_eq!(transposed_dense[[i, j]], expected[[i, j]], epsilon = 1e-10);
1273 }
1274 }
1275 }
1276}