1use crate::error::{SparseError, SparseResult};
7use scirs2_core::numeric::Zero;
8use scirs2_core::GpuDataType;
9use std::cmp::PartialEq;
10
11pub struct CscMatrix<T> {
16 rows: usize,
18 cols: usize,
20 indptr: Vec<usize>,
22 indices: Vec<usize>,
24 data: Vec<T>,
26}
27
28impl<T> CscMatrix<T>
29where
30 T: Clone + Copy + Zero + PartialEq,
31{
32 pub fn new(
59 data: Vec<T>,
60 rowindices: Vec<usize>,
61 colindices: Vec<usize>,
62 shape: (usize, usize),
63 ) -> SparseResult<Self> {
64 if data.len() != rowindices.len() || data.len() != colindices.len() {
66 return Err(SparseError::DimensionMismatch {
67 expected: data.len(),
68 found: std::cmp::min(rowindices.len(), colindices.len()),
69 });
70 }
71
72 let (rows, cols) = shape;
73
74 if rowindices.iter().any(|&i| i >= rows) {
76 return Err(SparseError::ValueError(
77 "Row index out of bounds".to_string(),
78 ));
79 }
80
81 if colindices.iter().any(|&i| i >= cols) {
82 return Err(SparseError::ValueError(
83 "Column index out of bounds".to_string(),
84 ));
85 }
86
87 let mut triplets: Vec<(usize, usize, T)> = colindices
90 .into_iter()
91 .zip(rowindices)
92 .zip(data)
93 .map(|((c, r), v)| (c, r, v))
94 .collect();
95 triplets.sort_by_key(|&(c, r_, _)| (c, r_));
96
97 let nnz = triplets.len();
99 let mut indptr = vec![0; cols + 1];
100 let mut indices = Vec::with_capacity(nnz);
101 let mut data_out = Vec::with_capacity(nnz);
102
103 for &(c_, _, _) in &triplets {
105 indptr[c_ + 1] += 1;
106 }
107
108 for i in 1..=cols {
110 indptr[i] += indptr[i - 1];
111 }
112
113 for (_, r, v) in triplets {
115 indices.push(r);
116 data_out.push(v);
117 }
118
119 Ok(CscMatrix {
120 rows,
121 cols,
122 indptr,
123 indices,
124 data: data_out,
125 })
126 }
127
128 pub fn from_raw_csc(
141 data: Vec<T>,
142 indptr: Vec<usize>,
143 indices: Vec<usize>,
144 shape: (usize, usize),
145 ) -> SparseResult<Self> {
146 let (rows, cols) = shape;
147
148 if indptr.len() != cols + 1 {
150 return Err(SparseError::DimensionMismatch {
151 expected: cols + 1,
152 found: indptr.len(),
153 });
154 }
155
156 if data.len() != indices.len() {
157 return Err(SparseError::DimensionMismatch {
158 expected: data.len(),
159 found: indices.len(),
160 });
161 }
162
163 for i in 1..indptr.len() {
165 if indptr[i] < indptr[i - 1] {
166 return Err(SparseError::ValueError(
167 "Column pointer array must be monotonically increasing".to_string(),
168 ));
169 }
170 }
171
172 if indptr[cols] != data.len() {
174 return Err(SparseError::ValueError(
175 "Last column pointer entry must match data length".to_string(),
176 ));
177 }
178
179 if indices.iter().any(|&i| i >= rows) {
181 return Err(SparseError::ValueError(
182 "Row index out of bounds".to_string(),
183 ));
184 }
185
186 Ok(CscMatrix {
187 rows,
188 cols,
189 indptr,
190 indices,
191 data,
192 })
193 }
194
195 pub fn from_csc_data(
217 values: Vec<T>,
218 rowindices: Vec<usize>,
219 col_ptrs: Vec<usize>,
220 shape: (usize, usize),
221 ) -> SparseResult<Self> {
222 Self::from_raw_csc(values, col_ptrs, rowindices, shape)
223 }
224
225 pub fn empty(shape: (usize, usize)) -> Self {
226 let (rows, cols) = shape;
227 let indptr = vec![0; cols + 1];
228
229 CscMatrix {
230 rows,
231 cols,
232 indptr,
233 indices: Vec::new(),
234 data: Vec::new(),
235 }
236 }
237
238 pub fn rows(&self) -> usize {
240 self.rows
241 }
242
243 pub fn cols(&self) -> usize {
245 self.cols
246 }
247
248 pub fn shape(&self) -> (usize, usize) {
250 (self.rows, self.cols)
251 }
252
253 pub fn nnz(&self) -> usize {
255 self.data.len()
256 }
257
258 pub fn col_range(&self, col: usize) -> std::ops::Range<usize> {
268 assert!(col < self.cols, "Column index out of bounds");
269 self.indptr[col]..self.indptr[col + 1]
270 }
271
272 pub fn rowindices(&self) -> &[usize] {
274 &self.indices
275 }
276
277 pub fn data(&self) -> &[T] {
279 &self.data
280 }
281
282 pub fn to_dense(&self) -> Vec<Vec<T>>
284 where
285 T: Zero + Copy,
286 {
287 let mut result = vec![vec![T::zero(); self.cols]; self.rows];
288
289 for col_idx in 0..self.cols {
290 for j in self.indptr[col_idx]..self.indptr[col_idx + 1] {
291 let row_idx = self.indices[j];
292 result[row_idx][col_idx] = self.data[j];
293 }
294 }
295
296 result
297 }
298
299 pub fn transpose(&self) -> Self {
301 let mut row_counts = vec![0; self.rows];
303 for &row in &self.indices {
304 row_counts[row] += 1;
305 }
306
307 let mut row_ptrs = vec![0; self.rows + 1];
309 for i in 0..self.rows {
310 row_ptrs[i + 1] = row_ptrs[i] + row_counts[i];
311 }
312
313 let nnz = self.nnz();
315 let mut indices_t = vec![0; nnz];
316 let mut data_t = vec![T::zero(); nnz];
317 let mut row_counts = vec![0; self.rows];
318
319 for col in 0..self.cols {
320 for j in self.indptr[col]..self.indptr[col + 1] {
321 let row = self.indices[j];
322 let dest = row_ptrs[row] + row_counts[row];
323
324 indices_t[dest] = col;
325 data_t[dest] = self.data[j];
326 row_counts[row] += 1;
327 }
328 }
329
330 CscMatrix {
331 rows: self.cols,
332 cols: self.rows,
333 indptr: row_ptrs,
334 indices: indices_t,
335 data: data_t,
336 }
337 }
338
339 pub fn to_csr(&self) -> crate::csr::CsrMatrix<T> {
341 let transposed = self.transpose();
343
344 crate::csr::CsrMatrix::from_raw_csr(
345 transposed.data,
346 transposed.indptr,
347 transposed.indices,
348 (self.rows, self.cols),
349 )
350 .unwrap()
351 }
352}
353
354impl CscMatrix<f64> {
355 pub fn dot(&self, vec: &[f64]) -> SparseResult<Vec<f64>> {
365 if vec.len() != self.cols {
366 return Err(SparseError::DimensionMismatch {
367 expected: self.cols,
368 found: vec.len(),
369 });
370 }
371
372 let mut result = vec![0.0; self.rows];
373
374 for (col_idx, &col_val) in vec.iter().enumerate().take(self.cols) {
375 for j in self.indptr[col_idx]..self.indptr[col_idx + 1] {
376 let row_idx = self.indices[j];
377 result[row_idx] += self.data[j] * col_val;
378 }
379 }
380
381 Ok(result)
382 }
383
384 pub fn gpu_dot(&self, vec: &[f64]) -> SparseResult<Vec<f64>> {
412 let csr_matrix = self.to_csr();
414 match csr_matrix.gpu_dot(vec) {
415 Ok(r) => Ok(r),
416 Err(SparseError::ComputationError(msg)) if msg.contains("GPU device required") => {
417 Err(SparseError::OperationNotSupported(
419 "GPU device unavailable in test environment".to_string(),
420 ))
421 }
422 Err(e) => Err(e),
423 }
424 }
425
426 pub fn gpu_dot_with_backend(
437 &self,
438 vec: &[f64],
439 backend: scirs2_core::gpu::GpuBackend,
440 ) -> SparseResult<Vec<f64>> {
441 let csr_matrix = self.to_csr();
443 csr_matrix.gpu_dot_with_backend(vec, backend)
444 }
445}
446
447impl<T> CscMatrix<T>
448where
449 T: scirs2_core::numeric::Float
450 + std::fmt::Debug
451 + Copy
452 + Default
453 + GpuDataType
454 + Send
455 + Sync
456 + 'static
457 + std::ops::AddAssign
458 + std::iter::Sum,
459{
460 pub fn gpu_dot_generic(&self, vec: &[T]) -> SparseResult<Vec<T>> {
470 let csr_matrix = self.to_csr();
472 csr_matrix.gpu_dot_generic(vec)
473 }
474
475 pub fn should_use_gpu(&self) -> bool {
481 let nnz_threshold = 10000;
483 let density = self.nnz() as f64 / (self.rows * self.cols) as f64;
484
485 self.nnz() > nnz_threshold && density < 0.5
486 }
487
488 pub fn gpu_backend_info() -> SparseResult<(crate::gpu_ops::GpuBackend, String)> {
494 Ok((crate::gpu_ops::GpuBackend::Cpu, "CPU Fallback".to_string()))
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502 use approx::assert_relative_eq;
503
504 #[test]
505 fn test_csc_create() {
506 let rows = vec![0, 0, 1, 2, 2];
508 let cols = vec![0, 2, 2, 0, 1];
509 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
510 let shape = (3, 3);
511
512 let matrix = CscMatrix::new(data, rows, cols, shape).unwrap();
513
514 assert_eq!(matrix.shape(), (3, 3));
515 assert_eq!(matrix.nnz(), 5);
516 }
517
518 #[test]
519 fn test_csc_to_dense() {
520 let rows = vec![0, 0, 1, 2, 2];
522 let cols = vec![0, 2, 2, 0, 1];
523 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
524 let shape = (3, 3);
525
526 let matrix = CscMatrix::new(data, rows, cols, shape).unwrap();
527 let dense = matrix.to_dense();
528
529 let expected = vec![
530 vec![1.0, 0.0, 2.0],
531 vec![0.0, 0.0, 3.0],
532 vec![4.0, 5.0, 0.0],
533 ];
534
535 assert_eq!(dense, expected);
536 }
537
538 #[test]
539 fn test_csc_dot() {
540 let rows = vec![0, 0, 1, 2, 2];
542 let cols = vec![0, 2, 2, 0, 1];
543 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
544 let shape = (3, 3);
545
546 let matrix = CscMatrix::new(data, rows, cols, shape).unwrap();
547
548 let vec = vec![1.0, 2.0, 3.0];
554 let result = matrix.dot(&vec).unwrap();
555
556 let expected = [7.0, 9.0, 14.0];
561
562 assert_eq!(result.len(), expected.len());
563 for (a, b) in result.iter().zip(expected.iter()) {
564 assert_relative_eq!(a, b, epsilon = 1e-10);
565 }
566 }
567
568 #[test]
569 fn test_csc_transpose() {
570 let rows = vec![0, 0, 1, 2, 2];
572 let cols = vec![0, 2, 2, 0, 1];
573 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
574 let shape = (3, 3);
575
576 let matrix = CscMatrix::new(data, rows, cols, shape).unwrap();
577 let transposed = matrix.transpose();
578
579 assert_eq!(transposed.shape(), (3, 3));
580 assert_eq!(transposed.nnz(), 5);
581
582 let dense = transposed.to_dense();
583 let expected = vec![
584 vec![1.0, 0.0, 4.0],
585 vec![0.0, 0.0, 5.0],
586 vec![2.0, 3.0, 0.0],
587 ];
588
589 assert_eq!(dense, expected);
590 }
591
592 #[test]
593 fn test_csc_to_csr() {
594 let rows = vec![0, 0, 1, 2, 2];
596 let cols = vec![0, 2, 2, 0, 1];
597 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
598 let shape = (3, 3);
599
600 let csc_matrix = CscMatrix::new(data, rows, cols, shape).unwrap();
601 let csr_matrix = csc_matrix.to_csr();
602
603 assert_eq!(csr_matrix.shape(), (3, 3));
604 assert_eq!(csr_matrix.nnz(), 5);
605
606 let dense_from_csc = csc_matrix.to_dense();
607 let dense_from_csr = csr_matrix.to_dense();
608
609 assert_eq!(dense_from_csc, dense_from_csr);
610 }
611
612 #[test]
613 fn test_gpu_dot() {
614 let rows = vec![0, 0, 1, 2, 2];
616 let cols = vec![0, 2, 2, 0, 1];
617 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
618 let shape = (3, 3);
619
620 let matrix = CscMatrix::new(data, rows, cols, shape).unwrap();
621 let vec = vec![1.0, 2.0, 3.0];
622
623 let gpu_result = matrix.gpu_dot(&vec);
625
626 match gpu_result {
628 Ok(result) => {
629 let expected = [7.0, 9.0, 14.0]; assert_eq!(result.len(), expected.len());
631 for (a, b) in result.iter().zip(expected.iter()) {
632 assert_relative_eq!(a, b, epsilon = 1e-10);
633 }
634 }
635 Err(crate::error::SparseError::OperationNotSupported(_)) => {
636 println!("GPU operations are temporarily disabled, skipping GPU test");
638 }
639 Err(e) => {
640 panic!("Unexpected error in GPU SpMV: {:?}", e);
641 }
642 }
643 }
644
645 #[test]
646 fn test_csc_should_use_gpu() {
647 let small_matrix = CscMatrix::new(vec![1.0, 2.0], vec![0, 1], vec![0, 1], (2, 2)).unwrap();
649 assert!(
650 !small_matrix.should_use_gpu(),
651 "Small matrix should not use GPU"
652 );
653
654 let large_data = vec![1.0; 15000];
656 let large_rows: Vec<usize> = (0..15000).collect();
657 let large_cols: Vec<usize> = (0..15000).collect();
658 let large_matrix =
659 CscMatrix::new(large_data, large_rows, large_cols, (15000, 15000)).unwrap();
660 assert!(
661 large_matrix.should_use_gpu(),
662 "Large sparse matrix should use GPU"
663 );
664 }
665
666 #[test]
667 fn test_csc_gpu_backend_info() {
668 let backend_info = CscMatrix::<f64>::gpu_backend_info();
669 assert!(
670 backend_info.is_ok(),
671 "Should be able to get GPU backend info"
672 );
673
674 if let Ok((backend, name)) = backend_info {
675 assert!(!name.is_empty(), "Backend name should not be empty");
676 match backend {
678 crate::gpu_ops::GpuBackend::Cuda
679 | crate::gpu_ops::GpuBackend::OpenCL
680 | crate::gpu_ops::GpuBackend::Metal
681 | crate::gpu_ops::GpuBackend::Cpu
682 | crate::gpu_ops::GpuBackend::Rocm
683 | crate::gpu_ops::GpuBackend::Wgpu => {}
684 }
685 }
686 }
687
688 #[test]
689 fn test_csc_gpu_dot_generic_f32() {
690 let rows = vec![0, 0, 1, 2, 2];
692 let cols = vec![0, 2, 2, 0, 1];
693 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
694 let shape = (3, 3);
695
696 let matrix = CscMatrix::new(data, rows, cols, shape).unwrap();
697 let vec = vec![1.0f32, 2.0, 3.0];
698
699 let gpu_result = matrix.gpu_dot_generic(&vec);
701 assert!(gpu_result.is_ok(), "Generic GPU SpMV should succeed");
702
703 if let Ok(result) = gpu_result {
704 let expected = [7.0f32, 9.0, 14.0];
705 assert_eq!(result.len(), expected.len());
706 for (a, b) in result.iter().zip(expected.iter()) {
707 assert_relative_eq!(a, b, epsilon = 1e-6);
708 }
709 }
710 }
711}