1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
//! The crate-level error enum.
//!
//! One `thiserror` enum for the whole crate, grouped by subsystem. Add new
//! variants to the matching section, not the bottom.
//!
//! Anything a caller could plausibly hit returns `Err`. Panics are for broken
//! invariants only.
use thiserror::Error;
/// Every failure mode in the crate.
#[derive(Debug, Error)]
pub enum EdgeErrors {
// --------- //
// arguments //
// --------- //
/// A parameter fell outside its documented domain.
#[error("Invalid argument: {0}")]
InvalidArgument(String),
/// A count, size or index that must be non-zero was zero.
#[error("Parameter '{0}' must be a positive non-zero integer.")]
MustBePositive(String),
/// A named method string did not match any known variant.
#[error("Unknown {kind} method '{name}'. Expected one of: {expected}.")]
UnknownMethod {
/// What kind of method was being selected, e.g. "normalisation"
kind: &'static str,
/// The string the caller supplied
name: String,
/// Comma-separated list of accepted values
expected: &'static str,
},
// ---------- //
// dimensions //
// ---------- //
/// Two operands disagreed on shape.
#[error("Expected shape {expected:?}; got {got:?}.")]
ShapeMismatch {
/// Shape that was required
expected: (usize, usize),
/// Shape that was supplied
got: (usize, usize),
},
/// A vector was not the length its consumer required.
#[error("'{name}' has length {got}, expected {expected}.")]
LengthMismatch {
/// Name of the offending argument
name: &'static str,
/// Length that was required
expected: usize,
/// Length that was supplied
got: usize,
},
/// The counts matrix had no genes or no samples.
#[error("Counts matrix is empty: {n_genes} genes by {n_samples} samples.")]
EmptyCounts {
/// Number of genes (rows)
n_genes: usize,
/// Number of samples (columns)
n_samples: usize,
},
// --------------- //
// design matrices //
// --------------- //
/// The design matrix is rank deficient, so coefficients are not identifiable.
#[error("Design matrix is not full rank: {n_cols} columns but rank {rank}.")]
DesignNotFullRank {
/// Number of columns in the design
n_cols: usize,
/// Numerical rank actually found
rank: usize,
},
/// A contrast put weight on a coefficient the design cannot estimate.
#[error(
"Contrast {contrast} puts weight on coefficient {coef}, which is aliased and so has no estimate."
)]
NotEstimableContrast {
/// Index of the offending contrast
contrast: usize,
/// Index of the non-estimable coefficient it touches
coef: usize,
},
/// A contrast or coefficient index pointed outside the design.
#[error("Coefficient index {index} is out of range for a design with {n_coef} coefficients.")]
CoefOutOfRange {
/// Index that was requested
index: usize,
/// Number of coefficients available
n_coef: usize,
},
// -------------- //
// linear algebra //
// -------------- //
/// A Cholesky factorisation failed, meaning the matrix was not positive definite.
#[error("Cholesky factorisation failed: {0}")]
CholeskyFailed(String),
/// A linear solve produced a non-finite result.
#[error("Linear solve failed: {0}")]
SolveFailed(String),
/// A symmetric eigendecomposition did not converge.
#[error("Eigendecomposition failed: {0}")]
EigenFailed(String),
// --------------- //
// numeric support //
// --------------- //
/// A scalar optimiser or root finder did not converge inside its iteration budget.
#[error(
"{routine} failed to converge after {iterations} iterations (last change {last_delta:e})."
)]
NoConvergence {
/// Name of the routine that gave up
routine: &'static str,
/// Iterations actually performed
iterations: usize,
/// Magnitude of the final update
last_delta: f64,
},
/// A bracketing root finder was handed an interval that does not bracket a root.
#[error(
"Interval [{lower}, {upper}] does not bracket a root: f(lower) = {f_lower:e}, f(upper) = {f_upper:e}."
)]
RootNotBracketed {
/// Lower end of the interval
lower: f64,
/// Upper end of the interval
upper: f64,
/// Function value at `lower`
f_lower: f64,
/// Function value at `upper`
f_upper: f64,
},
/// A bounded optimisation was given a box with a lower bound above its upper bound.
#[error("Invalid bounds for parameter {index}: lower {lower} exceeds upper {upper}.")]
InvalidBounds {
/// Index of the offending parameter
index: usize,
/// Lower bound supplied
lower: f64,
/// Upper bound supplied
upper: f64,
},
// ------ //
// sparse //
// ------ //
/// A compressed sparse structure violated its own invariants.
#[error("Malformed sparse matrix: {0}")]
MalformedSparse(String),
// ------------- //
// normalisation //
// ------------- //
/// Every gene was filtered out before a normalisation factor could be computed.
#[error("No genes survived filtering for normalisation; all {n_genes} were dropped.")]
NoGenesAfterFiltering {
/// Number of genes present before filtering
n_genes: usize,
},
/// The reference column for TMM could not be chosen or was out of range.
#[error("Invalid TMM reference column: {0}")]
InvalidReferenceColumn(String),
// --- //
// GLM //
// --- //
/// A dispersion outside `[0, inf)` was supplied to a negative binomial routine.
#[error("Dispersion must be non-negative and finite; got {0}.")]
InvalidDispersion(f64),
/// An adjusted profile likelihood was asked for before a gene was loaded.
#[error("No gene has been loaded into the workspace; call `begin_gene` first.")]
AplWorkspaceNotStarted,
// -------------------- //
// single cell / NEBULA //
// -------------------- //
/// Cells were not grouped contiguously by subject, which every inner loop assumes.
#[error(
"Cells are not contiguous by subject: subject {subject} appears in more than one block."
)]
SubjectsNotContiguous {
/// Identifier of the subject found in multiple blocks
subject: usize,
},
/// The design matrix handed to NEBULA has no intercept column.
#[error("NEBULA requires an intercept column in the design matrix; none was found.")]
MissingIntercept,
/// Fewer subjects than the mixed model can identify a random effect from.
#[error("NEBULA needs at least {required} subjects; got {got}.")]
TooFewSubjects {
/// Minimum number of subjects needed
required: usize,
/// Number of subjects supplied
got: usize,
},
// --- //
// GPU //
// --- //
/// The CubeCL runtime or a device limit rejected the work.
///
/// A busted device limit does not fail loudly on wgpu, so this is raised
/// from the validated dispatch geometry, not from the launch.
#[cfg(feature = "gpu")]
#[error("GPU error: {0}")]
Gpu(String),
}