oxicuda_sparse/format/
bsr.rs1use oxicuda_blas::GpuFloat;
14use oxicuda_memory::DeviceBuffer;
15
16use crate::error::{SparseError, SparseResult};
17
18pub struct BsrMatrix<T: GpuFloat> {
23 rows: u32,
25 cols: u32,
27 nnz_blocks: u32,
29 block_dim: u32,
31 row_ptr: DeviceBuffer<i32>,
33 col_idx: DeviceBuffer<i32>,
35 values: DeviceBuffer<T>,
37}
38
39impl<T: GpuFloat> BsrMatrix<T> {
40 #[allow(clippy::too_many_arguments)]
56 pub fn from_host(
57 rows: u32,
58 cols: u32,
59 block_dim: u32,
60 row_ptr: &[i32],
61 col_idx: &[i32],
62 values: &[T],
63 ) -> SparseResult<Self> {
64 if rows == 0 || cols == 0 || block_dim == 0 {
65 return Err(SparseError::InvalidFormat(
66 "rows, cols, and block_dim must be non-zero".to_string(),
67 ));
68 }
69 if rows % block_dim != 0 {
70 return Err(SparseError::InvalidFormat(format!(
71 "rows ({rows}) must be a multiple of block_dim ({block_dim})"
72 )));
73 }
74 if cols % block_dim != 0 {
75 return Err(SparseError::InvalidFormat(format!(
76 "cols ({cols}) must be a multiple of block_dim ({block_dim})"
77 )));
78 }
79
80 let block_rows = rows / block_dim;
81 let expected_row_ptr_len = block_rows as usize + 1;
82 if row_ptr.len() != expected_row_ptr_len {
83 return Err(SparseError::InvalidFormat(format!(
84 "row_ptr length ({}) must be block_rows + 1 ({})",
85 row_ptr.len(),
86 expected_row_ptr_len
87 )));
88 }
89
90 let nnz_blocks = col_idx.len() as u32;
91 if nnz_blocks == 0 {
92 return Err(SparseError::ZeroNnz);
93 }
94
95 let block_elems = block_dim as usize * block_dim as usize;
96 let expected_values_len = nnz_blocks as usize * block_elems;
97 if values.len() != expected_values_len {
98 return Err(SparseError::InvalidFormat(format!(
99 "values length ({}) must be nnz_blocks * block_dim^2 ({})",
100 values.len(),
101 expected_values_len
102 )));
103 }
104
105 if row_ptr[0] != 0 {
107 return Err(SparseError::InvalidFormat(
108 "row_ptr[0] must be 0".to_string(),
109 ));
110 }
111 if row_ptr[block_rows as usize] != nnz_blocks as i32 {
112 return Err(SparseError::InvalidFormat(format!(
113 "row_ptr[block_rows] ({}) must equal nnz_blocks ({})",
114 row_ptr[block_rows as usize], nnz_blocks
115 )));
116 }
117
118 let block_cols = cols / block_dim;
122 for (k, &c) in col_idx.iter().enumerate() {
123 if c < 0 || c as u32 >= block_cols {
124 return Err(SparseError::InvalidFormat(format!(
125 "col_idx[{k}] = {c} out of range [0, {block_cols})"
126 )));
127 }
128 }
129
130 let d_row_ptr = DeviceBuffer::from_host(row_ptr)?;
131 let d_col_idx = DeviceBuffer::from_host(col_idx)?;
132 let d_values = DeviceBuffer::from_host(values)?;
133
134 Ok(Self {
135 rows,
136 cols,
137 nnz_blocks,
138 block_dim,
139 row_ptr: d_row_ptr,
140 col_idx: d_col_idx,
141 values: d_values,
142 })
143 }
144
145 pub fn to_host(&self) -> SparseResult<(Vec<i32>, Vec<i32>, Vec<T>)> {
151 let mut h_row_ptr = vec![0i32; self.row_ptr.len()];
152 let mut h_col_idx = vec![0i32; self.col_idx.len()];
153 let mut h_values = vec![T::gpu_zero(); self.values.len()];
154
155 self.row_ptr.copy_to_host(&mut h_row_ptr)?;
156 self.col_idx.copy_to_host(&mut h_col_idx)?;
157 self.values.copy_to_host(&mut h_values)?;
158
159 Ok((h_row_ptr, h_col_idx, h_values))
160 }
161
162 #[inline]
164 pub fn rows(&self) -> u32 {
165 self.rows
166 }
167
168 #[inline]
170 pub fn cols(&self) -> u32 {
171 self.cols
172 }
173
174 #[inline]
176 pub fn nnz_blocks(&self) -> u32 {
177 self.nnz_blocks
178 }
179
180 #[inline]
182 pub fn block_dim(&self) -> u32 {
183 self.block_dim
184 }
185
186 #[inline]
188 pub fn block_rows(&self) -> u32 {
189 self.rows / self.block_dim
190 }
191
192 #[inline]
194 pub fn block_cols(&self) -> u32 {
195 self.cols / self.block_dim
196 }
197
198 #[inline]
200 pub fn scalar_nnz(&self) -> u32 {
201 self.nnz_blocks * self.block_dim * self.block_dim
202 }
203
204 #[inline]
206 pub fn row_ptr(&self) -> &DeviceBuffer<i32> {
207 &self.row_ptr
208 }
209
210 #[inline]
212 pub fn col_idx(&self) -> &DeviceBuffer<i32> {
213 &self.col_idx
214 }
215
216 #[inline]
218 pub fn values(&self) -> &DeviceBuffer<T> {
219 &self.values
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn bsr_validation_block_alignment() {
229 let result = BsrMatrix::<f32>::from_host(
231 5,
232 4,
233 2,
234 &[0, 1, 2, 3], &[0, 1, 0],
236 &[1.0; 12],
237 );
238 assert!(result.is_err());
239 }
240
241 #[test]
242 fn bsr_validation_col_idx_out_of_range() {
243 let result = BsrMatrix::<f32>::from_host(4, 4, 2, &[0, 1, 1], &[2], &[1.0; 4]);
246 assert!(matches!(result, Err(SparseError::InvalidFormat(_))));
247 }
248
249 #[test]
250 fn bsr_validation_negative_col_idx() {
251 let result = BsrMatrix::<f32>::from_host(4, 4, 2, &[0, 1, 1], &[-1], &[1.0; 4]);
252 assert!(matches!(result, Err(SparseError::InvalidFormat(_))));
253 }
254
255 #[test]
256 fn bsr_block_counts() {
257 let br = 4 / 2;
259 let bc = 4 / 2;
260 assert_eq!(br, 2);
261 assert_eq!(bc, 2);
262 }
263}