use crate::traits::AntiHermitianByConstruction;
use crate::{LieAlgebra, LieGroup};
use num_complex::Complex;
use std::f64::consts::PI;
use std::fmt;
use std::ops::{Add, Mul, MulAssign, Neg, Sub};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct U1Algebra(pub(crate) f64);
impl Add for U1Algebra {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl Add<&U1Algebra> for U1Algebra {
type Output = U1Algebra;
fn add(self, rhs: &U1Algebra) -> U1Algebra {
self + *rhs
}
}
impl Add<U1Algebra> for &U1Algebra {
type Output = U1Algebra;
fn add(self, rhs: U1Algebra) -> U1Algebra {
*self + rhs
}
}
impl Add<&U1Algebra> for &U1Algebra {
type Output = U1Algebra;
fn add(self, rhs: &U1Algebra) -> U1Algebra {
*self + *rhs
}
}
impl Sub for U1Algebra {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
impl Neg for U1Algebra {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
impl Mul<f64> for U1Algebra {
type Output = Self;
fn mul(self, scalar: f64) -> Self {
Self(self.0 * scalar)
}
}
impl Mul<U1Algebra> for f64 {
type Output = U1Algebra;
fn mul(self, rhs: U1Algebra) -> U1Algebra {
rhs * self
}
}
impl U1Algebra {
#[must_use]
pub fn new(value: f64) -> Self {
Self(value)
}
#[inline]
pub fn value(&self) -> f64 {
self.0
}
}
impl LieAlgebra for U1Algebra {
const DIM: usize = 1;
#[inline]
fn zero() -> Self {
Self(0.0)
}
#[inline]
fn add(&self, other: &Self) -> Self {
Self(self.0 + other.0)
}
#[inline]
fn scale(&self, scalar: f64) -> Self {
Self(self.0 * scalar)
}
#[inline]
fn norm(&self) -> f64 {
self.0.abs()
}
#[inline]
fn basis_element(i: usize) -> Self {
assert_eq!(i, 0, "U(1) algebra is 1-dimensional");
Self(1.0)
}
#[inline]
fn from_components(components: &[f64]) -> Self {
assert_eq!(components.len(), 1, "u(1) has dimension 1");
Self(components[0])
}
#[inline]
fn to_components(&self) -> Vec<f64> {
vec![self.0]
}
#[inline]
fn bracket(&self, _other: &Self) -> Self {
Self::zero()
}
#[inline]
fn inner(&self, other: &Self) -> f64 {
self.0 * other.0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct U1 {
theta: f64,
}
impl U1 {
#[must_use]
pub fn from_angle(theta: f64) -> Self {
Self {
theta: theta.rem_euclid(2.0 * PI),
}
}
#[must_use]
pub fn from_complex(z: Complex<f64>) -> Self {
Self::from_angle(z.arg())
}
#[must_use]
pub fn angle(&self) -> f64 {
self.theta
}
#[must_use]
pub fn to_complex(&self) -> Complex<f64> {
Complex::new(self.theta.cos(), self.theta.sin())
}
#[must_use]
pub fn trace_complex(&self) -> Complex<f64> {
self.to_complex()
}
#[must_use]
pub fn rotation(magnitude: f64) -> Self {
Self::from_angle(magnitude)
}
#[cfg(feature = "rand")]
#[must_use]
pub fn random<R: rand::Rng>(rng: &mut R) -> Self {
use rand::distributions::{Distribution, Uniform};
let dist = Uniform::new(0.0, 2.0 * PI);
Self::from_angle(dist.sample(rng))
}
#[cfg(feature = "rand")]
#[must_use]
pub fn random_small<R: rand::Rng>(step_size: f64, rng: &mut R) -> Self {
use rand::distributions::{Distribution, Uniform};
let dist = Uniform::new(-step_size, step_size);
let angle = dist.sample(rng);
Self::from_angle(angle)
}
}
impl approx::AbsDiffEq for U1Algebra {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
1e-10
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
(self.0 - other.0).abs() < epsilon
}
}
impl approx::RelativeEq for U1Algebra {
fn default_max_relative() -> Self::Epsilon {
1e-10
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
approx::RelativeEq::relative_eq(&self.0, &other.0, epsilon, max_relative)
}
}
impl fmt::Display for U1Algebra {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "u(1)({:.4})", self.0)
}
}
impl Mul<&U1> for &U1 {
type Output = U1;
fn mul(self, rhs: &U1) -> U1 {
self.compose(rhs)
}
}
impl Mul<&U1> for U1 {
type Output = U1;
fn mul(self, rhs: &U1) -> U1 {
self.compose(rhs)
}
}
impl MulAssign<&U1> for U1 {
fn mul_assign(&mut self, rhs: &U1) {
*self = self.compose(rhs);
}
}
impl LieGroup for U1 {
const MATRIX_DIM: usize = 1;
type Algebra = U1Algebra;
fn identity() -> Self {
Self { theta: 0.0 }
}
fn compose(&self, other: &Self) -> Self {
Self::from_angle(self.theta + other.theta)
}
fn inverse(&self) -> Self {
Self::from_angle(-self.theta)
}
fn conjugate_transpose(&self) -> Self {
self.inverse()
}
fn adjoint_action(&self, algebra_element: &U1Algebra) -> U1Algebra {
*algebra_element
}
fn distance_to_identity(&self) -> f64 {
let normalized = self.theta.rem_euclid(2.0 * PI);
let dist = normalized.min(2.0 * PI - normalized);
dist.abs()
}
fn exp(tangent: &U1Algebra) -> Self {
Self::from_angle(tangent.0)
}
fn log(&self) -> crate::error::LogResult<U1Algebra> {
Ok(U1Algebra(self.theta))
}
}
impl std::fmt::Display for U1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "U1(θ={:.4})", self.theta)
}
}
use crate::traits::{Abelian, Compact};
impl Compact for U1 {}
impl Abelian for U1 {}
impl AntiHermitianByConstruction for U1Algebra {}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
#[test]
fn test_from_angle() {
let g = U1::from_angle(0.5);
assert!((g.angle() - 0.5).abs() < 1e-10);
let h = U1::from_angle(2.0 * PI + 0.3);
assert!((h.angle() - 0.3).abs() < 1e-10);
let k = U1::from_angle(-0.5);
assert!((k.angle() - (2.0 * PI - 0.5)).abs() < 1e-10);
}
#[test]
fn test_to_complex() {
let e = U1::identity();
let z = e.to_complex();
assert!((z.re - 1.0).abs() < 1e-10);
assert!(z.im.abs() < 1e-10);
let g = U1::from_angle(PI / 2.0);
let z = g.to_complex();
assert!(z.re.abs() < 1e-10);
assert!((z.im - 1.0).abs() < 1e-10);
let h = U1::from_angle(1.234);
assert!((h.to_complex().norm() - 1.0).abs() < 1e-10);
}
#[test]
fn test_group_identity() {
let g = U1::from_angle(1.5);
let e = U1::identity();
let g_e = g.compose(&e);
let e_g = e.compose(&g);
assert!((g_e.angle() - g.angle()).abs() < 1e-10);
assert!((e_g.angle() - g.angle()).abs() < 1e-10);
}
#[test]
fn test_group_inverse() {
let g = U1::from_angle(1.2);
let g_inv = g.inverse();
let product = g.compose(&g_inv);
assert!(product.is_near_identity(1e-10));
let g_inv_inv = g_inv.inverse();
assert!((g_inv_inv.angle() - g.angle()).abs() < 1e-10);
}
#[test]
fn test_group_associativity() {
let g1 = U1::from_angle(0.5);
let g2 = U1::from_angle(1.2);
let g3 = U1::from_angle(0.8);
let left = g1.compose(&g2.compose(&g3));
let right = g1.compose(&g2).compose(&g3);
assert!((left.angle() - right.angle()).abs() < 1e-10);
}
#[test]
fn test_commutativity() {
let g = U1::from_angle(0.7);
let h = U1::from_angle(1.3);
let gh = g.compose(&h);
let hg = h.compose(&g);
assert!((gh.angle() - hg.angle()).abs() < 1e-10);
}
#[test]
fn test_distance_to_identity() {
let e = U1::identity();
assert!(e.distance_to_identity().abs() < 1e-10);
let g1 = U1::from_angle(0.1);
assert!((g1.distance_to_identity() - 0.1).abs() < 1e-10);
let g2 = U1::from_angle(1.9 * PI);
assert!((g2.distance_to_identity() - 0.1 * PI).abs() < 1e-10);
}
#[test]
fn test_distance_symmetry() {
let g = U1::from_angle(1.5);
let h = U1::from_angle(0.8);
let d_gh = g.distance(&h);
let d_hg = h.distance(&g);
assert!((d_gh - d_hg).abs() < 1e-10);
}
#[test]
fn test_adjoint_equals_inverse() {
let g = U1::from_angle(1.7);
let adj = g.conjugate_transpose();
let inv = g.inverse();
assert!((adj.angle() - inv.angle()).abs() < 1e-10);
}
#[test]
#[cfg(feature = "rand")]
fn test_random_distribution() {
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let samples: Vec<U1> = (0..1000).map(|_| U1::random(&mut rng)).collect();
for g in &samples {
assert!(g.angle() >= 0.0 && g.angle() < 2.0 * PI);
}
let mean_angle = samples.iter().map(super::U1::angle).sum::<f64>() / samples.len() as f64;
assert!((mean_angle - PI).abs() < 0.2); }
#[test]
#[cfg(feature = "rand")]
fn test_random_small() {
let mut rng = rand::rngs::StdRng::seed_from_u64(123);
let step_size = 0.1;
let samples: Vec<U1> = (0..100)
.map(|_| U1::random_small(step_size, &mut rng))
.collect();
let close_to_identity = samples
.iter()
.filter(|g| g.distance_to_identity() < 0.3)
.count();
assert!(close_to_identity > 80); }
#[test]
fn test_trace() {
let g = U1::from_angle(PI / 3.0);
let tr = g.trace_complex();
let expected = g.to_complex();
assert!((tr.re - expected.re).abs() < 1e-10);
assert!((tr.im - expected.im).abs() < 1e-10);
}
#[test]
fn test_log_identity() {
use crate::traits::LieGroup;
let e = U1::identity();
let log_e = e.log().unwrap();
assert!(log_e.norm() < 1e-14);
}
#[test]
fn test_log_exp_roundtrip() {
use crate::traits::LieGroup;
let x = U1Algebra(0.5);
let g = U1::exp(&x);
let x_recovered = g.log().unwrap();
assert!((x_recovered.0 - x.0).abs() < 1e-10);
}
#[test]
fn test_exp_log_roundtrip() {
use crate::traits::LieGroup;
let g = U1::from_angle(0.7);
let x = g.log().unwrap();
let g_recovered = U1::exp(&x);
assert!((g_recovered.angle() - g.angle()).abs() < 1e-10);
}
#[test]
fn test_log_branch_cut_at_zero() {
use crate::traits::LieGroup;
let eps = 0.01;
let g_before = U1::from_angle(2.0 * PI - eps);
let log_before = g_before.log().unwrap().value();
let g_after = U1::from_angle(eps);
let log_after = g_after.log().unwrap().value();
assert!(
(log_before - (2.0 * PI - eps)).abs() < 1e-10,
"Expected log(e^{{i(2π-ε)}}) ≈ 2π-ε, got {}",
log_before
);
assert!(
(log_after - eps).abs() < 1e-10,
"Expected log(e^{{iε}}) ≈ ε, got {}",
log_after
);
let jump = log_before - log_after;
assert!(
(jump - 2.0 * PI).abs() < 0.1,
"Expected discontinuity ≈ 2π, got {}",
jump
);
}
#[test]
fn test_log_principal_branch_coverage() {
use crate::traits::LieGroup;
let test_angles = vec![
0.0,
PI / 4.0,
PI / 2.0,
PI,
3.0 * PI / 2.0,
2.0 * PI - 0.001,
];
for theta in test_angles {
let g = U1::from_angle(theta);
let log_value = g.log().unwrap().value();
assert!(
(0.0..2.0 * PI).contains(&log_value),
"log value {} outside principal branch [0, 2π) for θ = {}",
log_value,
theta
);
assert!(
(log_value - g.angle()).abs() < 1e-14,
"log inconsistent with angle() for θ = {}",
theta
);
}
}
#[cfg(feature = "proptest")]
use proptest::prelude::*;
#[cfg(feature = "proptest")]
fn arb_u1() -> impl Strategy<Value = U1> {
(0.0..2.0 * PI).prop_map(U1::from_angle)
}
#[cfg(feature = "proptest")]
proptest! {
#[test]
fn prop_identity_axiom(g in arb_u1()) {
let e = U1::identity();
let left = e.compose(&g);
prop_assert!(
(left.angle() - g.angle()).abs() < 1e-10,
"Left identity failed: e·g != g"
);
let right = g.compose(&e);
prop_assert!(
(right.angle() - g.angle()).abs() < 1e-10,
"Right identity failed: g·e != g"
);
}
#[test]
fn prop_inverse_axiom(g in arb_u1()) {
let g_inv = g.inverse();
let right_product = g.compose(&g_inv);
prop_assert!(
right_product.is_near_identity(1e-10),
"Right inverse failed: g·g⁻¹ != e, distance = {}",
right_product.distance_to_identity()
);
let left_product = g_inv.compose(&g);
prop_assert!(
left_product.is_near_identity(1e-10),
"Left inverse failed: g⁻¹·g != e, distance = {}",
left_product.distance_to_identity()
);
}
#[test]
fn prop_associativity(g1 in arb_u1(), g2 in arb_u1(), g3 in arb_u1()) {
let left_assoc = g1.compose(&g2).compose(&g3);
let right_assoc = g1.compose(&g2.compose(&g3));
prop_assert!(
(left_assoc.angle() - right_assoc.angle()).abs() < 1e-10,
"Associativity failed: (g₁·g₂)·g₃ != g₁·(g₂·g₃)"
);
}
#[test]
fn prop_commutativity(g in arb_u1(), h in arb_u1()) {
let gh = g.compose(&h);
let hg = h.compose(&g);
prop_assert!(
(gh.angle() - hg.angle()).abs() < 1e-10,
"Commutativity failed: g·h != h·g, g·h = {}, h·g = {}",
gh.angle(),
hg.angle()
);
}
#[test]
fn prop_exponential_map_homomorphism(a in -PI..PI, b in -PI..PI) {
use crate::traits::LieGroup;
let exp_a = U1::exp(&U1Algebra(a));
let exp_b = U1::exp(&U1Algebra(b));
let exp_sum = U1::exp(&U1Algebra(a + b));
let product = exp_a.compose(&exp_b);
prop_assert!(
product.distance(&exp_sum) < 1e-10,
"Exponential map homomorphism failed: exp(a)·exp(b) != exp(a+b)"
);
}
#[test]
fn prop_compactness_angle_wrapping(theta in -10.0 * PI..10.0 * PI) {
let g = U1::from_angle(theta);
prop_assert!(
g.angle() >= 0.0 && g.angle() < 2.0 * PI,
"Angle not normalized: θ = {}",
g.angle()
);
let g_plus_2pi = U1::from_angle(theta + 2.0 * PI);
prop_assert!(
(g.angle() - g_plus_2pi.angle()).abs() < 1e-10,
"Adding 2π changed the element"
);
}
}
}