diffusionx 0.12.0

A multi-threaded crate for random number generation and stochastic process simulation, with optional GPU acceleration.
//! Error handling
//!
//! This module provides error types and result types used throughout the crate.
//! It defines various error categories for different components such as random number
//! sampling, simulation processes, and visualization.

use num_traits::Float;
use rand::distr::uniform::Error as UniformError;
use rand_distr::{ExpError, NormalError, PoissonError};
use std::fmt::Debug;
use thiserror::Error;

/// Result type for this crate
///
/// This is a specialized Result type that uses XError as the error type.
pub type XResult<T> = Result<T, XError>;

/// Main error type for this crate
///
/// This enum represents all possible errors that can occur within the crate.
/// It handles errors from various sources, including random number generation,
/// simulation processes, and visualization.
#[derive(Error, Debug, Clone)]
pub enum XError {
    /// Error for sampling from the uniform distribution
    #[error("Sample Uniform Distribution Error: {0}")]
    UniformSampleError(#[from] UniformError),
    /// Error for sampling from the normal distribution
    #[error("Sample Normal Distribution Error: {0}")]
    NormalSampleError(#[from] NormalError),
    /// Error for sampling from the Poisson distribution
    #[error("Sample Poisson Distribution Error: {0}")]
    PoissonSampleError(#[from] PoissonError),
    /// Error for sampling from the exponential distribution
    #[error("Sample Exponential Distribution Error: {0}")]
    ExpSampleError(#[from] ExpError),
    /// Error for sampling from the stable distribution
    #[error("Sample Stable Distribution Error: {0}")]
    StableSampleError(#[from] StableError),
    /// Error for sampling from the boolean distribution
    #[error("Probability must be between 0 and 1")]
    BoolSampleError,
    /// Error for simulating the process
    #[error("Simulate Error: {0}")]
    SimulateError(#[from] SimulationError),
    /// Error for visualization
    #[cfg(feature = "visualize")]
    #[error("Visualization Error: {0}")]
    VisualizationError(String),
    /// Error for invalid parameters in various contexts
    #[error("Invalid parameters: {0}")]
    InvalidParameters(String),
    /// Error for non-positive definite matrix
    #[error("Circulant embedding matrix is not positive definite, eigenvalue: {0}")]
    NotPositiveDefinite(f64),
    /// Error for FFT planner lock
    #[error("{0}")]
    FFTError(String),
    #[cfg(feature = "io")]
    /// Error for CSV files
    #[error("CSV IO Error: {0}")]
    CSVError(String),
    #[cfg(feature = "io")]
    /// Error for IO
    #[error("CSV Write Error: {0}")]
    CSVWriteError(String),
    /// Error for `plotter` config builder generated by `derive_builder` crate
    #[cfg(feature = "visualize")]
    #[error("Plotter Config Error: {0}")]
    BuilderError(String),
    /// Error for CUDA operations
    #[cfg(feature = "cuda")]
    #[error(transparent)]
    CUDAError(#[from] cudarc::driver::result::DriverError),
    #[cfg(feature = "cuda")]
    #[error(transparent)]
    SystemTimeError(#[from] std::time::SystemTimeError),
    #[cfg(feature = "cuda")]
    #[error(transparent)]
    CURANDError(#[from] cudarc::curand::result::CurandError),
    /// Other errors
    #[error("Error: {0}")]
    Other(String),
}

impl From<realfft::FftError> for XError {
    fn from(value: realfft::FftError) -> Self {
        XError::FFTError(value.to_string())
    }
}

/// Error type for stable distribution sampling
///
/// This enum represents the errors that can occur when sampling from
/// stable distributions with invalid parameters.
#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)]
pub enum StableError {
    /// Error for invalid index of stability
    #[error("Index of stability must be in the range (0, 2]")]
    InvalidIndex,
    /// Error for invalid skewness parameter
    #[error("Skewness parameter must be in the range [-1, 1]")]
    InvalidSkewness,
    /// Error for invalid scale parameter
    #[error("Scale parameter must be positive")]
    InvalidScale,
    /// Error for invalid location parameter
    #[error("Location parameter must be a real number")]
    InvalidLocation,
    /// Error for invalid index of skewness
    #[error("Index of skewness must be in the range (0, 1)")]
    InvalidSkewIndex,
}

/// Error type for simulation processes
///
/// This enum represents errors that can occur during
/// the simulation of stochastic processes.
#[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum SimulationError {
    /// Error for invalid parameters in simulation configuration
    #[error("Invalid parameters: {0}")]
    InvalidParameters(String),
    /// Error for invalid time step in simulation
    #[error("Invalid time step: {0}")]
    InvalidTimeStep(String),
    /// Error for invalid time interval in simulation
    #[error("Invalid time interval: {0}")]
    InvalidTimeInterval(String),
    /// Error for unknown simulation errors
    #[error("Unknown error, simulation failed")]
    Unknown,
    /// Error when no result is available
    #[error("No result available")]
    NoResult,
}

/// Error wrapper for the `plotters` crate visualization errors
///
/// This enum provides a more specific error classification for
/// plotting and visualization errors.
#[cfg(feature = "visualize")]
#[derive(Error, Debug)]
pub enum PlotterError {
    /// Error for invalid visualization configuration
    #[error("Config Error: {0}")]
    ConfigError(String),
    /// Error for invalid color specification
    #[error("Invalid color: {0}")]
    InvalidColor(String),
    /// Error from `plotters`
    #[error("Plot Error: {0}")]
    DrawingError(String),
}

#[cfg(feature = "visualize")]
impl<E: std::error::Error + Send + Sync> From<plotters::drawing::DrawingAreaErrorKind<E>>
    for XError
{
    fn from(err: plotters::drawing::DrawingAreaErrorKind<E>) -> Self {
        XError::VisualizationError(err.to_string())
    }
}

#[cfg(feature = "visualize")]
impl From<PlotterError> for XError {
    fn from(err: PlotterError) -> Self {
        XError::VisualizationError(err.to_string())
    }
}

#[cfg(feature = "visualize")]
impl From<crate::visualize::config::PlotConfigBuilderError> for XError {
    fn from(err: crate::visualize::config::PlotConfigBuilderError) -> Self {
        XError::BuilderError(err.to_string())
    }
}

#[cfg(feature = "io")]
impl From<csv::Error> for XError {
    fn from(err: csv::Error) -> Self {
        XError::CSVError(err.to_string())
    }
}

#[cfg(feature = "io")]
impl From<std::io::Error> for XError {
    fn from(err: std::io::Error) -> Self {
        XError::CSVWriteError(err.to_string())
    }
}

impl From<&XError> for XError {
    fn from(value: &XError) -> Self {
        value.clone()
    }
}

#[inline]
/// Check the duration and time step
pub(crate) fn check_duration_time_step<T: Float + Debug>(duration: T, time_step: T) -> XResult<()> {
    if duration <= T::zero() {
        return Err(SimulationError::InvalidParameters(format!(
            "The `duration` must be positive, got {duration:?}"
        ))
        .into());
    }

    if time_step <= T::zero() {
        return Err(SimulationError::InvalidParameters(format!(
            "The `time_step` must be positive, got `{time_step:?}`"
        ))
        .into());
    }

    if time_step > duration {
        return Err(SimulationError::InvalidParameters(format!(
                "The `time_step` must be less than or equal to the `duration`, got `{time_step:?}` > `{duration:?}`"
            ))
            .into());
    }

    Ok(())
}