1use core::fmt;
7
8#[derive(Clone, Debug, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum PykepError {
12 InvalidInput {
14 parameter: &'static str,
16 reason: String,
18 },
19 NonFiniteInput {
21 parameter: &'static str,
23 },
24 SingularGeometry {
26 operation: &'static str,
28 },
29 ConvergenceFailure {
31 operation: &'static str,
33 iterations: usize,
35 },
36 DimensionMismatch {
38 expected: usize,
40 actual: usize,
42 },
43 UnsupportedCapability {
45 provider: String,
47 capability: &'static str,
49 },
50 NumericalOverflow {
52 operation: &'static str,
54 },
55 IntegrationFailure {
57 model: &'static str,
59 reason: String,
61 },
62 DataUnavailable {
64 dataset: &'static str,
66 },
67}
68
69impl fmt::Display for PykepError {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::InvalidInput { parameter, reason } => {
73 write!(formatter, "invalid {parameter}: {reason}")
74 }
75 Self::NonFiniteInput { parameter } => {
76 write!(formatter, "{parameter} must be finite")
77 }
78 Self::SingularGeometry { operation } => {
79 write!(formatter, "singular geometry in {operation}")
80 }
81 Self::ConvergenceFailure {
82 operation,
83 iterations,
84 } => write!(
85 formatter,
86 "{operation} did not converge after {iterations} iterations"
87 ),
88 Self::DimensionMismatch { expected, actual } => {
89 write!(
90 formatter,
91 "dimension mismatch: expected {expected}, got {actual}"
92 )
93 }
94 Self::UnsupportedCapability {
95 provider,
96 capability,
97 } => write!(formatter, "{provider} does not support {capability}"),
98 Self::NumericalOverflow { operation } => {
99 write!(formatter, "floating-point overflow in {operation}")
100 }
101 Self::IntegrationFailure { model, reason } => {
102 write!(formatter, "integration of {model} failed: {reason}")
103 }
104 Self::DataUnavailable { dataset } => {
105 write!(formatter, "required dataset is unavailable: {dataset}")
106 }
107 }
108 }
109}
110
111impl std::error::Error for PykepError {}
112
113pub type Result<T> = core::result::Result<T, PykepError>;
115
116pub(crate) fn ensure_finite(parameter: &'static str, value: f64) -> Result<()> {
117 if value.is_finite() {
118 Ok(())
119 } else {
120 Err(PykepError::NonFiniteInput { parameter })
121 }
122}
123
124pub(crate) fn ensure_finite_values(names_and_values: &[(&'static str, f64)]) -> Result<()> {
125 for &(name, value) in names_and_values {
126 ensure_finite(name, value)?;
127 }
128 Ok(())
129}
130
131pub(crate) fn ensure_finite_output(operation: &'static str, value: f64) -> Result<f64> {
132 if value.is_finite() {
133 Ok(value)
134 } else {
135 Err(PykepError::NumericalOverflow { operation })
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::PykepError;
142
143 #[test]
144 fn errors_have_stable_useful_messages() {
145 let cases = [
146 (
147 PykepError::InvalidInput {
148 parameter: "x",
149 reason: "must be positive".into(),
150 },
151 "invalid x: must be positive",
152 ),
153 (
154 PykepError::NonFiniteInput { parameter: "x" },
155 "x must be finite",
156 ),
157 (
158 PykepError::SingularGeometry { operation: "orbit" },
159 "singular geometry in orbit",
160 ),
161 (
162 PykepError::ConvergenceFailure {
163 operation: "solver",
164 iterations: 10,
165 },
166 "solver did not converge after 10 iterations",
167 ),
168 (
169 PykepError::DimensionMismatch {
170 expected: 3,
171 actual: 2,
172 },
173 "dimension mismatch: expected 3, got 2",
174 ),
175 (
176 PykepError::UnsupportedCapability {
177 provider: "minimal".into(),
178 capability: "acceleration",
179 },
180 "minimal does not support acceleration",
181 ),
182 (
183 PykepError::NumericalOverflow {
184 operation: "multiply",
185 },
186 "floating-point overflow in multiply",
187 ),
188 (
189 PykepError::IntegrationFailure {
190 model: "model",
191 reason: "step limit".into(),
192 },
193 "integration of model failed: step limit",
194 ),
195 (
196 PykepError::DataUnavailable { dataset: "series" },
197 "required dataset is unavailable: series",
198 ),
199 ];
200 for (error, expected) in cases {
201 assert_eq!(error.to_string(), expected);
202 }
203 }
204}