cellular_raza_core/backend/chili/errors.rs
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 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
pub use crate::storage::StorageError;
use cellular_raza_concepts::*;
pub use cellular_raza_concepts::{DecomposeError, IndexError};
use core::any::type_name;
use core::fmt::{Debug, Display};
use std::sync::Arc;
use std::sync::atomic::AtomicBool; // TODO in the future use core::error::Error (unstable right now)
use crossbeam_channel::{RecvError, SendError};
macro_rules! impl_error_variant {
($name: ident, $($err_var: ident),+) => {
// Implement Display for ErrorVariant
impl Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(
$name::$err_var(message) => write!(f, "{}", message),
)+
}
}
}
}
}
macro_rules! impl_from_error {
($name: ident, $(($err_var: ident, $err_type: ty)),+) => {
$(
// Implement conversion from error to errorvariant
impl From<$err_type> for $name {
fn from(err: $err_type) -> Self {
$name::$err_var(err)
}
}
)+
}
}
/// Covers all errors that can occur in this Simulation
/// The errors are listed from very likely to be a user error from almost certainly an internal error.
///
/// # Categorization of errors
/// Some errors are more likely to be occurring due to an incorrect usage by an end user
/// while others are highly likely to be due to some internal implementation problem.
/// Independent of the exact reason, why they are occurring, some can be handled explicitly
/// while others force an abort of the simulation. See also [HandlingStrategy].
///
/// | Variant | Concept Implementation | Engine-Fault | Time Increment |
/// | --- |:---:|:---:|:---:|
/// | [CalcError](SimulationError::CalcError) | 8/10 | 2/10 | 0/10 |
/// | [DecomposeError](SimulationError::DecomposeError) | 7/10 | 3/10 | 0/10 |
/// | [ControllerError](SimulationError::ControllerError) | 8/10 | 1/10 | 1/10 |
/// | [DivisionError](SimulationError::DivisionError) | 8/10 | 1/10 | 1/10 |
/// | [DeathError](SimulationError::DeathError) | 8/10 | 1/10 | 1/10 |
/// | [BoundaryError](SimulationError::BoundaryError) | 7/10 | 1/10 | 2/10 |
/// | [DrawingError](SimulationError::DrawingError) | 9/10 | 1/10 | 0/10 |
/// | [IndexError](SimulationError::IndexError) | 6/10 | 2/10 | 2/10 |
/// | [SendError](SimulationError::SendError) | 3/10 | 6/10 | 1/10 |
/// | [ReceiveError](SimulationError::ReceiveError) | 3/10 | 6/10 | 1/10 |
/// | [StorageError](SimulationError::StorageError) | 3/10 | 7/10 | 0/10 |
/// | [RngError](SimulationError::RngError) | 1/10 | 8/10 | 1/10 |
/// | [IoError](SimulationError::IoError) | 1/10 | 9/10 | 0/10 |
#[derive(Debug)]
pub enum SimulationError {
// Very likely to be user errors
/// Occurs during calculations of any mathematical update steps such as
/// [Interaction](cellular_raza_concepts::Interaction) between cells.
CalcError(CalcError),
/// Error associated to splitting up the simulation domain into chunks
/// in order to effectively process it in parallel.
DecomposeError(DecomposeError),
/// Related to time-stepping events. See [crate::time].
TimeError(TimeError),
/// Error-type specifically related to the
/// [Controller](cellular_raza_concepts::domain_old::Controller) trait.
ControllerError(ControllerError),
/// An error specific to cell-division events by the
///
/// [Cycle](cellular_raza_concepts::Cycle) trait.
DivisionError(DivisionError),
/// Related to the [PhasedDeath](cellular_raza_concepts::CycleEvent::PhasedDeath) event.
/// This error can only occcurr during the
/// [update_conditional_phased_death](cellular_raza_concepts::Cycle::update_conditional_phased_death)
/// method.
DeathError(DeathError),
/// Enforcing boundary conditions on cells can exhibhit this boundary error.
BoundaryError(BoundaryError),
/// Plotting results. See also [cellular_raza_concepts::PlotSelf]
/// and [cellular_raza_concepts::CreatePlottingRoot].
DrawingError(DrawingError),
/// Mostly caused by trying to find a voxel by its index.
/// This error can also occcurr when applying too large simulation-steps.
IndexError(IndexError),
// Less likely but possible to be user errors
/// Sending information between threads fails
SendError(String),
/// Receiving information from another thread fails
ReceiveError(RecvError),
/// Storing results fails
StorageError(StorageError),
// Highly unlikely to be user errors
/// When writing to output files or reading from them.
/// See [std::io::Error]
IoError(std::io::Error),
/// Error related to random number generation by the [rand_chacha::ChaCha8Rng] struct.
RngError(RngError),
/// Only occurs when another thread returns an error
OtherThreadError(String),
}
impl_from_error! {SimulationError,
(ReceiveError, RecvError),
(CalcError, CalcError),
(DecomposeError, DecomposeError),
(TimeError, TimeError),
(ControllerError, ControllerError),
(DivisionError, DivisionError),
(DeathError, DeathError),
(BoundaryError, BoundaryError),
(IndexError, IndexError),
(IoError, std::io::Error),
(DrawingError, DrawingError),
(StorageError, StorageError),
(RngError, RngError)
}
impl_error_variant! {SimulationError,
SendError,
ReceiveError,
CalcError,
DecomposeError,
TimeError,
ControllerError,
DivisionError,
DeathError,
BoundaryError,
IndexError,
IoError,
DrawingError,
StorageError,
RngError,
OtherThreadError
}
// Implement the general error property
impl std::error::Error for SimulationError {}
// Implement conversion from Sending error manually
impl<T> From<SendError<T>> for SimulationError {
fn from(_err: SendError<T>) -> Self {
SimulationError::SendError(format!(
"Error receiving object of type {}",
type_name::<SendError<T>>()
))
}
}
/// Contains handling strategies for errors which can arise during the simulation process.
///
/// # Handling Strategies
/// A handler has multiple options on how to approach a recovery in a simulation.
///
/// [RevertChangeAccuracy](HandlingStrategy::RevertChangeAccuracy)
///
/// One option is to revert to the last known full snapshot of the simulation, increase the
/// accuracy of the solvers and try again from there. This requires that a working, deterministic
/// and accurate serialization/deserialization of the whole simulation state is setup (see
/// [storage](crate::storage) module for more details).
// TODO implement more of this
pub enum HandlingStrategy {
/// Throw away current results, return to last known good simulation state.
/// Load these results (possibly from disk) and try again with increased accuracy.
RevertChangeAccuracy,
/// Throw away current results and stop the simulation entire.
/// This represents a non-recoverable state.
Abort,
/// Ignores the error and continues with the simulation
Ignore,
}
/// Handle any error which may occur as specified by [SimulationError]
pub trait ErrorHandler: Sized {
/// Creates multiple handlers which are responsible for individual threads.
fn spawn_multiple(n_threads: usize) -> impl IntoIterator<Item = Self>;
/// Accumulates an error event.
fn handle_event(&mut self, value: Result<(), SimulationError>);
/// Determines possible [HandlingStrategy] after having accumulated all possible errors.
fn determine_strategy(&self, error: SimulationError) -> HandlingStrategy {
use HandlingStrategy::*;
use SimulationError::*;
// TODO make error handling more nuanced to be valuable
match error {
SendError(_) => Abort,
ReceiveError(_) => RevertChangeAccuracy,
CalcError(_) => RevertChangeAccuracy,
DecomposeError(_) => Abort,
TimeError(_) => Abort,
ControllerError(_) => Abort,
DivisionError(_) => RevertChangeAccuracy,
DeathError(_) => RevertChangeAccuracy,
BoundaryError(_) => Abort,
IndexError(_) => RevertChangeAccuracy,
IoError(_) => Ignore,
DrawingError(_) => Ignore,
StorageError(_) => Ignore,
RngError(_) => Abort,
OtherThreadError(_) => Abort,
}
}
/// Determines if the simulation is able to be continued or not.
fn do_continue(&mut self) -> Result<(), SimulationError>;
}
/// Handles any error which is encountered and caught (i.e. no panics) during runtime.
#[allow(unused)]
pub struct DefaultHandler {
thread: usize,
errors: Vec<SimulationError>,
do_continue: Arc<AtomicBool>,
}
impl ErrorHandler for DefaultHandler {
fn spawn_multiple(n_threads: usize) -> impl IntoIterator<Item = Self> {
let do_continue = Arc::new(AtomicBool::new(true));
(0..n_threads).map(move |thread| Self {
thread,
errors: vec![],
do_continue: Arc::clone(&do_continue),
})
}
fn handle_event(&mut self, error: Result<(), SimulationError>) {
match error {
Ok(_) => (),
Err(e) => self.errors.push(e),
}
}
fn do_continue(&mut self) -> Result<(), SimulationError> {
self.errors
.iter()
.for_each(|e| println!("thread {:2} detected error: {}", self.thread, e));
match self.errors.drain(..).next() {
Some(e) => Err(e),
None => Ok(()),
}
}
}
#[cfg(feature = "pyo3")]
impl From<SimulationError> for pyo3::PyErr {
fn from(value: SimulationError) -> Self {
use SimulationError::*;
use pyo3::exceptions::*;
match value {
CalcError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
DecomposeError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
TimeError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
ControllerError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
DivisionError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
DeathError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
BoundaryError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
DrawingError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
IndexError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
SendError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
ReceiveError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
StorageError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
RngError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
IoError(e) => pyo3::PyErr::new::<PyIOError, _>(format!("cr_err: {e:?}")),
OtherThreadError(e) => pyo3::PyErr::new::<PyValueError, _>(format!("cr_err: {e:?}")),
}
}
}