1use crate::compressed::{check_compressed, transpose};
50use crate::index::{Col, Row, dim_to_u32};
51use crate::real::{Real, is_exact_zero};
52use crate::{CsrMatrix, SparseError};
53
54#[derive(Clone, Debug, PartialEq)]
60pub struct CscMatrix<T: Real = f64> {
61 pub n_rows: usize,
63 pub col_ptr: Vec<usize>,
66 pub row_idx: Vec<u32>,
68 pub val: Vec<T>,
70}
71
72impl<T: Real> CscMatrix<T> {
73 pub fn new(n_rows: usize, n_cols: usize) -> Self {
79 Self::zeros(n_rows, n_cols)
80 }
81
82 pub fn zeros(n_rows: usize, n_cols: usize) -> Self {
87 assert!(
88 dim_to_u32("rows", n_rows).is_ok(),
89 "row count exceeds u32::MAX"
90 );
91 assert!(
92 dim_to_u32("columns", n_cols).is_ok(),
93 "column count exceeds u32::MAX"
94 );
95 Self {
96 n_rows,
97 col_ptr: vec![0; n_cols + 1],
98 row_idx: Vec::new(),
99 val: Vec::new(),
100 }
101 }
102
103 pub fn from_raw_parts(
105 n_rows: usize,
106 col_ptr: Vec<usize>,
107 row_idx: Vec<u32>,
108 val: Vec<T>,
109 ) -> Result<Self, SparseError> {
110 let a = Self {
111 n_rows,
112 col_ptr,
113 row_idx,
114 val,
115 };
116 a.check_invariants()?;
117 Ok(a)
118 }
119
120 pub fn from_triplets(
123 n_rows: usize,
124 n_cols: usize,
125 triplets: &[(Row, Col, T)],
126 ) -> Result<Self, SparseError> {
127 dim_to_u32("rows", n_rows)?;
128 dim_to_u32("columns", n_cols)?;
129
130 for (k, &(i, j, v)) in triplets.iter().enumerate() {
132 if i.index() >= n_rows {
133 return Err(SparseError::IndexOutOfRange {
134 what: "row",
135 index: i.index(),
136 bound: n_rows,
137 });
138 }
139 if j.index() >= n_cols {
140 return Err(SparseError::IndexOutOfRange {
141 what: "column",
142 index: j.index(),
143 bound: n_cols,
144 });
145 }
146 if !v.is_finite() {
147 return Err(SparseError::InvalidValue {
148 what: "triplet value",
149 index: k,
150 value: v.to_f64(),
151 });
152 }
153 }
154
155 let mut rptr = vec![0_usize; n_rows + 1];
157 for &(i, _, _) in triplets {
158 rptr[i.index() + 1] += 1;
159 }
160 for i in 0..n_rows {
161 rptr[i + 1] += rptr[i];
162 }
163 let mut next = rptr[..n_rows].to_vec();
164 let mut cidx = vec![0_u32; triplets.len()];
165 let mut cval = vec![T::ZERO; triplets.len()];
166 for &(i, j, v) in triplets {
167 let q = next[i.index()];
168 next[i.index()] += 1;
169 cidx[q] = j.0;
170 cval[q] = v;
171 }
172
173 let (col_ptr, mut row_idx, mut val) = transpose(n_cols, &rptr, &cidx, &cval);
175
176 let mut out_ptr = Vec::with_capacity(n_cols + 1);
178 out_ptr.push(0);
179 let mut w = 0;
180 for (j, win) in col_ptr.windows(2).enumerate() {
181 let (mut p, end) = (win[0], win[1]);
182 while p < end {
183 let i = row_idx[p];
184 let mut s = val[p];
185 p += 1;
186 while p < end && row_idx[p] == i {
187 s += val[p];
188 p += 1;
189 }
190 if !s.is_finite() {
191 return Err(SparseError::InvalidValue {
192 what: "sum of duplicate entries in column",
193 index: j,
194 value: s.to_f64(),
195 });
196 }
197 if !is_exact_zero(s) {
198 row_idx[w] = i;
199 val[w] = s;
200 w += 1;
201 }
202 }
203 out_ptr.push(w);
204 }
205 row_idx.truncate(w);
206 val.truncate(w);
207
208 let a = Self {
209 n_rows,
210 col_ptr: out_ptr,
211 row_idx,
212 val,
213 };
214 debug_assert!(a.check_invariants().is_ok(), "{:?}", a.check_invariants());
215 Ok(a)
216 }
217
218 #[inline]
220 pub fn n_rows(&self) -> usize {
221 self.n_rows
222 }
223
224 #[inline]
226 pub fn n_cols(&self) -> usize {
227 self.col_ptr.len() - 1
228 }
229
230 #[inline]
232 pub fn nnz(&self) -> usize {
233 self.val.len()
234 }
235
236 #[inline]
238 pub fn col(&self, j: Col) -> (&[u32], &[T]) {
239 let r = self.col_ptr[j.index()]..self.col_ptr[j.index() + 1];
240 (&self.row_idx[r.clone()], &self.val[r])
241 }
242
243 pub fn get(&self, i: Row, j: Col) -> T {
245 let (rows, vals) = self.col(j);
246 rows.binary_search(&i.0).map_or(T::ZERO, |p| vals[p])
247 }
248
249 pub fn gaxpy(&self, alpha: T, x: &[T], y: &mut [T]) {
256 assert_eq!(x.len(), self.n_cols(), "gaxpy: x has the wrong length");
257 assert_eq!(y.len(), self.n_rows, "gaxpy: y has the wrong length");
258 for (j, &xj) in x.iter().enumerate() {
259 if is_exact_zero(xj) {
260 continue;
261 }
262 let t = alpha * xj;
263 let (rows, vals) = self.col(Col::from_usize(j));
264 for (&i, &v) in rows.iter().zip(vals) {
265 y[i as usize] += v * t;
266 }
267 }
268 }
269
270 pub fn to_csr(&self) -> CsrMatrix<T> {
272 let (row_ptr, col_idx, val) =
273 transpose(self.n_rows, &self.col_ptr, &self.row_idx, &self.val);
274 let r = CsrMatrix {
275 n_cols: self.n_cols(),
276 row_ptr,
277 col_idx,
278 val,
279 };
280 debug_assert!(r.check_invariants().is_ok(), "{:?}", r.check_invariants());
281 r
282 }
283
284 #[must_use]
286 pub fn transpose(&self) -> CscMatrix<T> {
287 let (col_ptr, row_idx, val) =
288 transpose(self.n_rows, &self.col_ptr, &self.row_idx, &self.val);
289 let t = CscMatrix {
290 n_rows: self.n_cols(),
291 col_ptr,
292 row_idx,
293 val,
294 };
295 debug_assert!(t.check_invariants().is_ok(), "{:?}", t.check_invariants());
296 t
297 }
298
299 pub fn check_invariants(&self) -> Result<(), SparseError> {
301 check_compressed(
302 "column",
303 "row",
304 self.n_rows,
305 &self.col_ptr,
306 &self.row_idx,
307 &self.val,
308 )
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use crate::real::approx_eq;
316 use proptest::prelude::*;
317
318 fn dense_reference(m: usize, n: usize, t: &[(Row, Col, f64)]) -> Vec<Vec<f64>> {
320 let mut d = vec![vec![0.0; n]; m];
321 for &(i, j, v) in t {
322 d[i.index()][j.index()] += v;
323 }
324 d
325 }
326
327 fn triplets() -> impl Strategy<Value = (usize, usize, Vec<(Row, Col, f64)>)> {
331 (0_usize..8, 0_usize..8).prop_flat_map(|(m, n)| {
332 let entries = if m == 0 || n == 0 {
333 Just(Vec::new()).boxed()
334 } else {
335 proptest::collection::vec(
336 (0..m, 0..n, -3_i8..=3).prop_map(|(i, j, v)| {
337 (Row::from_usize(i), Col::from_usize(j), f64::from(v))
338 }),
339 0..40,
340 )
341 .boxed()
342 };
343 (Just(m), Just(n), entries)
344 })
345 }
346
347 #[test]
348 fn ex_1_1_small_example() {
349 let t = [
353 (Row(1), Col(2), 3.0),
354 (Row(0), Col(2), 0.5),
355 (Row(0), Col(0), 1.0),
356 (Row(1), Col(0), 0.0),
357 (Row(0), Col(2), 1.5),
358 ];
359 let a = CscMatrix::from_triplets(2, 3, &t).unwrap();
360 assert_eq!(a.col_ptr, vec![0, 1, 1, 3]);
361 assert_eq!(a.row_idx, vec![0, 0, 1]);
362 assert_eq!(a.val, vec![1.0, 2.0, 3.0]);
363 assert!(approx_eq(a.get(Row(0), Col(2)), 2.0, 0.0));
364 assert!(is_exact_zero(a.get(Row(1), Col(1))));
365 }
366
367 #[test]
368 fn rejects_bad_input() {
369 let oob = CscMatrix::from_triplets(1, 1, &[(Row(1), Col(0), 1.0)]);
370 assert!(matches!(
371 oob,
372 Err(SparseError::IndexOutOfRange { what: "row", .. })
373 ));
374 let nan = CscMatrix::from_triplets(1, 1, &[(Row(0), Col(0), f64::NAN)]);
375 assert!(matches!(nan, Err(SparseError::InvalidValue { .. })));
376 let ovf = CscMatrix::from_triplets(
377 1,
378 1,
379 &[(Row(0), Col(0), f64::MAX), (Row(0), Col(0), f64::MAX)],
380 );
381 assert!(matches!(ovf, Err(SparseError::InvalidValue { .. })));
382 }
383
384 #[test]
385 fn raw_parts_are_validated() {
386 assert!(CscMatrix::from_raw_parts(2, vec![0, 2], vec![0, 1], vec![1.0, 2.0]).is_ok());
387 assert!(CscMatrix::from_raw_parts(2, vec![0, 2], vec![1, 0], vec![1.0, 2.0]).is_err());
389 assert!(CscMatrix::from_raw_parts(2, vec![0, 2], vec![0, 0], vec![1.0, 2.0]).is_err());
391 assert!(CscMatrix::from_raw_parts(2, vec![0, 1], vec![0], vec![0.0]).is_err());
393 assert!(CscMatrix::from_raw_parts(1, vec![0, 1], vec![1], vec![1.0]).is_err());
395 assert!(CscMatrix::<f64>::from_raw_parts(2, vec![1, 1], vec![], vec![]).is_err());
397 assert!(CscMatrix::from_raw_parts(2, vec![0, 1, 0], vec![0], vec![1.0]).is_err());
398 assert!(CscMatrix::from_raw_parts(2, vec![0, 2], vec![0], vec![1.0]).is_err());
399 assert!(CscMatrix::<f64>::from_raw_parts(2, vec![], vec![], vec![]).is_err());
400 }
401
402 #[test]
403 fn gaxpy_matches_the_dense_product() {
404 let a = CscMatrix::from_raw_parts(2, vec![0, 1, 1, 3], vec![0, 0, 1], vec![1.0, 2.0, 3.0])
406 .unwrap();
407 let mut y = vec![1.0, -1.0];
408 a.gaxpy(2.0, &[1.0, 5.0, -1.0], &mut y);
409 assert!(approx_eq(y[0], 1.0 + 2.0 * (1.0 - 2.0), 0.0));
410 assert!(approx_eq(y[1], -1.0 + 2.0 * (-3.0), 0.0));
411 assert_eq!(CscMatrix::<f64>::new(2, 3), CscMatrix::zeros(2, 3));
412 }
413
414 #[test]
415 fn zeros_is_valid() {
416 let z = CscMatrix::<f64>::zeros(3, 4);
417 assert!(z.check_invariants().is_ok());
418 assert_eq!((z.n_rows(), z.n_cols(), z.nnz()), (3, 4, 0));
419 assert_eq!(z.to_csr().to_csc(), z);
420 }
421
422 proptest! {
423 #[test]
426 fn triplets_match_dense_reference((m, n, t) in triplets()) {
427 let a = CscMatrix::from_triplets(m, n, &t).unwrap();
428 prop_assert!(a.check_invariants().is_ok());
429 let d = dense_reference(m, n, &t);
430 let mut nnz = 0;
431 for (i, row) in d.iter().enumerate() {
432 for (j, &dij) in row.iter().enumerate() {
433 let aij = a.get(Row::from_usize(i), Col::from_usize(j));
434 prop_assert!(approx_eq(aij, dij, 0.0));
435 nnz += usize::from(!is_exact_zero(dij));
436 }
437 }
438 prop_assert_eq!(a.nnz(), nnz);
439 }
440
441 #[test]
443 fn csc_csr_round_trip((m, n, t) in triplets()) {
444 let a = CscMatrix::from_triplets(m, n, &t).unwrap();
445 let r = a.to_csr();
446 prop_assert!(r.check_invariants().is_ok());
447 prop_assert_eq!((r.n_rows(), r.n_cols(), r.nnz()), (m, n, a.nnz()));
448 for i in 0..m {
449 for j in 0..n {
450 let (i, j) = (Row::from_usize(i), Col::from_usize(j));
451 prop_assert!(approx_eq(r.get(i, j), a.get(i, j), 0.0));
452 }
453 }
454 prop_assert_eq!(r.to_csc(), a.clone());
455 prop_assert_eq!(a.transpose().transpose(), a);
456 }
457 }
458}