use thiserror::Error;
use torsh_core::error::TorshError;
pub type ClusterResult<T> = Result<T, ClusterError>;
#[derive(Error, Debug)]
pub enum ClusterError {
#[error("Invalid number of clusters: {0}. Must be positive and less than number of samples")]
InvalidClusters(usize),
#[error("Invalid input data: {0}")]
InvalidInput(String),
#[error("Algorithm failed to converge after {max_iters} iterations")]
ConvergenceFailure { max_iters: usize },
#[error("Dataset is empty")]
EmptyDataset,
#[error("Insufficient data points: need at least {required}, got {actual}")]
InsufficientData { required: usize, actual: usize },
#[error("Invalid distance metric: {0}")]
InvalidDistanceMetric(String),
#[error("Invalid linkage criterion: {0}")]
InvalidLinkage(String),
#[error("Invalid epsilon parameter: {0}. Must be positive")]
InvalidEpsilon(f64),
#[error("Invalid minimum samples: {0}. Must be positive")]
InvalidMinSamples(usize),
#[error("Invalid covariance type: {0}")]
InvalidCovarianceType(String),
#[error("Singular matrix encountered during computation")]
SingularMatrix,
#[error("Tensor operation failed: {0}")]
TensorError(#[from] TorshError),
#[error("SciRS2 core error: {0}")]
SciRS2Error(String),
#[error("Invalid initialization method: {0}")]
InvalidInitialization(String),
#[error("Invalid affinity matrix: {0}")]
InvalidAffinityMatrix(String),
#[error("Memory allocation failed: {0}")]
MemoryError(String),
#[error("Invalid feature dimension: expected {expected}, got {actual}")]
InvalidFeatureDimension { expected: usize, actual: usize },
#[error("Invalid cluster assignment: {0}")]
InvalidAssignment(String),
#[error("Numerical instability detected: {0}")]
NumericalInstability(String),
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Algorithm not implemented: {0}")]
NotImplemented(String),
}
impl ClusterError {
pub fn scirs2_error(msg: impl Into<String>) -> Self {
Self::SciRS2Error(msg.into())
}
pub fn invalid_input(msg: impl Into<String>) -> Self {
Self::InvalidInput(msg.into())
}
pub fn config_error(msg: impl Into<String>) -> Self {
Self::ConfigError(msg.into())
}
pub fn numerical_instability(msg: impl Into<String>) -> Self {
Self::NumericalInstability(msg.into())
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
ClusterError::ConvergenceFailure { .. }
| ClusterError::NumericalInstability(_)
| ClusterError::ConfigError(_)
)
}
pub fn severity(&self) -> ErrorSeverity {
match self {
ClusterError::EmptyDataset
| ClusterError::SingularMatrix
| ClusterError::MemoryError(_) => ErrorSeverity::Critical,
ClusterError::InvalidClusters(_)
| ClusterError::InvalidInput(_)
| ClusterError::InvalidDistanceMetric(_)
| ClusterError::InvalidLinkage(_)
| ClusterError::InvalidEpsilon(_)
| ClusterError::InvalidMinSamples(_)
| ClusterError::InvalidCovarianceType(_)
| ClusterError::InvalidInitialization(_)
| ClusterError::InvalidAffinityMatrix(_)
| ClusterError::InvalidFeatureDimension { .. }
| ClusterError::InvalidAssignment(_)
| ClusterError::ConfigError(_) => ErrorSeverity::High,
ClusterError::ConvergenceFailure { .. } | ClusterError::NumericalInstability(_) => {
ErrorSeverity::Medium
}
ClusterError::InsufficientData { .. }
| ClusterError::TensorError(_)
| ClusterError::SciRS2Error(_)
| ClusterError::NotImplemented(_) => ErrorSeverity::Low,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone)]
pub struct ClusterErrorContext {
pub algorithm: String,
pub data_shape: Option<Vec<usize>>,
pub n_clusters: Option<usize>,
pub iteration: Option<usize>,
pub additional_info: Option<String>,
}
impl ClusterErrorContext {
pub fn new(algorithm: impl Into<String>) -> Self {
Self {
algorithm: algorithm.into(),
data_shape: None,
n_clusters: None,
iteration: None,
additional_info: None,
}
}
pub fn with_data_shape(mut self, shape: Vec<usize>) -> Self {
self.data_shape = Some(shape);
self
}
pub fn with_n_clusters(mut self, n_clusters: usize) -> Self {
self.n_clusters = Some(n_clusters);
self
}
pub fn with_iteration(mut self, iteration: usize) -> Self {
self.iteration = Some(iteration);
self
}
pub fn with_info(mut self, info: impl Into<String>) -> Self {
self.additional_info = Some(info.into());
self
}
}