1use hybit_core::{HybitError, LinearOperator};
2use rayon::prelude::*;
3
4#[derive(Clone, Debug)]
5pub struct Csr32Matrix {
6 nrows: usize,
7 ncols: usize,
8 row_ptr: Vec<u32>,
9 col_idx: Vec<u32>,
10 values: Vec<f64>,
11}
12
13impl Csr32Matrix {
14 pub fn new(
15 nrows: usize,
16 ncols: usize,
17 row_ptr: Vec<u32>,
18 col_idx: Vec<u32>,
19 values: Vec<f64>,
20 ) -> Result<Self, HybitError> {
21 let matrix = Self {
22 nrows,
23 ncols,
24 row_ptr,
25 col_idx,
26 values,
27 };
28 matrix.validate()?;
29 Ok(matrix)
30 }
31
32 pub fn validate(&self) -> Result<(), HybitError> {
33 if self.row_ptr.len() != self.nrows + 1 {
34 return Err(HybitError::InvalidMatrix(
35 "row_ptr length must equal nrows + 1",
36 ));
37 }
38 if self.col_idx.len() != self.values.len() {
39 return Err(HybitError::InvalidMatrix(
40 "col_idx and values lengths differ",
41 ));
42 }
43 if self.row_ptr.first().copied().unwrap_or(1) != 0 {
44 return Err(HybitError::InvalidMatrix("row_ptr[0] must be zero"));
45 }
46 if self.values.len() > u32::MAX as usize {
47 return Err(HybitError::SizeOverflow);
48 }
49 let nnz = self.values.len() as u32;
50 if self.row_ptr.last().copied().unwrap_or(0) != nnz {
51 return Err(HybitError::InvalidMatrix("row_ptr[nrows] must equal nnz"));
52 }
53 for pair in self.row_ptr.windows(2) {
54 if pair[0] > pair[1] {
55 return Err(HybitError::InvalidMatrix(
56 "row_ptr must be monotonically nondecreasing",
57 ));
58 }
59 }
60 if self.col_idx.iter().any(|&c| c as usize >= self.ncols) {
61 return Err(HybitError::InvalidMatrix("column index out of range"));
62 }
63 if self.values.iter().any(|v| !v.is_finite()) {
64 return Err(HybitError::InvalidMatrix("matrix contains NaN or infinity"));
65 }
66 Ok(())
67 }
68
69 pub fn nrows(&self) -> usize {
70 self.nrows
71 }
72 pub fn ncols(&self) -> usize {
73 self.ncols
74 }
75 pub fn nnz(&self) -> usize {
76 self.values.len()
77 }
78 pub fn row_ptr(&self) -> &[u32] {
79 &self.row_ptr
80 }
81 pub fn col_idx(&self) -> &[u32] {
82 &self.col_idx
83 }
84 pub fn values(&self) -> &[f64] {
85 &self.values
86 }
87
88 pub fn storage_bytes(&self) -> usize {
89 self.row_ptr.len() * std::mem::size_of::<u32>()
90 + self.col_idx.len() * std::mem::size_of::<u32>()
91 + self.values.len() * std::mem::size_of::<f64>()
92 }
93
94 pub fn metadata_bytes(&self) -> usize {
95 self.row_ptr.len() * std::mem::size_of::<u32>()
96 + self.col_idx.len() * std::mem::size_of::<u32>()
97 }
98
99 pub fn diagonal(&self) -> Result<Vec<f64>, HybitError> {
100 if self.nrows != self.ncols {
101 return Err(HybitError::InvalidMatrix(
102 "diagonal requires a square matrix",
103 ));
104 }
105 let mut diagonal = vec![0.0; self.nrows];
106 let mut found = vec![false; self.nrows];
107 for row in 0..self.nrows {
108 let start = self.row_ptr[row] as usize;
109 let end = self.row_ptr[row + 1] as usize;
110 for p in start..end {
111 if self.col_idx[p] as usize == row {
112 diagonal[row] += self.values[p];
113 found[row] = true;
114 }
115 }
116 }
117 for (row, &was_found) in found.iter().enumerate() {
118 if !was_found {
119 return Err(HybitError::MissingDiagonal { row });
120 }
121 }
122 Ok(diagonal)
123 }
124
125 pub fn spmv(&self, x: &[f64]) -> Result<Vec<f64>, HybitError> {
126 let mut y = vec![0.0; self.nrows];
127 self.apply(x, &mut y)?;
128 Ok(y)
129 }
130}
131
132impl Csr32Matrix {
133 #[inline(always)]
134 unsafe fn dot_row_unchecked(&self, row: usize, x: &[f64]) -> f64 {
135 let start = unsafe { *self.row_ptr.get_unchecked(row) as usize };
139 let end = unsafe { *self.row_ptr.get_unchecked(row + 1) as usize };
140 let mut sum = 0.0;
141 for p in start..end {
142 let value = unsafe { *self.values.get_unchecked(p) };
143 let col = unsafe { *self.col_idx.get_unchecked(p) as usize };
144 let xv = unsafe { *x.get_unchecked(col) };
145 sum += value * xv;
146 }
147 sum
148 }
149
150 pub fn apply_parallel(&self, x: &[f64], y: &mut [f64]) -> Result<(), HybitError> {
158 if x.len() != self.ncols {
159 return Err(HybitError::DimensionMismatch {
160 expected: self.ncols,
161 actual: x.len(),
162 });
163 }
164 if y.len() != self.nrows {
165 return Err(HybitError::DimensionMismatch {
166 expected: self.nrows,
167 actual: y.len(),
168 });
169 }
170 y.par_iter_mut().enumerate().for_each(|(row, out)| {
171 *out = unsafe { self.dot_row_unchecked(row, x) };
175 });
176 Ok(())
177 }
178}
179
180#[derive(Clone, Copy, Debug)]
183pub struct ParallelCsr32Operator<'a> {
184 matrix: &'a Csr32Matrix,
185}
186
187impl<'a> ParallelCsr32Operator<'a> {
188 pub fn new(matrix: &'a Csr32Matrix) -> Self {
189 Self { matrix }
190 }
191 pub fn matrix(&self) -> &'a Csr32Matrix {
192 self.matrix
193 }
194 pub fn rayon_threads(&self) -> usize {
195 rayon::current_num_threads()
196 }
197}
198
199impl LinearOperator for ParallelCsr32Operator<'_> {
200 fn rows(&self) -> usize {
201 self.matrix.nrows
202 }
203 fn cols(&self) -> usize {
204 self.matrix.ncols
205 }
206
207 fn apply(&self, x: &[f64], y: &mut [f64]) -> Result<(), HybitError> {
208 self.matrix.apply_parallel(x, y)
209 }
210}
211
212impl LinearOperator for Csr32Matrix {
213 fn rows(&self) -> usize {
214 self.nrows
215 }
216 fn cols(&self) -> usize {
217 self.ncols
218 }
219
220 fn apply(&self, x: &[f64], y: &mut [f64]) -> Result<(), HybitError> {
221 if x.len() != self.ncols {
222 return Err(HybitError::DimensionMismatch {
223 expected: self.ncols,
224 actual: x.len(),
225 });
226 }
227 if y.len() != self.nrows {
228 return Err(HybitError::DimensionMismatch {
229 expected: self.nrows,
230 actual: y.len(),
231 });
232 }
233 for (row, out) in y.iter_mut().enumerate() {
234 *out = unsafe { self.dot_row_unchecked(row, x) };
237 }
238 Ok(())
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn csr_spmv() {
248 let a = Csr32Matrix::new(
249 3,
250 3,
251 vec![0, 2, 5, 7],
252 vec![0, 1, 0, 1, 2, 1, 2],
253 vec![2.0, -1.0, -1.0, 2.0, -1.0, -1.0, 2.0],
254 )
255 .unwrap();
256 let y = a.spmv(&[1.0, 2.0, 3.0]).unwrap();
257 assert_eq!(y, vec![0.0, 0.0, 4.0]);
258 }
259
260 #[test]
261 fn parallel_csr_matches_serial() {
262 let a = Csr32Matrix::new(
263 3,
264 3,
265 vec![0, 2, 5, 7],
266 vec![0, 1, 0, 1, 2, 1, 2],
267 vec![2.0, -1.0, -1.0, 2.0, -1.0, -1.0, 2.0],
268 )
269 .unwrap();
270 let x = [1.0, 2.0, 3.0];
271 let serial = a.spmv(&x).unwrap();
272 let mut parallel = vec![0.0; 3];
273 a.apply_parallel(&x, &mut parallel).unwrap();
274 assert_eq!(parallel, serial);
275 }
276}