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}