1#![allow(dead_code)]
17
18use std::sync::Arc;
19
20use oxicuda_blas::GpuFloat;
21use oxicuda_driver::Module;
22use oxicuda_driver::ffi::CUdeviceptr;
23use oxicuda_launch::{Kernel, LaunchParams, grid_size_for};
24use oxicuda_ptx::arch::SmVersion;
25use oxicuda_ptx::builder::KernelBuilder;
26use oxicuda_ptx::ir::PtxType;
27
28use crate::error::{SparseError, SparseResult};
29use crate::format::CsrMatrix;
30use crate::handle::SparseHandle;
31use crate::ptx_helpers::{fma_float, load_float_imm, load_global_float, store_global_float};
32
33const SPGEMM_BLOCK_SIZE: u32 = 256;
35
36const HASH_TABLE_SIZE: u32 = 512;
40
41pub fn spgemm_symbolic<T: GpuFloat>(
58 handle: &SparseHandle,
59 a: &CsrMatrix<T>,
60 b: &CsrMatrix<T>,
61) -> SparseResult<Vec<i32>> {
62 validate_spgemm_dims(a, b)?;
63
64 let m = a.rows();
65 if m == 0 {
66 return Ok(vec![0]);
67 }
68
69 let d_row_nnz = oxicuda_memory::DeviceBuffer::<i32>::zeroed(m as usize)?;
71
72 let ptx = emit_spgemm_symbolic_kernel::<T>(handle.sm_version())?;
73 let module = Arc::new(Module::from_ptx(&ptx)?);
74 let kernel = Kernel::from_module(module, "spgemm_symbolic")?;
75
76 let block_size = SPGEMM_BLOCK_SIZE;
77 let grid_size = grid_size_for(m, block_size);
78 let params = LaunchParams::new(grid_size, block_size);
79
80 kernel.launch(
81 ¶ms,
82 handle.stream(),
83 &(
84 a.row_ptr().as_device_ptr(),
85 a.col_idx().as_device_ptr(),
86 b.row_ptr().as_device_ptr(),
87 b.col_idx().as_device_ptr(),
88 d_row_nnz.as_device_ptr(),
89 m,
90 b.cols(),
91 ),
92 )?;
93
94 handle.stream().synchronize()?;
100
101 let mut h_row_nnz = vec![0i32; m as usize];
103 d_row_nnz.copy_to_host(&mut h_row_nnz)?;
104
105 let mut row_ptr = vec![0i32; m as usize + 1];
106 for i in 0..m as usize {
107 row_ptr[i + 1] = row_ptr[i] + h_row_nnz[i];
108 }
109
110 Ok(row_ptr)
111}
112
113#[allow(clippy::too_many_arguments)]
132pub fn spgemm_numeric<T: GpuFloat>(
133 handle: &SparseHandle,
134 a: &CsrMatrix<T>,
135 b: &CsrMatrix<T>,
136 c_row_ptr: CUdeviceptr,
137 c_col_idx: CUdeviceptr,
138 c_values: CUdeviceptr,
139) -> SparseResult<()> {
140 validate_spgemm_dims(a, b)?;
141
142 let m = a.rows();
143 if m == 0 {
144 return Ok(());
145 }
146
147 let ptx = emit_spgemm_numeric_kernel::<T>(handle.sm_version())?;
148 let module = Arc::new(Module::from_ptx(&ptx)?);
149 let kernel = Kernel::from_module(module, "spgemm_numeric")?;
150
151 let block_size = SPGEMM_BLOCK_SIZE;
152 let grid_size = grid_size_for(m, block_size);
153 let params = LaunchParams::new(grid_size, block_size);
154
155 kernel.launch(
156 ¶ms,
157 handle.stream(),
158 &(
159 a.row_ptr().as_device_ptr(),
160 a.col_idx().as_device_ptr(),
161 a.values().as_device_ptr(),
162 b.row_ptr().as_device_ptr(),
163 b.col_idx().as_device_ptr(),
164 b.values().as_device_ptr(),
165 c_row_ptr,
166 c_col_idx,
167 c_values,
168 m,
169 b.cols(),
170 ),
171 )?;
172
173 Ok(())
174}
175
176fn validate_spgemm_dims<T: GpuFloat>(a: &CsrMatrix<T>, b: &CsrMatrix<T>) -> SparseResult<()> {
178 if a.cols() != b.rows() {
179 return Err(SparseError::DimensionMismatch(format!(
180 "A.cols ({}) != B.rows ({})",
181 a.cols(),
182 b.rows()
183 )));
184 }
185 Ok(())
186}
187
188fn emit_spgemm_symbolic_kernel<T: GpuFloat>(sm: SmVersion) -> SparseResult<String> {
194 let _ = T::PTX_TYPE; KernelBuilder::new("spgemm_symbolic")
197 .target(sm)
198 .param("a_row_ptr", PtxType::U64)
199 .param("a_col_idx", PtxType::U64)
200 .param("b_row_ptr", PtxType::U64)
201 .param("b_col_idx", PtxType::U64)
202 .param("row_nnz", PtxType::U64)
203 .param("m", PtxType::U32)
204 .param("n", PtxType::U32)
205 .body(move |b| {
206 let gid = b.global_thread_id_x();
207 let m_param = b.load_param_u32("m");
208
209 let gid_inner = gid.clone();
210 b.if_lt_u32(gid, m_param, move |b| {
211 let row = gid_inner;
212 let a_row_ptr = b.load_param_u64("a_row_ptr");
213 let a_col_idx = b.load_param_u64("a_col_idx");
214 let b_row_ptr = b.load_param_u64("b_row_ptr");
215 let _b_col_idx = b.load_param_u64("b_col_idx");
216 let row_nnz_ptr = b.load_param_u64("row_nnz");
217
218 let a_rs_addr = b.byte_offset_addr(a_row_ptr.clone(), row.clone(), 4);
220 let a_rs_i32 = b.load_global_i32(a_rs_addr);
221 let a_rs = b.alloc_reg(PtxType::U32);
222 b.raw_ptx(&format!("mov.b32 {a_rs}, {a_rs_i32};"));
223
224 let row_p1 = b.alloc_reg(PtxType::U32);
225 b.raw_ptx(&format!("add.u32 {row_p1}, {row}, 1;"));
226 let a_re_addr = b.byte_offset_addr(a_row_ptr, row_p1, 4);
227 let a_re_i32 = b.load_global_i32(a_re_addr);
228 let a_re = b.alloc_reg(PtxType::U32);
229 b.raw_ptx(&format!("mov.b32 {a_re}, {a_re_i32};"));
230
231 let count = b.alloc_reg(PtxType::U32);
233 b.raw_ptx(&format!("mov.u32 {count}, 0;"));
234
235 let a_k = b.alloc_reg(PtxType::U32);
237 b.raw_ptx(&format!("mov.u32 {a_k}, {a_rs};"));
238
239 let outer_loop = b.fresh_label("spgemm_sym_outer");
240 let outer_done = b.fresh_label("spgemm_sym_outer_done");
241
242 b.label(&outer_loop);
243 let a_pred = b.alloc_reg(PtxType::Pred);
246 b.raw_ptx(&format!("setp.hs.u32 {a_pred}, {a_k}, {a_re};"));
247 b.branch_if(a_pred, &outer_done);
248
249 let a_ci_addr = b.byte_offset_addr(a_col_idx.clone(), a_k.clone(), 4);
251 let a_col_i32 = b.load_global_i32(a_ci_addr);
252 let a_col = b.alloc_reg(PtxType::U32);
253 b.raw_ptx(&format!("mov.b32 {a_col}, {a_col_i32};"));
254
255 let b_rs_addr = b.byte_offset_addr(b_row_ptr.clone(), a_col.clone(), 4);
257 let b_rs_i32 = b.load_global_i32(b_rs_addr);
258 let b_rs = b.alloc_reg(PtxType::U32);
259 b.raw_ptx(&format!("mov.b32 {b_rs}, {b_rs_i32};"));
260
261 let a_col_p1 = b.alloc_reg(PtxType::U32);
262 b.raw_ptx(&format!("add.u32 {a_col_p1}, {a_col}, 1;"));
263 let b_re_addr = b.byte_offset_addr(b_row_ptr.clone(), a_col_p1, 4);
264 let b_re_i32 = b.load_global_i32(b_re_addr);
265 let b_re = b.alloc_reg(PtxType::U32);
266 b.raw_ptx(&format!("mov.b32 {b_re}, {b_re_i32};"));
267
268 let b_j = b.alloc_reg(PtxType::U32);
270 b.raw_ptx(&format!("mov.u32 {b_j}, {b_rs};"));
271
272 let inner_loop = b.fresh_label("spgemm_sym_inner");
273 let inner_done = b.fresh_label("spgemm_sym_inner_done");
274
275 b.label(&inner_loop);
276 let b_pred = b.alloc_reg(PtxType::Pred);
278 b.raw_ptx(&format!("setp.hs.u32 {b_pred}, {b_j}, {b_re};"));
279 b.branch_if(b_pred, &inner_done);
280
281 b.raw_ptx(&format!("add.u32 {count}, {count}, 1;"));
286
287 b.raw_ptx(&format!("add.u32 {b_j}, {b_j}, 1;"));
288 b.branch(&inner_loop);
289 b.label(&inner_done);
290
291 b.raw_ptx(&format!("add.u32 {a_k}, {a_k}, 1;"));
292 b.branch(&outer_loop);
293 b.label(&outer_done);
294
295 let out_addr = b.byte_offset_addr(row_nnz_ptr, row, 4);
297 b.store_global_i32(out_addr, count);
298 });
299
300 b.ret();
301 })
302 .build()
303 .map_err(|e| SparseError::PtxGeneration(e.to_string()))
304}
305
306fn emit_spgemm_numeric_kernel<T: GpuFloat>(sm: SmVersion) -> SparseResult<String> {
312 let elem_bytes = T::size_u32();
313 let _is_f64 = T::SIZE == 8;
314
315 KernelBuilder::new("spgemm_numeric")
316 .target(sm)
317 .param("a_row_ptr", PtxType::U64)
318 .param("a_col_idx", PtxType::U64)
319 .param("a_values", PtxType::U64)
320 .param("b_row_ptr", PtxType::U64)
321 .param("b_col_idx", PtxType::U64)
322 .param("b_values", PtxType::U64)
323 .param("c_row_ptr", PtxType::U64)
324 .param("c_col_idx", PtxType::U64)
325 .param("c_values", PtxType::U64)
326 .param("m", PtxType::U32)
327 .param("n", PtxType::U32)
328 .body(move |b| {
329 let gid = b.global_thread_id_x();
330 let m_param = b.load_param_u32("m");
331
332 let gid_inner = gid.clone();
333 b.if_lt_u32(gid, m_param, move |b| {
334 let row = gid_inner;
335 let a_row_ptr = b.load_param_u64("a_row_ptr");
336 let a_col_idx = b.load_param_u64("a_col_idx");
337 let a_values = b.load_param_u64("a_values");
338 let b_row_ptr = b.load_param_u64("b_row_ptr");
339 let b_col_idx_p = b.load_param_u64("b_col_idx");
340 let b_values = b.load_param_u64("b_values");
341 let c_row_ptr = b.load_param_u64("c_row_ptr");
342 let c_col_idx_p = b.load_param_u64("c_col_idx");
343 let c_values = b.load_param_u64("c_values");
344
345 let a_rs_addr = b.byte_offset_addr(a_row_ptr.clone(), row.clone(), 4);
347 let a_rs_i32 = b.load_global_i32(a_rs_addr);
348 let a_rs = b.alloc_reg(PtxType::U32);
349 b.raw_ptx(&format!("mov.b32 {a_rs}, {a_rs_i32};"));
350
351 let row_p1 = b.alloc_reg(PtxType::U32);
352 b.raw_ptx(&format!("add.u32 {row_p1}, {row}, 1;"));
353 let a_re_addr = b.byte_offset_addr(a_row_ptr, row_p1, 4);
354 let a_re_i32 = b.load_global_i32(a_re_addr);
355 let a_re = b.alloc_reg(PtxType::U32);
356 b.raw_ptx(&format!("mov.b32 {a_re}, {a_re_i32};"));
357
358 let c_rs_addr = b.byte_offset_addr(c_row_ptr, row, 4);
360 let c_rs_i32 = b.load_global_i32(c_rs_addr);
361 let c_pos = b.alloc_reg(PtxType::U32);
362 b.raw_ptx(&format!("mov.b32 {c_pos}, {c_rs_i32};"));
363
364 let a_k = b.alloc_reg(PtxType::U32);
366 b.raw_ptx(&format!("mov.u32 {a_k}, {a_rs};"));
367
368 let outer_loop = b.fresh_label("spgemm_num_outer");
369 let outer_done = b.fresh_label("spgemm_num_outer_done");
370
371 b.label(&outer_loop);
372 let a_pred = b.alloc_reg(PtxType::Pred);
375 b.raw_ptx(&format!("setp.hs.u32 {a_pred}, {a_k}, {a_re};"));
376 b.branch_if(a_pred, &outer_done);
377
378 let a_ci_addr = b.byte_offset_addr(a_col_idx.clone(), a_k.clone(), 4);
380 let a_col_i32 = b.load_global_i32(a_ci_addr);
381 let a_col = b.alloc_reg(PtxType::U32);
382 b.raw_ptx(&format!("mov.b32 {a_col}, {a_col_i32};"));
383
384 let a_v_addr = b.byte_offset_addr(a_values.clone(), a_k.clone(), elem_bytes);
385 let a_val = load_global_float::<T>(b, a_v_addr);
386
387 let b_rs_addr = b.byte_offset_addr(b_row_ptr.clone(), a_col.clone(), 4);
389 let b_rs_i32 = b.load_global_i32(b_rs_addr);
390 let b_rs = b.alloc_reg(PtxType::U32);
391 b.raw_ptx(&format!("mov.b32 {b_rs}, {b_rs_i32};"));
392
393 let a_col_p1 = b.alloc_reg(PtxType::U32);
394 b.raw_ptx(&format!("add.u32 {a_col_p1}, {a_col}, 1;"));
395 let b_re_addr = b.byte_offset_addr(b_row_ptr.clone(), a_col_p1, 4);
396 let b_re_i32 = b.load_global_i32(b_re_addr);
397 let b_re = b.alloc_reg(PtxType::U32);
398 b.raw_ptx(&format!("mov.b32 {b_re}, {b_re_i32};"));
399
400 let b_j = b.alloc_reg(PtxType::U32);
402 b.raw_ptx(&format!("mov.u32 {b_j}, {b_rs};"));
403
404 let inner_loop = b.fresh_label("spgemm_num_inner");
405 let inner_done = b.fresh_label("spgemm_num_inner_done");
406
407 b.label(&inner_loop);
408 let b_pred = b.alloc_reg(PtxType::Pred);
410 b.raw_ptx(&format!("setp.hs.u32 {b_pred}, {b_j}, {b_re};"));
411 b.branch_if(b_pred, &inner_done);
412
413 let b_ci_addr = b.byte_offset_addr(b_col_idx_p.clone(), b_j.clone(), 4);
415 let b_col_i32 = b.load_global_i32(b_ci_addr);
416
417 let b_v_addr = b.byte_offset_addr(b_values.clone(), b_j.clone(), elem_bytes);
418 let b_val = load_global_float::<T>(b, b_v_addr);
419
420 let zero = load_float_imm::<T>(b, 0.0);
422 let c_val = fma_float::<T>(b, a_val.clone(), b_val, zero);
423
424 let c_ci_addr = b.byte_offset_addr(c_col_idx_p.clone(), c_pos.clone(), 4);
426 b.store_global_i32(c_ci_addr, b_col_i32);
427
428 let c_v_addr = b.byte_offset_addr(c_values.clone(), c_pos.clone(), elem_bytes);
430 store_global_float::<T>(b, c_v_addr, c_val);
431
432 b.raw_ptx(&format!("add.u32 {c_pos}, {c_pos}, 1;"));
434
435 b.raw_ptx(&format!("add.u32 {b_j}, {b_j}, 1;"));
436 b.branch(&inner_loop);
437 b.label(&inner_done);
438
439 b.raw_ptx(&format!("add.u32 {a_k}, {a_k}, 1;"));
440 b.branch(&outer_loop);
441 b.label(&outer_done);
442 });
443
444 b.ret();
445 })
446 .build()
447 .map_err(|e| SparseError::PtxGeneration(e.to_string()))
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::ptx_helpers::test_support::assert_assembles_and_clean;
454 use oxicuda_ptx::arch::SmVersion;
455
456 #[test]
459 fn spgemm_symbolic_numeric_f32_f64_assemble_sm86() {
460 let sym_f32 = emit_spgemm_symbolic_kernel::<f32>(SmVersion::Sm86).expect("sym f32");
461 assert_assembles_and_clean("spgemm_symbolic_f32", &sym_f32);
462 let sym_f64 = emit_spgemm_symbolic_kernel::<f64>(SmVersion::Sm86).expect("sym f64");
463 assert_assembles_and_clean("spgemm_symbolic_f64", &sym_f64);
464
465 let num_f32 = emit_spgemm_numeric_kernel::<f32>(SmVersion::Sm86).expect("num f32");
466 assert_assembles_and_clean("spgemm_numeric_f32", &num_f32);
467 let num_f64 = emit_spgemm_numeric_kernel::<f64>(SmVersion::Sm86).expect("num f64");
468 assert_assembles_and_clean("spgemm_numeric_f64", &num_f64);
469 assert!(
470 !num_f64.contains("0F00000000"),
471 "f64 SpGEMM numeric kernel must not materialize an f32 0.0 immediate:\n{num_f64}"
472 );
473 }
474
475 #[test]
476 fn spgemm_symbolic_ptx_generates_f32() {
477 let ptx = emit_spgemm_symbolic_kernel::<f32>(SmVersion::Sm80);
478 assert!(ptx.is_ok());
479 let ptx_str = ptx.expect("test: PTX gen should succeed");
480 assert!(ptx_str.contains(".entry spgemm_symbolic"));
481 }
482
483 #[test]
484 fn spgemm_symbolic_ptx_generates_f64() {
485 let ptx = emit_spgemm_symbolic_kernel::<f64>(SmVersion::Sm80);
486 assert!(ptx.is_ok());
487 }
488
489 #[test]
490 fn spgemm_numeric_ptx_generates_f32() {
491 let ptx = emit_spgemm_numeric_kernel::<f32>(SmVersion::Sm80);
492 assert!(ptx.is_ok());
493 let ptx_str = ptx.expect("test: PTX gen should succeed");
494 assert!(ptx_str.contains(".entry spgemm_numeric"));
495 }
496
497 #[test]
498 fn spgemm_numeric_ptx_generates_f64() {
499 let ptx = emit_spgemm_numeric_kernel::<f64>(SmVersion::Sm80);
500 assert!(ptx.is_ok());
501 }
502
503 #[test]
504 fn validate_dims_mismatch() {
505 let err = SparseError::DimensionMismatch("A.cols (3) != B.rows (4)".to_string());
507 assert!(err.to_string().contains("A.cols"));
508 }
509}
510
511#[cfg(all(test, feature = "gpu-tests"))]
516mod gpu_device_tests {
517 use super::*;
518 use crate::gpu_test_support::gpu_handle;
519 use crate::host_csr::{f64_to_gpu, gpu_to_f64};
520 use oxicuda_memory::DeviceBuffer;
521
522 fn cpu_spgemm_expanded(
527 a_rows: usize,
528 a_row_ptr: &[i32],
529 a_col_idx: &[i32],
530 a_values: &[f64],
531 b_row_ptr: &[i32],
532 b_col_idx: &[i32],
533 b_values: &[f64],
534 ) -> (Vec<i32>, Vec<i32>, Vec<f64>) {
535 let mut row_ptr = vec![0i32];
536 let mut col_idx = Vec::new();
537 let mut values = Vec::new();
538 for row in 0..a_rows {
539 for ak in a_row_ptr[row] as usize..a_row_ptr[row + 1] as usize {
540 let a_col = a_col_idx[ak] as usize;
541 let a_val = a_values[ak];
542 for bj in b_row_ptr[a_col] as usize..b_row_ptr[a_col + 1] as usize {
543 col_idx.push(b_col_idx[bj]);
544 values.push(a_val * b_values[bj]);
545 }
546 }
547 row_ptr.push(col_idx.len() as i32);
548 }
549 (row_ptr, col_idx, values)
550 }
551
552 #[allow(clippy::too_many_arguments)]
553 fn run_spgemm<T: GpuFloat>(
554 a_rows: u32,
555 a_cols: u32,
556 a_row_ptr: &[i32],
557 a_col_idx: &[i32],
558 a_values: &[f64],
559 b_rows: u32,
560 b_cols: u32,
561 b_row_ptr: &[i32],
562 b_col_idx: &[i32],
563 b_values: &[f64],
564 tol: f64,
565 tag: &str,
566 ) {
567 let Some(handle) = gpu_handle() else {
568 return;
569 };
570 let a_dev: Vec<T> = a_values.iter().map(|&v| f64_to_gpu::<T>(v)).collect();
571 let b_dev: Vec<T> = b_values.iter().map(|&v| f64_to_gpu::<T>(v)).collect();
572 let a = CsrMatrix::<T>::from_host(a_rows, a_cols, a_row_ptr, a_col_idx, &a_dev)
573 .expect("test: build A");
574 let b = CsrMatrix::<T>::from_host(b_rows, b_cols, b_row_ptr, b_col_idx, &b_dev)
575 .expect("test: build B");
576
577 let c_row_ptr = spgemm_symbolic::<T>(&handle, &a, &b).expect("test: symbolic");
579
580 let (want_rp, want_ci, want_vals) = cpu_spgemm_expanded(
581 a_rows as usize,
582 a_row_ptr,
583 a_col_idx,
584 a_values,
585 b_row_ptr,
586 b_col_idx,
587 b_values,
588 );
589 assert_eq!(c_row_ptr, want_rp, "{tag}: symbolic row_ptr mismatch");
590
591 let nnz_c = *c_row_ptr.last().expect("test: row_ptr non-empty") as usize;
592 let c_row_ptr_buf = DeviceBuffer::from_host(&c_row_ptr).expect("test: upload C row_ptr");
593 let c_col_idx_buf =
594 DeviceBuffer::from_host(&vec![0i32; nnz_c]).expect("test: alloc C col_idx");
595 let c_values_buf =
596 DeviceBuffer::from_host(&vec![T::gpu_zero(); nnz_c]).expect("test: alloc C values");
597
598 spgemm_numeric::<T>(
599 &handle,
600 &a,
601 &b,
602 c_row_ptr_buf.as_device_ptr(),
603 c_col_idx_buf.as_device_ptr(),
604 c_values_buf.as_device_ptr(),
605 )
606 .expect("test: numeric");
607 handle.stream().synchronize().expect("test: sync");
608
609 let mut got_ci = vec![0i32; nnz_c];
610 c_col_idx_buf
611 .copy_to_host(&mut got_ci)
612 .expect("test: download col_idx");
613 let mut got_vals_t = vec![T::gpu_zero(); nnz_c];
614 c_values_buf
615 .copy_to_host(&mut got_vals_t)
616 .expect("test: download values");
617 let got_vals: Vec<f64> = got_vals_t.iter().map(|&v| gpu_to_f64(v)).collect();
618
619 assert_eq!(got_ci, want_ci, "{tag}: numeric col_idx mismatch");
620 assert_eq!(got_vals.len(), want_vals.len(), "{tag}: values length");
621 for (i, (g, w)) in got_vals.iter().zip(want_vals.iter()).enumerate() {
622 let diff = (g - w).abs();
623 let scale = w.abs().max(1.0);
624 assert!(
625 diff <= tol * scale,
626 "{tag}: value {i}: got {g}, want {w} (|diff| {diff})"
627 );
628 }
629 }
630
631 #[test]
632 fn spgemm_2x3_times_3x2_f64() {
633 let a_rp = vec![0, 2, 3];
638 let a_ci = vec![0, 2, 1];
639 let a_v = vec![1.0, 2.0, 3.0];
640 let b_rp = vec![0, 2, 3, 4];
641 let b_ci = vec![0, 1, 0, 1];
642 let b_v = vec![10.0, 20.0, 30.0, 40.0];
643 run_spgemm::<f64>(
644 2,
645 3,
646 &a_rp,
647 &a_ci,
648 &a_v,
649 3,
650 2,
651 &b_rp,
652 &b_ci,
653 &b_v,
654 1e-10,
655 "spgemm_f64",
656 );
657 }
658
659 #[test]
660 fn spgemm_2x3_times_3x2_f32() {
661 let a_rp = vec![0, 2, 3];
662 let a_ci = vec![0, 2, 1];
663 let a_v = vec![1.5, -2.0, 3.25];
664 let b_rp = vec![0, 2, 3, 4];
665 let b_ci = vec![0, 1, 0, 1];
666 let b_v = vec![10.0, -20.0, 30.0, 40.0];
667 run_spgemm::<f32>(
668 2,
669 3,
670 &a_rp,
671 &a_ci,
672 &a_v,
673 3,
674 2,
675 &b_rp,
676 &b_ci,
677 &b_v,
678 1e-4,
679 "spgemm_f32",
680 );
681 }
682
683 #[test]
684 fn spgemm_identity_left_f64() {
685 let i_rp = vec![0, 1, 2, 3];
687 let i_ci = vec![0, 1, 2];
688 let i_v = vec![1.0, 1.0, 1.0];
689 let b_rp = vec![0, 2, 3, 5];
690 let b_ci = vec![0, 2, 1, 0, 2];
691 let b_v = vec![7.0, 8.0, 9.0, 11.0, 13.0];
692 run_spgemm::<f64>(
693 3,
694 3,
695 &i_rp,
696 &i_ci,
697 &i_v,
698 3,
699 3,
700 &b_rp,
701 &b_ci,
702 &b_v,
703 1e-10,
704 "spgemm_identity",
705 );
706 }
707}