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}