1use oxicuda_blas::GpuFloat;
12
13use crate::error::{SparseError, SparseResult};
14use crate::handle::SparseHandle;
15
16use super::bsr::BsrMatrix;
17use super::coo::CooMatrix;
18use super::csc::CscMatrix;
19use super::csr::CsrMatrix;
20use super::ell::EllMatrix;
21
22pub fn csr_to_csc<T: GpuFloat>(
30 _handle: &SparseHandle,
31 csr: &CsrMatrix<T>,
32) -> SparseResult<CscMatrix<T>> {
33 csr.to_csc()
34}
35
36pub fn csc_to_csr<T: GpuFloat>(
44 _handle: &SparseHandle,
45 csc: &CscMatrix<T>,
46) -> SparseResult<CsrMatrix<T>> {
47 csc.to_csr()
48}
49
50pub fn coo_to_csr<T: GpuFloat>(
58 _handle: &SparseHandle,
59 coo: &CooMatrix<T>,
60) -> SparseResult<CsrMatrix<T>> {
61 coo.to_csr()
62}
63
64pub fn coo_to_csc<T: GpuFloat>(
72 _handle: &SparseHandle,
73 coo: &CooMatrix<T>,
74) -> SparseResult<CscMatrix<T>> {
75 coo.to_csc()
76}
77
78pub fn csr_to_ell<T: GpuFloat>(
86 _handle: &SparseHandle,
87 csr: &CsrMatrix<T>,
88) -> SparseResult<EllMatrix<T>> {
89 EllMatrix::from_csr(csr)
90}
91
92pub fn csr_to_bsr<T: GpuFloat>(
104 _handle: &SparseHandle,
105 csr: &CsrMatrix<T>,
106 block_dim: u32,
107) -> SparseResult<BsrMatrix<T>> {
108 if block_dim == 0 {
109 return Err(SparseError::InvalidArgument(
110 "block_dim must be non-zero".to_string(),
111 ));
112 }
113 if csr.rows() % block_dim != 0 {
114 return Err(SparseError::InvalidFormat(format!(
115 "rows ({}) must be a multiple of block_dim ({})",
116 csr.rows(),
117 block_dim
118 )));
119 }
120 if csr.cols() % block_dim != 0 {
121 return Err(SparseError::InvalidFormat(format!(
122 "cols ({}) must be a multiple of block_dim ({})",
123 csr.cols(),
124 block_dim
125 )));
126 }
127
128 let (h_row_ptr, h_col_idx, h_values) = csr.to_host()?;
129
130 let block_rows = csr.rows() / block_dim;
131 let block_cols = csr.cols() / block_dim;
132 let bd = block_dim as usize;
133
134 let mut block_entries: Vec<Vec<u32>> = Vec::with_capacity(block_rows as usize);
137 for br in 0..block_rows as usize {
138 let mut block_col_set = Vec::new();
139 for local_row in 0..bd {
140 let global_row = br * bd + local_row;
141 let start = h_row_ptr[global_row] as usize;
142 let end = h_row_ptr[global_row + 1] as usize;
143 for &cj in &h_col_idx[start..end] {
144 let bc = cj as u32 / block_dim;
145 if !block_col_set.contains(&bc) {
146 block_col_set.push(bc);
147 }
148 }
149 }
150 block_col_set.sort_unstable();
151 block_entries.push(block_col_set);
152 }
153
154 let mut bsr_row_ptr = vec![0i32; block_rows as usize + 1];
156 let mut bsr_col_idx = Vec::new();
157
158 for br in 0..block_rows as usize {
159 bsr_row_ptr[br + 1] = bsr_row_ptr[br] + block_entries[br].len() as i32;
160 bsr_col_idx.extend(block_entries[br].iter().map(|&c| c as i32));
161 }
162
163 let nnz_blocks = bsr_col_idx.len();
164 if nnz_blocks == 0 {
165 return Err(SparseError::ZeroNnz);
166 }
167
168 let block_elems = bd * bd;
170 let mut bsr_values = vec![T::gpu_zero(); nnz_blocks * block_elems];
171
172 for br in 0..block_rows as usize {
173 let block_start = bsr_row_ptr[br] as usize;
174 let block_cols_for_row = &block_entries[br];
175
176 for local_row in 0..bd {
177 let global_row = br * bd + local_row;
178 let start = h_row_ptr[global_row] as usize;
179 let end = h_row_ptr[global_row + 1] as usize;
180 for j in start..end {
181 let global_col = h_col_idx[j] as usize;
182 let bc = global_col / bd;
183 let local_col = global_col % bd;
184
185 if let Ok(block_offset) = block_cols_for_row.binary_search(&(bc as u32)) {
187 let block_idx = block_start + block_offset;
188 let val_idx = block_idx * block_elems + local_row * bd + local_col;
189 bsr_values[val_idx] = h_values[j];
190 }
191 }
192 }
193 }
194
195 let _ = block_cols;
197
198 BsrMatrix::from_host(
199 csr.rows(),
200 csr.cols(),
201 block_dim,
202 &bsr_row_ptr,
203 &bsr_col_idx,
204 &bsr_values,
205 )
206}
207
208#[cfg(test)]
209mod tests {
210 #[test]
211 fn block_dim_zero_rejected() {
212 assert_eq!(4 % 2, 0);
214 assert_ne!(5 % 2, 0);
215 }
216
217 fn host_csr_to_csc(
229 rows: usize,
230 cols: usize,
231 row_ptr: &[i32],
232 col_idx: &[i32],
233 values: &[f32],
234 ) -> (Vec<i32>, Vec<i32>, Vec<f32>) {
235 let nnz = values.len();
236
237 let mut col_counts = vec![0i32; cols];
239 for &c in col_idx {
240 col_counts[c as usize] += 1;
241 }
242
243 let mut col_ptr = vec![0i32; cols + 1];
245 for i in 0..cols {
246 col_ptr[i + 1] = col_ptr[i] + col_counts[i];
247 }
248
249 let mut out_row_idx = vec![0i32; nnz];
251 let mut out_values = vec![0.0f32; nnz];
252 let mut write_pos = col_ptr.clone();
253
254 for row in 0..rows {
255 let start = row_ptr[row] as usize;
256 let end = row_ptr[row + 1] as usize;
257 for j in start..end {
258 let col = col_idx[j] as usize;
259 let dest = write_pos[col] as usize;
260 out_row_idx[dest] = row as i32;
261 out_values[dest] = values[j];
262 write_pos[col] += 1;
263 }
264 }
265
266 (col_ptr, out_row_idx, out_values)
267 }
268
269 fn host_csc_to_csr(
271 rows: usize,
272 cols: usize,
273 col_ptr: &[i32],
274 row_idx: &[i32],
275 values: &[f32],
276 ) -> (Vec<i32>, Vec<i32>, Vec<f32>) {
277 let nnz = values.len();
278
279 let mut row_counts = vec![0i32; rows];
281 for &r in row_idx {
282 row_counts[r as usize] += 1;
283 }
284
285 let mut out_row_ptr = vec![0i32; rows + 1];
287 for i in 0..rows {
288 out_row_ptr[i + 1] = out_row_ptr[i] + row_counts[i];
289 }
290
291 let mut out_col_idx = vec![0i32; nnz];
293 let mut out_values = vec![0.0f32; nnz];
294 let mut write_pos = out_row_ptr.clone();
295
296 for col in 0..cols {
297 let start = col_ptr[col] as usize;
298 let end = col_ptr[col + 1] as usize;
299 for j in start..end {
300 let row = row_idx[j] as usize;
301 let dest = write_pos[row] as usize;
302 out_col_idx[dest] = col as i32;
303 out_values[dest] = values[j];
304 write_pos[row] += 1;
305 }
306 }
307
308 for r in 0..rows {
310 let s = out_row_ptr[r] as usize;
311 let e = out_row_ptr[r + 1] as usize;
312 let mut row_pairs: Vec<(i32, f32)> =
314 (s..e).map(|i| (out_col_idx[i], out_values[i])).collect();
315 row_pairs.sort_by_key(|&(c, _)| c);
316 for (i, (c, v)) in row_pairs.into_iter().enumerate() {
317 out_col_idx[s + i] = c;
318 out_values[s + i] = v;
319 }
320 }
321
322 (out_row_ptr, out_col_idx, out_values)
323 }
324
325 fn host_coo_to_csr(
327 rows: usize,
328 row_idx: &[i32],
329 col_idx: &[i32],
330 values: &[f32],
331 ) -> (Vec<i32>, Vec<i32>, Vec<f32>) {
332 let nnz = values.len();
333
334 let mut triplets: Vec<(i32, i32, f32)> = (0..nnz)
336 .map(|i| (row_idx[i], col_idx[i], values[i]))
337 .collect();
338 triplets.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
339
340 let mut row_ptr = vec![0i32; rows + 1];
342 for &(r, _, _) in &triplets {
343 row_ptr[r as usize + 1] += 1;
344 }
345 for i in 0..rows {
346 row_ptr[i + 1] += row_ptr[i];
347 }
348
349 let sorted_col_idx: Vec<i32> = triplets.iter().map(|&(_, c, _)| c).collect();
350 let sorted_values: Vec<f32> = triplets.iter().map(|&(_, _, v)| v).collect();
351
352 (row_ptr, sorted_col_idx, sorted_values)
353 }
354
355 #[test]
356 fn test_csr_to_csc_round_trip() {
357 let rows = 4usize;
368 let cols = 4usize;
369 let row_ptr = vec![0i32, 2, 3, 5, 6];
370 let col_idx = vec![0i32, 3, 1, 2, 3, 0];
371 let values = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
372
373 let (col_ptr, csc_row_idx, csc_values) =
375 host_csr_to_csc(rows, cols, &row_ptr, &col_idx, &values);
376
377 assert_eq!(col_ptr, vec![0, 2, 3, 4, 6], "col_ptr mismatch");
379
380 let (rt_row_ptr, rt_col_idx, rt_values) =
382 host_csc_to_csr(rows, cols, &col_ptr, &csc_row_idx, &csc_values);
383
384 assert_eq!(rt_row_ptr, row_ptr, "round-trip row_ptr mismatch");
386 assert_eq!(rt_col_idx, col_idx, "round-trip col_idx mismatch");
387 for (a, b) in rt_values.iter().zip(values.iter()) {
388 assert!(
389 (a - b).abs() < 1e-6,
390 "round-trip value mismatch: {a} vs {b}"
391 );
392 }
393 }
394
395 #[test]
396 fn test_coo_to_csr_sorted() {
397 let rows = 3usize;
403 let row_idx = vec![2i32, 0, 1, 2, 0];
404 let col_idx = vec![3i32, 1, 0, 1, 3];
405 let values = vec![9.0f32, 1.0, 4.0, 7.0, 2.0];
406
407 let (row_ptr, out_col_idx, out_values) = host_coo_to_csr(rows, &row_idx, &col_idx, &values);
408
409 assert_eq!(row_ptr, vec![0, 2, 3, 5]);
411
412 for r in 0..rows {
414 let s = row_ptr[r] as usize;
415 let e = row_ptr[r + 1] as usize;
416 for i in s + 1..e {
417 assert!(
418 out_col_idx[i] >= out_col_idx[i - 1],
419 "row {r}: col_idx not sorted at position {i}"
420 );
421 }
422 }
423
424 assert_eq!(out_col_idx[0], 1);
427 assert!((out_values[0] - 1.0).abs() < 1e-6);
428 assert_eq!(out_col_idx[1], 3);
429 assert!((out_values[1] - 2.0).abs() < 1e-6);
430 assert_eq!(out_col_idx[2], 0);
432 assert!((out_values[2] - 4.0).abs() < 1e-6);
433 assert_eq!(out_col_idx[3], 1);
435 assert!((out_values[3] - 7.0).abs() < 1e-6);
436 assert_eq!(out_col_idx[4], 3);
437 assert!((out_values[4] - 9.0).abs() < 1e-6);
438 }
439
440 #[test]
441 fn test_csr_to_ell_padding() {
442 let rows = 3usize;
453 let max_nnz_per_row = 3usize;
454 let ell_sentinel = -1i32;
455
456 let ell_col_idx = vec![
461 2i32,
462 0,
463 1, ell_sentinel,
465 1,
466 3, ell_sentinel,
468 3,
469 ell_sentinel, ];
471 let ell_values = vec![
472 5.0f32, 1.0, 3.0, 0.0, 2.0, 6.0, 0.0, 4.0, 0.0, ];
476
477 assert_eq!(ell_col_idx.len(), rows * max_nnz_per_row);
478 assert_eq!(ell_values.len(), rows * max_nnz_per_row);
479
480 let expected_nnz_per_row = [1usize, 3, 2];
482 for r in 0..rows {
483 let count = (0..max_nnz_per_row)
484 .filter(|&k| ell_col_idx[k * rows + r] != ell_sentinel)
485 .count();
486 assert_eq!(
487 count, expected_nnz_per_row[r],
488 "row {r}: expected {} real entries, found {}",
489 expected_nnz_per_row[r], count
490 );
491 }
492
493 for idx in 0..ell_col_idx.len() {
495 if ell_col_idx[idx] == ell_sentinel {
496 assert!(
497 ell_values[idx].abs() < 1e-10,
498 "padded ELL value at index {idx} should be zero"
499 );
500 }
501 }
502
503 {
506 let r = 0usize;
507 let entries: Vec<(i32, f32)> = (0..max_nnz_per_row)
508 .filter_map(|k| {
509 let c = ell_col_idx[k * rows + r];
510 if c != ell_sentinel {
511 Some((c, ell_values[k * rows + r]))
512 } else {
513 None
514 }
515 })
516 .collect();
517 assert_eq!(entries, vec![(2, 5.0)]);
518 }
519 {
521 let r = 1usize;
522 let entries: Vec<(i32, f32)> = (0..max_nnz_per_row)
523 .filter_map(|k| {
524 let c = ell_col_idx[k * rows + r];
525 if c != ell_sentinel {
526 Some((c, ell_values[k * rows + r]))
527 } else {
528 None
529 }
530 })
531 .collect();
532 assert_eq!(entries, vec![(0, 1.0), (1, 2.0), (3, 4.0)]);
533 }
534 }
535}