Skip to main content

baracuda_cusparse/
lib.rs

1//! Safe Rust wrappers for NVIDIA cuSPARSE.
2//!
3//! Covers the modern generic-API surface: `Handle`, `SpMat` (CSR/CSC/COO/BSR),
4//! `DnMat`, `DnVec`, and the family of op algorithms — SpMV, SpMM, SpGEMM,
5//! SpSV, SpSM, SDDMM — plus CSR↔CSC and sparse↔dense conversions, and the
6//! sparse BLAS-1 helpers (`axpby`, `gather`, `scatter`, `rot`).
7//!
8//! All matrix/vector descriptors borrow the underlying [`DeviceBuffer`]s and
9//! tie their lifetime to them so the buffers can't be freed while cuSPARSE
10//! is still holding references.
11//!
12//! # Examples
13//!
14//! **SpMV (sparse-times-dense vector):** `y = A · x` for a 4×4 CSR matrix
15//! with 6 non-zeros.
16//!
17//! ```no_run
18//! use baracuda_cusparse::{spmv, spmv_buffer_size, DnVec, Handle, Op, SpMVAlg, SpMat};
19//! use baracuda_driver::{Context, Device, DeviceBuffer};
20//!
21//! # fn demo() -> Result<(), Box<dyn std::error::Error>> {
22//! baracuda_driver::init()?;
23//! let device = Device::get(0)?;
24//! let ctx = Context::new(&device)?;
25//! let handle = Handle::new()?;
26//!
27//! // A = [[1, 0, 0, 2], [0, 3, 0, 0], [0, 0, 0, 4], [5, 0, 6, 0]], nnz = 6
28//! let mut row_offsets = DeviceBuffer::from_slice(&ctx, &[0i32, 2, 3, 4, 6])?;
29//! let mut col_indices = DeviceBuffer::from_slice(&ctx, &[0i32, 3, 1, 3, 0, 2])?;
30//! let mut values     = DeviceBuffer::from_slice(&ctx, &[1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0])?;
31//! let mut x          = DeviceBuffer::from_slice(&ctx, &[1.0f32, 2.0, 3.0, 4.0])?;
32//! let mut y: DeviceBuffer<f32> = DeviceBuffer::zeros(&ctx, 4)?;
33//!
34//! let a = SpMat::csr(4, 4, 6, &mut row_offsets, &mut col_indices, &mut values)?;
35//! let x_d = DnVec::new(&mut x)?;
36//! let mut y_d = DnVec::new(&mut y)?;
37//!
38//! let bytes = spmv_buffer_size::<f32>(
39//!     &handle, Op::N, &1.0, &a, &x_d, &0.0, &y_d, SpMVAlg::Default)?;
40//! let mut workspace: DeviceBuffer<u8> = DeviceBuffer::new(&ctx, bytes.max(1))?;
41//! spmv::<f32>(&handle, Op::N, &1.0, &a, &x_d, &0.0, &mut y_d, SpMVAlg::Default, &mut workspace)?;
42//! # Ok(()) }
43//! ```
44//!
45//! **COO descriptor build** — feed an unsorted `(row, col, value)` triple
46//! stream into cuSPARSE without pre-converting to CSR:
47//!
48//! ```no_run
49//! use baracuda_cusparse::SpMat;
50//! use baracuda_driver::{Context, Device, DeviceBuffer};
51//!
52//! # fn demo() -> Result<(), Box<dyn std::error::Error>> {
53//! baracuda_driver::init()?;
54//! let device = Device::get(0)?;
55//! let ctx = Context::new(&device)?;
56//!
57//! // Same 4×4 matrix as above, expressed as (row, col, value) triples.
58//! let mut row_idx = DeviceBuffer::from_slice(&ctx, &[0i32, 0, 1, 2, 3, 3])?;
59//! let mut col_idx = DeviceBuffer::from_slice(&ctx, &[0i32, 3, 1, 3, 0, 2])?;
60//! let mut values  = DeviceBuffer::from_slice(&ctx, &[1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0])?;
61//!
62//! let _coo = SpMat::coo(4, 4, 6, &mut row_idx, &mut col_idx, &mut values)?;
63//! // Pass `_coo` to `spmv`, `spmm`, or `sparse_to_dense` just like a CSR descriptor.
64//! # Ok(()) }
65//! ```
66//!
67//! **SpMM (sparse-times-dense matrix):** `C = A · B` where `A` is sparse,
68//! `B` and `C` are dense column-major.
69//!
70//! ```no_run
71//! use baracuda_cusparse::{spmm, spmm_buffer_size, DnMat, Handle, Op, Order, SpMMAlg, SpMat};
72//! use baracuda_driver::{Context, Device, DeviceBuffer};
73//!
74//! # fn demo() -> Result<(), Box<dyn std::error::Error>> {
75//! baracuda_driver::init()?;
76//! let device = Device::get(0)?;
77//! let ctx = Context::new(&device)?;
78//! let handle = Handle::new()?;
79//!
80//! // 4×4 CSR A (same as the SpMV example) times a 4×2 dense B → 4×2 dense C.
81//! let mut row_offsets = DeviceBuffer::from_slice(&ctx, &[0i32, 2, 3, 4, 6])?;
82//! let mut col_indices = DeviceBuffer::from_slice(&ctx, &[0i32, 3, 1, 3, 0, 2])?;
83//! let mut values     = DeviceBuffer::from_slice(&ctx, &[1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0])?;
84//! let mut b_data: DeviceBuffer<f32> = DeviceBuffer::zeros(&ctx, 4 * 2)?;
85//! let mut c_data: DeviceBuffer<f32> = DeviceBuffer::zeros(&ctx, 4 * 2)?;
86//!
87//! let a = SpMat::csr(4, 4, 6, &mut row_offsets, &mut col_indices, &mut values)?;
88//! let b = DnMat::new(4, 2, /* ld */ 4, Order::Col, &mut b_data)?;
89//! let mut c = DnMat::new(4, 2, /* ld */ 4, Order::Col, &mut c_data)?;
90//!
91//! let bytes = spmm_buffer_size::<f32>(
92//!     &handle, Op::N, Op::N, &1.0, &a, &b, &0.0, &c, SpMMAlg::Default)?;
93//! let mut workspace: DeviceBuffer<u8> = DeviceBuffer::new(&ctx, bytes.max(1))?;
94//! spmm::<f32>(&handle, Op::N, Op::N, &1.0, &a, &b, &0.0, &mut c, SpMMAlg::Default,
95//!     &mut workspace)?;
96//! # Ok(()) }
97//! ```
98
99#![warn(missing_debug_implementations)]
100
101use core::ffi::c_void;
102use std::marker::PhantomData;
103
104use baracuda_cusparse_sys::{
105    cudaDataType, cusparse, cusparseDiagType_t, cusparseDnMatDescr_t, cusparseDnVecDescr_t,
106    cusparseFillMode_t, cusparseHandle_t, cusparseIndexBase_t, cusparseIndexType_t,
107    cusparseOperation_t, cusparseOrder_t, cusparseSpGEMMDescr_t, cusparseSpMatAttribute_t,
108    cusparseSpMatDescr_t, cusparseSpSMDescr_t, cusparseSpSVDescr_t, cusparseStatus_t,
109};
110use baracuda_driver::{DeviceBuffer, Stream};
111use baracuda_types::{Complex32, Complex64};
112
113pub use baracuda_cusparse_sys::{
114    cusparseCsr2CscAlg_t as Csr2CscAlg, cusparseIndexBase_t as IndexBase,
115    cusparseSDDMMAlg_t as SDDMMAlg, cusparseSpGEMMAlg_t as SpGEMMAlg,
116    cusparseSpMMAlg_t as SpMMAlg, cusparseSpMVAlg_t as SpMVAlg, cusparseSpSMAlg_t as SpSMAlg,
117    cusparseSpSVAlg_t as SpSVAlg,
118};
119
120/// Error type for cuSPARSE operations.
121pub type Error = baracuda_core::Error<cusparseStatus_t>;
122/// Result alias.
123pub type Result<T, E = Error> = core::result::Result<T, E>;
124
125#[inline]
126fn check(status: cusparseStatus_t) -> Result<()> {
127    Error::check(status)
128}
129
130// ---- scalar <-> cudaDataType --------------------------------------------
131
132/// Types that cuSPARSE's generic API accepts as element / compute type.
133pub trait SparseScalar: sealed::Sealed + Copy + 'static {
134    /// cuSPARSE / cuBLAS element-type tag.
135    fn data_type() -> cudaDataType;
136}
137
138impl SparseScalar for f32 {
139    fn data_type() -> cudaDataType {
140        cudaDataType::R_32F
141    }
142}
143impl SparseScalar for f64 {
144    fn data_type() -> cudaDataType {
145        cudaDataType::R_64F
146    }
147}
148impl SparseScalar for Complex32 {
149    fn data_type() -> cudaDataType {
150        cudaDataType::C_32F
151    }
152}
153impl SparseScalar for Complex64 {
154    fn data_type() -> cudaDataType {
155        cudaDataType::C_64F
156    }
157}
158
159mod sealed {
160    use baracuda_types::{Complex32, Complex64};
161    pub trait Sealed {}
162    impl Sealed for f32 {}
163    impl Sealed for f64 {}
164    impl Sealed for Complex32 {}
165    impl Sealed for Complex64 {}
166}
167
168// ---- Op / Order / Fill / Diag wrappers ----------------------------------
169
170/// Transpose / conjugate-transpose selector.
171#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
172pub enum Op {
173    /// No transpose.
174    #[default]
175    N,
176    /// Transpose.
177    T,
178    /// Conjugate transpose (real types: same as `T`).
179    C,
180}
181
182impl Op {
183    fn raw(self) -> cusparseOperation_t {
184        match self {
185            Op::N => cusparseOperation_t::N,
186            Op::T => cusparseOperation_t::T,
187            Op::C => cusparseOperation_t::C,
188        }
189    }
190}
191
192/// Dense-matrix storage order.
193#[derive(Copy, Clone, Debug, Eq, PartialEq)]
194pub enum Order {
195    /// Row-major (C-style) storage.
196    Row,
197    /// Column-major (Fortran-style) storage.
198    Col,
199}
200
201impl Order {
202    fn raw(self) -> cusparseOrder_t {
203        match self {
204            Order::Row => cusparseOrder_t::Row,
205            Order::Col => cusparseOrder_t::Col,
206        }
207    }
208}
209
210/// Triangular storage mode for sparse matrices.
211#[derive(Copy, Clone, Debug, Eq, PartialEq)]
212pub enum Fill {
213    /// Lower triangle is the populated half.
214    Lower,
215    /// Upper triangle is the populated half.
216    Upper,
217}
218
219impl Fill {
220    fn raw(self) -> cusparseFillMode_t {
221        match self {
222            Fill::Lower => cusparseFillMode_t::Lower,
223            Fill::Upper => cusparseFillMode_t::Upper,
224        }
225    }
226}
227
228/// Unit-diagonal flag for triangular sparse matrices.
229#[derive(Copy, Clone, Debug, Eq, PartialEq)]
230pub enum Diag {
231    /// Diagonal entries are read from the matrix.
232    NonUnit,
233    /// Diagonal entries are implicitly 1 (not read).
234    Unit,
235}
236
237impl Diag {
238    fn raw(self) -> cusparseDiagType_t {
239        match self {
240            Diag::NonUnit => cusparseDiagType_t::NonUnit,
241            Diag::Unit => cusparseDiagType_t::Unit,
242        }
243    }
244}
245
246// ---- Handle -------------------------------------------------------------
247
248/// Owned cuSPARSE handle.
249pub struct Handle {
250    handle: cusparseHandle_t,
251}
252
253unsafe impl Send for Handle {}
254
255impl core::fmt::Debug for Handle {
256    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
257        f.debug_struct("cusparse::Handle")
258            .field("handle", &self.handle)
259            .finish()
260    }
261}
262
263impl Handle {
264    /// Create a new cuSPARSE handle (`cusparseCreate`).
265    pub fn new() -> Result<Self> {
266        let c = cusparse()?;
267        let cu = c.cusparse_create()?;
268        let mut h: cusparseHandle_t = core::ptr::null_mut();
269        check(unsafe { cu(&mut h) })?;
270        Ok(Self { handle: h })
271    }
272
273    /// Bind this handle to `stream`; subsequent cuSPARSE calls dispatch on
274    /// it (`cusparseSetStream`).
275    pub fn set_stream(&self, stream: &Stream) -> Result<()> {
276        let c = cusparse()?;
277        let cu = c.cusparse_set_stream()?;
278        check(unsafe { cu(self.handle, stream.as_raw() as _) })
279    }
280
281    /// cuSPARSE library version (`cusparseGetVersion`),
282    /// e.g. `12604` for cuSPARSE 12.6.4.
283    pub fn version(&self) -> Result<i32> {
284        let c = cusparse()?;
285        let cu = c.cusparse_get_version()?;
286        let mut v: core::ffi::c_int = 0;
287        check(unsafe { cu(self.handle, &mut v) })?;
288        Ok(v)
289    }
290
291    /// Raw `cusparseHandle_t`. Use with care.
292    #[inline]
293    pub fn as_raw(&self) -> cusparseHandle_t {
294        self.handle
295    }
296}
297
298impl Drop for Handle {
299    fn drop(&mut self) {
300        if let Ok(c) = cusparse() {
301            if let Ok(cu) = c.cusparse_destroy() {
302                let _ = unsafe { cu(self.handle) };
303            }
304        }
305    }
306}
307
308// ---- Sparse matrix descriptor -------------------------------------------
309
310/// A sparse-matrix descriptor (CSR / CSC / COO / BSR). The descriptor keeps
311/// pointers to externally-owned device buffers; the lifetime parameter ties
312/// those buffers to the descriptor.
313pub struct SpMat<'buf, T> {
314    descr: cusparseSpMatDescr_t,
315    _markers: PhantomData<&'buf mut T>,
316}
317
318unsafe impl<T> Send for SpMat<'_, T> {}
319
320impl<T> core::fmt::Debug for SpMat<'_, T> {
321    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
322        f.debug_struct("SpMat")
323            .field("descr", &self.descr)
324            .finish_non_exhaustive()
325    }
326}
327
328impl<'buf, T: SparseScalar + baracuda_types::DeviceRepr> SpMat<'buf, T> {
329    /// Build a CSR (compressed sparse row) descriptor.
330    ///
331    /// `row_offsets.len()` must equal `rows + 1`; `col_indices.len()` and
332    /// `values.len()` must equal `nnz`.
333    pub fn csr(
334        rows: i64,
335        cols: i64,
336        nnz: i64,
337        row_offsets: &'buf mut DeviceBuffer<i32>,
338        col_indices: &'buf mut DeviceBuffer<i32>,
339        values: &'buf mut DeviceBuffer<T>,
340    ) -> Result<Self> {
341        let c = cusparse()?;
342        let cu = c.cusparse_create_csr()?;
343        let mut descr: cusparseSpMatDescr_t = core::ptr::null_mut();
344        check(unsafe {
345            cu(
346                &mut descr,
347                rows,
348                cols,
349                nnz,
350                row_offsets.as_raw().0 as *mut c_void,
351                col_indices.as_raw().0 as *mut c_void,
352                values.as_raw().0 as *mut c_void,
353                cusparseIndexType_t::I32I,
354                cusparseIndexType_t::I32I,
355                cusparseIndexBase_t::Zero,
356                T::data_type(),
357            )
358        })?;
359        Ok(Self {
360            descr,
361            _markers: PhantomData,
362        })
363    }
364
365    /// Build a CSC (compressed sparse column) descriptor.
366    pub fn csc(
367        rows: i64,
368        cols: i64,
369        nnz: i64,
370        col_offsets: &'buf mut DeviceBuffer<i32>,
371        row_indices: &'buf mut DeviceBuffer<i32>,
372        values: &'buf mut DeviceBuffer<T>,
373    ) -> Result<Self> {
374        let c = cusparse()?;
375        let cu = c.cusparse_create_csc()?;
376        let mut descr: cusparseSpMatDescr_t = core::ptr::null_mut();
377        check(unsafe {
378            cu(
379                &mut descr,
380                rows,
381                cols,
382                nnz,
383                col_offsets.as_raw().0 as *mut c_void,
384                row_indices.as_raw().0 as *mut c_void,
385                values.as_raw().0 as *mut c_void,
386                cusparseIndexType_t::I32I,
387                cusparseIndexType_t::I32I,
388                cusparseIndexBase_t::Zero,
389                T::data_type(),
390            )
391        })?;
392        Ok(Self {
393            descr,
394            _markers: PhantomData,
395        })
396    }
397
398    /// Build a BSR (block-sparse-row) descriptor.
399    #[allow(clippy::too_many_arguments)]
400    pub fn bsr(
401        brows: i64,
402        bcols: i64,
403        bnnz: i64,
404        row_block_dim: i64,
405        col_block_dim: i64,
406        order: Order,
407        row_offsets: &'buf mut DeviceBuffer<i32>,
408        col_indices: &'buf mut DeviceBuffer<i32>,
409        values: &'buf mut DeviceBuffer<T>,
410    ) -> Result<Self> {
411        let c = cusparse()?;
412        let cu = c.cusparse_create_bsr()?;
413        let mut descr: cusparseSpMatDescr_t = core::ptr::null_mut();
414        check(unsafe {
415            cu(
416                &mut descr,
417                brows,
418                bcols,
419                bnnz,
420                row_block_dim,
421                col_block_dim,
422                row_offsets.as_raw().0 as *mut c_void,
423                col_indices.as_raw().0 as *mut c_void,
424                values.as_raw().0 as *mut c_void,
425                cusparseIndexType_t::I32I,
426                cusparseIndexType_t::I32I,
427                cusparseIndexBase_t::Zero,
428                T::data_type(),
429                order.raw(),
430            )
431        })?;
432        Ok(Self {
433            descr,
434            _markers: PhantomData,
435        })
436    }
437
438    /// Build a COO (coordinate) descriptor.
439    pub fn coo(
440        rows: i64,
441        cols: i64,
442        nnz: i64,
443        row_indices: &'buf mut DeviceBuffer<i32>,
444        col_indices: &'buf mut DeviceBuffer<i32>,
445        values: &'buf mut DeviceBuffer<T>,
446    ) -> Result<Self> {
447        let c = cusparse()?;
448        let cu = c.cusparse_create_coo()?;
449        let mut descr: cusparseSpMatDescr_t = core::ptr::null_mut();
450        check(unsafe {
451            cu(
452                &mut descr,
453                rows,
454                cols,
455                nnz,
456                row_indices.as_raw().0 as *mut c_void,
457                col_indices.as_raw().0 as *mut c_void,
458                values.as_raw().0 as *mut c_void,
459                cusparseIndexType_t::I32I,
460                cusparseIndexBase_t::Zero,
461                T::data_type(),
462            )
463        })?;
464        Ok(Self {
465            descr,
466            _markers: PhantomData,
467        })
468    }
469}
470
471impl<T> SpMat<'_, T> {
472    /// Sparse matrix dimensions: `(rows, cols, nnz)`.
473    pub fn shape(&self) -> Result<(i64, i64, i64)> {
474        let c = cusparse()?;
475        let cu = c.cusparse_sp_mat_get_size()?;
476        let (mut r, mut col, mut nz) = (0i64, 0i64, 0i64);
477        check(unsafe { cu(self.descr, &mut r, &mut col, &mut nz) })?;
478        Ok((r, col, nz))
479    }
480
481    /// Rebind a CSR descriptor's underlying device pointers without
482    /// rebuilding it. Saves descriptor recreation when the same shape
483    /// is reused with new data.
484    ///
485    /// # Safety
486    ///
487    /// All three pointers must be live device allocations matching the
488    /// original `(rows + 1, nnz, nnz)` element counts and the original
489    /// element types. They must stay valid until the next operation
490    /// on this descriptor completes.
491    pub unsafe fn set_csr_pointers(
492        &self,
493        row_offsets: *mut c_void,
494        col_indices: *mut c_void,
495        values: *mut c_void,
496    ) -> Result<()> { unsafe {
497        let c = cusparse()?;
498        let cu = c.cusparse_csr_set_pointers()?;
499        check(cu(self.descr, row_offsets, col_indices, values))
500    }}
501
502    /// Rebind a CSC descriptor's underlying device pointers.
503    ///
504    /// # Safety
505    ///
506    /// See [`Self::set_csr_pointers`].
507    pub unsafe fn set_csc_pointers(
508        &self,
509        col_offsets: *mut c_void,
510        row_indices: *mut c_void,
511        values: *mut c_void,
512    ) -> Result<()> { unsafe {
513        let c = cusparse()?;
514        let cu = c.cusparse_csc_set_pointers()?;
515        check(cu(self.descr, col_offsets, row_indices, values))
516    }}
517
518    /// Rebind a COO descriptor's underlying device pointers.
519    ///
520    /// # Safety
521    ///
522    /// See [`Self::set_csr_pointers`].
523    pub unsafe fn set_coo_pointers(
524        &self,
525        row_indices: *mut c_void,
526        col_indices: *mut c_void,
527        values: *mut c_void,
528    ) -> Result<()> { unsafe {
529        let c = cusparse()?;
530        let cu = c.cusparse_coo_set_pointers()?;
531        check(cu(self.descr, row_indices, col_indices, values))
532    }}
533
534    /// Set the fill-triangle attribute (for triangular solves).
535    pub fn set_fill(&self, fill: Fill) -> Result<()> {
536        let c = cusparse()?;
537        let cu = c.cusparse_sp_mat_set_attribute()?;
538        let raw = fill.raw();
539        check(unsafe {
540            cu(
541                self.descr,
542                cusparseSpMatAttribute_t::FillMode,
543                &raw as *const _ as *const c_void,
544                core::mem::size_of::<cusparseFillMode_t>(),
545            )
546        })
547    }
548
549    /// Set the diagonal-type attribute (unit vs non-unit, for triangular solves).
550    pub fn set_diag(&self, diag: Diag) -> Result<()> {
551        let c = cusparse()?;
552        let cu = c.cusparse_sp_mat_set_attribute()?;
553        let raw = diag.raw();
554        check(unsafe {
555            cu(
556                self.descr,
557                cusparseSpMatAttribute_t::DiagType,
558                &raw as *const _ as *const c_void,
559                core::mem::size_of::<cusparseDiagType_t>(),
560            )
561        })
562    }
563
564    /// Raw `cusparseSpMatDescr_t`. Use with care.
565    #[inline]
566    pub fn as_raw(&self) -> cusparseSpMatDescr_t {
567        self.descr
568    }
569}
570
571impl<T> Drop for SpMat<'_, T> {
572    fn drop(&mut self) {
573        if let Ok(c) = cusparse() {
574            if let Ok(cu) = c.cusparse_destroy_sp_mat() {
575                let _ = unsafe { cu(self.descr) };
576            }
577        }
578    }
579}
580
581// ---- Dense vector / matrix -----------------------------------------------
582
583/// Dense vector descriptor for cuSPARSE's generic API.
584///
585/// Borrows the underlying [`DeviceBuffer`]; the buffer cannot be freed while
586/// the descriptor is alive.
587pub struct DnVec<'buf, T> {
588    descr: cusparseDnVecDescr_t,
589    _marker: PhantomData<&'buf mut T>,
590}
591
592unsafe impl<T> Send for DnVec<'_, T> {}
593
594impl<T> core::fmt::Debug for DnVec<'_, T> {
595    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
596        f.debug_struct("DnVec")
597            .field("descr", &self.descr)
598            .finish_non_exhaustive()
599    }
600}
601
602impl<'buf, T: SparseScalar + baracuda_types::DeviceRepr> DnVec<'buf, T> {
603    /// Build a dense-vector descriptor (`cusparseCreateDnVec`)
604    /// over the buffer `values`.
605    pub fn new(values: &'buf mut DeviceBuffer<T>) -> Result<Self> {
606        let c = cusparse()?;
607        let cu = c.cusparse_create_dn_vec()?;
608        let mut descr: cusparseDnVecDescr_t = core::ptr::null_mut();
609        check(unsafe {
610            cu(
611                &mut descr,
612                values.len() as i64,
613                values.as_raw().0 as *mut c_void,
614                T::data_type(),
615            )
616        })?;
617        Ok(Self {
618            descr,
619            _marker: PhantomData,
620        })
621    }
622}
623
624impl<T> DnVec<'_, T> {
625    /// Raw `cusparseDnVecDescr_t`. Use with care.
626    #[inline]
627    pub fn as_raw(&self) -> cusparseDnVecDescr_t {
628        self.descr
629    }
630}
631
632impl<T> Drop for DnVec<'_, T> {
633    fn drop(&mut self) {
634        if let Ok(c) = cusparse() {
635            if let Ok(cu) = c.cusparse_destroy_dn_vec() {
636                let _ = unsafe { cu(self.descr) };
637            }
638        }
639    }
640}
641
642/// Dense matrix descriptor for cuSPARSE's generic API.
643///
644/// Borrows the underlying [`DeviceBuffer`]; the buffer cannot be freed while
645/// the descriptor is alive.
646pub struct DnMat<'buf, T> {
647    descr: cusparseDnMatDescr_t,
648    _marker: PhantomData<&'buf mut T>,
649}
650
651unsafe impl<T> Send for DnMat<'_, T> {}
652
653impl<T> core::fmt::Debug for DnMat<'_, T> {
654    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
655        f.debug_struct("DnMat")
656            .field("descr", &self.descr)
657            .finish_non_exhaustive()
658    }
659}
660
661impl<'buf, T: SparseScalar + baracuda_types::DeviceRepr> DnMat<'buf, T> {
662    /// Build a dense-matrix descriptor (`cusparseCreateDnMat`) for the
663    /// given shape, leading dimension, and storage order.
664    pub fn new(
665        rows: i64,
666        cols: i64,
667        ld: i64,
668        order: Order,
669        values: &'buf mut DeviceBuffer<T>,
670    ) -> Result<Self> {
671        let c = cusparse()?;
672        let cu = c.cusparse_create_dn_mat()?;
673        let mut descr: cusparseDnMatDescr_t = core::ptr::null_mut();
674        check(unsafe {
675            cu(
676                &mut descr,
677                rows,
678                cols,
679                ld,
680                values.as_raw().0 as *mut c_void,
681                T::data_type(),
682                order.raw(),
683            )
684        })?;
685        Ok(Self {
686            descr,
687            _marker: PhantomData,
688        })
689    }
690}
691
692impl<T> DnMat<'_, T> {
693    /// Raw `cusparseDnMatDescr_t`. Use with care.
694    #[inline]
695    pub fn as_raw(&self) -> cusparseDnMatDescr_t {
696        self.descr
697    }
698}
699
700impl<T> Drop for DnMat<'_, T> {
701    fn drop(&mut self) {
702        if let Ok(c) = cusparse() {
703            if let Ok(cu) = c.cusparse_destroy_dn_mat() {
704                let _ = unsafe { cu(self.descr) };
705            }
706        }
707    }
708}
709
710// ---- SpMV ---------------------------------------------------------------
711
712/// Query buffer-size for `y = alpha * op(A) * x + beta * y`.
713#[allow(clippy::too_many_arguments)]
714pub fn spmv_buffer_size<T: SparseScalar>(
715    handle: &Handle,
716    op: Op,
717    alpha: &T,
718    a: &SpMat<'_, T>,
719    x: &DnVec<'_, T>,
720    beta: &T,
721    y: &DnVec<'_, T>,
722    alg: SpMVAlg,
723) -> Result<usize> {
724    let c = cusparse()?;
725    let cu = c.cusparse_spmv_buffer_size()?;
726    let mut size: usize = 0;
727    check(unsafe {
728        cu(
729            handle.as_raw(),
730            op.raw(),
731            alpha as *const T as *const c_void,
732            a.descr,
733            x.descr,
734            beta as *const T as *const c_void,
735            y.descr,
736            T::data_type(),
737            alg,
738            &mut size,
739        )
740    })?;
741    Ok(size)
742}
743
744/// `y = alpha * op(A) * x + beta * y`.
745#[allow(clippy::too_many_arguments)]
746pub fn spmv<T: SparseScalar>(
747    handle: &Handle,
748    op: Op,
749    alpha: &T,
750    a: &SpMat<'_, T>,
751    x: &DnVec<'_, T>,
752    beta: &T,
753    y: &mut DnVec<'_, T>,
754    alg: SpMVAlg,
755    workspace: &mut DeviceBuffer<u8>,
756) -> Result<()> {
757    let c = cusparse()?;
758    let cu = c.cusparse_spmv()?;
759    check(unsafe {
760        cu(
761            handle.as_raw(),
762            op.raw(),
763            alpha as *const T as *const c_void,
764            a.descr,
765            x.descr,
766            beta as *const T as *const c_void,
767            y.descr,
768            T::data_type(),
769            alg,
770            workspace.as_raw().0 as *mut c_void,
771        )
772    })
773}
774
775// ---- SpMM ---------------------------------------------------------------
776
777/// Query buffer-size for `C = alpha * op(A) * op(B) + beta * C`, `A` sparse.
778#[allow(clippy::too_many_arguments)]
779pub fn spmm_buffer_size<T: SparseScalar>(
780    handle: &Handle,
781    op_a: Op,
782    op_b: Op,
783    alpha: &T,
784    a: &SpMat<'_, T>,
785    b: &DnMat<'_, T>,
786    beta: &T,
787    c: &DnMat<'_, T>,
788    alg: SpMMAlg,
789) -> Result<usize> {
790    let c_api = cusparse()?;
791    let cu = c_api.cusparse_spmm_buffer_size()?;
792    let mut size = 0usize;
793    check(unsafe {
794        cu(
795            handle.as_raw(),
796            op_a.raw(),
797            op_b.raw(),
798            alpha as *const T as *const c_void,
799            a.descr,
800            b.descr,
801            beta as *const T as *const c_void,
802            c.descr,
803            T::data_type(),
804            alg,
805            &mut size,
806        )
807    })?;
808    Ok(size)
809}
810
811/// `C = alpha * op(A) * op(B) + beta * C` with `A` sparse, `B`/`C` dense
812/// (`cusparseSpMM`).
813#[allow(clippy::too_many_arguments)]
814pub fn spmm<T: SparseScalar>(
815    handle: &Handle,
816    op_a: Op,
817    op_b: Op,
818    alpha: &T,
819    a: &SpMat<'_, T>,
820    b: &DnMat<'_, T>,
821    beta: &T,
822    c: &mut DnMat<'_, T>,
823    alg: SpMMAlg,
824    workspace: &mut DeviceBuffer<u8>,
825) -> Result<()> {
826    let c_api = cusparse()?;
827    let cu = c_api.cusparse_spmm()?;
828    check(unsafe {
829        cu(
830            handle.as_raw(),
831            op_a.raw(),
832            op_b.raw(),
833            alpha as *const T as *const c_void,
834            a.descr,
835            b.descr,
836            beta as *const T as *const c_void,
837            c.descr,
838            T::data_type(),
839            alg,
840            workspace.as_raw().0 as *mut c_void,
841        )
842    })
843}
844
845/// One-time preprocessing before [`spmm`]. Pre-computes algorithm-specific
846/// state into `workspace` so subsequent [`spmm`] calls (with the same A
847/// sparsity pattern + dimensions) are faster. Use this when the same
848/// matrix is multiplied many times.
849#[allow(clippy::too_many_arguments)]
850pub fn spmm_preprocess<T: SparseScalar>(
851    handle: &Handle,
852    op_a: Op,
853    op_b: Op,
854    alpha: &T,
855    a: &SpMat<'_, T>,
856    b: &DnMat<'_, T>,
857    beta: &T,
858    c: &mut DnMat<'_, T>,
859    alg: SpMMAlg,
860    workspace: &mut DeviceBuffer<u8>,
861) -> Result<()> {
862    let c_api = cusparse()?;
863    let cu = c_api.cusparse_spmm_preprocess()?;
864    check(unsafe {
865        cu(
866            handle.as_raw(),
867            op_a.raw(),
868            op_b.raw(),
869            alpha as *const T as *const c_void,
870            a.descr,
871            b.descr,
872            beta as *const T as *const c_void,
873            c.descr,
874            T::data_type(),
875            alg,
876            workspace.as_raw().0 as *mut c_void,
877        )
878    })
879}
880
881// ---- SpGEMM -------------------------------------------------------------
882
883/// Per-plan handle for a 3-phase SpGEMM computation.
884#[derive(Debug)]
885pub struct SpGEMMPlan {
886    raw: cusparseSpGEMMDescr_t,
887}
888
889impl SpGEMMPlan {
890    /// Create a new SpGEMM plan descriptor
891    /// (`cusparseSpGEMM_createDescr`).
892    pub fn new() -> Result<Self> {
893        let c = cusparse()?;
894        let cu = c.cusparse_spgemm_create_descr()?;
895        let mut d: cusparseSpGEMMDescr_t = core::ptr::null_mut();
896        check(unsafe { cu(&mut d) })?;
897        Ok(Self { raw: d })
898    }
899}
900
901impl Drop for SpGEMMPlan {
902    fn drop(&mut self) {
903        if let Ok(c) = cusparse() {
904            if let Ok(cu) = c.cusparse_spgemm_destroy_descr() {
905                let _ = unsafe { cu(self.raw) };
906            }
907        }
908    }
909}
910
911/// Phase 1: work-estimation. The caller provides `buffer1` whose size is
912/// returned in `size1`; pass `null` the first time, then allocate & re-call.
913///
914/// # Safety
915///
916/// `buffer1` must be either null (size-query mode) or a valid device
917/// pointer to at least `*size1` bytes of writable scratch memory that
918/// remains live for the duration of the underlying cuSPARSE call.
919#[allow(clippy::too_many_arguments)]
920pub unsafe fn spgemm_work_estimation<T: SparseScalar>(
921    handle: &Handle,
922    op_a: Op,
923    op_b: Op,
924    alpha: &T,
925    a: &SpMat<'_, T>,
926    b: &SpMat<'_, T>,
927    beta: &T,
928    c: &mut SpMat<'_, T>,
929    alg: SpGEMMAlg,
930    plan: &SpGEMMPlan,
931    size1: &mut usize,
932    buffer1: *mut c_void,
933) -> Result<()> { unsafe {
934    let c_api = cusparse()?;
935    let cu = c_api.cusparse_spgemm_work_estimation()?;
936    check(cu(
937        handle.as_raw(),
938        op_a.raw(),
939        op_b.raw(),
940        alpha as *const T as *const c_void,
941        a.descr,
942        b.descr,
943        beta as *const T as *const c_void,
944        c.descr,
945        T::data_type(),
946        alg,
947        plan.raw,
948        size1,
949        buffer1,
950    ))
951}}
952
953/// Phase 2: compute. Same two-step pattern for `buffer2`.
954///
955/// # Safety
956///
957/// `buffer2` must be either null (size-query mode) or a valid device
958/// pointer to at least `*size2` bytes of writable scratch memory that
959/// remains live for the duration of the underlying cuSPARSE call.
960#[allow(clippy::too_many_arguments)]
961pub unsafe fn spgemm_compute<T: SparseScalar>(
962    handle: &Handle,
963    op_a: Op,
964    op_b: Op,
965    alpha: &T,
966    a: &SpMat<'_, T>,
967    b: &SpMat<'_, T>,
968    beta: &T,
969    c: &mut SpMat<'_, T>,
970    alg: SpGEMMAlg,
971    plan: &SpGEMMPlan,
972    size2: &mut usize,
973    buffer2: *mut c_void,
974) -> Result<()> { unsafe {
975    let c_api = cusparse()?;
976    let cu = c_api.cusparse_spgemm_compute()?;
977    check(cu(
978        handle.as_raw(),
979        op_a.raw(),
980        op_b.raw(),
981        alpha as *const T as *const c_void,
982        a.descr,
983        b.descr,
984        beta as *const T as *const c_void,
985        c.descr,
986        T::data_type(),
987        alg,
988        plan.raw,
989        size2,
990        buffer2,
991    ))
992}}
993
994/// Phase 3: write output arrays into the pre-populated output `SpMat`.
995#[allow(clippy::too_many_arguments)]
996pub fn spgemm_copy<T: SparseScalar>(
997    handle: &Handle,
998    op_a: Op,
999    op_b: Op,
1000    alpha: &T,
1001    a: &SpMat<'_, T>,
1002    b: &SpMat<'_, T>,
1003    beta: &T,
1004    c: &mut SpMat<'_, T>,
1005    alg: SpGEMMAlg,
1006    plan: &SpGEMMPlan,
1007) -> Result<()> {
1008    let c_api = cusparse()?;
1009    let cu = c_api.cusparse_spgemm_copy()?;
1010    check(unsafe {
1011        cu(
1012            handle.as_raw(),
1013            op_a.raw(),
1014            op_b.raw(),
1015            alpha as *const T as *const c_void,
1016            a.descr,
1017            b.descr,
1018            beta as *const T as *const c_void,
1019            c.descr,
1020            T::data_type(),
1021            alg,
1022            plan.raw,
1023        )
1024    })
1025}
1026
1027// ---- SpSV / SpSM --------------------------------------------------------
1028
1029/// Per-plan handle for an SpSV (sparse triangular solve, single RHS).
1030#[derive(Debug)]
1031pub struct SpSVPlan {
1032    raw: cusparseSpSVDescr_t,
1033}
1034
1035impl SpSVPlan {
1036    /// Create a new SpSV plan descriptor
1037    /// (`cusparseSpSV_createDescr`).
1038    pub fn new() -> Result<Self> {
1039        let c = cusparse()?;
1040        let cu = c.cusparse_spsv_create_descr()?;
1041        let mut d: cusparseSpSVDescr_t = core::ptr::null_mut();
1042        check(unsafe { cu(&mut d) })?;
1043        Ok(Self { raw: d })
1044    }
1045}
1046
1047impl Drop for SpSVPlan {
1048    fn drop(&mut self) {
1049        if let Ok(c) = cusparse() {
1050            if let Ok(cu) = c.cusparse_spsv_destroy_descr() {
1051                let _ = unsafe { cu(self.raw) };
1052            }
1053        }
1054    }
1055}
1056
1057/// Query buffer-size for a sparse triangular solve
1058/// (`cusparseSpSV_bufferSize`).
1059#[allow(clippy::too_many_arguments)]
1060pub fn spsv_buffer_size<T: SparseScalar>(
1061    handle: &Handle,
1062    op: Op,
1063    alpha: &T,
1064    a: &SpMat<'_, T>,
1065    x: &DnVec<'_, T>,
1066    y: &DnVec<'_, T>,
1067    alg: SpSVAlg,
1068    plan: &SpSVPlan,
1069) -> Result<usize> {
1070    let c = cusparse()?;
1071    let cu = c.cusparse_spsv_buffer_size()?;
1072    let mut size = 0usize;
1073    check(unsafe {
1074        cu(
1075            handle.as_raw(),
1076            op.raw(),
1077            alpha as *const T as *const c_void,
1078            a.descr,
1079            x.descr,
1080            y.descr,
1081            T::data_type(),
1082            alg,
1083            plan.raw,
1084            &mut size,
1085        )
1086    })?;
1087    Ok(size)
1088}
1089
1090/// Run the SpSV analysis phase
1091/// (`cusparseSpSV_analysis`) — must be called once before [`spsv_solve`].
1092#[allow(clippy::too_many_arguments)]
1093pub fn spsv_analysis<T: SparseScalar>(
1094    handle: &Handle,
1095    op: Op,
1096    alpha: &T,
1097    a: &SpMat<'_, T>,
1098    x: &DnVec<'_, T>,
1099    y: &DnVec<'_, T>,
1100    alg: SpSVAlg,
1101    plan: &SpSVPlan,
1102    workspace: &mut DeviceBuffer<u8>,
1103) -> Result<()> {
1104    let c = cusparse()?;
1105    let cu = c.cusparse_spsv_analysis()?;
1106    check(unsafe {
1107        cu(
1108            handle.as_raw(),
1109            op.raw(),
1110            alpha as *const T as *const c_void,
1111            a.descr,
1112            x.descr,
1113            y.descr,
1114            T::data_type(),
1115            alg,
1116            plan.raw,
1117            workspace.as_raw().0 as *mut c_void,
1118        )
1119    })
1120}
1121
1122/// Execute the sparse triangular solve `op(A) * y = alpha * x`
1123/// (`cusparseSpSV_solve`). Requires a prior [`spsv_analysis`] call.
1124#[allow(clippy::too_many_arguments)]
1125pub fn spsv_solve<T: SparseScalar>(
1126    handle: &Handle,
1127    op: Op,
1128    alpha: &T,
1129    a: &SpMat<'_, T>,
1130    x: &DnVec<'_, T>,
1131    y: &mut DnVec<'_, T>,
1132    alg: SpSVAlg,
1133    plan: &SpSVPlan,
1134) -> Result<()> {
1135    let c = cusparse()?;
1136    let cu = c.cusparse_spsv_solve()?;
1137    check(unsafe {
1138        cu(
1139            handle.as_raw(),
1140            op.raw(),
1141            alpha as *const T as *const c_void,
1142            a.descr,
1143            x.descr,
1144            y.descr,
1145            T::data_type(),
1146            alg,
1147            plan.raw,
1148        )
1149    })
1150}
1151
1152/// Per-plan handle for an SpSM (sparse triangular solve, multiple RHS).
1153#[derive(Debug)]
1154pub struct SpSMPlan {
1155    raw: cusparseSpSMDescr_t,
1156}
1157
1158impl SpSMPlan {
1159    /// Create a new SpSM plan descriptor
1160    /// (`cusparseSpSM_createDescr`).
1161    pub fn new() -> Result<Self> {
1162        let c = cusparse()?;
1163        let cu = c.cusparse_spsm_create_descr()?;
1164        let mut d: cusparseSpSMDescr_t = core::ptr::null_mut();
1165        check(unsafe { cu(&mut d) })?;
1166        Ok(Self { raw: d })
1167    }
1168}
1169
1170impl Drop for SpSMPlan {
1171    fn drop(&mut self) {
1172        if let Ok(c) = cusparse() {
1173            if let Ok(cu) = c.cusparse_spsm_destroy_descr() {
1174                let _ = unsafe { cu(self.raw) };
1175            }
1176        }
1177    }
1178}
1179
1180/// Query buffer-size for a multi-RHS sparse triangular solve
1181/// (`cusparseSpSM_bufferSize`).
1182#[allow(clippy::too_many_arguments)]
1183pub fn spsm_buffer_size<T: SparseScalar>(
1184    handle: &Handle,
1185    op_a: Op,
1186    op_b: Op,
1187    alpha: &T,
1188    a: &SpMat<'_, T>,
1189    b: &DnMat<'_, T>,
1190    c: &DnMat<'_, T>,
1191    alg: SpSMAlg,
1192    plan: &SpSMPlan,
1193) -> Result<usize> {
1194    let c_api = cusparse()?;
1195    let cu = c_api.cusparse_spsm_buffer_size()?;
1196    let mut size = 0usize;
1197    check(unsafe {
1198        cu(
1199            handle.as_raw(),
1200            op_a.raw(),
1201            op_b.raw(),
1202            alpha as *const T as *const c_void,
1203            a.descr,
1204            b.descr,
1205            c.descr,
1206            T::data_type(),
1207            alg,
1208            plan.raw,
1209            &mut size,
1210        )
1211    })?;
1212    Ok(size)
1213}
1214
1215/// Run the SpSM analysis phase
1216/// (`cusparseSpSM_analysis`) — must be called once before [`spsm_solve`].
1217#[allow(clippy::too_many_arguments)]
1218pub fn spsm_analysis<T: SparseScalar>(
1219    handle: &Handle,
1220    op_a: Op,
1221    op_b: Op,
1222    alpha: &T,
1223    a: &SpMat<'_, T>,
1224    b: &DnMat<'_, T>,
1225    c: &DnMat<'_, T>,
1226    alg: SpSMAlg,
1227    plan: &SpSMPlan,
1228    workspace: &mut DeviceBuffer<u8>,
1229) -> Result<()> {
1230    let c_api = cusparse()?;
1231    let cu = c_api.cusparse_spsm_analysis()?;
1232    check(unsafe {
1233        cu(
1234            handle.as_raw(),
1235            op_a.raw(),
1236            op_b.raw(),
1237            alpha as *const T as *const c_void,
1238            a.descr,
1239            b.descr,
1240            c.descr,
1241            T::data_type(),
1242            alg,
1243            plan.raw,
1244            workspace.as_raw().0 as *mut c_void,
1245        )
1246    })
1247}
1248
1249/// Execute the multi-RHS sparse triangular solve `op(A) * C = alpha * op(B)`
1250/// (`cusparseSpSM_solve`).
1251#[allow(clippy::too_many_arguments)]
1252pub fn spsm_solve<T: SparseScalar>(
1253    handle: &Handle,
1254    op_a: Op,
1255    op_b: Op,
1256    alpha: &T,
1257    a: &SpMat<'_, T>,
1258    b: &DnMat<'_, T>,
1259    c: &mut DnMat<'_, T>,
1260    alg: SpSMAlg,
1261    plan: &SpSMPlan,
1262) -> Result<()> {
1263    let c_api = cusparse()?;
1264    let cu = c_api.cusparse_spsm_solve()?;
1265    check(unsafe {
1266        cu(
1267            handle.as_raw(),
1268            op_a.raw(),
1269            op_b.raw(),
1270            alpha as *const T as *const c_void,
1271            a.descr,
1272            b.descr,
1273            c.descr,
1274            T::data_type(),
1275            alg,
1276            plan.raw,
1277        )
1278    })
1279}
1280
1281// ---- SDDMM -------------------------------------------------------------
1282
1283/// Query buffer-size for the sampled dense-dense matmul
1284/// (`cusparseSDDMM_bufferSize`).
1285#[allow(clippy::too_many_arguments)]
1286pub fn sddmm_buffer_size<T: SparseScalar>(
1287    handle: &Handle,
1288    op_a: Op,
1289    op_b: Op,
1290    alpha: &T,
1291    a: &DnMat<'_, T>,
1292    b: &DnMat<'_, T>,
1293    beta: &T,
1294    c: &SpMat<'_, T>,
1295    alg: SDDMMAlg,
1296) -> Result<usize> {
1297    let c_api = cusparse()?;
1298    let cu = c_api.cusparse_sddmm_buffer_size()?;
1299    let mut size = 0usize;
1300    check(unsafe {
1301        cu(
1302            handle.as_raw(),
1303            op_a.raw(),
1304            op_b.raw(),
1305            alpha as *const T as *const c_void,
1306            a.descr,
1307            b.descr,
1308            beta as *const T as *const c_void,
1309            c.descr,
1310            T::data_type(),
1311            alg,
1312            &mut size,
1313        )
1314    })?;
1315    Ok(size)
1316}
1317
1318/// Sampled dense-dense matrix multiply (`cusparseSDDMM`): computes
1319/// `C = alpha * op(A) * op(B) + beta * C` at the sparsity pattern of `C`.
1320#[allow(clippy::too_many_arguments)]
1321pub fn sddmm<T: SparseScalar>(
1322    handle: &Handle,
1323    op_a: Op,
1324    op_b: Op,
1325    alpha: &T,
1326    a: &DnMat<'_, T>,
1327    b: &DnMat<'_, T>,
1328    beta: &T,
1329    c: &mut SpMat<'_, T>,
1330    alg: SDDMMAlg,
1331    workspace: &mut DeviceBuffer<u8>,
1332) -> Result<()> {
1333    let c_api = cusparse()?;
1334    let cu = c_api.cusparse_sddmm()?;
1335    check(unsafe {
1336        cu(
1337            handle.as_raw(),
1338            op_a.raw(),
1339            op_b.raw(),
1340            alpha as *const T as *const c_void,
1341            a.descr,
1342            b.descr,
1343            beta as *const T as *const c_void,
1344            c.descr,
1345            T::data_type(),
1346            alg,
1347            workspace.as_raw().0 as *mut c_void,
1348        )
1349    })
1350}
1351
1352/// One-time preprocessing before [`sddmm`]. See [`spmm_preprocess`] for
1353/// the rationale.
1354#[allow(clippy::too_many_arguments)]
1355pub fn sddmm_preprocess<T: SparseScalar>(
1356    handle: &Handle,
1357    op_a: Op,
1358    op_b: Op,
1359    alpha: &T,
1360    a: &DnMat<'_, T>,
1361    b: &DnMat<'_, T>,
1362    beta: &T,
1363    c: &mut SpMat<'_, T>,
1364    alg: SDDMMAlg,
1365    workspace: &mut DeviceBuffer<u8>,
1366) -> Result<()> {
1367    let c_api = cusparse()?;
1368    let cu = c_api.cusparse_sddmm_preprocess()?;
1369    check(unsafe {
1370        cu(
1371            handle.as_raw(),
1372            op_a.raw(),
1373            op_b.raw(),
1374            alpha as *const T as *const c_void,
1375            a.descr,
1376            b.descr,
1377            beta as *const T as *const c_void,
1378            c.descr,
1379            T::data_type(),
1380            alg,
1381            workspace.as_raw().0 as *mut c_void,
1382        )
1383    })
1384}
1385
1386// ---- Sparse / dense conversions ----------------------------------------
1387
1388/// Query buffer-size for a sparse → dense conversion
1389/// (`cusparseSparseToDense_bufferSize`).
1390pub fn sparse_to_dense_buffer_size<T: SparseScalar>(
1391    handle: &Handle,
1392    sp: &SpMat<'_, T>,
1393    dn: &DnMat<'_, T>,
1394) -> Result<usize> {
1395    let c = cusparse()?;
1396    let cu = c.cusparse_sparse_to_dense_buffer_size()?;
1397    let mut size = 0usize;
1398    check(unsafe { cu(handle.as_raw(), sp.descr, dn.descr, 0, &mut size) })?;
1399    Ok(size)
1400}
1401
1402/// Convert a sparse matrix into a dense one
1403/// (`cusparseSparseToDense`).
1404pub fn sparse_to_dense<T: SparseScalar>(
1405    handle: &Handle,
1406    sp: &SpMat<'_, T>,
1407    dn: &mut DnMat<'_, T>,
1408    workspace: &mut DeviceBuffer<u8>,
1409) -> Result<()> {
1410    let c = cusparse()?;
1411    let cu = c.cusparse_sparse_to_dense()?;
1412    check(unsafe {
1413        cu(
1414            handle.as_raw(),
1415            sp.descr,
1416            dn.descr,
1417            0,
1418            workspace.as_raw().0 as *mut c_void,
1419        )
1420    })
1421}
1422
1423/// Query buffer-size for a dense → sparse conversion
1424/// (`cusparseDenseToSparse_bufferSize`).
1425pub fn dense_to_sparse_buffer_size<T: SparseScalar>(
1426    handle: &Handle,
1427    dn: &DnMat<'_, T>,
1428    sp: &SpMat<'_, T>,
1429) -> Result<usize> {
1430    let c = cusparse()?;
1431    let cu = c.cusparse_dense_to_sparse_buffer_size()?;
1432    let mut size = 0usize;
1433    check(unsafe { cu(handle.as_raw(), dn.descr, sp.descr, 0, &mut size) })?;
1434    Ok(size)
1435}
1436
1437/// Run the dense → sparse analysis phase
1438/// (`cusparseDenseToSparse_analysis`); fills in the sparsity pattern.
1439pub fn dense_to_sparse_analysis<T: SparseScalar>(
1440    handle: &Handle,
1441    dn: &DnMat<'_, T>,
1442    sp: &SpMat<'_, T>,
1443    workspace: &mut DeviceBuffer<u8>,
1444) -> Result<()> {
1445    let c = cusparse()?;
1446    let cu = c.cusparse_dense_to_sparse_analysis()?;
1447    check(unsafe {
1448        cu(
1449            handle.as_raw(),
1450            dn.descr,
1451            sp.descr,
1452            0,
1453            workspace.as_raw().0 as *mut c_void,
1454        )
1455    })
1456}
1457
1458/// Execute the dense → sparse conversion
1459/// (`cusparseDenseToSparse_convert`); fills in the sparse values.
1460pub fn dense_to_sparse_convert<T: SparseScalar>(
1461    handle: &Handle,
1462    dn: &DnMat<'_, T>,
1463    sp: &mut SpMat<'_, T>,
1464    workspace: &mut DeviceBuffer<u8>,
1465) -> Result<()> {
1466    let c = cusparse()?;
1467    let cu = c.cusparse_dense_to_sparse_convert()?;
1468    check(unsafe {
1469        cu(
1470            handle.as_raw(),
1471            dn.descr,
1472            sp.descr,
1473            0,
1474            workspace.as_raw().0 as *mut c_void,
1475        )
1476    })
1477}
1478
1479/// Workspace size in bytes for [`csr2csc_ex2`].
1480#[allow(clippy::too_many_arguments)]
1481pub fn csr2csc_ex2_buffer_size<T: SparseScalar + baracuda_types::DeviceRepr>(
1482    handle: &Handle,
1483    m: i32,
1484    n: i32,
1485    nnz: i32,
1486    csr_val: &DeviceBuffer<T>,
1487    csr_row_ptr: &DeviceBuffer<i32>,
1488    csr_col_ind: &DeviceBuffer<i32>,
1489    csc_val: &mut DeviceBuffer<T>,
1490    csc_col_ptr: &mut DeviceBuffer<i32>,
1491    csc_row_ind: &mut DeviceBuffer<i32>,
1492    copy_values: bool,
1493    idx_base: IndexBase,
1494    alg: Csr2CscAlg,
1495) -> Result<usize> {
1496    let c = cusparse()?;
1497    let cu = c.cusparse_csr2csc_ex2_buffer_size()?;
1498    let mut size = 0usize;
1499    check(unsafe {
1500        cu(
1501            handle.as_raw(),
1502            m,
1503            n,
1504            nnz,
1505            csr_val.as_raw().0 as *const c_void,
1506            csr_row_ptr.as_raw().0 as *const i32,
1507            csr_col_ind.as_raw().0 as *const i32,
1508            csc_val.as_raw().0 as *mut c_void,
1509            csc_col_ptr.as_raw().0 as *mut i32,
1510            csc_row_ind.as_raw().0 as *mut i32,
1511            T::data_type(),
1512            copy_values as i32,
1513            idx_base,
1514            alg,
1515            &mut size,
1516        )
1517    })?;
1518    Ok(size)
1519}
1520
1521/// Convert a CSR matrix to CSC format using the modern Ex2 entry point —
1522/// supports algorithm selection (`alg`) and arbitrary value types.
1523#[allow(clippy::too_many_arguments)]
1524pub fn csr2csc_ex2<T: SparseScalar + baracuda_types::DeviceRepr>(
1525    handle: &Handle,
1526    m: i32,
1527    n: i32,
1528    nnz: i32,
1529    csr_val: &DeviceBuffer<T>,
1530    csr_row_ptr: &DeviceBuffer<i32>,
1531    csr_col_ind: &DeviceBuffer<i32>,
1532    csc_val: &mut DeviceBuffer<T>,
1533    csc_col_ptr: &mut DeviceBuffer<i32>,
1534    csc_row_ind: &mut DeviceBuffer<i32>,
1535    copy_values: bool,
1536    idx_base: IndexBase,
1537    alg: Csr2CscAlg,
1538    workspace: &mut DeviceBuffer<u8>,
1539) -> Result<()> {
1540    let c = cusparse()?;
1541    let cu = c.cusparse_csr2csc_ex2()?;
1542    check(unsafe {
1543        cu(
1544            handle.as_raw(),
1545            m,
1546            n,
1547            nnz,
1548            csr_val.as_raw().0 as *const c_void,
1549            csr_row_ptr.as_raw().0 as *const i32,
1550            csr_col_ind.as_raw().0 as *const i32,
1551            csc_val.as_raw().0 as *mut c_void,
1552            csc_col_ptr.as_raw().0 as *mut i32,
1553            csc_row_ind.as_raw().0 as *mut i32,
1554            T::data_type(),
1555            copy_values as i32,
1556            idx_base,
1557            alg,
1558            workspace.as_raw().0 as *mut c_void,
1559        )
1560    })
1561}
1562
1563// ---- Sparse BLAS-1 helpers ---------------------------------------------
1564
1565/// Sparse AXPBY: `y = alpha * x + beta * y` over dense vectors
1566/// (`cusparseAxpby`).
1567pub fn axpby<T: SparseScalar>(
1568    handle: &Handle,
1569    alpha: &T,
1570    x: &DnVec<'_, T>,
1571    beta: &T,
1572    y: &mut DnVec<'_, T>,
1573) -> Result<()> {
1574    let c = cusparse()?;
1575    let cu = c.cusparse_axpby()?;
1576    check(unsafe {
1577        cu(
1578            handle.as_raw(),
1579            alpha as *const T as *const c_void,
1580            x.descr,
1581            beta as *const T as *const c_void,
1582            y.descr,
1583        )
1584    })
1585}
1586
1587/// Sparse gather: `x = y[indices]` (`cusparseGather`).
1588pub fn gather<T: SparseScalar>(
1589    handle: &Handle,
1590    y: &DnVec<'_, T>,
1591    x: &mut DnVec<'_, T>,
1592) -> Result<()> {
1593    let c = cusparse()?;
1594    let cu = c.cusparse_gather()?;
1595    check(unsafe { cu(handle.as_raw(), y.descr, x.descr) })
1596}
1597
1598/// Sparse scatter: `y[indices] = x` (`cusparseScatter`).
1599pub fn scatter<T: SparseScalar>(
1600    handle: &Handle,
1601    x: &DnVec<'_, T>,
1602    y: &mut DnVec<'_, T>,
1603) -> Result<()> {
1604    let c = cusparse()?;
1605    let cu = c.cusparse_scatter()?;
1606    check(unsafe { cu(handle.as_raw(), x.descr, y.descr) })
1607}
1608
1609/// Givens rotation applied to a pair of sparse/dense vectors
1610/// (`cusparseRot`).
1611pub fn rot<T: SparseScalar>(
1612    handle: &Handle,
1613    c_cos: &T,
1614    s_sin: &T,
1615    x: &mut DnVec<'_, T>,
1616    y: &mut DnVec<'_, T>,
1617) -> Result<()> {
1618    let c_api = cusparse()?;
1619    let cu = c_api.cusparse_rot()?;
1620    check(unsafe {
1621        cu(
1622            handle.as_raw(),
1623            c_cos as *const T as *const c_void,
1624            s_sin as *const T as *const c_void,
1625            x.descr,
1626            y.descr,
1627        )
1628    })
1629}
1630
1631// ---- Back-compat re-exports for existing users --------------------------
1632
1633/// Legacy alias kept for callers from v0.1 — prefer [`SpMat::csr`].
1634pub type CsrMatrix<'buf> = SpMat<'buf, f32>;
1635/// Legacy alias — prefer [`DnVec`].
1636pub type DenseVector<'buf, T> = DnVec<'buf, T>;