bixverse_sparse/errors.rs
1//! Errors raised by `bixverse-sparse`.
2
3use thiserror::Error;
4
5/// Error variants raised by the sparse structures and operations.
6///
7/// `bixverse-core` converts these into the identically named `CoreErrors`
8/// variants, so callers of the core crate keep matching on `CoreErrors`.
9#[derive(Debug, Error)]
10pub enum SparseErrors {
11 /// Error in situations were data_2 in
12 /// [`CompressedSparseData2`](crate::sparse::CompressedSparseData2) is
13 /// asked for but not available
14 #[error("data_2 slot is None but was requested")]
15 Data2NotAvailable,
16
17 /// Error if the slice index is out of bounds
18 #[error("The slice index ({index}) is out of bounds (length: {len})")]
19 SliceIndexOutOfBounds {
20 /// The chosen index
21 index: usize,
22 /// The actual length
23 len: usize,
24 },
25
26 /// Error if the slice index is duplicated
27 #[error("You provided a duplicated slice index {0}")]
28 DuplicateSliceIndex(usize),
29
30 /// Error for sparse multiplication dimension mismatches
31 #[error(
32 "The dimensions of the matrix do not support sparse multiplication (matrix a n_col: {n_col_a}; matrix b n_row: {n_row_b})"
33 )]
34 SparseMatrixMultiplication {
35 /// Number of columns in matrix a
36 n_col_a: usize,
37 /// Number of rows in matrix b
38 n_row_b: usize,
39 },
40
41 /// Dimension mismatch error during construction
42 #[error("The indices (len: {indices_len}) and data (len: {data_len}) need to have same length")]
43 DimensionMisMatchSparse {
44 /// Length indices
45 indices_len: usize,
46 /// Length data
47 data_len: usize,
48 },
49
50 /// Error if the `indptr` is inconsistent with the shape it declares or
51 /// with the stored values.
52 ///
53 /// Raised by
54 /// [`CompressedSparseData2::validate`](crate::sparse::CompressedSparseData2::validate)
55 /// on input that crossed an FFI boundary, where the struct's public fields
56 /// could have been populated with anything.
57 #[error("The sparse indptr is inconsistent ({detail}): expected {expected}, got {got}")]
58 SparseIndptrInvalid {
59 /// Which invariant failed
60 detail: &'static str,
61 /// The value implied by the rest of the structure
62 expected: usize,
63 /// The value actually found
64 got: usize,
65 },
66
67 /// Error if there is a dimension mismatch in terms of the two matrices
68 #[error("The shape of the two sparse matrices must be the same.")]
69 ShapeMismatchSparse,
70
71 /// Error if the sparse matrix is not in CSR.
72 #[error("The SparseCompressedData2 must be in CSR format")]
73 SparseMatrixMustBeCsr,
74
75 /// A row summed to (effectively) zero where a row-stochastic normalisation
76 /// was asked for.
77 ///
78 /// On a kNN-derived affinity matrix every node has `k` out-edges, so a zero
79 /// row means the kernel weights underflowed rather than that the graph is
80 /// legitimately disconnected. Anything built on top of the operator would
81 /// be meaningless, hence an error rather than leaving the row at zero.
82 #[error(
83 "Row {row} of the sparse matrix sums to {row_sum}, which is not positive, so it cannot be normalised to a row-stochastic form (isolated node, or the kernel weights underflowed)"
84 )]
85 SparseMatrixIsolatedRow {
86 /// The offending row index
87 row: usize,
88 /// The row sum that was rejected
89 row_sum: f64,
90 },
91
92 /// A CSR matrix failed one of its structural invariants.
93 ///
94 /// Raised by [`validate_square_csr`](crate::sparse::validate_square_csr).
95 /// The static string names the invariant, because "the graph is malformed"
96 /// alone leaves the caller to bisect their own input.
97 #[error("The CSR matrix is malformed: {0}")]
98 MalformedCsr(&'static str),
99
100 /// Error if the sparse matrix is not in CSC.
101 #[error("The SparseCompressedData2 must be in CSC format")]
102 SparseMatrixMustBeCsc,
103
104 /// Shape mismatch problem for matrices
105 #[error("Expected this shape {expected:?}; got {got:?}.")]
106 ShapeMismatch {
107 /// Expected shape
108 expected: (usize, usize),
109 /// Provided shape
110 got: (usize, usize),
111 },
112
113 // -- serialisation --
114 /// The bytes do not open with the magic of the requested structure.
115 #[error("The bytes are not a serialised {0} (magic bytes do not match).")]
116 NotSerialised(&'static str),
117
118 /// The bytes were written by a different version of the byte layout.
119 #[error("Serialised format version {found} is not supported (expected {supported}).")]
120 SerialisedVersionMismatch {
121 /// Version found in the header
122 found: u32,
123 /// Version this build reads and writes
124 supported: u32,
125 },
126
127 /// The bytes were written for different element types, e.g. an `f64`
128 /// structure read back as `f32`.
129 #[error("Serialised element widths {found:?} do not match the requested {expected:?} bytes.")]
130 SerialisedWidthMismatch {
131 /// Widths of the requested types
132 expected: [u8; 2],
133 /// Widths found in the header
134 found: [u8; 2],
135 },
136
137 /// Encoding failed, or the payload behind a valid header did not decode.
138 #[error("Serialisation failed: {0}")]
139 SerialisationFailed(String),
140}