oxicuda_sparse/format/
coo.rs1use oxicuda_blas::GpuFloat;
11use oxicuda_memory::DeviceBuffer;
12
13use crate::error::{SparseError, SparseResult};
14
15pub struct CooMatrix<T: GpuFloat> {
20 rows: u32,
22 cols: u32,
24 nnz: u32,
26 row_idx: DeviceBuffer<i32>,
28 col_idx: DeviceBuffer<i32>,
30 values: DeviceBuffer<T>,
32 sorted: bool,
34}
35
36impl<T: GpuFloat> CooMatrix<T> {
37 pub fn from_host(
54 rows: u32,
55 cols: u32,
56 row_idx: &[i32],
57 col_idx: &[i32],
58 values: &[T],
59 ) -> SparseResult<Self> {
60 if rows == 0 || cols == 0 {
61 return Err(SparseError::InvalidFormat(
62 "rows and cols must be non-zero".to_string(),
63 ));
64 }
65
66 let nnz = values.len();
67 if nnz == 0 {
68 return Err(SparseError::ZeroNnz);
69 }
70 if row_idx.len() != nnz || col_idx.len() != nnz {
71 return Err(SparseError::InvalidFormat(format!(
72 "row_idx ({}), col_idx ({}), and values ({}) must have equal length",
73 row_idx.len(),
74 col_idx.len(),
75 nnz
76 )));
77 }
78
79 let d_row_idx = DeviceBuffer::from_host(row_idx)?;
80 let d_col_idx = DeviceBuffer::from_host(col_idx)?;
81 let d_values = DeviceBuffer::from_host(values)?;
82
83 Ok(Self {
84 rows,
85 cols,
86 nnz: nnz as u32,
87 row_idx: d_row_idx,
88 col_idx: d_col_idx,
89 values: d_values,
90 sorted: false,
91 })
92 }
93
94 pub fn from_device(
100 rows: u32,
101 cols: u32,
102 nnz: u32,
103 row_idx: DeviceBuffer<i32>,
104 col_idx: DeviceBuffer<i32>,
105 values: DeviceBuffer<T>,
106 ) -> SparseResult<Self> {
107 if row_idx.len() != nnz as usize
108 || col_idx.len() != nnz as usize
109 || values.len() != nnz as usize
110 {
111 return Err(SparseError::InvalidFormat(
112 "all arrays must have length equal to nnz".to_string(),
113 ));
114 }
115 Ok(Self {
116 rows,
117 cols,
118 nnz,
119 row_idx,
120 col_idx,
121 values,
122 sorted: false,
123 })
124 }
125
126 #[must_use]
131 pub fn with_sorted(mut self, sorted: bool) -> Self {
132 self.sorted = sorted;
133 self
134 }
135
136 pub fn to_host(&self) -> SparseResult<(Vec<i32>, Vec<i32>, Vec<T>)> {
142 let mut h_row_idx = vec![0i32; self.row_idx.len()];
143 let mut h_col_idx = vec![0i32; self.col_idx.len()];
144 let mut h_values = vec![T::gpu_zero(); self.values.len()];
145
146 self.row_idx.copy_to_host(&mut h_row_idx)?;
147 self.col_idx.copy_to_host(&mut h_col_idx)?;
148 self.values.copy_to_host(&mut h_values)?;
149
150 Ok((h_row_idx, h_col_idx, h_values))
151 }
152
153 pub fn to_csr(&self) -> SparseResult<super::CsrMatrix<T>> {
161 let (h_row_idx, h_col_idx, h_values) = self.to_host()?;
162
163 let mut row_counts = vec![0i32; self.rows as usize];
165 for &r in &h_row_idx {
166 row_counts[r as usize] += 1;
167 }
168
169 let mut h_row_ptr = vec![0i32; self.rows as usize + 1];
170 for i in 0..self.rows as usize {
171 h_row_ptr[i + 1] = h_row_ptr[i] + row_counts[i];
172 }
173
174 let mut h_csr_col_idx = vec![0i32; self.nnz as usize];
176 let mut h_csr_values = vec![T::gpu_zero(); self.nnz as usize];
177 let mut write_pos = h_row_ptr.clone();
178
179 for i in 0..self.nnz as usize {
180 let row = h_row_idx[i] as usize;
181 let dest = write_pos[row] as usize;
182 h_csr_col_idx[dest] = h_col_idx[i];
183 h_csr_values[dest] = h_values[i];
184 write_pos[row] += 1;
185 }
186
187 super::CsrMatrix::from_host(
188 self.rows,
189 self.cols,
190 &h_row_ptr,
191 &h_csr_col_idx,
192 &h_csr_values,
193 )
194 }
195
196 pub fn to_csc(&self) -> SparseResult<super::CscMatrix<T>> {
204 let (h_row_idx, h_col_idx, h_values) = self.to_host()?;
205
206 let mut col_counts = vec![0i32; self.cols as usize];
207 for &c in &h_col_idx {
208 col_counts[c as usize] += 1;
209 }
210
211 let mut h_col_ptr = vec![0i32; self.cols as usize + 1];
212 for i in 0..self.cols as usize {
213 h_col_ptr[i + 1] = h_col_ptr[i] + col_counts[i];
214 }
215
216 let mut h_csc_row_idx = vec![0i32; self.nnz as usize];
217 let mut h_csc_values = vec![T::gpu_zero(); self.nnz as usize];
218 let mut write_pos = h_col_ptr.clone();
219
220 for i in 0..self.nnz as usize {
221 let col = h_col_idx[i] as usize;
222 let dest = write_pos[col] as usize;
223 h_csc_row_idx[dest] = h_row_idx[i];
224 h_csc_values[dest] = h_values[i];
225 write_pos[col] += 1;
226 }
227
228 super::CscMatrix::from_host(
229 self.rows,
230 self.cols,
231 &h_col_ptr,
232 &h_csc_row_idx,
233 &h_csc_values,
234 )
235 }
236
237 #[inline]
239 pub fn is_sorted(&self) -> bool {
240 self.sorted
241 }
242
243 #[inline]
245 pub fn rows(&self) -> u32 {
246 self.rows
247 }
248
249 #[inline]
251 pub fn cols(&self) -> u32 {
252 self.cols
253 }
254
255 #[inline]
257 pub fn nnz(&self) -> u32 {
258 self.nnz
259 }
260
261 #[inline]
263 pub fn row_idx(&self) -> &DeviceBuffer<i32> {
264 &self.row_idx
265 }
266
267 #[inline]
269 pub fn col_idx(&self) -> &DeviceBuffer<i32> {
270 &self.col_idx
271 }
272
273 #[inline]
275 pub fn values(&self) -> &DeviceBuffer<T> {
276 &self.values
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn coo_validation_mismatched_lengths() {
286 let result = CooMatrix::<f32>::from_host(3, 3, &[0, 1], &[0, 1, 2], &[1.0; 3]);
287 assert!(result.is_err());
288 }
289
290 #[test]
291 fn coo_validation_zero_nnz() {
292 let result = CooMatrix::<f32>::from_host(2, 2, &[], &[], &[]);
293 assert!(matches!(result, Err(SparseError::ZeroNnz)));
294 }
295
296 #[test]
297 fn coo_sorted_flag() {
298 }
300}