Skip to main content

lie_groups/
u1.rs

1//! U(1): The Circle Group
2//!
3//! This module implements U(1), the group of complex numbers with unit modulus.
4//! U(1) appears as the gauge group of electromagnetism and in many other contexts.
5//!
6//! # Mathematical Background
7//!
8//! ## Definition
9//!
10//! ```text
11//! U(1) = { e^{iθ} ∈ ℂ | θ ∈ ℝ } ≅ S¹
12//! ```
13//!
14//! The circle group: complex numbers z with |z| = 1, isomorphic to the unit circle.
15//!
16//! ## Group Structure
17//!
18//! - **Multiplication**: e^{iθ₁} · e^{iθ₂} = e^{i(θ₁+θ₂)}
19//! - **Identity**: e^{i·0} = 1
20//! - **Inverse**: (e^{iθ})^{-1} = e^{-iθ}
21//! - **Abelian**: e^{iθ₁} · e^{iθ₂} = e^{iθ₂} · e^{iθ₁}
22//!
23//! ## Lie Algebra
24//!
25//! ```text
26//! u(1) = iℝ (purely imaginary numbers)
27//! Exponential map: exp(ia) = e^{ia} for a ∈ ℝ
28//! ```
29//!
30//! ## Logarithm and Branch Cuts
31//!
32//! The logarithm map log: U(1) → u(1) is **multivalued** - for any θ, the values
33//! θ, θ + 2π, θ + 4π, ... all represent the same group element e^{iθ}.
34//!
35//! **Principal Branch**: We use **θ ∈ [0, 2π)** with branch cut at θ = 0.
36//!
37//! ### Why [0, 2π) instead of (-π, π]?
38//!
39//! | Choice | Branch Cut | Pros | Cons |
40//! |--------|------------|------|------|
41//! | **[0, 2π)** (ours) | At θ = 0 (positive real axis) | Natural for angles, matches `from_angle()` normalization | Non-standard in complex analysis |
42//! | (-π, π] (std) | At θ = π (negative real axis) | Standard in complex analysis | Asymmetric around identity |
43//!
44//! We chose [0, 2π) for **consistency**: our internal representation normalizes
45//! angles to [0, 2π) in `from_angle()`, so `log(g)` returns exactly the stored value.
46//!
47//! ### Discontinuity Example
48//!
49//! ```text
50//! lim_{ε→0⁺} log(e^{i(2π-ε)}) = 2π - ε  →  2π
51//! lim_{ε→0⁺} log(e^{iε})      = ε       →  0
52//!
53//! Jump discontinuity of 2π at θ = 0 (the identity).
54//! ```
55//!
56//! ### Practical Impact
57//!
58//! - **Gauge fixing**: When computing holonomy defects `∫ log(g)`, discontinuities
59//!   at branch cuts can cause artificial jumps in the objective function.
60//! - **Unwrapping**: For smooth paths that cross θ = 0, consider phase unwrapping
61//!   algorithms that track accumulated 2π crossings.
62//! - **Optimization**: If optimizing over U(1) connections, use angle differences
63//!   `Δθ = θ₂ - θ₁` which are smooth, rather than logarithms.
64//!
65//! ## Physical Interpretation
66//!
67//! ### Electromagnetism
68//!
69//! - **Gauge field**: Vector potential `A_μ(x)`
70//! - **Field strength**: `F_μν` = ∂_μ `A_ν` - ∂_ν `A_μ` (electromagnetic field)
71//! - **Gauge transformation**: `A_μ` → `A_μ` + ∂_μ λ for λ: M → ℝ
72//! - **Connection**: U(x,y) = exp(i ∫_x^y `A_μ` dx^μ) ∈ U(1)
73//!
74//! ### Wilson Loops
75//!
76//! For a closed path C:
77//! ```text
78//! W_C = exp(i ∮_C A_μ dx^μ) = exp(i Φ)
79//! ```
80//! where Φ is the magnetic flux through C (Aharonov-Bohm phase).
81//!
82//! ## Applications
83//!
84//! 1. **Lattice QED**: Discretized electromagnetic field theory
85//! 2. **Phase synchronization**: Kuramoto model, coupled oscillators
86//! 3. **Superconductivity**: Order parameter ψ = |ψ|e^{iθ}, θ ∈ U(1)
87//! 4. **XY model**: Classical spin system with continuous symmetry
88//! 5. **Clock synchronization**: Distributed network of oscillators
89
90use crate::traits::AntiHermitianByConstruction;
91use crate::{LieAlgebra, LieGroup};
92use num_complex::Complex;
93use std::f64::consts::PI;
94use std::fmt;
95use std::ops::{Add, Mul, MulAssign, Neg, Sub};
96
97/// Lie algebra u(1) ≅ ℝ
98///
99/// The Lie algebra of U(1) consists of purely imaginary numbers i·a where a ∈ ℝ.
100/// We represent this as a 1-dimensional real vector space.
101///
102/// # Mathematical Structure
103///
104/// Elements of u(1) have the form:
105/// ```text
106/// X = i·a where a ∈ ℝ
107/// ```
108///
109/// The exponential map is simply:
110/// ```text
111/// exp(i·a) = e^{ia} ∈ U(1)
112/// ```
113///
114/// # Examples
115///
116/// ```
117/// use lie_groups::u1::U1Algebra;
118/// use lie_groups::traits::LieAlgebra;
119///
120/// // Create algebra element
121/// let v = U1Algebra::from_components(&[1.5]);
122///
123/// // Scale
124/// let w = v.scale(2.0);
125/// assert_eq!(w.value(), 3.0);
126/// ```
127#[derive(Clone, Copy, Debug, PartialEq)]
128pub struct U1Algebra(pub(crate) f64);
129
130impl Add for U1Algebra {
131    type Output = Self;
132    fn add(self, rhs: Self) -> Self {
133        Self(self.0 + rhs.0)
134    }
135}
136
137impl Add<&U1Algebra> for U1Algebra {
138    type Output = U1Algebra;
139    fn add(self, rhs: &U1Algebra) -> U1Algebra {
140        self + *rhs
141    }
142}
143
144impl Add<U1Algebra> for &U1Algebra {
145    type Output = U1Algebra;
146    fn add(self, rhs: U1Algebra) -> U1Algebra {
147        *self + rhs
148    }
149}
150
151impl Add<&U1Algebra> for &U1Algebra {
152    type Output = U1Algebra;
153    fn add(self, rhs: &U1Algebra) -> U1Algebra {
154        *self + *rhs
155    }
156}
157
158impl Sub for U1Algebra {
159    type Output = Self;
160    fn sub(self, rhs: Self) -> Self {
161        Self(self.0 - rhs.0)
162    }
163}
164
165impl Neg for U1Algebra {
166    type Output = Self;
167    fn neg(self) -> Self {
168        Self(-self.0)
169    }
170}
171
172impl Mul<f64> for U1Algebra {
173    type Output = Self;
174    fn mul(self, scalar: f64) -> Self {
175        Self(self.0 * scalar)
176    }
177}
178
179impl Mul<U1Algebra> for f64 {
180    type Output = U1Algebra;
181    fn mul(self, rhs: U1Algebra) -> U1Algebra {
182        rhs * self
183    }
184}
185
186impl U1Algebra {
187    /// Create a new u(1) algebra element.
188    ///
189    /// The value `a` represents the element `i·a ∈ u(1)`.
190    #[must_use]
191    pub fn new(value: f64) -> Self {
192        Self(value)
193    }
194
195    /// Get the real value a from the algebra element i·a
196    #[inline]
197    pub fn value(&self) -> f64 {
198        self.0
199    }
200}
201
202impl LieAlgebra for U1Algebra {
203    const DIM: usize = 1;
204
205    #[inline]
206    fn zero() -> Self {
207        Self(0.0)
208    }
209
210    #[inline]
211    fn add(&self, other: &Self) -> Self {
212        Self(self.0 + other.0)
213    }
214
215    #[inline]
216    fn scale(&self, scalar: f64) -> Self {
217        Self(self.0 * scalar)
218    }
219
220    #[inline]
221    fn norm(&self) -> f64 {
222        self.0.abs()
223    }
224
225    #[inline]
226    fn basis_element(i: usize) -> Self {
227        assert_eq!(i, 0, "U(1) algebra is 1-dimensional");
228        Self(1.0)
229    }
230
231    #[inline]
232    fn from_components(components: &[f64]) -> Self {
233        assert_eq!(components.len(), 1, "u(1) has dimension 1");
234        Self(components[0])
235    }
236
237    #[inline]
238    fn to_components(&self) -> Vec<f64> {
239        vec![self.0]
240    }
241
242    // u(1) is abelian: [X, Y] = 0 for all X, Y.
243    // Stated explicitly rather than inherited from the trait default,
244    // so the algebraic property is visible at the definition site.
245    #[inline]
246    fn bracket(&self, _other: &Self) -> Self {
247        Self::zero()
248    }
249
250    #[inline]
251    fn inner(&self, other: &Self) -> f64 {
252        self.0 * other.0
253    }
254}
255
256/// An element of U(1), the circle group
257///
258/// Represented as a phase angle θ ∈ [0, 2π), corresponding to e^{iθ}.
259///
260/// # Representation
261///
262/// We use the angle representation rather than storing the complex number
263/// directly to avoid floating-point accumulation errors and to make the
264/// group structure (angle addition) explicit.
265///
266/// # Examples
267///
268/// ```rust
269/// use lie_groups::{LieGroup, U1};
270/// use std::f64::consts::PI;
271///
272/// // Create elements
273/// let g = U1::from_angle(0.5);
274/// let h = U1::from_angle(0.3);
275///
276/// // Group multiplication (angle addition)
277/// let product = g.compose(&h);
278/// assert!((product.angle() - 0.8).abs() < 1e-10);
279///
280/// // Inverse (angle negation)
281/// let g_inv = g.inverse();
282/// assert!((g_inv.angle() - (2.0 * PI - 0.5)).abs() < 1e-10);
283/// ```
284#[derive(Clone, Copy, Debug, PartialEq)]
285pub struct U1 {
286    /// Phase angle θ ∈ [0, 2π)
287    ///
288    /// Represents the group element e^{iθ}
289    theta: f64,
290}
291
292impl U1 {
293    /// Create U(1) element from angle in radians
294    ///
295    /// Automatically normalizes to [0, 2π)
296    ///
297    /// # Arguments
298    ///
299    /// * `theta` - Phase angle in radians (any real number)
300    ///
301    /// # Examples
302    ///
303    /// ```rust
304    /// use lie_groups::U1;
305    ///
306    /// let g = U1::from_angle(0.5);
307    /// assert!((g.angle() - 0.5).abs() < 1e-10);
308    ///
309    /// // Normalization
310    /// let h = U1::from_angle(2.0 * std::f64::consts::PI + 0.3);
311    /// assert!((h.angle() - 0.3).abs() < 1e-10);
312    /// ```
313    #[must_use]
314    pub fn from_angle(theta: f64) -> Self {
315        Self {
316            theta: theta.rem_euclid(2.0 * PI),
317        }
318    }
319
320    /// Create U(1) element from complex number
321    ///
322    /// Extracts the phase angle from z = re^{iθ}, ignoring the magnitude.
323    ///
324    /// # Arguments
325    ///
326    /// * `z` - Complex number (does not need to have unit modulus)
327    ///
328    /// # Examples
329    ///
330    /// ```rust
331    /// use lie_groups::U1;
332    /// use num_complex::Complex;
333    ///
334    /// let z = Complex::new(0.0, 1.0);  // i
335    /// let g = U1::from_complex(z);
336    /// assert!((g.angle() - std::f64::consts::PI / 2.0).abs() < 1e-10);
337    /// ```
338    #[must_use]
339    pub fn from_complex(z: Complex<f64>) -> Self {
340        Self::from_angle(z.arg())
341    }
342
343    /// Get the phase angle θ ∈ [0, 2π)
344    ///
345    /// # Examples
346    ///
347    /// ```rust
348    /// use lie_groups::U1;
349    ///
350    /// let g = U1::from_angle(1.5);
351    /// assert!((g.angle() - 1.5).abs() < 1e-10);
352    /// ```
353    #[must_use]
354    pub fn angle(&self) -> f64 {
355        self.theta
356    }
357
358    /// Convert to complex number e^{iθ}
359    ///
360    /// Returns the complex number representation with unit modulus.
361    ///
362    /// # Examples
363    ///
364    /// ```rust
365    /// use lie_groups::U1;
366    ///
367    /// let g = U1::from_angle(std::f64::consts::PI / 2.0);  // π/2
368    /// let z = g.to_complex();
369    ///
370    /// assert!(z.re.abs() < 1e-10);  // cos(π/2) ≈ 0
371    /// assert!((z.im - 1.0).abs() < 1e-10);  // sin(π/2) = 1
372    /// assert!((z.norm() - 1.0).abs() < 1e-10);  // |z| = 1
373    /// ```
374    #[must_use]
375    pub fn to_complex(&self) -> Complex<f64> {
376        Complex::new(self.theta.cos(), self.theta.sin())
377    }
378
379    /// Trace of the 1×1 matrix representation
380    ///
381    /// For U(1), the trace is just the complex number itself: Tr(e^{iθ}) = e^{iθ}
382    ///
383    /// # Examples
384    ///
385    /// ```rust
386    /// use lie_groups::U1;
387    ///
388    /// let g = U1::from_angle(0.0);  // Identity
389    /// let tr = g.trace_complex();
390    ///
391    /// assert!((tr.re - 1.0).abs() < 1e-10);
392    /// assert!(tr.im.abs() < 1e-10);
393    /// ```
394    #[must_use]
395    pub fn trace_complex(&self) -> Complex<f64> {
396        self.to_complex()
397    }
398
399    /// Rotation generators for lattice updates
400    ///
401    /// Returns a small U(1) element for MCMC proposals
402    ///
403    /// # Arguments
404    ///
405    /// * `magnitude` - Step size in radians
406    ///
407    /// # Examples
408    ///
409    /// ```rust
410    /// use lie_groups::U1;
411    ///
412    /// let perturbation = U1::rotation(0.1);
413    /// assert!((perturbation.angle() - 0.1).abs() < 1e-10);
414    /// ```
415    #[must_use]
416    pub fn rotation(magnitude: f64) -> Self {
417        Self::from_angle(magnitude)
418    }
419
420    /// Random U(1) element uniformly distributed on the circle
421    ///
422    /// Requires the `rand` feature (enabled by default).
423    /// Samples θ uniformly from [0, 2π).
424    ///
425    /// # Examples
426    ///
427    /// ```rust
428    /// use lie_groups::U1;
429    /// use rand::SeedableRng;
430    ///
431    /// let mut rng = rand::rngs::StdRng::seed_from_u64(42);
432    /// let g = U1::random(&mut rng);
433    ///
434    /// assert!(g.angle() >= 0.0 && g.angle() < 2.0 * std::f64::consts::PI);
435    /// ```
436    #[cfg(feature = "rand")]
437    #[must_use]
438    pub fn random<R: rand::Rng>(rng: &mut R) -> Self {
439        use rand::distributions::{Distribution, Uniform};
440        let dist = Uniform::new(0.0, 2.0 * PI);
441        Self::from_angle(dist.sample(rng))
442    }
443
444    /// Random small perturbation for MCMC proposals
445    ///
446    /// Samples θ uniformly from [-`step_size`, +`step_size`]
447    ///
448    /// # Arguments
449    ///
450    /// * `step_size` - Maximum angle deviation
451    ///
452    /// # Examples
453    ///
454    /// ```rust
455    /// use lie_groups::{U1, LieGroup};
456    /// use rand::SeedableRng;
457    ///
458    /// let mut rng = rand::rngs::StdRng::seed_from_u64(42);
459    /// let delta = U1::random_small(0.1, &mut rng);
460    ///
461    /// // Should be close to identity
462    /// assert!(delta.distance_to_identity() <= 0.1);
463    /// ```
464    #[cfg(feature = "rand")]
465    #[must_use]
466    pub fn random_small<R: rand::Rng>(step_size: f64, rng: &mut R) -> Self {
467        use rand::distributions::{Distribution, Uniform};
468        let dist = Uniform::new(-step_size, step_size);
469        let angle = dist.sample(rng);
470        Self::from_angle(angle)
471    }
472}
473
474impl approx::AbsDiffEq for U1Algebra {
475    type Epsilon = f64;
476
477    fn default_epsilon() -> Self::Epsilon {
478        1e-10
479    }
480
481    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
482        (self.0 - other.0).abs() < epsilon
483    }
484}
485
486impl approx::RelativeEq for U1Algebra {
487    fn default_max_relative() -> Self::Epsilon {
488        1e-10
489    }
490
491    fn relative_eq(
492        &self,
493        other: &Self,
494        epsilon: Self::Epsilon,
495        max_relative: Self::Epsilon,
496    ) -> bool {
497        approx::RelativeEq::relative_eq(&self.0, &other.0, epsilon, max_relative)
498    }
499}
500
501impl fmt::Display for U1Algebra {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        write!(f, "u(1)({:.4})", self.0)
504    }
505}
506
507/// Group multiplication: g₁ · g₂ (phase addition)
508impl Mul<&U1> for &U1 {
509    type Output = U1;
510    fn mul(self, rhs: &U1) -> U1 {
511        self.compose(rhs)
512    }
513}
514
515impl Mul<&U1> for U1 {
516    type Output = U1;
517    fn mul(self, rhs: &U1) -> U1 {
518        self.compose(rhs)
519    }
520}
521
522impl MulAssign<&U1> for U1 {
523    fn mul_assign(&mut self, rhs: &U1) {
524        *self = self.compose(rhs);
525    }
526}
527
528impl LieGroup for U1 {
529    const MATRIX_DIM: usize = 1;
530
531    type Algebra = U1Algebra;
532
533    fn identity() -> Self {
534        Self { theta: 0.0 }
535    }
536
537    fn compose(&self, other: &Self) -> Self {
538        Self::from_angle(self.theta + other.theta)
539    }
540
541    fn inverse(&self) -> Self {
542        Self::from_angle(-self.theta)
543    }
544
545    fn conjugate_transpose(&self) -> Self {
546        // For U(1) (abelian group), conjugate transpose = complex conjugate = inverse
547        self.inverse()
548    }
549
550    fn adjoint_action(&self, algebra_element: &U1Algebra) -> U1Algebra {
551        // For abelian groups, the adjoint representation is trivial:
552        // Ad_g(X) = g X g⁻¹ = X (since everything commutes)
553        //
554        // Mathematically: For U(1), [g, X] = 0 for all g, X
555        // Therefore: g X g⁻¹ = g g⁻¹ X = X
556        *algebra_element
557    }
558
559    fn distance_to_identity(&self) -> f64 {
560        // Shortest arc distance on circle
561        let normalized = self.theta.rem_euclid(2.0 * PI);
562        let dist = normalized.min(2.0 * PI - normalized);
563        dist.abs()
564    }
565
566    fn exp(tangent: &U1Algebra) -> Self {
567        // exp(i·a) = e^{ia}
568        // Represented as angle a (mod 2π)
569        Self::from_angle(tangent.0)
570    }
571
572    fn log(&self) -> crate::error::LogResult<U1Algebra> {
573        // For U(1), log(e^{iθ}) = iθ
574        //
575        // **Principal Branch Choice**: θ ∈ [0, 2π) with branch cut at θ = 0
576        //
577        // See module documentation for detailed discussion of branch cuts.
578        //
579        // # Mathematical Properties
580        //
581        // - **Surjectivity**: Every algebra element a ∈ [0, 2π) has exp(ia) ∈ U(1)
582        // - **Branch cut**: Discontinuity at θ = 0 (positive real axis, identity)
583        // - **Consistency**: Returns exactly the normalized angle from `from_angle()`
584        //
585        // # Examples
586        //
587        // ```
588        // use lie_groups::{LieGroup, U1};
589        // use std::f64::consts::PI;
590        //
591        // // Identity
592        // let e = U1::identity();
593        // assert!(e.log().unwrap().value().abs() < 1e-14);
594        //
595        // // π/2
596        // let g = U1::from_angle(PI / 2.0);
597        // assert!((g.log().unwrap().value() - PI / 2.0).abs() < 1e-14);
598        //
599        // // Branch cut demonstration: values near 2π
600        // let g1 = U1::from_angle(2.0 * PI - 0.01);  // Just before branch cut
601        // let g2 = U1::from_angle(0.01);              // Just after branch cut
602        //
603        // // log values jump discontinuously
604        // assert!(g1.log().unwrap().value() > 6.0);   // ≈ 2π - 0.01
605        // assert!(g2.log().unwrap().value() < 0.1);   // ≈ 0.01
606        // // Jump size ≈ 2π
607        // ```
608        //
609        // # Design Rationale
610        //
611        // Unlike SU(2) or SU(3), where log() can fail for elements far from identity
612        // (returning `Err`), U(1) log is **always defined** because:
613        // 1. U(1) is 1-dimensional (no exponential map singularities)
614        // 2. Our angle representation is already the logarithm
615        // 3. The covering map exp: ℝ → U(1) is a local diffeomorphism everywhere
616        //
617        // However, users should be aware of the **discontinuity** at θ = 0 when
618        // using log in optimization or when computing angle differences.
619        Ok(U1Algebra(self.theta))
620    }
621}
622
623/// Display implementation shows the phase angle
624impl std::fmt::Display for U1 {
625    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
626        write!(f, "U1(θ={:.4})", self.theta)
627    }
628}
629
630// ============================================================================
631// Mathematical Property Implementations for U(1)
632// ============================================================================
633
634use crate::traits::{Abelian, Compact};
635
636/// U(1) is compact.
637///
638/// The circle group {e^{iθ} : θ ∈ ℝ} is diffeomorphic to S¹.
639/// All elements have unit modulus: |e^{iθ}| = 1.
640///
641/// # Topological Structure
642///
643/// U(1) ≅ SO(2) ≅ S¹ (the unit circle)
644/// - Closed and bounded in ℂ
645/// - Every sequence has a convergent subsequence (Bolzano-Weierstrass)
646/// - Admits a finite Haar measure
647///
648/// # Physical Significance
649///
650/// Compactness of U(1) ensures:
651/// - Well-defined Yang-Mills action
652/// - Completely reducible representations
653/// - Quantization of electric charge
654impl Compact for U1 {}
655
656/// U(1) is abelian.
657///
658/// Complex multiplication commutes: e^{iα} · e^{iβ} = e^{iβ} · e^{iα}
659///
660/// # Mathematical Structure
661///
662/// For all g, h ∈ U(1):
663/// ```text
664/// g · h = e^{i(α+β)} = e^{i(β+α)} = h · g
665/// ```
666///
667/// This makes U(1) the structure group for **Maxwell's electromagnetism**.
668///
669/// # Type Safety Example
670///
671/// ```ignore
672/// // This function requires an abelian gauge group
673/// fn compute_chern_number<G: Abelian>(conn: &NetworkConnection<G>) -> i32 {
674///     // Chern class computation requires commutativity
675/// }
676///
677/// // ✅ Compiles: U(1) is abelian
678/// let u1_conn = NetworkConnection::<U1>::new(graph);
679/// compute_chern_number(&u1_conn);
680///
681/// // ❌ Won't compile: SU(2) is not abelian
682/// let su2_conn = NetworkConnection::<SU2>::new(graph);
683/// // compute_chern_number(&su2_conn);  // Compile error!
684/// ```
685///
686/// # Physical Significance
687///
688/// - Gauge transformations commute (no self-interaction)
689/// - Field strength is linear: F = dA (no `[A,A]` term)
690/// - Maxwell's equations are linear PDEs
691/// - Superposition principle holds
692impl Abelian for U1 {}
693
694// Note: U(1) is NOT Simple or SemiSimple
695// - Abelian groups (except trivial group) are not simple
696// - They contain proper normal subgroups
697// - The center Z(U(1)) = U(1) itself
698
699// ============================================================================
700// Algebra Marker Traits
701// ============================================================================
702
703/// u(1) algebra elements are anti-Hermitian by construction.
704///
705/// The representation `U1Algebra(f64)` stores a real number `a` representing
706/// the purely imaginary element `ia ∈ iℝ ⊂ ℂ`. Since `(ia)* = -ia`, these
707/// are anti-Hermitian (as 1×1 complex matrices).
708///
709/// # Note: NOT `TracelessByConstruction`
710///
711/// Unlike su(n), the u(1) algebra is NOT traceless. For a 1×1 matrix `[ia]`,
712/// the trace is ia ≠ 0 (for a ≠ 0). This is why U(1) ≠ SU(1).
713///
714/// The theorem `det(exp(X)) = exp(tr(X))` still holds, but gives:
715/// - det(exp(ia)) = exp(ia) ≠ 1 in general
716/// - This is correct: U(1) elements have |det| = 1, not det = 1
717impl AntiHermitianByConstruction for U1Algebra {}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722    use rand::SeedableRng;
723
724    #[test]
725    fn test_from_angle() {
726        let g = U1::from_angle(0.5);
727        assert!((g.angle() - 0.5).abs() < 1e-10);
728
729        // Test normalization
730        let h = U1::from_angle(2.0 * PI + 0.3);
731        assert!((h.angle() - 0.3).abs() < 1e-10);
732
733        // Negative angles
734        let k = U1::from_angle(-0.5);
735        assert!((k.angle() - (2.0 * PI - 0.5)).abs() < 1e-10);
736    }
737
738    #[test]
739    fn test_to_complex() {
740        // Identity
741        let e = U1::identity();
742        let z = e.to_complex();
743        assert!((z.re - 1.0).abs() < 1e-10);
744        assert!(z.im.abs() < 1e-10);
745
746        // π/2
747        let g = U1::from_angle(PI / 2.0);
748        let z = g.to_complex();
749        assert!(z.re.abs() < 1e-10);
750        assert!((z.im - 1.0).abs() < 1e-10);
751
752        // All should have unit modulus
753        let h = U1::from_angle(1.234);
754        assert!((h.to_complex().norm() - 1.0).abs() < 1e-10);
755    }
756
757    #[test]
758    fn test_group_identity() {
759        let g = U1::from_angle(1.5);
760        let e = U1::identity();
761
762        let g_e = g.compose(&e);
763        let e_g = e.compose(&g);
764
765        assert!((g_e.angle() - g.angle()).abs() < 1e-10);
766        assert!((e_g.angle() - g.angle()).abs() < 1e-10);
767    }
768
769    #[test]
770    fn test_group_inverse() {
771        let g = U1::from_angle(1.2);
772        let g_inv = g.inverse();
773        let product = g.compose(&g_inv);
774
775        assert!(product.is_near_identity(1e-10));
776
777        // Double inverse
778        let g_inv_inv = g_inv.inverse();
779        assert!((g_inv_inv.angle() - g.angle()).abs() < 1e-10);
780    }
781
782    #[test]
783    fn test_group_associativity() {
784        let g1 = U1::from_angle(0.5);
785        let g2 = U1::from_angle(1.2);
786        let g3 = U1::from_angle(0.8);
787
788        let left = g1.compose(&g2.compose(&g3));
789        let right = g1.compose(&g2).compose(&g3);
790
791        assert!((left.angle() - right.angle()).abs() < 1e-10);
792    }
793
794    #[test]
795    fn test_commutativity() {
796        // U(1) is abelian
797        let g = U1::from_angle(0.7);
798        let h = U1::from_angle(1.3);
799
800        let gh = g.compose(&h);
801        let hg = h.compose(&g);
802
803        assert!((gh.angle() - hg.angle()).abs() < 1e-10);
804    }
805
806    #[test]
807    fn test_distance_to_identity() {
808        let e = U1::identity();
809        assert!(e.distance_to_identity().abs() < 1e-10);
810
811        // Small angle
812        let g1 = U1::from_angle(0.1);
813        assert!((g1.distance_to_identity() - 0.1).abs() < 1e-10);
814
815        // Angle > π should take shorter arc
816        let g2 = U1::from_angle(1.9 * PI);
817        assert!((g2.distance_to_identity() - 0.1 * PI).abs() < 1e-10);
818    }
819
820    #[test]
821    fn test_distance_symmetry() {
822        let g = U1::from_angle(1.5);
823        let h = U1::from_angle(0.8);
824
825        let d_gh = g.distance(&h);
826        let d_hg = h.distance(&g);
827
828        assert!((d_gh - d_hg).abs() < 1e-10);
829    }
830
831    #[test]
832    fn test_adjoint_equals_inverse() {
833        // For abelian U(1), adjoint = inverse
834        let g = U1::from_angle(1.7);
835        let adj = g.conjugate_transpose();
836        let inv = g.inverse();
837
838        assert!((adj.angle() - inv.angle()).abs() < 1e-10);
839    }
840
841    #[test]
842    #[cfg(feature = "rand")]
843    fn test_random_distribution() {
844        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
845
846        // Generate many random elements
847        let samples: Vec<U1> = (0..1000).map(|_| U1::random(&mut rng)).collect();
848
849        // Check all are in [0, 2π)
850        for g in &samples {
851            assert!(g.angle() >= 0.0 && g.angle() < 2.0 * PI);
852        }
853
854        // Check distribution is roughly uniform (basic test)
855        let mean_angle = samples.iter().map(super::U1::angle).sum::<f64>() / samples.len() as f64;
856        assert!((mean_angle - PI).abs() < 0.2); // Should be near π
857    }
858
859    #[test]
860    #[cfg(feature = "rand")]
861    fn test_random_small() {
862        let mut rng = rand::rngs::StdRng::seed_from_u64(123);
863        let step_size = 0.1;
864
865        let samples: Vec<U1> = (0..100)
866            .map(|_| U1::random_small(step_size, &mut rng))
867            .collect();
868
869        // Most should be close to identity
870        let close_to_identity = samples
871            .iter()
872            .filter(|g| g.distance_to_identity() < 0.3)
873            .count();
874
875        assert!(close_to_identity > 80); // At least 80% within 3σ
876    }
877
878    #[test]
879    fn test_trace() {
880        let g = U1::from_angle(PI / 3.0);
881        let tr = g.trace_complex();
882
883        // Tr(e^{iθ}) = e^{iθ}
884        let expected = g.to_complex();
885        assert!((tr.re - expected.re).abs() < 1e-10);
886        assert!((tr.im - expected.im).abs() < 1e-10);
887    }
888
889    // ========================================================================
890    // Logarithm Map Tests
891    // ========================================================================
892
893    #[test]
894    fn test_log_identity() {
895        use crate::traits::LieGroup;
896
897        let e = U1::identity();
898        let log_e = e.log().unwrap();
899
900        // log(identity) = 0
901        assert!(log_e.norm() < 1e-14);
902    }
903
904    #[test]
905    fn test_log_exp_roundtrip() {
906        use crate::traits::LieGroup;
907
908        // For small algebra elements, log(exp(X)) ≈ X
909        let x = U1Algebra(0.5);
910        let g = U1::exp(&x);
911        let x_recovered = g.log().unwrap();
912
913        // Should match original
914        assert!((x_recovered.0 - x.0).abs() < 1e-10);
915    }
916
917    #[test]
918    fn test_exp_log_roundtrip() {
919        use crate::traits::LieGroup;
920
921        // For group elements near identity, exp(log(g)) ≈ g
922        let g = U1::from_angle(0.7);
923        let x = g.log().unwrap();
924        let g_recovered = U1::exp(&x);
925
926        // Should match original
927        assert!((g_recovered.angle() - g.angle()).abs() < 1e-10);
928    }
929
930    #[test]
931    fn test_log_branch_cut_at_zero() {
932        use crate::traits::LieGroup;
933
934        // Test the branch cut at θ = 0 (identity)
935        // Values just before 2π should log to ~2π
936        // Values just after 0 should log to ~0
937        // Demonstrating the discontinuity
938
939        let eps = 0.01;
940
941        // Just before the branch cut (θ ≈ 2π - ε)
942        let g_before = U1::from_angle(2.0 * PI - eps);
943        let log_before = g_before.log().unwrap().value();
944
945        // Just after the branch cut (θ ≈ 0 + ε)
946        let g_after = U1::from_angle(eps);
947        let log_after = g_after.log().unwrap().value();
948
949        // Verify we're on the correct sides
950        assert!(
951            (log_before - (2.0 * PI - eps)).abs() < 1e-10,
952            "Expected log(e^{{i(2π-ε)}}) ≈ 2π-ε, got {}",
953            log_before
954        );
955        assert!(
956            (log_after - eps).abs() < 1e-10,
957            "Expected log(e^{{iε}}) ≈ ε, got {}",
958            log_after
959        );
960
961        // The jump discontinuity is ~2π
962        let jump = log_before - log_after;
963        assert!(
964            (jump - 2.0 * PI).abs() < 0.1,
965            "Expected discontinuity ≈ 2π, got {}",
966            jump
967        );
968    }
969
970    #[test]
971    fn test_log_principal_branch_coverage() {
972        use crate::traits::LieGroup;
973
974        // Verify log returns values in [0, 2π) for all inputs
975        let test_angles = vec![
976            0.0,
977            PI / 4.0,
978            PI / 2.0,
979            PI,
980            3.0 * PI / 2.0,
981            2.0 * PI - 0.001,
982        ];
983
984        for theta in test_angles {
985            let g = U1::from_angle(theta);
986            let log_value = g.log().unwrap().value();
987
988            // Should be in principal branch [0, 2π)
989            assert!(
990                (0.0..2.0 * PI).contains(&log_value),
991                "log value {} outside principal branch [0, 2π) for θ = {}",
992                log_value,
993                theta
994            );
995
996            // Should match the stored angle
997            assert!(
998                (log_value - g.angle()).abs() < 1e-14,
999                "log inconsistent with angle() for θ = {}",
1000                theta
1001            );
1002        }
1003    }
1004
1005    // ========================================================================
1006    // Property-Based Tests for Group Axioms
1007    // ========================================================================
1008    //
1009    // These tests use proptest to verify that U(1) satisfies the
1010    // mathematical axioms of a Lie group for randomly generated elements.
1011    //
1012    // U(1) is **abelian**, so we also test commutativity.
1013    //
1014    // Run with: cargo test --features property-tests
1015
1016    #[cfg(feature = "proptest")]
1017    use proptest::prelude::*;
1018
1019    /// Strategy for generating arbitrary U(1) elements.
1020    ///
1021    /// We generate U(1) elements by sampling angles uniformly from [0, 2π).
1022    #[cfg(feature = "proptest")]
1023    fn arb_u1() -> impl Strategy<Value = U1> {
1024        (0.0..2.0 * PI).prop_map(U1::from_angle)
1025    }
1026
1027    #[cfg(feature = "proptest")]
1028    proptest! {
1029        /// **Group Axiom 1: Identity Element**
1030        ///
1031        /// For all g ∈ U(1):
1032        /// - e · g = g (left identity)
1033        /// - g · e = g (right identity)
1034        #[test]
1035        fn prop_identity_axiom(g in arb_u1()) {
1036            let e = U1::identity();
1037
1038            // Left identity: e · g = g
1039            let left = e.compose(&g);
1040            prop_assert!(
1041                (left.angle() - g.angle()).abs() < 1e-10,
1042                "Left identity failed: e·g != g"
1043            );
1044
1045            // Right identity: g · e = g
1046            let right = g.compose(&e);
1047            prop_assert!(
1048                (right.angle() - g.angle()).abs() < 1e-10,
1049                "Right identity failed: g·e != g"
1050            );
1051        }
1052
1053        /// **Group Axiom 2: Inverse Element**
1054        ///
1055        /// For all g ∈ U(1):
1056        /// - g · g⁻¹ = e (right inverse)
1057        /// - g⁻¹ · g = e (left inverse)
1058        #[test]
1059        fn prop_inverse_axiom(g in arb_u1()) {
1060            let g_inv = g.inverse();
1061
1062            // Right inverse: g · g⁻¹ = e
1063            let right_product = g.compose(&g_inv);
1064            prop_assert!(
1065                right_product.is_near_identity(1e-10),
1066                "Right inverse failed: g·g⁻¹ != e, distance = {}",
1067                right_product.distance_to_identity()
1068            );
1069
1070            // Left inverse: g⁻¹ · g = e
1071            let left_product = g_inv.compose(&g);
1072            prop_assert!(
1073                left_product.is_near_identity(1e-10),
1074                "Left inverse failed: g⁻¹·g != e, distance = {}",
1075                left_product.distance_to_identity()
1076            );
1077        }
1078
1079        /// **Group Axiom 3: Associativity**
1080        ///
1081        /// For all g₁, g₂, g₃ ∈ U(1):
1082        /// - (g₁ · g₂) · g₃ = g₁ · (g₂ · g₃)
1083        #[test]
1084        fn prop_associativity(g1 in arb_u1(), g2 in arb_u1(), g3 in arb_u1()) {
1085            // Left association: (g₁ · g₂) · g₃
1086            let left_assoc = g1.compose(&g2).compose(&g3);
1087
1088            // Right association: g₁ · (g₂ · g₃)
1089            let right_assoc = g1.compose(&g2.compose(&g3));
1090
1091            prop_assert!(
1092                (left_assoc.angle() - right_assoc.angle()).abs() < 1e-10,
1093                "Associativity failed: (g₁·g₂)·g₃ != g₁·(g₂·g₃)"
1094            );
1095        }
1096
1097        /// **Abelian Property: Commutativity**
1098        ///
1099        /// For all g, h ∈ U(1):
1100        /// - g · h = h · g
1101        ///
1102        /// U(1) is abelian (commutative), which is NOT true for SU(2).
1103        /// This is why U(1) is used for electromagnetism (no self-interaction).
1104        #[test]
1105        fn prop_commutativity(g in arb_u1(), h in arb_u1()) {
1106            let gh = g.compose(&h);
1107            let hg = h.compose(&g);
1108
1109            prop_assert!(
1110                (gh.angle() - hg.angle()).abs() < 1e-10,
1111                "Commutativity failed: g·h != h·g, g·h = {}, h·g = {}",
1112                gh.angle(),
1113                hg.angle()
1114            );
1115        }
1116
1117        /// **Exponential Map Property**
1118        ///
1119        /// For U(1), exp: u(1) → U(1) is the exponential map.
1120        /// We verify that exp(a) · exp(b) = exp(a + b) for scalars a, b ∈ ℝ.
1121        ///
1122        /// This is the Baker-Campbell-Hausdorff formula specialized to the
1123        /// abelian case (where it simplifies dramatically).
1124        #[test]
1125        fn prop_exponential_map_homomorphism(a in -PI..PI, b in -PI..PI) {
1126            use crate::traits::LieGroup;
1127
1128            let exp_a = U1::exp(&U1Algebra(a));
1129            let exp_b = U1::exp(&U1Algebra(b));
1130            let exp_sum = U1::exp(&U1Algebra(a + b));
1131
1132            let product = exp_a.compose(&exp_b);
1133
1134            prop_assert!(
1135                product.distance(&exp_sum) < 1e-10,
1136                "Exponential map homomorphism failed: exp(a)·exp(b) != exp(a+b)"
1137            );
1138        }
1139
1140        /// **Compactness: Angles Wrap Around**
1141        ///
1142        /// U(1) is compact (isomorphic to S¹, the circle).
1143        /// This means angles wrap around modulo 2π.
1144        #[test]
1145        fn prop_compactness_angle_wrapping(theta in -10.0 * PI..10.0 * PI) {
1146            let g = U1::from_angle(theta);
1147
1148            // All angles should be normalized to [0, 2π)
1149            prop_assert!(
1150                g.angle() >= 0.0 && g.angle() < 2.0 * PI,
1151                "Angle not normalized: θ = {}",
1152                g.angle()
1153            );
1154
1155            // Adding 2πn should give the same element
1156            let g_plus_2pi = U1::from_angle(theta + 2.0 * PI);
1157            prop_assert!(
1158                (g.angle() - g_plus_2pi.angle()).abs() < 1e-10,
1159                "Adding 2π changed the element"
1160            );
1161        }
1162    }
1163}