use scirs2_core::CoreError;
use thiserror::Error;
#[derive(Error, Debug, Clone)]
pub enum LinalgError {
#[error("Computation error: {0}")]
ComputationError(String),
#[error("Domain error: {0}")]
DomainError(String),
#[error("Convergence error: {0}")]
ConvergenceError(String),
#[error("Dimension mismatch error: {0}")]
DimensionError(String),
#[error("Shape error: {0}")]
ShapeError(String),
#[error("Index out of bounds: {0}")]
IndexError(String),
#[error("Singular matrix error: {0}")]
SingularMatrixError(String),
#[error("Non-positive definite matrix error: {0}")]
NonPositiveDefiniteError(String),
#[error("Not implemented: {0}")]
NotImplementedError(String),
#[error("Implementation error: {0}")]
ImplementationError(String),
#[error("Value error: {0}")]
ValueError(String),
#[error("Invalid input error: {0}")]
InvalidInputError(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Numerical error: {0}")]
NumericalError(String),
}
impl LinalgError {
pub fn singularmatrix_with_suggestions(
operation: &str,
matrixshape: (usize, usize),
condition_number: Option<f64>,
) -> Self {
let base_msg = format!("Matrix is singular during {operation} operation");
let rows = matrixshape.0;
let cols = matrixshape.1;
let shape_info = format!("Matrix shape: {rows}×{cols}");
let mut suggestions = vec![
"Consider the following regularization approaches:".to_string(),
"1. Ridge regularization: Add λI to the matrix (small positive λ)".to_string(),
"2. Pseudo-inverse: Use SVD-based pseudo-inverse for rank-deficient matrices"
.to_string(),
"3. Truncated SVD: Remove small singular values below a threshold".to_string(),
];
if let Some(cond) = condition_number {
suggestions.push(format!(
"4. Condition number: {cond:.2e} (>1e12 indicates ill-conditioning)"
));
if cond > 1e12 {
suggestions.push(
"5. Use extended precision arithmetic for better numerical stability"
.to_string(),
);
}
}
suggestions.extend_from_slice(&[
"6. Check input data for linear dependencies or scaling issues".to_string(),
"7. Use iterative refinement for improved accuracy".to_string(),
]);
let suggestions_str = suggestions.join("\n");
let full_msg = format!("{base_msg}\n{shape_info}\n{suggestions_str}");
LinalgError::SingularMatrixError(full_msg)
}
pub fn non_positive_definite_with_suggestions(
operation: &str,
matrixshape: (usize, usize),
negative_eigenvalues: Option<usize>,
) -> Self {
let base_msg = format!("Matrix is not positive definite during {operation} operation");
let rows = matrixshape.0;
let cols = matrixshape.1;
let shape_info = format!("Matrix shape: {rows}×{cols}");
let mut suggestions = vec![
"Consider the following regularization approaches:".to_string(),
"1. Diagonal regularization: Add λI where λ > |most negative eigenvalue|".to_string(),
"2. Modified Cholesky: Use algorithms that ensure positive definiteness".to_string(),
"3. Eigenvalue clipping: Replace negative _eigenvalues with small positive values"
.to_string(),
"4. Use LDL decomposition instead of Cholesky for indefinite matrices".to_string(),
];
if let Some(neg_count) = negative_eigenvalues {
suggestions.push(format!(
"5. Found {neg_count} negative eigenvalue(s) - consider spectral regularization"
));
}
suggestions.extend_from_slice(&[
"6. Check if matrix is symmetric (required for Cholesky)".to_string(),
"7. Use pivoted Cholesky for rank-deficient positive semidefinite matrices".to_string(),
"8. Consider using QR or LU decomposition for non-symmetric matrices".to_string(),
]);
let suggestions_str = suggestions.join("\n");
let full_msg = format!("{base_msg}\n{shape_info}\n{suggestions_str}");
LinalgError::NonPositiveDefiniteError(full_msg)
}
pub fn convergence_with_suggestions(
algorithm: &str,
iterations: usize,
tolerance: f64,
current_residual: Option<f64>,
) -> Self {
let base_msg = format!("{algorithm} failed to converge after {iterations} iterations");
let tolerance_info = format!("Target tolerance: {tolerance:.2e}");
let mut suggestions = vec![
"Consider the following approaches to improve convergence:".to_string(),
"1. Increase maximum iterations limit".to_string(),
"2. Relax convergence tolerance".to_string(),
"3. Use preconditioning to improve condition number".to_string(),
"4. Try different initial guess or starting point".to_string(),
];
if let Some(_residual) = current_residual {
suggestions.push(format!(
"5. Current _residual: {_residual:.2e} (target: {tolerance:.2e})"
));
if _residual / tolerance < 10.0 {
suggestions.push(
"6. Close to convergence - try increasing iterations slightly".to_string(),
);
} else {
suggestions
.push("6. Far from convergence - consider algorithm changes".to_string());
}
}
suggestions.extend_from_slice(&[
"7. Use mixed precision arithmetic for better numerical stability".to_string(),
"8. Check matrix conditioning - use regularization if poorly conditioned".to_string(),
"9. Consider switching to direct methods for smaller problems".to_string(),
]);
let suggestions_str = suggestions.join("\n");
let full_msg = format!("{base_msg}\n{tolerance_info}\n{suggestions_str}");
LinalgError::ConvergenceError(full_msg)
}
}
pub type LinalgResult<T> = Result<T, LinalgError>;
impl From<CoreError> for LinalgError {
fn from(error: CoreError) -> Self {
match error {
CoreError::ShapeError(msg) => LinalgError::ShapeError(msg.to_string()),
CoreError::DimensionError(msg) => LinalgError::DimensionError(msg.to_string()),
CoreError::IndexError(msg) => LinalgError::IndexError(msg.to_string()),
CoreError::ValueError(msg) => LinalgError::ValueError(msg.to_string()),
CoreError::InvalidInput(msg) => LinalgError::InvalidInput(msg.to_string()),
CoreError::ComputationError(msg) => LinalgError::ComputationError(msg.to_string()),
CoreError::NotImplementedError(msg) => {
LinalgError::NotImplementedError(msg.to_string())
}
CoreError::ImplementationError(msg) => {
LinalgError::ImplementationError(msg.to_string())
}
_ => LinalgError::ComputationError(format!("Core error: {error}")),
}
}
}
#[allow(dead_code)]
pub fn check_domain<S: AsRef<str>>(condition: bool, message: S) -> LinalgResult<()> {
if condition {
Ok(())
} else {
Err(LinalgError::DomainError(message.as_ref().to_string()))
}
}
#[allow(dead_code)]
pub fn check_dimensions<S: AsRef<str>>(condition: bool, message: S) -> LinalgResult<()> {
if condition {
Ok(())
} else {
Err(LinalgError::DimensionError(message.as_ref().to_string()))
}
}
#[allow(dead_code)]
pub fn check_value<S: AsRef<str>>(condition: bool, message: S) -> LinalgResult<()> {
if condition {
Ok(())
} else {
Err(LinalgError::ValueError(message.as_ref().to_string()))
}
}