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