oxicuda_sparse/format/
csc.rs1use oxicuda_blas::GpuFloat;
12use oxicuda_memory::DeviceBuffer;
13
14use crate::error::{SparseError, SparseResult};
15
16pub struct CscMatrix<T: GpuFloat> {
20 rows: u32,
22 cols: u32,
24 nnz: u32,
26 col_ptr: DeviceBuffer<i32>,
28 row_idx: DeviceBuffer<i32>,
30 values: DeviceBuffer<T>,
32}
33
34impl<T: GpuFloat> CscMatrix<T> {
35 pub fn from_host(
49 rows: u32,
50 cols: u32,
51 col_ptr: &[i32],
52 row_idx: &[i32],
53 values: &[T],
54 ) -> SparseResult<Self> {
55 if rows == 0 || cols == 0 {
56 return Err(SparseError::InvalidFormat(
57 "rows and cols must be non-zero".to_string(),
58 ));
59 }
60
61 let expected_col_ptr_len = cols as usize + 1;
62 if col_ptr.len() != expected_col_ptr_len {
63 return Err(SparseError::InvalidFormat(format!(
64 "col_ptr length ({}) must be cols + 1 ({})",
65 col_ptr.len(),
66 expected_col_ptr_len
67 )));
68 }
69
70 let nnz = values.len();
71 if nnz == 0 {
72 return Err(SparseError::ZeroNnz);
73 }
74 if row_idx.len() != nnz {
75 return Err(SparseError::InvalidFormat(format!(
76 "row_idx length ({}) must equal values length ({})",
77 row_idx.len(),
78 nnz
79 )));
80 }
81
82 if col_ptr[0] != 0 {
83 return Err(SparseError::InvalidFormat(
84 "col_ptr[0] must be 0".to_string(),
85 ));
86 }
87 if col_ptr[cols as usize] != nnz as i32 {
88 return Err(SparseError::InvalidFormat(format!(
89 "col_ptr[cols] ({}) must equal nnz ({})",
90 col_ptr[cols as usize], nnz
91 )));
92 }
93 for i in 0..cols as usize {
94 if col_ptr[i] > col_ptr[i + 1] {
95 return Err(SparseError::InvalidFormat(format!(
96 "col_ptr must be non-decreasing: col_ptr[{}]={} > col_ptr[{}]={}",
97 i,
98 col_ptr[i],
99 i + 1,
100 col_ptr[i + 1]
101 )));
102 }
103 }
104
105 let d_col_ptr = DeviceBuffer::from_host(col_ptr)?;
106 let d_row_idx = DeviceBuffer::from_host(row_idx)?;
107 let d_values = DeviceBuffer::from_host(values)?;
108
109 Ok(Self {
110 rows,
111 cols,
112 nnz: nnz as u32,
113 col_ptr: d_col_ptr,
114 row_idx: d_row_idx,
115 values: d_values,
116 })
117 }
118
119 pub fn from_device(
125 rows: u32,
126 cols: u32,
127 nnz: u32,
128 col_ptr: DeviceBuffer<i32>,
129 row_idx: DeviceBuffer<i32>,
130 values: DeviceBuffer<T>,
131 ) -> SparseResult<Self> {
132 if col_ptr.len() != (cols as usize + 1) {
133 return Err(SparseError::InvalidFormat(format!(
134 "col_ptr length ({}) must be cols + 1 ({})",
135 col_ptr.len(),
136 cols as usize + 1
137 )));
138 }
139 if row_idx.len() != nnz as usize || values.len() != nnz as usize {
140 return Err(SparseError::InvalidFormat(
141 "row_idx and values lengths must equal nnz".to_string(),
142 ));
143 }
144 Ok(Self {
145 rows,
146 cols,
147 nnz,
148 col_ptr,
149 row_idx,
150 values,
151 })
152 }
153
154 pub fn to_host(&self) -> SparseResult<(Vec<i32>, Vec<i32>, Vec<T>)> {
160 let mut h_col_ptr = vec![0i32; self.col_ptr.len()];
161 let mut h_row_idx = vec![0i32; self.row_idx.len()];
162 let mut h_values = vec![T::gpu_zero(); self.values.len()];
163
164 self.col_ptr.copy_to_host(&mut h_col_ptr)?;
165 self.row_idx.copy_to_host(&mut h_row_idx)?;
166 self.values.copy_to_host(&mut h_values)?;
167
168 Ok((h_col_ptr, h_row_idx, h_values))
169 }
170
171 pub fn to_csr(&self) -> SparseResult<super::CsrMatrix<T>> {
180 let (h_col_ptr, h_row_idx, h_values) = self.to_host()?;
181
182 let mut row_counts = vec![0i32; self.rows as usize];
184 for &r in &h_row_idx {
185 row_counts[r as usize] += 1;
186 }
187
188 let mut h_row_ptr = vec![0i32; self.rows as usize + 1];
189 for i in 0..self.rows as usize {
190 h_row_ptr[i + 1] = h_row_ptr[i] + row_counts[i];
191 }
192
193 let mut h_csr_col_idx = vec![0i32; self.nnz as usize];
194 let mut h_csr_values = vec![T::gpu_zero(); self.nnz as usize];
195 let mut write_pos = h_row_ptr.clone();
196
197 for col in 0..self.cols as usize {
198 let start = h_col_ptr[col] as usize;
199 let end = h_col_ptr[col + 1] as usize;
200 for j in start..end {
201 let row = h_row_idx[j] as usize;
202 let dest = write_pos[row] as usize;
203 h_csr_col_idx[dest] = col as i32;
204 h_csr_values[dest] = h_values[j];
205 write_pos[row] += 1;
206 }
207 }
208
209 super::CsrMatrix::from_host(
210 self.rows,
211 self.cols,
212 &h_row_ptr,
213 &h_csr_col_idx,
214 &h_csr_values,
215 )
216 }
217
218 #[inline]
220 pub fn rows(&self) -> u32 {
221 self.rows
222 }
223
224 #[inline]
226 pub fn cols(&self) -> u32 {
227 self.cols
228 }
229
230 #[inline]
232 pub fn nnz(&self) -> u32 {
233 self.nnz
234 }
235
236 #[inline]
238 pub fn col_ptr(&self) -> &DeviceBuffer<i32> {
239 &self.col_ptr
240 }
241
242 #[inline]
244 pub fn row_idx(&self) -> &DeviceBuffer<i32> {
245 &self.row_idx
246 }
247
248 #[inline]
250 pub fn values(&self) -> &DeviceBuffer<T> {
251 &self.values
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn csc_validation_col_ptr_length() {
261 let result = CscMatrix::<f32>::from_host(3, 3, &[0, 2, 4], &[0, 1, 0, 2], &[1.0; 4]);
262 assert!(result.is_err());
263 }
264
265 #[test]
266 fn csc_validation_zero_nnz() {
267 let result = CscMatrix::<f32>::from_host(2, 2, &[0, 0, 0], &[], &[]);
268 assert!(matches!(result, Err(SparseError::ZeroNnz)));
269 }
270}