lie_groups/error.rs
1//! Error types for Lie group and Lie algebra operations
2//!
3//! Provides principled error handling for Lie-theoretic computations.
4
5use std::fmt;
6
7/// Errors that can occur during Lie group logarithm computation
8///
9/// The logarithm map `log: G → 𝔤` is only well-defined on a neighborhood
10/// of the identity. Elements far from the identity may not have a unique
11/// or well-defined logarithm.
12///
13/// # Mathematical Background
14///
15/// For matrix Lie groups, the logarithm is the inverse of the exponential map.
16/// However, unlike `exp: 𝔤 → G` which is always defined, `log: G → 𝔤` has
17/// a restricted domain:
18///
19/// - **U(1)**: Log defined for all elements except exact multiples of e^{iπ}
20/// - **SU(2)**: Log defined for all elements except -I
21/// - **SU(3)**: Log defined in a neighborhood of the identity
22/// - **SO(3)**: Log defined for rotations with angle < π
23///
24/// # Physical Interpretation
25///
26/// In lattice gauge theory, when computing curvature F = log(U_□) from
27/// the Wilson loop U_□:
28///
29/// - If U_□ ≈ I (small plaquette, weak field), log is well-defined
30/// - If U_□ far from I (large plaquette, strong field), log may fail
31/// - Solution: Use smaller lattice spacing or smearing techniques
32#[derive(Debug, Clone, PartialEq)]
33pub enum LogError {
34 /// Element is too far from identity for logarithm to be computed
35 ///
36 /// The logarithm map is only guaranteed to exist in a neighborhood
37 /// of the identity. This error indicates the element is outside
38 /// that neighborhood.
39 NotNearIdentity {
40 /// Distance from identity
41 distance: f64,
42 /// Maximum allowed distance for this group
43 threshold: f64,
44 },
45
46 /// Element is exactly at a singularity of the log map
47 ///
48 /// For example:
49 /// - SU(2): element = -I (rotation by π with ambiguous axis)
50 /// - SO(3): rotation by exactly π (axis ambiguous)
51 Singularity {
52 /// Description of the singularity
53 reason: String,
54 },
55
56 /// Numerical precision insufficient for accurate logarithm
57 ///
58 /// When the element is very close to a singularity, numerical
59 /// errors can dominate. This indicates the computation would
60 /// be unreliable.
61 NumericalInstability {
62 /// Description of the instability
63 reason: String,
64 },
65}
66
67impl fmt::Display for LogError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 LogError::NotNearIdentity {
71 distance,
72 threshold,
73 } => write!(
74 f,
75 "Element too far from identity for log: distance {:.6} exceeds threshold {:.6}",
76 distance, threshold
77 ),
78 LogError::Singularity { reason } => {
79 write!(f, "Logarithm undefined at singularity: {}", reason)
80 }
81 LogError::NumericalInstability { reason } => {
82 write!(f, "Logarithm numerically unstable: {}", reason)
83 }
84 }
85 }
86}
87
88impl std::error::Error for LogError {}
89
90/// Result type for Lie group logarithm operations
91pub type LogResult<T> = Result<T, LogError>;
92
93/// Errors that can occur during representation theory computations
94///
95/// Representation theory involves constructing explicit matrices for
96/// group actions on vector spaces. Some representations require complex
97/// tensor product constructions that may not be implemented.
98#[derive(Debug, Clone, PartialEq)]
99pub enum RepresentationError {
100 /// The requested representation is not yet implemented
101 ///
102 /// This occurs for higher-dimensional representations that require
103 /// tensor product construction or other advanced techniques.
104 UnsupportedRepresentation {
105 /// Description of the representation (e.g., Dynkin labels)
106 representation: String,
107 /// Reason why it's not supported
108 reason: String,
109 },
110
111 /// Invalid representation parameters
112 ///
113 /// For example, negative Dynkin labels or inconsistent dimensions.
114 InvalidParameters {
115 /// Description of the invalid parameters
116 description: String,
117 },
118
119 /// Integration method not implemented for this group
120 ///
121 /// Generic character integration requires group-specific Haar measure
122 /// sampling, which must be implemented separately for each Lie group.
123 UnsupportedIntegrationMethod {
124 /// The integration method attempted
125 method: String,
126 /// The group type (e.g., "generic `LieGroup`")
127 group: String,
128 /// Suggested alternative
129 suggestion: String,
130 },
131}
132
133impl fmt::Display for RepresentationError {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self {
136 RepresentationError::UnsupportedRepresentation {
137 representation,
138 reason,
139 } => write!(
140 f,
141 "Representation {} not supported: {}",
142 representation, reason
143 ),
144 RepresentationError::InvalidParameters { description } => {
145 write!(f, "Invalid representation parameters: {}", description)
146 }
147 RepresentationError::UnsupportedIntegrationMethod {
148 method,
149 group,
150 suggestion,
151 } => write!(
152 f,
153 "{} integration not implemented for {}. {}",
154 method, group, suggestion
155 ),
156 }
157 }
158}
159
160impl std::error::Error for RepresentationError {}
161
162/// Result type for representation theory operations
163pub type RepresentationResult<T> = Result<T, RepresentationError>;
164
165// ============================================================================
166// Logarithm Conditioning
167// ============================================================================
168
169/// Conditioning information for logarithm computations.
170///
171/// The logarithm map on Lie groups becomes ill-conditioned near the cut locus
172/// (e.g., θ → π for SU(2), where the rotation axis becomes ambiguous).
173/// This structure provides quantitative information about the reliability
174/// of the computed logarithm.
175///
176/// # Condition Number Interpretation
177///
178/// The condition number κ measures how sensitive the output is to input
179/// perturbations: `|δ(log U)|/|log U| ≤ κ · |δU|/|U|`
180///
181/// - κ ≈ 1: Well-conditioned, result is reliable
182/// - κ ~ 10: Mildly ill-conditioned, expect ~1 digit loss of precision
183/// - κ > 100: Severely ill-conditioned, result may be unreliable
184/// - κ = ∞: At singularity (cut locus), axis is undefined
185///
186/// # Example
187///
188/// ```ignore
189/// let (log_u, cond) = g.log_with_condition()?;
190/// if cond.is_well_conditioned() {
191/// // Safe to use log_u
192/// } else if cond.is_usable() {
193/// // Use with caution, reduced precision
194/// eprintln!("Warning: log condition number = {:.1}", cond.condition_number);
195/// } else {
196/// // Result unreliable
197/// return Err(...);
198/// }
199/// ```
200#[derive(Debug, Clone)]
201pub struct LogCondition {
202 /// Condition number for the logarithm computation.
203 ///
204 /// For SU(2), this is approximately 1/sin(θ/2) for rotation angle θ,
205 /// which diverges as θ → π (approaching the cut locus at -I).
206 pub condition_number: f64,
207
208 /// Rotation angle (for SO(3)/SU(2)) or equivalent metric.
209 ///
210 /// Useful for understanding why conditioning is poor.
211 pub angle: f64,
212
213 /// Distance from the cut locus (θ = 2π for SU(2), θ = π for SO(3)).
214 ///
215 /// Values close to 0 indicate the element is near the cut locus
216 /// where the logarithm is ill-defined or ill-conditioned.
217 pub distance_to_cut_locus: f64,
218
219 /// Quality assessment of the logarithm computation.
220 pub quality: LogQuality,
221}
222
223/// Quality levels for logarithm computations.
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum LogQuality {
226 /// Excellent conditioning: κ < 2, full precision expected
227 Excellent,
228 /// Good conditioning: κ < 10, reliable results
229 Good,
230 /// Acceptable conditioning: κ < 100, ~2 digits precision loss
231 Acceptable,
232 /// Poor conditioning: κ < 1000, significant precision loss
233 Poor,
234 /// At or near singularity, result unreliable
235 AtSingularity,
236}
237
238impl std::fmt::Display for LogQuality {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 match self {
241 LogQuality::Excellent => write!(f, "Excellent"),
242 LogQuality::Good => write!(f, "Good"),
243 LogQuality::Acceptable => write!(f, "Acceptable"),
244 LogQuality::Poor => write!(f, "Poor"),
245 LogQuality::AtSingularity => write!(f, "AtSingularity"),
246 }
247 }
248}
249
250impl LogCondition {
251 /// Create a new `LogCondition` from the rotation angle.
252 ///
253 /// For SU(2)/SO(3), the condition number is approximately 1/sin(θ/2)
254 /// where θ is the rotation angle.
255 pub fn from_angle(angle: f64) -> Self {
256 let half_angle = angle / 2.0;
257 let sin_half = half_angle.sin().abs();
258 // For SU(2), the cut locus is at θ = 2π (U = -I); for SO(3) it's at θ = π.
259 // We use 2π here as this is primarily used by SU(2)'s log_with_condition.
260 let distance_to_cut_locus = (2.0 * std::f64::consts::PI - angle).abs();
261
262 // Condition number: For extracting axis from sin(θ/2) division
263 // κ ≈ 1/sin(θ/2), but bounded by practical considerations
264 let condition_number = if sin_half > 1e-10 {
265 1.0 / sin_half
266 } else {
267 f64::INFINITY
268 };
269
270 let quality = if condition_number < 2.0 {
271 LogQuality::Excellent
272 } else if condition_number < 10.0 {
273 LogQuality::Good
274 } else if condition_number < 100.0 {
275 LogQuality::Acceptable
276 } else if condition_number < 1000.0 {
277 LogQuality::Poor
278 } else {
279 LogQuality::AtSingularity
280 };
281
282 Self {
283 condition_number,
284 angle,
285 distance_to_cut_locus,
286 quality,
287 }
288 }
289
290 /// Returns true if the result is well-conditioned (κ < 10).
291 #[must_use]
292 pub fn is_well_conditioned(&self) -> bool {
293 matches!(self.quality, LogQuality::Excellent | LogQuality::Good)
294 }
295
296 /// Returns true if the result is usable (κ < 100).
297 #[must_use]
298 pub fn is_usable(&self) -> bool {
299 matches!(
300 self.quality,
301 LogQuality::Excellent | LogQuality::Good | LogQuality::Acceptable
302 )
303 }
304
305 /// Returns true if the result is at or near a singularity.
306 #[must_use]
307 pub fn is_singular(&self) -> bool {
308 matches!(self.quality, LogQuality::AtSingularity)
309 }
310}
311
312/// Result type for conditioned logarithm operations.
313pub type ConditionedLogResult<T> = Result<(T, LogCondition), LogError>;
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use std::f64::consts::PI;
319
320 // ========================================================================
321 // Condition Number Formula Tests
322 // ========================================================================
323
324 /// Verify condition number formula: κ = 1/sin(θ/2)
325 ///
326 /// The condition number captures the difficulty of extracting the rotation
327 /// axis from sin(θ/2). It diverges as θ → 0 (small angle, axis hard to
328 /// determine) and is well-conditioned near θ = π.
329 #[test]
330 fn test_condition_number_formula() {
331 // For θ = π/2, κ = 1/sin(π/4) = √2 ≈ 1.414
332 let cond_pi_2 = LogCondition::from_angle(PI / 2.0);
333 let expected = 1.0 / (PI / 4.0).sin();
334 assert!(
335 (cond_pi_2.condition_number - expected).abs() < 1e-10,
336 "θ=π/2: got {}, expected {}",
337 cond_pi_2.condition_number,
338 expected
339 );
340 assert_eq!(cond_pi_2.quality, LogQuality::Excellent);
341
342 // For θ = π/3, κ = 1/sin(π/6) = 2.0 (at boundary, classified as Good)
343 let cond_pi_3 = LogCondition::from_angle(PI / 3.0);
344 let expected = 1.0 / (PI / 6.0).sin();
345 assert!(
346 (cond_pi_3.condition_number - expected).abs() < 1e-10,
347 "θ=π/3: got {}, expected {}",
348 cond_pi_3.condition_number,
349 expected
350 );
351 // κ = 2.0 exactly, which is at the boundary (κ < 2 is Excellent, κ ≥ 2 is Good)
352 assert_eq!(cond_pi_3.quality, LogQuality::Good);
353
354 // For θ = 0.1, κ = 1/sin(0.05) ≈ 20.0
355 let cond_small = LogCondition::from_angle(0.1);
356 let expected = 1.0 / (0.05_f64).sin();
357 assert!(
358 (cond_small.condition_number - expected).abs() < 1e-8,
359 "θ=0.1: got {}, expected {}",
360 cond_small.condition_number,
361 expected
362 );
363 }
364
365 /// Verify quality classification thresholds
366 #[test]
367 fn test_quality_classification() {
368 // Excellent: κ < 2 → need sin(θ/2) > 0.5 → θ/2 > π/6 → θ > π/3
369 let excellent = LogCondition::from_angle(PI * 0.7); // θ/2 = 0.35π, sin ≈ 0.89
370 assert_eq!(excellent.quality, LogQuality::Excellent);
371 assert!(excellent.is_well_conditioned());
372 assert!(excellent.is_usable());
373
374 // Good: 2 ≤ κ < 10 → 0.1 < sin(θ/2) ≤ 0.5
375 let good = LogCondition::from_angle(PI * 0.3); // θ/2 = 0.15π, sin ≈ 0.45
376 assert_eq!(good.quality, LogQuality::Good);
377 assert!(good.is_well_conditioned());
378
379 // Acceptable: 10 ≤ κ < 100 → 0.01 < sin(θ/2) ≤ 0.1
380 let acceptable = LogCondition::from_angle(0.1); // θ/2 = 0.05, sin ≈ 0.05
381 assert_eq!(acceptable.quality, LogQuality::Acceptable);
382 assert!(!acceptable.is_well_conditioned());
383 assert!(acceptable.is_usable());
384
385 // Poor: 100 ≤ κ < 1000 → 0.001 < sin(θ/2) ≤ 0.01
386 let poor = LogCondition::from_angle(0.01); // θ/2 = 0.005, sin ≈ 0.005
387 assert_eq!(poor.quality, LogQuality::Poor);
388 assert!(!poor.is_well_conditioned());
389 assert!(!poor.is_usable());
390
391 // AtSingularity: κ ≥ 1000 → sin(θ/2) ≤ 0.001
392 let singular = LogCondition::from_angle(0.001); // θ/2 = 0.0005, sin ≈ 0.0005
393 assert_eq!(singular.quality, LogQuality::AtSingularity);
394 assert!(singular.is_singular());
395 }
396
397 /// Verify distance to cut locus computation
398 ///
399 /// For SU(2), the cut locus is at θ = 2π (U = -I), not θ = π.
400 #[test]
401 fn test_distance_to_cut_locus() {
402 // At θ = π/2, distance to cut locus = 2π - π/2 = 3π/2
403 let cond = LogCondition::from_angle(PI / 2.0);
404 assert!(
405 (cond.distance_to_cut_locus - 3.0 * PI / 2.0).abs() < 1e-10,
406 "Distance to cut locus mismatch"
407 );
408
409 // At θ = 2π - 0.1, distance = 0.1
410 let cond_near = LogCondition::from_angle(2.0 * PI - 0.1);
411 assert!(
412 (cond_near.distance_to_cut_locus - 0.1).abs() < 1e-10,
413 "Near cut locus distance mismatch"
414 );
415
416 // At θ ≈ 0, distance ≈ 2π
417 let cond_far = LogCondition::from_angle(0.01);
418 assert!(
419 (cond_far.distance_to_cut_locus - (2.0 * PI - 0.01)).abs() < 1e-10,
420 "Far from cut locus distance mismatch"
421 );
422 }
423
424 /// Verify behavior near θ = 0 (where axis extraction becomes ill-conditioned)
425 ///
426 /// The condition number κ = 1/sin(θ/2) diverges at both θ → 0 and θ → 2π.
427 /// Near θ = 0, the rotation is near-identity; near θ = 2π, U → -I.
428 #[test]
429 fn test_small_angle_conditioning() {
430 // As θ → 0, condition number → ∞
431 let small_angles = [0.5, 0.1, 0.01, 0.001, 0.0001];
432 let mut prev_kappa = 0.0;
433
434 for &angle in &small_angles {
435 let cond = LogCondition::from_angle(angle);
436
437 // Condition number should increase monotonically as angle decreases
438 assert!(
439 cond.condition_number > prev_kappa,
440 "Condition number should increase as θ→0: θ={}, κ={}",
441 angle,
442 cond.condition_number
443 );
444 prev_kappa = cond.condition_number;
445 }
446
447 // At θ = 1e-11, should be effectively singular
448 let cond_singular = LogCondition::from_angle(1e-11);
449 assert_eq!(cond_singular.quality, LogQuality::AtSingularity);
450 }
451
452 /// Verify behavior near θ = π and θ = 2π
453 ///
454 /// For SU(2): θ = π is well-conditioned (sin(π/2) = 1, axis extractable).
455 /// The true cut locus is at θ = 2π (U = -I, sin(π) = 0).
456 #[test]
457 fn test_near_cut_locus() {
458 // At θ = π, κ = 1/sin(π/2) = 1 (well-conditioned!)
459 let cond_pi = LogCondition::from_angle(PI);
460 assert!(
461 (cond_pi.condition_number - 1.0).abs() < 1e-10,
462 "At θ=π, κ should be 1: got {}",
463 cond_pi.condition_number
464 );
465 assert_eq!(cond_pi.quality, LogQuality::Excellent);
466
467 // At θ = π - 0.1, still well-conditioned
468 let cond_near_pi = LogCondition::from_angle(PI - 0.1);
469 assert!(cond_near_pi.is_well_conditioned());
470
471 // Distance to cut locus at θ = π should be π (since cut locus is at 2π)
472 assert!(
473 (cond_pi.distance_to_cut_locus - PI).abs() < 1e-10,
474 "Distance to cut locus at θ=π should be π: got {}",
475 cond_pi.distance_to_cut_locus
476 );
477
478 // At θ = 2π, distance to cut locus should be 0
479 let cond_2pi = LogCondition::from_angle(2.0 * PI);
480 assert!(
481 cond_2pi.distance_to_cut_locus < 1e-10,
482 "Distance to cut locus at θ=2π should be 0"
483 );
484 }
485
486 // ========================================================================
487 // Precision Loss Estimation Tests
488 // ========================================================================
489
490 /// Estimate actual precision loss based on condition number
491 ///
492 /// If κ is the condition number and ε is machine epsilon (~2.2e-16),
493 /// then the expected error bound is roughly κ·ε.
494 /// We expect to lose log10(κ) digits of precision.
495 #[test]
496 fn test_precision_loss_estimate() {
497 let machine_eps = f64::EPSILON;
498
499 // For θ = π/2: κ ≈ 1.41, expect ~0 digits lost
500 let cond_good = LogCondition::from_angle(PI / 2.0);
501 let expected_error = cond_good.condition_number * machine_eps;
502 let digits_lost = cond_good.condition_number.log10();
503 assert!(digits_lost < 1.0, "Should lose < 1 digit at θ=π/2");
504 println!(
505 "θ=π/2: κ={:.2}, expected error={:.2e}, digits lost={:.2}",
506 cond_good.condition_number, expected_error, digits_lost
507 );
508
509 // For θ = 0.1: κ ≈ 20, expect ~1.3 digits lost
510 let cond_moderate = LogCondition::from_angle(0.1);
511 let digits_lost = cond_moderate.condition_number.log10();
512 assert!(
513 digits_lost > 1.0 && digits_lost < 2.0,
514 "Should lose ~1-2 digits at θ=0.1"
515 );
516 println!(
517 "θ=0.1: κ={:.2}, digits lost={:.2}",
518 cond_moderate.condition_number, digits_lost
519 );
520
521 // For θ = 0.01: κ ≈ 200, expect ~2.3 digits lost
522 let cond_poor = LogCondition::from_angle(0.01);
523 let digits_lost = cond_poor.condition_number.log10();
524 assert!(
525 digits_lost > 2.0 && digits_lost < 3.0,
526 "Should lose ~2-3 digits at θ=0.01"
527 );
528 println!(
529 "θ=0.01: κ={:.2}, digits lost={:.2}",
530 cond_poor.condition_number, digits_lost
531 );
532 }
533
534 // ========================================================================
535 // LogError Tests
536 // ========================================================================
537
538 #[test]
539 fn test_log_error_display() {
540 let err = LogError::NotNearIdentity {
541 distance: 2.5,
542 threshold: 1.0,
543 };
544 let msg = format!("{}", err);
545 assert!(msg.contains("2.5"));
546 assert!(msg.contains("1.0"));
547
548 let err2 = LogError::Singularity {
549 reason: "rotation by π".to_string(),
550 };
551 let msg2 = format!("{}", err2);
552 assert!(msg2.contains("rotation by π"));
553 }
554
555 #[test]
556 fn test_representation_error_display() {
557 let err = RepresentationError::UnsupportedRepresentation {
558 representation: "(3,3)".to_string(),
559 reason: "too high dimensional".to_string(),
560 };
561 let msg = format!("{}", err);
562 assert!(msg.contains("(3,3)"));
563 assert!(msg.contains("too high dimensional"));
564 }
565}