use std::fmt;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum LogError {
NotNearIdentity {
distance: f64,
threshold: f64,
},
Singularity {
reason: String,
},
NumericalInstability {
reason: String,
},
}
impl fmt::Display for LogError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LogError::NotNearIdentity {
distance,
threshold,
} => write!(
f,
"Element too far from identity for log: distance {:.6} exceeds threshold {:.6}",
distance, threshold
),
LogError::Singularity { reason } => {
write!(f, "Logarithm undefined at singularity: {}", reason)
}
LogError::NumericalInstability { reason } => {
write!(f, "Logarithm numerically unstable: {}", reason)
}
}
}
}
impl std::error::Error for LogError {}
pub type LogResult<T> = Result<T, LogError>;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum RepresentationError {
UnsupportedRepresentation {
representation: String,
reason: String,
},
InvalidParameters {
description: String,
},
UnsupportedIntegrationMethod {
method: String,
group: String,
suggestion: String,
},
}
impl fmt::Display for RepresentationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RepresentationError::UnsupportedRepresentation {
representation,
reason,
} => write!(
f,
"Representation {} not supported: {}",
representation, reason
),
RepresentationError::InvalidParameters { description } => {
write!(f, "Invalid representation parameters: {}", description)
}
RepresentationError::UnsupportedIntegrationMethod {
method,
group,
suggestion,
} => write!(
f,
"{} integration not implemented for {}. {}",
method, group, suggestion
),
}
}
}
impl std::error::Error for RepresentationError {}
pub type RepresentationResult<T> = Result<T, RepresentationError>;
#[derive(Debug, Clone)]
pub struct LogCondition {
condition_number: f64,
angle: f64,
distance_to_cut_locus: f64,
quality: LogQuality,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum LogQuality {
Excellent,
Good,
Acceptable,
Poor,
AtSingularity,
}
impl std::fmt::Display for LogQuality {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LogQuality::Excellent => write!(f, "Excellent"),
LogQuality::Good => write!(f, "Good"),
LogQuality::Acceptable => write!(f, "Acceptable"),
LogQuality::Poor => write!(f, "Poor"),
LogQuality::AtSingularity => write!(f, "AtSingularity"),
}
}
}
impl LogCondition {
pub fn from_angle(angle: f64) -> Self {
let half_angle = angle / 2.0;
let sin_half = half_angle.sin().abs();
let distance_to_cut_locus = (2.0 * std::f64::consts::PI - angle).abs();
let condition_number = if sin_half > 1e-10 {
1.0 / sin_half
} else {
f64::INFINITY
};
let quality = if condition_number < 2.0 {
LogQuality::Excellent
} else if condition_number < 10.0 {
LogQuality::Good
} else if condition_number < 100.0 {
LogQuality::Acceptable
} else if condition_number < 1000.0 {
LogQuality::Poor
} else {
LogQuality::AtSingularity
};
Self {
condition_number,
angle,
distance_to_cut_locus,
quality,
}
}
#[must_use]
pub fn condition_number(&self) -> f64 {
self.condition_number
}
#[must_use]
pub fn angle(&self) -> f64 {
self.angle
}
#[must_use]
pub fn distance_to_cut_locus(&self) -> f64 {
self.distance_to_cut_locus
}
#[must_use]
pub fn quality(&self) -> LogQuality {
self.quality
}
#[must_use]
pub fn is_well_conditioned(&self) -> bool {
matches!(self.quality, LogQuality::Excellent | LogQuality::Good)
}
#[must_use]
pub fn is_usable(&self) -> bool {
matches!(
self.quality,
LogQuality::Excellent | LogQuality::Good | LogQuality::Acceptable
)
}
#[must_use]
pub fn is_singular(&self) -> bool {
matches!(self.quality, LogQuality::AtSingularity)
}
}
pub type ConditionedLogResult<T> = Result<(T, LogCondition), LogError>;
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
#[test]
fn test_condition_number_formula() {
let cond_pi_2 = LogCondition::from_angle(PI / 2.0);
let expected = 1.0 / (PI / 4.0).sin();
assert!(
(cond_pi_2.condition_number() - expected).abs() < 1e-10,
"θ=π/2: got {}, expected {}",
cond_pi_2.condition_number(),
expected
);
assert_eq!(cond_pi_2.quality(), LogQuality::Excellent);
let cond_pi_3 = LogCondition::from_angle(PI / 3.0);
let expected = 1.0 / (PI / 6.0).sin();
assert!(
(cond_pi_3.condition_number() - expected).abs() < 1e-10,
"θ=π/3: got {}, expected {}",
cond_pi_3.condition_number(),
expected
);
assert_eq!(cond_pi_3.quality(), LogQuality::Good);
let cond_small = LogCondition::from_angle(0.1);
let expected = 1.0 / (0.05_f64).sin();
assert!(
(cond_small.condition_number() - expected).abs() < 1e-8,
"θ=0.1: got {}, expected {}",
cond_small.condition_number(),
expected
);
}
#[test]
fn test_quality_classification() {
let excellent = LogCondition::from_angle(PI * 0.7); assert_eq!(excellent.quality(), LogQuality::Excellent);
assert!(excellent.is_well_conditioned());
assert!(excellent.is_usable());
let good = LogCondition::from_angle(PI * 0.3); assert_eq!(good.quality(), LogQuality::Good);
assert!(good.is_well_conditioned());
let acceptable = LogCondition::from_angle(0.1); assert_eq!(acceptable.quality(), LogQuality::Acceptable);
assert!(!acceptable.is_well_conditioned());
assert!(acceptable.is_usable());
let poor = LogCondition::from_angle(0.01); assert_eq!(poor.quality(), LogQuality::Poor);
assert!(!poor.is_well_conditioned());
assert!(!poor.is_usable());
let singular = LogCondition::from_angle(0.001); assert_eq!(singular.quality(), LogQuality::AtSingularity);
assert!(singular.is_singular());
}
#[test]
fn test_distance_to_cut_locus() {
let cond = LogCondition::from_angle(PI / 2.0);
assert!(
(cond.distance_to_cut_locus() - 3.0 * PI / 2.0).abs() < 1e-10,
"Distance to cut locus mismatch"
);
let cond_near = LogCondition::from_angle(2.0 * PI - 0.1);
assert!(
(cond_near.distance_to_cut_locus() - 0.1).abs() < 1e-10,
"Near cut locus distance mismatch"
);
let cond_far = LogCondition::from_angle(0.01);
assert!(
(cond_far.distance_to_cut_locus() - (2.0 * PI - 0.01)).abs() < 1e-10,
"Far from cut locus distance mismatch"
);
}
#[test]
fn test_small_angle_conditioning() {
let small_angles = [0.5, 0.1, 0.01, 0.001, 0.0001];
let mut prev_kappa = 0.0;
for &angle in &small_angles {
let cond = LogCondition::from_angle(angle);
assert!(
cond.condition_number() > prev_kappa,
"Condition number should increase as θ→0: θ={}, κ={}",
angle,
cond.condition_number()
);
prev_kappa = cond.condition_number();
}
let cond_singular = LogCondition::from_angle(1e-11);
assert_eq!(cond_singular.quality(), LogQuality::AtSingularity);
}
#[test]
fn test_near_cut_locus() {
let cond_pi = LogCondition::from_angle(PI);
assert!(
(cond_pi.condition_number() - 1.0).abs() < 1e-10,
"At θ=π, κ should be 1: got {}",
cond_pi.condition_number()
);
assert_eq!(cond_pi.quality(), LogQuality::Excellent);
let cond_near_pi = LogCondition::from_angle(PI - 0.1);
assert!(cond_near_pi.is_well_conditioned());
assert!(
(cond_pi.distance_to_cut_locus() - PI).abs() < 1e-10,
"Distance to cut locus at θ=π should be π: got {}",
cond_pi.distance_to_cut_locus()
);
let cond_2pi = LogCondition::from_angle(2.0 * PI);
assert!(
cond_2pi.distance_to_cut_locus() < 1e-10,
"Distance to cut locus at θ=2π should be 0"
);
}
#[test]
fn test_precision_loss_estimate() {
let machine_eps = f64::EPSILON;
let cond_good = LogCondition::from_angle(PI / 2.0);
let expected_error = cond_good.condition_number() * machine_eps;
let digits_lost = cond_good.condition_number().log10();
assert!(digits_lost < 1.0, "Should lose < 1 digit at θ=π/2");
println!(
"θ=π/2: κ={:.2}, expected error={:.2e}, digits lost={:.2}",
cond_good.condition_number(),
expected_error,
digits_lost
);
let cond_moderate = LogCondition::from_angle(0.1);
let digits_lost = cond_moderate.condition_number().log10();
assert!(
digits_lost > 1.0 && digits_lost < 2.0,
"Should lose ~1-2 digits at θ=0.1"
);
println!(
"θ=0.1: κ={:.2}, digits lost={:.2}",
cond_moderate.condition_number(),
digits_lost
);
let cond_poor = LogCondition::from_angle(0.01);
let digits_lost = cond_poor.condition_number().log10();
assert!(
digits_lost > 2.0 && digits_lost < 3.0,
"Should lose ~2-3 digits at θ=0.01"
);
println!(
"θ=0.01: κ={:.2}, digits lost={:.2}",
cond_poor.condition_number(),
digits_lost
);
}
#[test]
fn test_log_error_display() {
let err = LogError::NotNearIdentity {
distance: 2.5,
threshold: 1.0,
};
let msg = format!("{}", err);
assert!(msg.contains("2.5"));
assert!(msg.contains("1.0"));
let err2 = LogError::Singularity {
reason: "rotation by π".to_string(),
};
let msg2 = format!("{}", err2);
assert!(msg2.contains("rotation by π"));
}
#[test]
fn test_representation_error_display() {
let err = RepresentationError::UnsupportedRepresentation {
representation: "(3,3)".to_string(),
reason: "too high dimensional".to_string(),
};
let msg = format!("{}", err);
assert!(msg.contains("(3,3)"));
assert!(msg.contains("too high dimensional"));
}
}