cellular_raza_core/backend/chili/errors.rs
1pub use crate::storage::StorageError;
2use cellular_raza_concepts::*;
3pub use cellular_raza_concepts::{DecomposeError, IndexError};
4use core::any::type_name;
5use core::fmt::{Debug, Display};
6
7use std::sync::Arc;
8use std::sync::atomic::AtomicBool; // TODO in the future use core::error::Error (unstable right now)
9
10use crossbeam_channel::{RecvError, SendError};
11
12macro_rules! impl_error_variant {
13 ($name: ident, $(
14 $(#[$($tt:tt)*])?
15 $err_var: ident
16 ),+) => {
17 // Implement Display for ErrorVariant
18 impl Display for $name {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 $(
22 $(#[$($tt)*])?
23 $name::$err_var(message) => write!(f, "{}", message),
24 )+
25 }
26 }
27 }
28 }
29}
30
31macro_rules! impl_from_error {
32 ($name: ident,
33 $($(#[$($tt:tt)*])?
34 ($err_var: ident, $err_type: ty)
35 ),+) => {
36 $(
37 // Implement conversion from error to errorvariant
38 $(#[$($tt)*])?
39 impl From<$err_type> for $name {
40 fn from(err: $err_type) -> Self {
41 $name::$err_var(err)
42 }
43 }
44 )+
45 }
46}
47
48/// Covers all errors that can occur in this Simulation
49/// The errors are listed from very likely to be a user error from almost certainly an internal error.
50///
51/// # Categorization of errors
52/// Some errors are more likely to be occurring due to an incorrect usage by an end user
53/// while others are highly likely to be due to some internal implementation problem.
54/// Independent of the exact reason, why they are occurring, some can be handled explicitly
55/// while others force an abort of the simulation. See also [HandlingStrategy].
56///
57/// | Variant | Concept Implementation | Engine-Fault | Time Increment |
58/// | --- |:---:|:---:|:---:|
59/// | [CalcError](SimulationError::CalcError) | 8/10 | 2/10 | 0/10 |
60/// | [DecomposeError](SimulationError::DecomposeError) | 7/10 | 3/10 | 0/10 |
61/// | [ControllerError](SimulationError::ControllerError) | 8/10 | 1/10 | 1/10 |
62/// | [DivisionError](SimulationError::DivisionError) | 8/10 | 1/10 | 1/10 |
63/// | [DeathError](SimulationError::DeathError) | 8/10 | 1/10 | 1/10 |
64/// | [BoundaryError](SimulationError::BoundaryError) | 7/10 | 1/10 | 2/10 |
65/// | [DrawingError](SimulationError::DrawingError) | 9/10 | 1/10 | 0/10 |
66/// | [IndexError](SimulationError::IndexError) | 6/10 | 2/10 | 2/10 |
67/// | [SendError](SimulationError::SendError) | 3/10 | 6/10 | 1/10 |
68/// | [ReceiveError](SimulationError::ReceiveError) | 3/10 | 6/10 | 1/10 |
69/// | [StorageError](SimulationError::StorageError) | 3/10 | 7/10 | 0/10 |
70/// | [RngError](SimulationError::RngError) | 1/10 | 8/10 | 1/10 |
71/// | [IoError](SimulationError::IoError) | 1/10 | 9/10 | 0/10 |
72#[derive(Debug)]
73pub enum SimulationError {
74 // Very likely to be user errors
75 /// Occurs during calculations of any mathematical update steps such as
76 /// [Interaction](cellular_raza_concepts::Interaction) between cells.
77 CalcError(CalcError),
78 /// Error associated to splitting up the simulation domain into chunks
79 /// in order to effectively process it in parallel.
80 DecomposeError(DecomposeError),
81 /// Related to time-stepping events. See [crate::time].
82 TimeError(TimeError),
83 /// Error-type specifically related to the
84 /// [Controller](cellular_raza_concepts::domain_old::Controller) trait.
85 ControllerError(ControllerError),
86 /// An error specific to cell-division events by the
87 ///
88 /// [Cycle](cellular_raza_concepts::Cycle) trait.
89 DivisionError(DivisionError),
90 /// Related to the [PhasedDeath](cellular_raza_concepts::CycleEvent::PhasedDeath) event.
91 /// This error can only occcurr during the
92 /// [update_conditional_phased_death](cellular_raza_concepts::Cycle::update_conditional_phased_death)
93 /// method.
94 DeathError(DeathError),
95 /// Enforcing boundary conditions on cells can exhibhit this boundary error.
96 BoundaryError(BoundaryError),
97 /// Plotting results. See also [cellular_raza_concepts::PlotSelf]
98 /// and [cellular_raza_concepts::CreatePlottingRoot].
99 DrawingError(DrawingError),
100 /// Mostly caused by trying to find a voxel by its index.
101 /// This error can also occcurr when applying too large simulation-steps.
102 IndexError(IndexError),
103
104 // Less likely but possible to be user errors
105 /// Sending information between threads fails
106 SendError(String),
107 /// Receiving information from another thread fails
108 ReceiveError(RecvError),
109 /// Storing results fails
110 StorageError(StorageError),
111
112 // Highly unlikely to be user errors
113 /// When writing to output files or reading from them.
114 /// See [std::io::Error]
115 IoError(std::io::Error),
116
117 /// Error related to random number generation by the [rand_chacha::ChaCha8Rng] struct.
118 RngError(RngError),
119
120 /// Only occurs when another thread returns an error
121 OtherThreadError(String),
122 /// Error related to [pyo3]
123 #[cfg(feature = "pyo3")]
124 Pyo3Error(pyo3::PyErr),
125}
126
127impl_from_error! {SimulationError,
128 (ReceiveError, RecvError),
129 (CalcError, CalcError),
130 (DecomposeError, DecomposeError),
131 (TimeError, TimeError),
132 (ControllerError, ControllerError),
133 (DivisionError, DivisionError),
134 (DeathError, DeathError),
135 (BoundaryError, BoundaryError),
136 (IndexError, IndexError),
137 (IoError, std::io::Error),
138 (DrawingError, DrawingError),
139 (StorageError, StorageError),
140 (RngError, RngError),
141 #[cfg(feature = "pyo3")]
142 (Pyo3Error, pyo3::PyErr)
143}
144
145impl_error_variant! {SimulationError,
146 SendError,
147 ReceiveError,
148 CalcError,
149 DecomposeError,
150 TimeError,
151 ControllerError,
152 DivisionError,
153 DeathError,
154 BoundaryError,
155 IndexError,
156 IoError,
157 DrawingError,
158 StorageError,
159 RngError,
160 OtherThreadError,
161 #[cfg(feature = "pyo3")]
162 Pyo3Error
163}
164
165// Implement the general error property
166impl std::error::Error for SimulationError {}
167
168// Implement conversion from Sending error manually
169impl<T> From<SendError<T>> for SimulationError {
170 fn from(_err: SendError<T>) -> Self {
171 SimulationError::SendError(format!(
172 "Error receiving object of type {}",
173 type_name::<SendError<T>>()
174 ))
175 }
176}
177
178/// Contains handling strategies for errors which can arise during the simulation process.
179///
180/// # Handling Strategies
181/// A handler has multiple options on how to approach a recovery in a simulation.
182///
183/// [RevertChangeAccuracy](HandlingStrategy::RevertChangeAccuracy)
184///
185/// One option is to revert to the last known full snapshot of the simulation, increase the
186/// accuracy of the solvers and try again from there. This requires that a working, deterministic
187/// and accurate serialization/deserialization of the whole simulation state is setup (see
188/// [storage](crate::storage) module for more details).
189// TODO implement more of this
190pub enum HandlingStrategy {
191 /// Throw away current results, return to last known good simulation state.
192 /// Load these results (possibly from disk) and try again with increased accuracy.
193 RevertChangeAccuracy,
194 /// Throw away current results and stop the simulation entire.
195 /// This represents a non-recoverable state.
196 Abort,
197 /// Ignores the error and continues with the simulation
198 Ignore,
199}
200
201/// Handle any error which may occur as specified by [SimulationError]
202pub trait ErrorHandler: Sized {
203 /// Creates multiple handlers which are responsible for individual threads.
204 fn spawn_multiple(n_threads: usize) -> impl IntoIterator<Item = Self>;
205
206 /// Accumulates an error event.
207 fn handle_event(&mut self, value: Result<(), SimulationError>);
208
209 /// Determines possible [HandlingStrategy] after having accumulated all possible errors.
210 fn determine_strategy(&self, error: SimulationError) -> HandlingStrategy {
211 use HandlingStrategy::*;
212 use SimulationError::*;
213 // TODO make error handling more nuanced to be valuable
214 match error {
215 SendError(_) => Abort,
216 ReceiveError(_) => RevertChangeAccuracy,
217 CalcError(_) => RevertChangeAccuracy,
218 DecomposeError(_) => Abort,
219 TimeError(_) => Abort,
220 ControllerError(_) => Abort,
221 DivisionError(_) => RevertChangeAccuracy,
222 DeathError(_) => RevertChangeAccuracy,
223 BoundaryError(_) => Abort,
224 IndexError(_) => RevertChangeAccuracy,
225 IoError(_) => Ignore,
226 DrawingError(_) => Ignore,
227 StorageError(_) => Ignore,
228 RngError(_) => Abort,
229 OtherThreadError(_) => Abort,
230 #[cfg(feature = "pyo3")]
231 Pyo3Error(_) => Abort,
232 }
233 }
234
235 /// Determines if the simulation is able to be continued or not.
236 fn do_continue(&mut self) -> Result<(), SimulationError>;
237}
238
239/// Handles any error which is encountered and caught (i.e. no panics) during runtime.
240#[allow(unused)]
241pub struct DefaultHandler {
242 thread: usize,
243 errors: Vec<SimulationError>,
244 do_continue: Arc<AtomicBool>,
245}
246
247impl ErrorHandler for DefaultHandler {
248 fn spawn_multiple(n_threads: usize) -> impl IntoIterator<Item = Self> {
249 let do_continue = Arc::new(AtomicBool::new(true));
250 (0..n_threads).map(move |thread| Self {
251 thread,
252 errors: vec![],
253 do_continue: Arc::clone(&do_continue),
254 })
255 }
256 fn handle_event(&mut self, error: Result<(), SimulationError>) {
257 match error {
258 Ok(_) => (),
259 Err(e) => self.errors.push(e),
260 }
261 }
262
263 fn do_continue(&mut self) -> Result<(), SimulationError> {
264 self.errors
265 .iter()
266 .for_each(|e| println!("thread {:2} detected error: {}", self.thread, e));
267 match self.errors.drain(..).next() {
268 Some(e) => Err(e),
269 None => Ok(()),
270 }
271 }
272}
273
274#[cfg(feature = "pyo3")]
275impl From<SimulationError> for pyo3::PyErr {
276 fn from(value: SimulationError) -> Self {
277 use SimulationError::*;
278 use pyo3::exceptions::*;
279 match value {
280 CalcError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
281 DecomposeError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
282 TimeError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
283 ControllerError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
284 DivisionError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
285 DeathError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
286 BoundaryError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
287 DrawingError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
288 IndexError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
289 SendError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
290 ReceiveError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
291 StorageError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
292 RngError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
293 IoError(e) => pyo3::PyErr::from(e),
294 OtherThreadError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
295 #[cfg(feature = "pyo3")]
296 Pyo3Error(e) => e,
297 }
298 }
299}