lie_groups/traits.rs
1//! Traits for Lie groups and Lie algebras.
2//!
3//! This module defines the core abstractions for Lie theory. These traits
4//! capture the mathematical structure
5//! of Lie groups as both algebraic objects (groups) and geometric objects
6//! (differentiable manifolds).
7//!
8//! # Mathematical Background
9//!
10//! A **Lie group** G is a smooth manifold that is also a group, where the
11//! group operations are smooth maps:
12//!
13//! - Multiplication: G × G → G is smooth
14//! - Inversion: G → G is smooth
15//! - Identity: e ∈ G
16//!
17//! A **Lie algebra** 𝔤 is the tangent space at the identity, with a Lie bracket
18//! operation [·,·] : 𝔤 × 𝔤 → 𝔤 satisfying bilinearity, antisymmetry, and the Jacobi identity.
19//!
20//! The **exponential map** exp: 𝔤 → G connects the Lie algebra to the Lie group,
21//! mapping tangent vectors to group elements via one-parameter subgroups.
22//!
23//! # Design Philosophy
24//!
25//! These traits enable **generic programming over Lie groups**: algorithms written
26//! against the `LieGroup` trait work for SU(2), U(1), SU(3), etc. without modification.
27//! This mirrors how mathematicians work—proving theorems for "a Lie group G"
28//! rather than for each specific group.
29//!
30//! # Examples
31//!
32//! ```
33//! use lie_groups::{LieGroup, SU2};
34//!
35//! // Generic parallel transport along a path
36//! fn parallel_transport<G: LieGroup>(path: &[G]) -> G {
37//! path.iter().fold(G::identity(), |acc, g| acc.compose(g))
38//! }
39//!
40//! // Works for any Lie group!
41//! let su2_path = vec![SU2::rotation_x(0.1), SU2::rotation_y(0.2)];
42//! let holonomy = parallel_transport(&su2_path);
43//! ```
44//!
45//! # Constrained Representations: Compile-Time Guarantees
46//!
47//! This module uses **constrained representations** to encode mathematical
48//! properties at the type level. The key insight: instead of checking if a
49//! value satisfies a property at runtime, use a representation that makes
50//! invalid values **unrepresentable**.
51//!
52//! ## The Pattern
53//!
54//! | Property | Representation | Why It Works |
55//! |----------|----------------|--------------|
56//! | Traceless | Basis coefficients | Pauli/Gell-Mann matrices are traceless |
57//! | Anti-Hermitian | `i·(Hermitian basis)` | `(iH)† = -iH† = -iH` |
58//! | Unit norm | Private fields + normalizing constructor | Can only create unit values |
59//! | Fixed size | `[T; N]` instead of `Vec<T>` | Compile-time size guarantee |
60//!
61//! ## Example: Traceless by Construction
62//!
63//! ```text
64//! // BAD: General matrix, might not be traceless
65//! struct BadAlgebra { matrix: [[f64; 2]; 2] } // tr(M) could be anything
66//!
67//! // GOOD: Pauli basis coefficients, always traceless
68//! struct Su2Algebra([f64; 3]); // X = a·iσ₁ + b·iσ₂ + c·iσ₃, tr(X) = 0 always
69//! ```
70//!
71//! ## Connection to Lean Proofs
72//!
73//! The marker traits in this module connect to formal proofs:
74//!
75//! - [`TracelessByConstruction`]: Combined with the Lean theorem
76//! `det_exp_eq_exp_trace`, gives `det(exp(X)) = exp(0) = 1` for all X.
77//!
78//! - [`AntiHermitianByConstruction`]: Combined with `exp_antiHermitian_unitary`,
79//! gives `exp(X)† · exp(X) = I` for all X.
80//!
81//! These are **compile-time guarantees**: if your code compiles with the trait
82//! bound `A: TracelessByConstruction`, you know all algebra elements are
83//! traceless without any runtime checks.
84//!
85//! ## Zero Runtime Overhead
86//!
87//! Marker traits are empty—no methods, no data:
88//!
89//! ```ignore
90//! pub trait TracelessByConstruction: LieAlgebra {}
91//! impl TracelessByConstruction for Su2Algebra {} // Empty impl
92//! ```
93//!
94//! The compiler uses them for type checking, then erases them completely.
95//! Same pattern as `Send`, `Sync`, `Copy` in std.
96//!
97//! ## When to Use Each Pattern
98//!
99//! | Pattern | Use When | Example |
100//! |---------|----------|---------|
101//! | Sealed marker trait | Property depends on type, not value | `TracelessByConstruction` |
102//! | Private fields | Property depends on value (invariant) | `UnitQuaternion` unit norm |
103//! | Fixed-size array | Size is known at compile time | `Plaquette([NodeIndex; 4])` |
104//! | Builder pattern | Invariant built incrementally | Path with consecutive edges |
105
106use num_complex::Complex;
107
108// ============================================================================
109// Sealed Trait Pattern for Marker Traits
110// ============================================================================
111//
112// Marker traits (Compact, Abelian, Simple, SemiSimple) are sealed to prevent
113// incorrect claims about mathematical properties. These encode theorems:
114// - "U(1) is abelian" is a mathematical fact that shouldn't be claimable by arbitrary types
115// - "SU(2) is compact" likewise
116//
117// The core `LieGroup` and `LieAlgebra` traits are OPEN - the associated type
118// constraints provide compile-time safety without sealing.
119
120mod sealed {
121 // Import types for cleaner impl blocks
122 use super::super::rplus::RPlus;
123 use super::super::so3::{So3Algebra, SO3};
124 use super::super::su2::{Su2Algebra, SU2};
125 use super::super::su3::{Su3Algebra, SU3};
126 use super::super::sun::{SunAlgebra, SUN};
127 use super::super::u1::{U1Algebra, U1};
128
129 /// Sealed trait for compact groups.
130 pub trait SealedCompact {}
131
132 /// Sealed trait for abelian groups.
133 pub trait SealedAbelian {}
134
135 /// Sealed trait for simple groups.
136 pub trait SealedSimple {}
137
138 /// Sealed trait for semi-simple groups.
139 pub trait SealedSemiSimple {}
140
141 /// Sealed trait for algebras that are traceless by construction.
142 ///
143 /// An algebra is "traceless by construction" if its representation
144 /// structurally guarantees tr(X) = 0 for all elements X.
145 ///
146 /// # Examples
147 /// - `Su2Algebra([f64; 3])`: Pauli basis coefficients → always traceless
148 /// - `Su3Algebra([f64; 8])`: Gell-Mann basis coefficients → always traceless
149 ///
150 /// # Non-examples
151 /// - `U1Algebra(f64)`: Represents iℝ, trace = i·a ≠ 0
152 /// - General matrix algebras: Can have arbitrary trace
153 pub trait SealedTraceless {}
154
155 /// Sealed trait for algebras that are anti-Hermitian by construction.
156 ///
157 /// An algebra is "anti-Hermitian by construction" if its representation
158 /// structurally guarantees X† = -X for all elements X.
159 ///
160 /// # Examples
161 /// - `Su2Algebra`: Uses i·σ basis (anti-Hermitian)
162 /// - `U1Algebra`: Represents iℝ ⊂ ℂ (purely imaginary = anti-Hermitian)
163 /// - `So3Algebra`: Real antisymmetric matrices (= anti-Hermitian)
164 pub trait SealedAntiHermitian {}
165
166 // =========================================================================
167 // Sealed trait implementations
168 // =========================================================================
169
170 // Compact groups (all our groups are compact)
171 impl SealedCompact for SU2 {}
172 impl SealedCompact for SU3 {}
173 impl SealedCompact for U1 {}
174 impl SealedCompact for SO3 {}
175 impl<const N: usize> SealedCompact for SUN<N> {}
176
177 // Abelian groups
178 impl SealedAbelian for U1 {}
179 impl SealedAbelian for RPlus {}
180
181 // Simple groups (SU(2), SU(3), SO(3), SU(N) for N >= 2)
182 impl SealedSimple for SU2 {}
183 impl SealedSimple for SU3 {}
184 impl SealedSimple for SO3 {}
185 // SUN<N> is simple (hence semi-simple) for all N >= 2.
186 // The const assert in SUN prevents N < 2 at compile time.
187 impl<const N: usize> SealedSimple for SUN<N> {}
188
189 // Semi-simple groups (all simple groups are semi-simple)
190 impl SealedSemiSimple for SU2 {}
191 impl SealedSemiSimple for SU3 {}
192 impl SealedSemiSimple for SO3 {}
193 impl<const N: usize> SealedSemiSimple for SUN<N> {}
194
195 // Traceless algebras (representation guarantees tr(X) = 0)
196 // NOT U1Algebra: u(1) = iℝ has tr(ia) = ia ≠ 0
197 impl SealedTraceless for Su2Algebra {}
198 impl SealedTraceless for Su3Algebra {}
199 impl SealedTraceless for So3Algebra {}
200 impl<const N: usize> SealedTraceless for SunAlgebra<N> {}
201
202 // Anti-Hermitian algebras (representation guarantees X† = -X)
203 impl SealedAntiHermitian for Su2Algebra {}
204 impl SealedAntiHermitian for Su3Algebra {}
205 impl SealedAntiHermitian for So3Algebra {}
206 impl SealedAntiHermitian for U1Algebra {}
207 impl<const N: usize> SealedAntiHermitian for SunAlgebra<N> {}
208}
209
210// ============================================================================
211// Marker Traits for Mathematical Properties
212// ============================================================================
213
214/// Marker trait for compact Lie groups.
215///
216/// A Lie group is **compact** if it is compact as a topological space.
217///
218/// # Examples
219/// - `U(1)`, `SU(2)`, `SU(3)`, `SO(3)` - all compact
220///
221/// # Significance
222/// - Representations are completely reducible
223/// - Yang-Mills theory well-defined
224/// - Haar measure exists
225///
226/// # Sealed Trait
227///
228/// This trait is sealed - only verified compact groups can implement it.
229pub trait Compact: LieGroup + sealed::SealedCompact {}
230
231/// Marker trait for abelian (commutative) Lie groups.
232///
233/// Satisfies: `g.compose(&h) == h.compose(&g)` for all g, h
234///
235/// # Examples
236/// - `U(1)` - abelian (electromagnetism)
237///
238/// # Type Safety
239/// ```ignore
240/// fn maxwell_theory<G: Abelian>(conn: &NetworkConnection<G>) {
241/// // Compiles only for abelian groups!
242/// }
243/// ```
244///
245/// # Sealed Trait
246///
247/// This trait is sealed - only verified abelian groups can implement it.
248pub trait Abelian: LieGroup + sealed::SealedAbelian {}
249
250/// Marker trait for simple Lie groups (no non-trivial normal subgroups).
251///
252/// # Examples
253/// - `SU(2)`, `SU(3)`, `SO(3)` - simple
254///
255/// # Sealed Trait
256///
257/// This trait is sealed - only verified simple groups can implement it.
258pub trait Simple: SemiSimple + sealed::SealedSimple {}
259
260/// Marker trait for semi-simple Lie groups (products of simple groups).
261///
262/// # Examples
263/// - `SU(2)`, `SU(3)` - semi-simple
264///
265/// # Sealed Trait
266///
267/// This trait is sealed - only verified semi-simple groups can implement it.
268pub trait SemiSimple: LieGroup + sealed::SealedSemiSimple {}
269
270/// Marker trait for Lie algebras whose representation is structurally traceless.
271///
272/// A Lie algebra is "traceless by construction" when its representation
273/// **guarantees** tr(X) = 0 for all elements X, without runtime verification.
274///
275/// # Mathematical Significance
276///
277/// Combined with the theorem `det(exp(X)) = exp(tr(X))` (proven in Lean as
278/// `Matrix.det_exp_eq_exp_trace`), this gives a **compile-time guarantee**:
279///
280/// ```text
281/// A: TracelessByConstruction ⟹ tr(X) = 0 for all X ∈ A
282/// ⟹ det(exp(X)) = exp(0) = 1
283/// ```
284///
285/// Therefore, `exp` maps traceless algebras to special (det = 1) groups.
286///
287/// # Examples
288///
289/// - `Su2Algebra([f64; 3])`: Pauli basis coefficients → always traceless
290/// - `Su3Algebra([f64; 8])`: Gell-Mann basis coefficients → always traceless
291/// - `So3Algebra([f64; 3])`: Antisymmetric matrices → always traceless
292///
293/// # Non-examples
294///
295/// - `U1Algebra(f64)`: Represents iℝ ⊂ ℂ, where tr(ia) = ia ≠ 0
296/// - General `Matrix<N,N>`: Can have arbitrary trace
297///
298/// # Type-Level Proof Pattern
299///
300/// ```ignore
301/// fn exp_to_special_unitary<A>(x: &A) -> SpecialUnitary
302/// where
303/// A: TracelessByConstruction,
304/// {
305/// // Compiler knows: tr(x) = 0 by construction
306/// // Lean theorem: det(exp(x)) = exp(tr(x)) = exp(0) = 1
307/// // Therefore: exp(x) ∈ SU(n), not just U(n)
308/// }
309/// ```
310///
311/// # Sealed Trait
312///
313/// This trait is sealed - only algebras with verified traceless representations
314/// can implement it.
315pub trait TracelessByConstruction: LieAlgebra + sealed::SealedTraceless {}
316
317/// Marker trait for Lie algebras whose representation is structurally anti-Hermitian.
318///
319/// A Lie algebra is "anti-Hermitian by construction" when its representation
320/// **guarantees** X† = -X for all elements X, without runtime verification.
321///
322/// # Mathematical Significance
323///
324/// Combined with the theorem `exp(X)† · exp(X) = I` for anti-Hermitian X
325/// (proven in Lean as `Matrix.exp_antiHermitian_unitary`), this gives a
326/// **compile-time guarantee**:
327///
328/// ```text
329/// A: AntiHermitianByConstruction ⟹ X† = -X for all X ∈ A
330/// ⟹ exp(X) is unitary
331/// ```
332///
333/// # Examples
334///
335/// - `Su2Algebra`: Uses i·σ basis (anti-Hermitian Pauli matrices)
336/// - `Su3Algebra`: Uses i·λ basis (anti-Hermitian Gell-Mann matrices)
337/// - `So3Algebra`: Real antisymmetric matrices (= anti-Hermitian over ℝ)
338/// - `U1Algebra`: Represents iℝ ⊂ ℂ (purely imaginary = anti-Hermitian)
339///
340/// # Sealed Trait
341///
342/// This trait is sealed - only algebras with verified anti-Hermitian representations
343/// can implement it.
344pub trait AntiHermitianByConstruction: LieAlgebra + sealed::SealedAntiHermitian {}
345
346/// Trait for Lie algebra elements.
347///
348/// A Lie algebra 𝔤 is a vector space equipped with a bilinear, antisymmetric
349/// operation called the Lie bracket [·,·] : 𝔤 × 𝔤 → 𝔤 that satisfies the Jacobi identity.
350///
351/// In this library, we represent Lie algebras as the tangent space at the identity
352/// of a Lie group. For matrix groups, algebra elements are traceless anti-Hermitian matrices.
353///
354/// # Mathematical Structure
355///
356/// For classical Lie groups:
357/// - **u(1)**: ℝ (1-dimensional)
358/// - **su(2)**: ℝ³ (3-dimensional, isomorphic to pure quaternions)
359/// - **su(3)**: ℝ⁸ (8-dimensional, Gell-Mann basis)
360///
361/// # Design
362///
363/// This trait enables generic gradient descent on Lie groups via the exponential map.
364/// The gradient lives in the Lie algebra, and we use `exp: 𝔤 → G` to move on the manifold.
365///
366/// # Examples
367///
368/// ```ignore
369/// use lie_groups::{LieAlgebra, Su2Algebra};
370///
371/// let v = Su2Algebra::basis_element(0); // Pauli X direction
372/// let w = Su2Algebra::basis_element(1); // Pauli Y direction
373/// let sum = v.add(&w); // Linear combination
374/// let scaled = v.scale(2.0); // Scalar multiplication
375/// ```
376///
377/// # Open Trait
378///
379/// This trait is open for implementation. The associated type constraints
380/// on `LieGroup` (requiring `type Algebra: LieAlgebra`) provide compile-time
381/// safety by enforcing group-algebra correspondence.
382pub trait LieAlgebra: Clone + Sized {
383 /// Dimension of the Lie algebra as a compile-time constant.
384 ///
385 /// This enables compile-time dimension checking where possible.
386 /// For example, `from_components` can verify array length at compile time
387 /// when called with a fixed-size array.
388 ///
389 /// # Values
390 ///
391 /// - `U1Algebra`: 1
392 /// - `Su2Algebra`: 3
393 /// - `So3Algebra`: 3
394 /// - `Su3Algebra`: 8
395 /// - `SunAlgebra<N>`: N² - 1
396 ///
397 /// # Note
398 ///
399 /// Rust doesn't yet support `where A: LieAlgebra<DIM = 3>` bounds
400 /// (requires `generic_const_exprs`). Use `const_assert!` or runtime
401 /// checks with `Self::DIM` for now.
402 const DIM: usize;
403
404 /// Dimension of the Lie algebra (dim 𝔤).
405 ///
406 /// This is the dimension as a real vector space:
407 /// - u(1): 1
408 /// - su(2): 3
409 /// - su(3): 8
410 /// - so(3): 3
411 ///
412 /// # Note
413 ///
414 /// Prefer using [`Self::DIM`] for compile-time checks. This method
415 /// exists for backward compatibility and dynamic dispatch scenarios.
416 #[must_use]
417 fn dim() -> usize {
418 Self::DIM
419 }
420
421 /// Zero element (additive identity) 0 ∈ 𝔤.
422 ///
423 /// Satisfies: `v.add(&Self::zero()) == v` for all v ∈ 𝔤.
424 #[must_use]
425 fn zero() -> Self;
426
427 /// Add two algebra elements: v + w
428 ///
429 /// The Lie algebra is a vector space, so addition is defined.
430 ///
431 /// # Properties
432 ///
433 /// - Commutative: `v.add(&w) == w.add(&v)`
434 /// - Associative: `u.add(&v.add(&w)) == u.add(&v).add(&w)`
435 /// - Identity: `v.add(&Self::zero()) == v`
436 #[must_use]
437 fn add(&self, other: &Self) -> Self;
438
439 /// Scalar multiplication: α · v
440 ///
441 /// Scale the algebra element by a real number.
442 ///
443 /// # Properties
444 ///
445 /// - Distributive: `v.scale(a + b) ≈ v.scale(a).add(&v.scale(b))`
446 /// - Associative: `v.scale(a * b) ≈ v.scale(a).scale(b)`
447 /// - Identity: `v.scale(1.0) == v`
448 #[must_use]
449 fn scale(&self, scalar: f64) -> Self;
450
451 /// Euclidean norm: ||v||
452 ///
453 /// For finite-dimensional vector spaces, this is the standard L² norm:
454 /// ```text
455 /// ||v|| = √(Σᵢ vᵢ²)
456 /// ```
457 ///
458 /// Used for:
459 /// - Gradient descent step size control
460 /// - Checking convergence (||∇f|| < ε)
461 ///
462 /// # Returns
463 ///
464 /// Non-negative real number. Zero iff `self` is the zero element.
465 #[must_use]
466 fn norm(&self) -> f64;
467
468 /// Get the i-th basis element of the Lie algebra.
469 ///
470 /// For su(2), the basis is {σₓ, σᵧ, σᵤ}/2i (Pauli matrices).
471 /// For u(1), the single basis element is i (imaginary unit).
472 ///
473 /// # Parameters
474 ///
475 /// - `i`: Index in [0, `dim()`)
476 ///
477 /// # Panics
478 ///
479 /// If `i >= Self::dim()`.
480 ///
481 /// # Example
482 ///
483 /// ```ignore
484 /// use lie_groups::{LieAlgebra, Su2Algebra};
485 ///
486 /// let sigma_x = Su2Algebra::basis_element(0);
487 /// let sigma_y = Su2Algebra::basis_element(1);
488 /// let sigma_z = Su2Algebra::basis_element(2);
489 /// ```
490 #[must_use]
491 fn basis_element(i: usize) -> Self;
492
493 /// Construct algebra element from basis coordinates.
494 ///
495 /// Given coefficients [c₀, c₁, ..., c_{n-1}], constructs:
496 /// ```text
497 /// v = Σᵢ cᵢ eᵢ
498 /// ```
499 /// where eᵢ are the basis elements.
500 ///
501 /// # Parameters
502 ///
503 /// - `components`: Slice of length `Self::dim()`
504 ///
505 /// # Panics
506 ///
507 /// If `components.len() != Self::dim()`.
508 ///
509 /// # Example
510 ///
511 /// ```ignore
512 /// use lie_groups::{LieAlgebra, Su2Algebra};
513 ///
514 /// let v = Su2Algebra::from_components(&[1.0, 0.0, 0.0]); // Pure X
515 /// let w = Su2Algebra::from_components(&[0.5, 0.5, 0.0]); // X + Y mix
516 /// ```
517 #[must_use]
518 fn from_components(components: &[f64]) -> Self;
519
520 /// Extract basis coordinates from algebra element.
521 ///
522 /// Returns the coefficients [c₀, c₁, ..., c_{n-1}] such that:
523 /// ```text
524 /// self = Σᵢ cᵢ eᵢ
525 /// ```
526 /// where eᵢ are the basis elements.
527 ///
528 /// # Returns
529 ///
530 /// Vector of length `Self::dim()` containing the basis coordinates.
531 ///
532 /// # Example
533 ///
534 /// ```ignore
535 /// use lie_groups::{LieAlgebra, Su2Algebra};
536 ///
537 /// let v = Su2Algebra::from_components(&[1.0, 2.0, 3.0]);
538 /// let coords = v.to_components();
539 /// assert_eq!(coords, vec![1.0, 2.0, 3.0]);
540 /// ```
541 #[must_use]
542 fn to_components(&self) -> Vec<f64>;
543
544 /// Lie bracket operation: [X, Y] ∈ 𝔤
545 ///
546 /// The Lie bracket is a binary operation on the Lie algebra that captures
547 /// the non-commutativity of the group. For matrix Lie algebras:
548 /// ```text
549 /// [X, Y] = XY - YX (matrix commutator)
550 /// ```
551 ///
552 /// # Mathematical Properties
553 ///
554 /// The Lie bracket must satisfy:
555 ///
556 /// 1. **Bilinearity**:
557 /// - `[aX + bY, Z] = a[X,Z] + b[Y,Z]`
558 /// - `[X, aY + bZ] = a[X,Y] + b[X,Z]`
559 ///
560 /// 2. **Antisymmetry**: `[X, Y] = -[Y, X]`
561 ///
562 /// 3. **Jacobi Identity**:
563 /// ```text
564 /// [X, [Y, Z]] + [Y, [Z, X]] + [Z, [X, Y]] = 0
565 /// ```
566 ///
567 /// # Geometric Interpretation
568 ///
569 /// The bracket measures the "infinitesimal non-commutativity" of the group.
570 /// For SU(2) with structure constants `[eᵢ, eⱼ] = −εᵢⱼₖeₖ`:
571 /// ```text
572 /// [e₁, e₂] = −e₃, [e₂, e₃] = −e₁, [e₃, e₁] = −e₂
573 /// ```
574 ///
575 /// # Applications
576 ///
577 /// - **Structure constants**: Define the algebra's multiplication table
578 /// - **Curvature formulas**: F = dA + [A, A]
579 /// - **Representation theory**: Adjoint representation
580 ///
581 /// # Examples
582 ///
583 /// ```ignore
584 /// use lie_groups::{LieAlgebra, Su2Algebra};
585 ///
586 /// let e1 = Su2Algebra::basis_element(0);
587 /// let e2 = Su2Algebra::basis_element(1);
588 /// let bracket = e1.bracket(&e2);
589 ///
590 /// // [e₁, e₂] = −e₃, so bracket has norm 1
591 /// assert!((bracket.norm() - 1.0).abs() < 1e-10);
592 /// ```
593 ///
594 #[must_use]
595 fn bracket(&self, other: &Self) -> Self;
596
597 /// Inner product: ⟨v, w⟩
598 ///
599 /// The inner product induced by the Killing form (or Frobenius norm for
600 /// matrix Lie algebras). For an orthonormal basis {eᵢ}:
601 /// ```text
602 /// ⟨v, w⟩ = Σᵢ vᵢ wᵢ
603 /// ```
604 ///
605 /// # Numerical Stability
606 ///
607 /// This method provides a numerically stable inner product computation.
608 /// The default implementation uses `to_components()` for direct summation,
609 /// avoiding the catastrophic cancellation issues of the polarization identity.
610 ///
611 /// # Returns
612 ///
613 /// The inner product as a real number. For orthonormal bases, this equals
614 /// the dot product of the coefficient vectors.
615 ///
616 /// # Default Implementation
617 ///
618 /// Computes `Σᵢ self.to_components()[i] * other.to_components()[i]`.
619 /// Override for algebras with more efficient direct computation.
620 #[must_use]
621 fn inner(&self, other: &Self) -> f64 {
622 let v = self.to_components();
623 let w = other.to_components();
624 v.iter().zip(w.iter()).map(|(vi, wi)| vi * wi).sum()
625 }
626}
627
628/// Core trait for Lie group elements.
629///
630/// A Lie group is a smooth manifold with a compatible group structure.
631/// This trait captures both the algebraic (group operations) and geometric
632/// (distance, smoothness) aspects.
633///
634/// # Mathematical Properties
635///
636/// Implementations must satisfy the group axioms:
637///
638/// 1. **Identity**: `g.compose(&G::identity()) == g`
639/// 2. **Inverse**: `g.compose(&g.inverse()) == G::identity()`
640/// 3. **Associativity**: `g1.compose(&g2.compose(&g3)) == g1.compose(&g2).compose(&g3)`
641/// 4. **Closure**: `compose` always produces a valid group element
642///
643/// Additionally, the manifold structure should satisfy:
644///
645/// 5. **Metric**: `distance_to_identity()` measures geodesic distance on the manifold
646/// 6. **Smoothness**: Small parameter changes produce smooth paths in the group
647///
648/// # Type Parameters vs Associated Types
649///
650/// This trait uses `Self` for the group element type, allowing different
651/// representations of the same abstract group (e.g., matrix vs quaternion
652/// representation of SU(2)).
653///
654/// # Examples
655///
656/// ```
657/// use lie_groups::{LieGroup, SU2};
658///
659/// let g = SU2::rotation_x(1.0);
660/// let h = SU2::rotation_y(0.5);
661///
662/// // Group operations
663/// let product = g.compose(&h);
664/// let inv = g.inverse();
665///
666/// // Check group axioms
667/// assert!(g.compose(&g.inverse()).distance_to_identity() < 1e-10);
668/// ```
669///
670/// # Open Trait
671///
672/// This trait is open for implementation. The associated type `Algebra`
673/// must implement `LieAlgebra`, providing compile-time enforcement of
674/// the group-algebra correspondence.
675pub trait LieGroup: Clone + Sized {
676 /// Matrix dimension in the fundamental representation.
677 ///
678 /// For matrix Lie groups, this is the size of the matrices:
679 /// - U(1): `DIM = 1` (complex numbers as 1×1 matrices)
680 /// - SU(2): `DIM = 2` (2×2 unitary matrices)
681 /// - SU(3): `DIM = 3` (3×3 unitary matrices)
682 /// - SO(3): `DIM = 3` (3×3 orthogonal matrices)
683 ///
684 /// # Compile-Time Dimension Safety
685 ///
686 /// This enables the compiler to verify dimension compatibility:
687 ///
688 /// ```ignore
689 /// // ✅ Compiles: Same dimension
690 /// fn compatible<G: LieGroup<DIM = 2>>() {
691 /// let g1 = SU2::identity();
692 /// // All operations have compatible dimensions
693 /// }
694 ///
695 /// // ❌ Compile error: Dimension mismatch
696 /// fn incompatible<G1: LieGroup<DIM = 2>, G2: LieGroup<DIM = 3>>() {
697 /// // Can't mix 2×2 and 3×3 operations
698 /// }
699 /// ```
700 ///
701 /// # Note
702 ///
703 /// This is **not** the dimension of the Lie algebra:
704 /// - dim(SU(2)) = 2 (matrix size)
705 /// - dim(su(2)) = 3 (algebra dimension)
706 const DIM: usize;
707
708 /// Associated Lie algebra type.
709 ///
710 /// The Lie algebra 𝔤 is the tangent space at the identity of the Lie group G.
711 /// This associated type enables generic algorithms that work in the algebra
712 /// (e.g., gradient descent, exponential coordinates).
713 ///
714 /// # Examples
715 ///
716 /// - `SU(2)::Algebra` = `Su2Algebra` (3-dimensional)
717 /// - `U(1)::Algebra` = `U1Algebra` (1-dimensional)
718 /// - `SU(3)::Algebra` = `Su3Algebra` (8-dimensional)
719 type Algebra: LieAlgebra;
720
721 /// The identity element e ∈ G.
722 ///
723 /// Satisfies: `g.compose(&Self::identity()) == g` for all g.
724 ///
725 /// # Mathematical Note
726 ///
727 /// For matrix groups, this is the identity matrix I.
728 /// For additive groups, this would be zero.
729 #[must_use]
730 fn identity() -> Self;
731
732 /// Group composition (multiplication): g₁ · g₂
733 ///
734 /// Computes the product of two group elements. For matrix groups,
735 /// this is matrix multiplication. For SU(2), preserves unitarity.
736 ///
737 /// # Properties
738 ///
739 /// - Associative: `g1.compose(&g2.compose(&g3)) == g1.compose(&g2).compose(&g3)`
740 /// - Identity: `g.compose(&Self::identity()) == g`
741 ///
742 /// # Complexity
743 ///
744 /// | Group | Time | Space | Notes |
745 /// |-------|------|-------|-------|
746 /// | SU(2) via quaternion | O(1) | O(1) | 16 multiplications |
747 /// | SU(N) via matrix | O(N³) | O(N²) | Matrix multiplication |
748 /// | U(1) | O(1) | O(1) | Complex multiplication |
749 ///
750 /// # Examples
751 ///
752 /// ```
753 /// use lie_groups::{LieGroup, SU2};
754 ///
755 /// let g = SU2::rotation_x(0.5);
756 /// let h = SU2::rotation_y(0.3);
757 /// let product = g.compose(&h);
758 /// ```
759 #[must_use]
760 fn compose(&self, other: &Self) -> Self;
761
762 /// Group inverse: g⁻¹
763 ///
764 /// Computes the unique element such that `g.compose(&g.inverse()) == identity`.
765 /// For unitary matrix groups, this is the conjugate transpose (adjoint).
766 ///
767 /// # Properties
768 ///
769 /// - Involutive: `g.inverse().inverse() == g`
770 /// - Inverse property: `g.compose(&g.inverse()) == Self::identity()`
771 ///
772 /// # Complexity
773 ///
774 /// | Group | Time | Space | Notes |
775 /// |-------|------|-------|-------|
776 /// | SU(2) via quaternion | O(1) | O(1) | Quaternion conjugate |
777 /// | SU(N) unitary | O(N²) | O(N²) | Conjugate transpose |
778 /// | U(1) | O(1) | O(1) | Complex conjugate |
779 ///
780 /// # Examples
781 ///
782 /// ```
783 /// use lie_groups::{LieGroup, SU2};
784 ///
785 /// let g = SU2::rotation_z(1.2);
786 /// let g_inv = g.inverse();
787 ///
788 /// // Verify inverse property (up to numerical precision)
789 /// assert!(g.compose(&g_inv).distance_to_identity() < 1e-10);
790 /// ```
791 #[must_use]
792 fn inverse(&self) -> Self;
793
794 /// Adjoint representation element (for matrix groups: conjugate transpose).
795 ///
796 /// For matrix groups: `adjoint(g) = g†` (conjugate transpose)
797 /// For unitary groups: `adjoint(g) = inverse(g)`
798 ///
799 /// # Gauge Theory Application
800 ///
801 /// In gauge transformations, fields transform as:
802 /// ```text
803 /// A' = g A g† + g dg†
804 /// ```
805 /// where g† is the adjoint.
806 ///
807 /// # Examples
808 ///
809 /// ```
810 /// use lie_groups::{LieGroup, SU2};
811 ///
812 /// let g = SU2::rotation_x(0.7);
813 /// let g_adj = g.adjoint();
814 ///
815 /// // For unitary groups, adjoint equals inverse
816 /// assert!(g_adj.compose(&g).distance_to_identity() < 1e-10);
817 /// ```
818 #[must_use]
819 fn adjoint(&self) -> Self;
820
821 /// Adjoint representation: `Ad_g`: 𝔤 → 𝔤
822 ///
823 /// The adjoint representation is a group homomorphism `Ad`: G → Aut(𝔤) that maps
824 /// each group element g to a linear automorphism of the Lie algebra.
825 ///
826 /// # Mathematical Definition
827 ///
828 /// For matrix Lie groups:
829 /// ```text
830 /// Ad_g(X) = g X g⁻¹
831 /// ```
832 ///
833 /// For unitary groups where g† = g⁻¹:
834 /// ```text
835 /// Ad_g(X) = g X g†
836 /// ```
837 ///
838 /// # Distinction from `adjoint()`
839 ///
840 /// - `adjoint()`: Returns g† (conjugate transpose), a **group element**
841 /// - `adjoint_action()`: Returns `Ad_g(X)` (conjugation), a **Lie algebra element**
842 ///
843 /// | Method | Signature | Output Type | Mathematical Meaning |
844 /// |--------|-----------|-------------|---------------------|
845 /// | `adjoint()` | `g → g†` | Group element | Hermitian conjugate |
846 /// | `adjoint_action()` | `(g, X) → gXg⁻¹` | Algebra element | Conjugation action |
847 ///
848 /// # Properties
849 ///
850 /// The adjoint representation is a group homomorphism:
851 /// ```text
852 /// Ad_{g₁g₂}(X) = Ad_{g₁}(Ad_{g₂}(X))
853 /// Ad_e(X) = X
854 /// Ad_g⁻¹(X) = Ad_g⁻¹(X)
855 /// ```
856 ///
857 /// It preserves the Lie bracket:
858 /// ```text
859 /// Ad_g([X, Y]) = [Ad_g(X), Ad_g(Y)]
860 /// ```
861 ///
862 /// For abelian groups (e.g., U(1)):
863 /// ```text
864 /// Ad_g(X) = X (trivial action)
865 /// ```
866 ///
867 /// # Gauge Theory Application
868 ///
869 /// In gauge transformations, the connection A ∈ 𝔤 transforms as:
870 /// ```text
871 /// A' = Ad_g(A) + g dg†
872 /// = g A g† + (derivative term)
873 /// ```
874 ///
875 /// The adjoint action captures how gauge fields rotate under symmetry transformations.
876 ///
877 /// # Examples
878 ///
879 /// ```ignore
880 /// use lie_groups::{LieGroup, SU2, Su2Algebra};
881 ///
882 /// let g = SU2::rotation_z(std::f64::consts::PI / 2.0); // 90° rotation
883 /// let X = Su2Algebra::basis_element(0); // σ_x direction
884 ///
885 /// // Conjugation rotates the algebra element
886 /// let rotated_X = g.adjoint_action(&X);
887 ///
888 /// // Should now point in σ_y direction (rotated by 90°)
889 /// ```
890 ///
891 /// # Complexity
892 ///
893 /// | Group | Time | Space | Notes |
894 /// |-------|------|-------|-------|
895 /// | SU(2) via quaternion | O(1) | O(1) | Quaternion sandwich: qvq* |
896 /// | SU(N) via matrix | O(N³) | O(N²) | Two matrix multiplications |
897 /// | U(1) | O(1) | O(1) | Trivial (abelian) |
898 /// | SO(3) | O(1) | O(1) | 3×3 matrix rotation |
899 ///
900 /// # See Also
901 ///
902 /// - [`exp`](Self::exp) - Exponential map 𝔤 → G
903 /// - [`adjoint`](Self::adjoint) - Conjugate transpose g → g†
904 /// - [`LieAlgebra::bracket`] - Lie bracket [·,·]
905 #[must_use]
906 fn adjoint_action(&self, algebra_element: &Self::Algebra) -> Self::Algebra;
907
908 /// Geodesic distance from identity: d(g, e)
909 ///
910 /// Measures the "size" of the group element as the length of the
911 /// shortest geodesic curve from the identity to g on the Lie group
912 /// manifold.
913 ///
914 /// # Mathematical Definition
915 ///
916 /// For matrix Lie groups, typically computed as:
917 /// ```text
918 /// d(g, e) = ||log(g)||_F
919 /// ```
920 /// where ||·||_F is the Frobenius norm and log is the matrix logarithm.
921 ///
922 /// For SU(2), this simplifies to:
923 /// ```text
924 /// d(g, e) = arccos(Re(Tr(g))/2)
925 /// ```
926 /// which is the rotation angle.
927 ///
928 /// # Properties
929 ///
930 /// - Non-negative: `d(g, e) ≥ 0`
931 /// - Identity: `d(e, e) = 0`
932 /// - Symmetric: `d(g, e) = d(g⁻¹, e)`
933 ///
934 /// # Returns
935 ///
936 /// Distance in radians (for rotation groups) or appropriate units.
937 ///
938 /// # Examples
939 ///
940 /// ```
941 /// use lie_groups::{LieGroup, SU2};
942 ///
943 /// let identity = SU2::identity();
944 /// assert!(identity.distance_to_identity().abs() < 1e-10);
945 ///
946 /// let rotation = SU2::rotation_x(1.0);
947 /// assert!((rotation.distance_to_identity() - 1.0).abs() < 1e-10);
948 /// ```
949 #[must_use]
950 fn distance_to_identity(&self) -> f64;
951
952 /// Distance between two group elements: d(g, h)
953 ///
954 /// Computed as the distance from the identity to g⁻¹h:
955 /// ```text
956 /// d(g, h) = d(g⁻¹h, e)
957 /// ```
958 ///
959 /// This is the canonical left-invariant metric on the Lie group.
960 ///
961 /// # Default Implementation
962 ///
963 /// Provided via left-invariance of the metric. Can be overridden
964 /// for efficiency if a direct formula is available.
965 #[must_use]
966 fn distance(&self, other: &Self) -> f64 {
967 self.inverse().compose(other).distance_to_identity()
968 }
969
970 /// Check if this element is approximately the identity.
971 ///
972 /// Useful for numerical algorithms to check convergence or
973 /// validate gauge fixing.
974 ///
975 /// # Parameters
976 ///
977 /// - `tolerance`: Maximum allowed distance from identity
978 ///
979 /// # Examples
980 ///
981 /// ```
982 /// use lie_groups::{LieGroup, SU2};
983 ///
984 /// let almost_identity = SU2::rotation_x(1e-12);
985 /// assert!(almost_identity.is_near_identity(1e-10));
986 /// ```
987 #[must_use]
988 fn is_near_identity(&self, tolerance: f64) -> bool {
989 self.distance_to_identity() < tolerance
990 }
991
992 /// Exponential map: 𝔤 → G
993 ///
994 /// Maps elements from the Lie algebra (tangent space at identity) to the Lie group
995 /// via the matrix exponential or equivalent operation.
996 ///
997 /// # Mathematical Definition
998 ///
999 /// For matrix groups:
1000 /// ```text
1001 /// exp(X) = I + X + X²/2! + X³/3! + ...
1002 /// ```
1003 ///
1004 /// For SU(2) with X = θ·n̂·(iσ/2) (rotation by angle θ around axis n̂):
1005 /// ```text
1006 /// exp(X) = cos(θ/2)I + i·sin(θ/2)·(n̂·σ)
1007 /// ```
1008 ///
1009 /// For U(1) with X = iθ:
1010 /// ```text
1011 /// exp(iθ) = e^(iθ)
1012 /// ```
1013 ///
1014 /// # Properties
1015 ///
1016 /// - exp(0) = identity
1017 /// - exp is a local diffeomorphism near 0
1018 /// - exp(t·X) traces a geodesic on the group manifold
1019 ///
1020 /// # Applications
1021 ///
1022 /// - **Optimization**: Gradient descent on manifolds via retraction
1023 /// - **Integration**: Solving ODEs on Lie groups
1024 /// - **Parameterization**: Unconstrained → constrained optimization
1025 ///
1026 /// # Complexity
1027 ///
1028 /// | Group | Time | Space | Notes |
1029 /// |-------|------|-------|-------|
1030 /// | SU(2) via quaternion | O(1) | O(1) | Rodrigues formula |
1031 /// | SU(N) via Padé | O(N³) | O(N²) | Matrix exp via scaling+squaring |
1032 /// | U(1) | O(1) | O(1) | e^(iθ) |
1033 /// | SO(3) via Rodrigues | O(1) | O(1) | Closed-form rotation |
1034 ///
1035 /// # Examples
1036 ///
1037 /// ```ignore
1038 /// use lie_groups::{LieGroup, SU2, Su2Algebra};
1039 ///
1040 /// let tangent = Su2Algebra::from_components(&[1.0, 0.0, 0.0]);
1041 /// let group_element = SU2::exp(&tangent);
1042 /// // group_element is a rotation by 1 radian around X-axis
1043 /// ```
1044 #[must_use]
1045 fn exp(tangent: &Self::Algebra) -> Self;
1046
1047 /// Logarithm map: G → 𝔤 (inverse of exponential)
1048 ///
1049 /// Maps group elements near the identity back to the Lie algebra.
1050 /// This is the **inverse** of the exponential map in a neighborhood of the identity.
1051 ///
1052 /// # Mathematical Definition
1053 ///
1054 /// For g ∈ G sufficiently close to the identity, returns X ∈ 𝔤 such that:
1055 /// ```text
1056 /// exp(X) = g
1057 /// ```
1058 ///
1059 /// For matrix groups, this is the matrix logarithm:
1060 /// ```text
1061 /// log(g) = Σ_{n=1}^∞ (-1)^{n+1} (g - I)^n / n
1062 /// ```
1063 ///
1064 /// For specific groups:
1065 ///
1066 /// - **U(1)**: log(e^{iθ}) = iθ (principal branch)
1067 /// - **SU(2)**: log(exp(θ n̂·σ)) = θ n̂·σ for θ ∈ [0, π)
1068 /// - **SO(3)**: Rodrigues' formula in reverse
1069 ///
1070 /// # Domain Restrictions
1071 ///
1072 /// Unlike `exp` which is defined on all of 𝔤, `log` is only well-defined
1073 /// on a **neighborhood of the identity**:
1074 ///
1075 /// - **U(1)**: All elements except negative real axis
1076 /// - **SU(2)**: All elements except -I (rotation by π with ambiguous axis)
1077 /// - **SU(3)**: Elements with all eigenvalues in a small neighborhood
1078 ///
1079 /// # Errors
1080 ///
1081 /// Returns `Err(LogError::NotNearIdentity)` if the element is too far from identity.
1082 /// Returns `Err(LogError::Singularity)` if at an exact singularity (e.g., -I for SU(2)).
1083 ///
1084 /// # Gauge Theory Application
1085 ///
1086 /// In lattice gauge theory, the **curvature** (field strength) is computed
1087 /// from the **holonomy** (Wilson loop) via the logarithm:
1088 ///
1089 /// ```text
1090 /// F_□ = log(U_□) / a²
1091 /// ```
1092 ///
1093 /// where:
1094 /// - U_□ ∈ G is the plaquette holonomy (group element)
1095 /// - F_□ ∈ 𝔤 is the curvature (Lie algebra element)
1096 /// - a is the lattice spacing
1097 ///
1098 /// This is the **discrete analog** of the continuum formula:
1099 /// ```text
1100 /// F_{μν} = ∂_μ A_ν - ∂_ν A_μ + [A_μ, A_ν] ∈ 𝔤
1101 /// ```
1102 ///
1103 /// # Numerical Considerations
1104 ///
1105 /// - For elements very close to identity, use Taylor expansion of log
1106 /// - For elements near singularities, logarithm is numerically unstable
1107 /// - In lattice gauge theory, use smaller lattice spacing if log fails
1108 ///
1109 /// # Complexity
1110 ///
1111 /// | Group | Time | Space | Notes |
1112 /// |-------|------|-------|-------|
1113 /// | SU(2) via quaternion | O(1) | O(1) | atan2 + normalize |
1114 /// | SU(N) via eigendecomp | O(N³) | O(N²) | Schur decomposition |
1115 /// | U(1) | O(1) | O(1) | atan2 |
1116 /// | SO(3) via Rodrigues | O(1) | O(1) | Closed-form |
1117 ///
1118 /// # Properties
1119 ///
1120 /// - log(identity) = 0
1121 /// - log(exp(X)) = X for small X (in the cut)
1122 /// - log(g^{-1}) = -log(g) for g near identity
1123 /// - log is a local diffeomorphism near the identity
1124 ///
1125 /// # Examples
1126 ///
1127 /// ```ignore
1128 /// use lie_groups::{LieGroup, SU2, Su2Algebra};
1129 ///
1130 /// // Create rotation around X-axis by 0.5 radians
1131 /// let tangent = Su2Algebra::from_components(&[0.5, 0.0, 0.0]);
1132 /// let g = SU2::exp(&tangent);
1133 ///
1134 /// // Recover the algebra element
1135 /// let recovered = g.log().unwrap();
1136 ///
1137 /// // Should match original (up to numerical precision)
1138 /// assert!(tangent.add(&recovered.scale(-1.0)).norm() < 1e-10);
1139 /// ```
1140 ///
1141 /// # See Also
1142 ///
1143 /// - [`exp`](Self::exp) - Exponential map 𝔤 → G
1144 /// - [`LogError`](crate::LogError) - Error types for logarithm failures
1145 fn log(&self) -> crate::error::LogResult<Self::Algebra>;
1146
1147 /// Dimension of the fundamental representation.
1148 ///
1149 /// For matrix Lie groups, this is the size of the matrices:
1150 /// - U(1): 1 (complex numbers as 1×1 matrices)
1151 /// - SU(2): 2 (2×2 unitary matrices with det=1)
1152 /// - SU(3): 3 (3×3 unitary matrices with det=1)
1153 /// - SO(3): 3 (3×3 orthogonal matrices with det=1)
1154 ///
1155 /// **Note**: This is different from the dimension of the Lie algebra!
1156 /// - dim(SU(2)) = 2 (matrix size)
1157 /// - dim(su(2)) = 3 (algebra dimension)
1158 ///
1159 /// # Relationship to `DIM` Const
1160 ///
1161 /// This method returns the same value as the `DIM` associated constant,
1162 /// but is available at runtime. Prefer using `G::DIM` for compile-time
1163 /// dimension checking when possible.
1164 ///
1165 /// # Uses
1166 ///
1167 /// - Computing Yang-Mills action: S = Σ (dim - Re Tr(F))
1168 /// - Normalizing traces and invariants
1169 /// - Checking unitarity constraints
1170 ///
1171 /// # Examples
1172 ///
1173 /// ```ignore
1174 /// use lie_groups::{LieGroup, SU2, U1};
1175 ///
1176 /// assert_eq!(SU2::dim(), 2);
1177 /// assert_eq!(SU2::DIM, 2); // Same value, compile-time
1178 /// assert_eq!(U1::dim(), 1);
1179 /// ```
1180 #[must_use]
1181 fn dim() -> usize {
1182 Self::DIM // Default implementation
1183 }
1184
1185 /// Trace in the fundamental representation.
1186 ///
1187 /// For matrix groups, computes Tr(M) = Σᵢ Mᵢᵢ (sum of diagonal elements).
1188 ///
1189 /// # Mathematical Properties
1190 ///
1191 /// For unitary matrices U:
1192 /// - Tr(U) is complex with |Tr(U)| ≤ dim
1193 /// - Tr(I) = dim (identity has maximal trace)
1194 /// - Tr(U†) = Tr(U)* (trace of adjoint is complex conjugate)
1195 /// - Tr(UV) = Tr(VU) (cyclicity)
1196 ///
1197 /// # Physical Significance
1198 ///
1199 /// In Yang-Mills theory, the action uses traces of field strength:
1200 /// ```text
1201 /// S = -1/(4g²) ∫ Tr(F_μν F^μν) d⁴x
1202 /// ```
1203 ///
1204 /// On the lattice:
1205 /// ```text
1206 /// S = β Σ_p (dim - Re Tr(U_p))
1207 /// ```
1208 /// where `U_p` is the plaquette holonomy.
1209 ///
1210 /// # Examples
1211 ///
1212 /// ```ignore
1213 /// use lie_groups::{LieGroup, SU2};
1214 /// use num_complex::Complex;
1215 ///
1216 /// let identity = SU2::identity();
1217 /// let tr = identity.trace();
1218 /// assert!((tr - Complex::new(2.0, 0.0)).norm() < 1e-10); // Tr(I₂) = 2
1219 ///
1220 /// let rotation = SU2::rotation_x(std::f64::consts::PI);
1221 /// let tr_rot = rotation.trace();
1222 /// // Rotation by π has Tr = 0
1223 /// assert!(tr_rot.norm() < 1e-10);
1224 /// ```
1225 #[must_use]
1226 fn trace(&self) -> Complex<f64>;
1227
1228 /// Trace of the identity element
1229 ///
1230 /// For SU(N), SO(N), and related matrix groups, the identity matrix I has trace N.
1231 /// This is used in gauge theory computations:
1232 /// - Yang-Mills action: `S_p = N - Re Tr(U_p)` (measures deviation from identity)
1233 /// - Field strength: `|F| ∝ √(N - Re Tr(U))`
1234 ///
1235 /// # Examples
1236 ///
1237 /// ```
1238 /// use lie_groups::{SU2, SU3, LieGroup};
1239 ///
1240 /// assert_eq!(SU2::trace_identity(), 2.0);
1241 /// assert_eq!(SU3::trace_identity(), 3.0);
1242 /// ```
1243 #[must_use]
1244 fn trace_identity() -> f64 {
1245 Self::DIM as f64
1246 }
1247
1248 /// Project element back onto the group manifold using Gram-Schmidt orthogonalization.
1249 ///
1250 /// **Critical for numerical stability** (Tao priority): After many `compose()` or `exp()` operations,
1251 /// floating-point errors accumulate and the result may drift from the group manifold.
1252 /// This method projects the element back onto the manifold, restoring group constraints.
1253 ///
1254 /// # Mathematical Background
1255 ///
1256 /// For matrix Lie groups, the group manifold is defined by constraints:
1257 /// - **SU(N)**: U†U = I (unitary) and det(U) = 1 (special)
1258 /// - **SO(N)**: R†R = I (orthogonal) and det(R) = +1 (special)
1259 ///
1260 /// Gram-Schmidt reorthogonalization ensures U†U = I by:
1261 /// 1. Orthogonalizing columns via modified Gram-Schmidt
1262 /// 2. Rescaling det(U) → 1 if needed
1263 ///
1264 /// # When to Use
1265 ///
1266 /// Call after:
1267 /// - Long chains: `g₁.compose(&g₂).compose(&g₃)....compose(&gₙ)` for large n
1268 /// - Many exponentials: `exp(X₁).compose(&exp(X₂))....`
1269 /// - Scaling-and-squaring: After matrix powers in `exp()` algorithm
1270 ///
1271 /// # Performance vs Accuracy Trade-off
1272 ///
1273 /// - **Cost**: O(N³) for N×N matrices (Gram-Schmidt)
1274 /// - **Benefit**: Prevents catastrophic error accumulation
1275 /// - **Guideline**: Reorthogonalize every ~100 operations in long chains
1276 ///
1277 /// # Example
1278 ///
1279 /// ```ignore
1280 /// use lie_groups::{LieGroup, SU2};
1281 ///
1282 /// // Long composition chain
1283 /// let mut g = SU2::identity();
1284 /// for i in 0..1000 {
1285 /// g = g.compose(&SU2::rotation_x(0.01));
1286 /// if i % 100 == 0 {
1287 /// g = g.reorthogonalize(); // Prevent drift every 100 steps
1288 /// }
1289 /// }
1290 ///
1291 /// // Verify still on manifold
1292 /// assert!(g.adjoint().compose(&g).distance_to_identity() < 1e-12);
1293 /// ```
1294 ///
1295 /// # Default Implementation
1296 ///
1297 /// The default implementation uses the exponential/logarithm round-trip:
1298 /// ```text
1299 /// reorthogonalize(g) = exp(log(g))
1300 /// ```
1301 ///
1302 /// This works because:
1303 /// 1. `log(g)` extracts the Lie algebra element (always exact algebraically)
1304 /// 2. `exp(X)` produces an element exactly on the manifold
1305 ///
1306 /// Groups may override this with more efficient methods (e.g., direct Gram-Schmidt for SU(N)).
1307 ///
1308 /// # Error Handling
1309 ///
1310 /// - **Never fails** for elements close to the manifold
1311 /// - For elements far from manifold (catastrophic numerical error), returns best approximation
1312 /// - If `log()` fails (element too far from identity), falls back to identity
1313 ///
1314 /// # See Also
1315 ///
1316 /// - [`exp`](Self::exp) - Exponential map (always produces elements on manifold)
1317 /// - [`log`](Self::log) - Logarithm map (inverse of exp near identity)
1318 /// - [Modified Gram-Schmidt Algorithm](https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process)
1319 ///
1320 /// # References
1321 ///
1322 /// - Tao, "Topics in Random Matrix Theory" (2012) - Numerical stability
1323 /// - Higham, "Accuracy and Stability of Numerical Algorithms" (2002) - Matrix orthogonalization
1324 #[must_use]
1325 fn reorthogonalize(&self) -> Self {
1326 // Default: Round-trip through log-exp
1327 // This works because exp() always produces elements on the manifold
1328 match self.log() {
1329 Ok(algebra) => Self::exp(&algebra),
1330 Err(_) => {
1331 // Fallback: If log fails (element too corrupted), return identity
1332 Self::identity()
1333 }
1334 }
1335 }
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 use super::*;
1341
1342 /// Mock Lie algebra for testing (u(1) ≅ ℝ)
1343 #[derive(Clone, Debug, PartialEq)]
1344 struct U1Algebra {
1345 value: f64,
1346 }
1347
1348 impl LieAlgebra for U1Algebra {
1349 const DIM: usize = 1;
1350
1351 fn zero() -> Self {
1352 Self { value: 0.0 }
1353 }
1354
1355 fn add(&self, other: &Self) -> Self {
1356 Self {
1357 value: self.value + other.value,
1358 }
1359 }
1360
1361 fn scale(&self, scalar: f64) -> Self {
1362 Self {
1363 value: self.value * scalar,
1364 }
1365 }
1366
1367 fn norm(&self) -> f64 {
1368 self.value.abs()
1369 }
1370
1371 fn basis_element(i: usize) -> Self {
1372 assert_eq!(i, 0, "U(1) algebra is 1-dimensional");
1373 Self { value: 1.0 }
1374 }
1375
1376 fn from_components(components: &[f64]) -> Self {
1377 assert_eq!(components.len(), 1);
1378 Self {
1379 value: components[0],
1380 }
1381 }
1382
1383 fn to_components(&self) -> Vec<f64> {
1384 vec![self.value]
1385 }
1386
1387 fn bracket(&self, _other: &Self) -> Self {
1388 Self::zero() // u(1) is abelian
1389 }
1390 }
1391
1392 /// Mock Lie group for testing trait laws
1393 #[derive(Clone, Debug, PartialEq)]
1394 struct U1 {
1395 angle: f64, // U(1) = circle group, element = e^{iθ}
1396 }
1397
1398 impl LieGroup for U1 {
1399 const DIM: usize = 1;
1400
1401 type Algebra = U1Algebra;
1402
1403 fn identity() -> Self {
1404 Self { angle: 0.0 }
1405 }
1406
1407 fn compose(&self, other: &Self) -> Self {
1408 Self {
1409 angle: (self.angle + other.angle) % (2.0 * std::f64::consts::PI),
1410 }
1411 }
1412
1413 fn inverse(&self) -> Self {
1414 Self {
1415 angle: (-self.angle).rem_euclid(2.0 * std::f64::consts::PI),
1416 }
1417 }
1418
1419 fn adjoint(&self) -> Self {
1420 self.inverse() // U(1) is abelian, so adjoint = inverse
1421 }
1422
1423 fn adjoint_action(&self, algebra_element: &Self::Algebra) -> Self::Algebra {
1424 // Trivial adjoint action for abelian group
1425 algebra_element.clone()
1426 }
1427
1428 fn distance_to_identity(&self) -> f64 {
1429 // Shortest arc distance on circle [0, 2π]
1430 let normalized = self.angle.rem_euclid(2.0 * std::f64::consts::PI);
1431 let dist = normalized.min(2.0 * std::f64::consts::PI - normalized);
1432 dist.abs()
1433 }
1434
1435 fn exp(tangent: &Self::Algebra) -> Self {
1436 // exp(iθ) = e^(iθ), represented as angle θ
1437 Self {
1438 angle: tangent.value.rem_euclid(2.0 * std::f64::consts::PI),
1439 }
1440 }
1441
1442 fn log(&self) -> crate::error::LogResult<Self::Algebra> {
1443 // For U(1), log(e^{iθ}) = iθ
1444 Ok(U1Algebra { value: self.angle })
1445 }
1446
1447 fn dim() -> usize {
1448 1 // U(1) is 1-dimensional (complex numbers as 1×1 matrices)
1449 }
1450
1451 fn trace(&self) -> Complex<f64> {
1452 // Trace of e^(iθ) viewed as 1×1 matrix
1453 Complex::new(self.angle.cos(), self.angle.sin())
1454 }
1455 }
1456
1457 #[test]
1458 fn test_identity_law() {
1459 let g = U1 { angle: 1.0 };
1460 let e = U1::identity();
1461
1462 let g_times_e = g.compose(&e);
1463 assert!((g_times_e.angle - g.angle).abs() < 1e-10);
1464 }
1465
1466 #[test]
1467 fn test_inverse_law() {
1468 let g = U1 { angle: 2.5 };
1469 let g_inv = g.inverse();
1470 let product = g.compose(&g_inv);
1471
1472 assert!(product.is_near_identity(1e-10));
1473 }
1474
1475 #[test]
1476 fn test_associativity() {
1477 let g1 = U1 { angle: 0.5 };
1478 let g2 = U1 { angle: 1.2 };
1479 let g3 = U1 { angle: 0.8 };
1480
1481 let left = g1.compose(&g2.compose(&g3));
1482 let right = g1.compose(&g2).compose(&g3);
1483
1484 assert!((left.angle - right.angle).abs() < 1e-10);
1485 }
1486
1487 #[test]
1488 fn test_distance_symmetry() {
1489 let g = U1 { angle: 1.5 };
1490 let g_inv = g.inverse();
1491
1492 let d1 = g.distance_to_identity();
1493 let d2 = g_inv.distance_to_identity();
1494
1495 assert!((d1 - d2).abs() < 1e-10);
1496 }
1497
1498 #[test]
1499 fn test_is_near_identity() {
1500 let e = U1::identity();
1501 assert!(e.is_near_identity(1e-10));
1502
1503 let g = U1 { angle: 1e-12 };
1504 assert!(g.is_near_identity(1e-10));
1505
1506 let h = U1 { angle: 0.1 };
1507 assert!(!h.is_near_identity(1e-10));
1508 }
1509
1510 #[test]
1511 fn test_exponential_map() {
1512 // exp(0) = identity
1513 let zero = U1Algebra::zero();
1514 let exp_zero = U1::exp(&zero);
1515 assert!(exp_zero.is_near_identity(1e-10));
1516
1517 // exp(iθ) = e^(iθ)
1518 let tangent = U1Algebra { value: 1.5 };
1519 let group_elem = U1::exp(&tangent);
1520 assert!((group_elem.angle - 1.5).abs() < 1e-10);
1521 }
1522
1523 #[test]
1524 fn test_dimension() {
1525 assert_eq!(U1::dim(), 1);
1526 }
1527
1528 #[test]
1529 fn test_trace() {
1530 // Tr(identity) = dim
1531 let e = U1::identity();
1532 let tr_e = e.trace();
1533 assert!((tr_e - Complex::new(1.0, 0.0)).norm() < 1e-10);
1534
1535 // Tr(e^(iπ)) = e^(iπ) = -1
1536 let g = U1 {
1537 angle: std::f64::consts::PI,
1538 };
1539 let tr_g = g.trace();
1540 assert!((tr_g - Complex::new(-1.0, 0.0)).norm() < 1e-10);
1541 }
1542
1543 #[test]
1544 fn test_lie_algebra_zero() {
1545 let zero = U1Algebra::zero();
1546 assert_eq!(zero.value, 0.0);
1547 }
1548
1549 #[test]
1550 fn test_lie_algebra_add() {
1551 let v = U1Algebra { value: 1.0 };
1552 let w = U1Algebra { value: 2.0 };
1553 let sum = v.add(&w);
1554 assert_eq!(sum.value, 3.0);
1555 }
1556
1557 #[test]
1558 fn test_lie_algebra_scale() {
1559 let v = U1Algebra { value: 2.0 };
1560 let scaled = v.scale(3.0);
1561 assert_eq!(scaled.value, 6.0);
1562 }
1563
1564 #[test]
1565 fn test_lie_algebra_norm() {
1566 let v = U1Algebra { value: -3.0 };
1567 assert_eq!(v.norm(), 3.0);
1568 }
1569
1570 #[test]
1571 fn test_lie_algebra_basis() {
1572 let basis = U1Algebra::basis_element(0);
1573 assert_eq!(basis.value, 1.0);
1574 }
1575
1576 #[test]
1577 fn test_lie_algebra_from_components() {
1578 let v = U1Algebra::from_components(&[4.5]);
1579 assert_eq!(v.value, 4.5);
1580 }
1581
1582 #[test]
1583 fn test_const_dim_available_at_compile_time() {
1584 // Verify that DIM const is accessible
1585 assert_eq!(U1::DIM, 1);
1586
1587 // Verify dim() method returns same value
1588 assert_eq!(U1::dim(), U1::DIM);
1589 }
1590
1591 // Note: Associated const equality (e.g., `LieGroup<DIM = 1>`) is not yet stable
1592 // See https://github.com/rust-lang/rust/issues/92827
1593 //
1594 // Once stabilized, we can write functions like:
1595 //
1596 // ```ignore
1597 // fn dimension_one_only<G: LieGroup<DIM = 1>>() -> usize {
1598 // G::DIM
1599 // }
1600 // ```
1601 //
1602 // This would enable compile-time verification of dimension compatibility.
1603
1604 // ========================================================================
1605 // Reorthogonalization Tests (TAO PRIORITY - Numerical Stability)
1606 // ========================================================================
1607
1608 /// Test that reorthogonalization fixes drift in long composition chains
1609 #[test]
1610 fn test_reorthogonalize_prevents_drift_su2() {
1611 use crate::SU2;
1612
1613 // Create a long composition chain without reorthogonalization
1614 let mut g_no_reorth = SU2::identity();
1615 for _ in 0..1000 {
1616 g_no_reorth = g_no_reorth.compose(&SU2::rotation_x(0.01));
1617 }
1618
1619 // Check unitarity: U†U should be identity
1620 let unitarity_error_no_reorth = g_no_reorth
1621 .adjoint()
1622 .compose(&g_no_reorth)
1623 .distance_to_identity();
1624
1625 // Same chain WITH reorthogonalization every 100 steps
1626 let mut g_with_reorth = SU2::identity();
1627 for i in 0..1000 {
1628 g_with_reorth = g_with_reorth.compose(&SU2::rotation_x(0.01));
1629 if i % 100 == 0 {
1630 g_with_reorth = g_with_reorth.reorthogonalize();
1631 }
1632 }
1633
1634 let unitarity_error_with_reorth = g_with_reorth
1635 .adjoint()
1636 .compose(&g_with_reorth)
1637 .distance_to_identity();
1638
1639 // Reorthogonalization should reduce drift significantly
1640 assert!(
1641 unitarity_error_with_reorth < unitarity_error_no_reorth,
1642 "Reorthogonalization should reduce drift: {} vs {}",
1643 unitarity_error_with_reorth,
1644 unitarity_error_no_reorth
1645 );
1646
1647 // With reorthogonalization, should stay very close to manifold
1648 // Tolerance allows for numerical variation across library versions
1649 assert!(
1650 unitarity_error_with_reorth < 1e-7,
1651 "With reorthogonalization, drift should be minimal: {}",
1652 unitarity_error_with_reorth
1653 );
1654 }
1655
1656 /// Test that `reorthogonalize()` is idempotent
1657 #[test]
1658 fn test_reorthogonalize_idempotent() {
1659 use crate::SU2;
1660 use std::f64::consts::PI;
1661
1662 let g = SU2::rotation_y(PI / 3.0);
1663
1664 let g1 = g.reorthogonalize();
1665 let g2 = g1.reorthogonalize();
1666
1667 // Applying twice should give same result (idempotent)
1668 assert!(
1669 g1.distance(&g2) < 1e-14,
1670 "Reorthogonalization should be idempotent"
1671 );
1672 }
1673
1674 /// Test reorthogonalization preserves group element
1675 #[test]
1676 fn test_reorthogonalize_preserves_element() {
1677 use crate::SU2;
1678 use std::f64::consts::PI;
1679
1680 let g = SU2::rotation_z(PI / 4.0);
1681 let g_reorth = g.reorthogonalize();
1682
1683 // Should be very close to original
1684 assert!(
1685 g.distance(&g_reorth) < 1e-12,
1686 "Reorthogonalization should preserve element"
1687 );
1688
1689 // Reorthogonalized version should be exactly on manifold
1690 let unitarity_error = g_reorth.adjoint().compose(&g_reorth).distance_to_identity();
1691 assert!(
1692 unitarity_error < 1e-14,
1693 "Reorthogonalized element should be on manifold: {}",
1694 unitarity_error
1695 );
1696 }
1697
1698 // ========================================================================
1699 // Operator overloading tests
1700 // ========================================================================
1701
1702 use crate::so3::{So3Algebra, SO3};
1703 use crate::su2::{Su2Algebra, SU2};
1704 use crate::su3::Su3Algebra;
1705
1706 #[test]
1707 fn test_su2_algebra_operators() {
1708 let x = Su2Algebra([1.0, 2.0, 3.0]);
1709 let y = Su2Algebra([0.5, 1.0, 1.5]);
1710
1711 // Add
1712 let sum = x + y;
1713 assert_eq!(sum.0, [1.5, 3.0, 4.5]);
1714
1715 // Add with references
1716 let sum_ref = &x + &y;
1717 assert_eq!(sum_ref.0, [1.5, 3.0, 4.5]);
1718
1719 // Sub
1720 let diff = x - y;
1721 assert_eq!(diff.0, [0.5, 1.0, 1.5]);
1722
1723 // Neg
1724 let neg = -x;
1725 assert_eq!(neg.0, [-1.0, -2.0, -3.0]);
1726
1727 // Scalar mul (both directions)
1728 let scaled = x * 2.0;
1729 assert_eq!(scaled.0, [2.0, 4.0, 6.0]);
1730 let scaled2 = 2.0 * x;
1731 assert_eq!(scaled2.0, [2.0, 4.0, 6.0]);
1732 }
1733
1734 #[test]
1735 fn test_su2_group_mul_operator() {
1736 let g = SU2::rotation_x(0.3);
1737 let h = SU2::rotation_y(0.7);
1738
1739 // Operator should match compose
1740 let product_op = &g * &h;
1741 let product_compose = g.compose(&h);
1742 assert_eq!(product_op.matrix, product_compose.matrix);
1743
1744 // MulAssign
1745 let mut g2 = g.clone();
1746 g2 *= &h;
1747 assert_eq!(g2.matrix, product_compose.matrix);
1748 }
1749
1750 #[test]
1751 fn test_so3_algebra_operators() {
1752 let x = So3Algebra([1.0, 0.0, 0.0]);
1753 let y = So3Algebra([0.0, 1.0, 0.0]);
1754
1755 let sum = x + y;
1756 assert_eq!(sum.0, [1.0, 1.0, 0.0]);
1757
1758 let scaled = 3.0 * x;
1759 assert_eq!(scaled.0, [3.0, 0.0, 0.0]);
1760
1761 let neg = -y;
1762 assert_eq!(neg.0, [0.0, -1.0, 0.0]);
1763 }
1764
1765 #[test]
1766 fn test_so3_group_mul_operator() {
1767 let r1 = SO3::rotation_x(0.5);
1768 let r2 = SO3::rotation_z(0.3);
1769 let product = &r1 * &r2;
1770 let expected = r1.compose(&r2);
1771 assert!(product.distance(&expected) < 1e-14);
1772 }
1773
1774 #[test]
1775 fn test_u1_algebra_operators() {
1776 let x = crate::U1Algebra(1.5);
1777 let y = crate::U1Algebra(0.5);
1778
1779 assert_eq!((x + y).0, 2.0);
1780 assert_eq!((x - y).0, 1.0);
1781 assert_eq!((-x).0, -1.5);
1782 assert_eq!((x * 3.0).0, 4.5);
1783 assert_eq!((3.0 * x).0, 4.5);
1784 }
1785
1786 #[test]
1787 fn test_u1_group_mul_operator() {
1788 let g = crate::U1::from_angle(1.0);
1789 let h = crate::U1::from_angle(2.0);
1790 let product = &g * &h;
1791 let expected = g.compose(&h);
1792 assert!(product.distance(&expected) < 1e-14);
1793 }
1794
1795 #[test]
1796 fn test_rplus_algebra_operators() {
1797 let x = crate::RPlusAlgebra(2.0);
1798 let y = crate::RPlusAlgebra(0.5);
1799
1800 assert_eq!((x + y).0, 2.5);
1801 assert_eq!((x - y).0, 1.5);
1802 assert_eq!((-x).0, -2.0);
1803 assert_eq!((x * 4.0).0, 8.0);
1804 assert_eq!((4.0 * x).0, 8.0);
1805 }
1806
1807 #[test]
1808 fn test_su3_algebra_operators() {
1809 let x = Su3Algebra([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
1810 let y = Su3Algebra([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
1811
1812 let sum = x + y;
1813 assert_eq!(sum.0[0], 1.0);
1814 assert_eq!(sum.0[1], 1.0);
1815
1816 let scaled = 2.0 * x;
1817 assert_eq!(scaled.0[0], 2.0);
1818 }
1819
1820 #[test]
1821 fn test_sun_algebra_operators() {
1822 use crate::sun::SunAlgebra;
1823
1824 let x = SunAlgebra::<2>::basis_element(0);
1825 let y = SunAlgebra::<2>::basis_element(1);
1826
1827 let sum = &x + &y;
1828 assert_eq!(sum.coefficients, vec![1.0, 1.0, 0.0]);
1829
1830 let diff = x.clone() - y.clone();
1831 assert_eq!(diff.coefficients, vec![1.0, -1.0, 0.0]);
1832
1833 let neg = -x.clone();
1834 assert_eq!(neg.coefficients, vec![-1.0, 0.0, 0.0]);
1835
1836 let scaled = x.clone() * 5.0;
1837 assert_eq!(scaled.coefficients, vec![5.0, 0.0, 0.0]);
1838
1839 let scaled2 = 5.0 * x;
1840 assert_eq!(scaled2.coefficients, vec![5.0, 0.0, 0.0]);
1841 }
1842
1843 #[test]
1844 fn test_sun_group_mul_operator() {
1845 use crate::sun::SUN;
1846
1847 let g = SUN::<3>::identity();
1848 let h = SUN::<3>::identity();
1849 let product = &g * &h;
1850 assert!(product.distance_to_identity() < 1e-14);
1851 }
1852
1853 #[test]
1854 fn test_algebra_operator_consistency_with_trait_methods() {
1855 // Verify that operators produce the same results as trait methods
1856 let x = Su2Algebra([0.3, -0.7, 1.2]);
1857 let y = Su2Algebra([0.5, 0.1, -0.4]);
1858
1859 // operator + should equal LieAlgebra::add
1860 let op_sum = x + y;
1861 let trait_sum = LieAlgebra::add(&x, &y);
1862 assert_eq!(op_sum.0, trait_sum.0);
1863
1864 // operator * scalar should equal LieAlgebra::scale
1865 let op_scaled = x * 2.5;
1866 let trait_scaled = x.scale(2.5);
1867 assert_eq!(op_scaled.0, trait_scaled.0);
1868 }
1869}