Skip to main content

ariadnetor_tensor/
sector.rs

1//! Sector trait and concrete types for abelian symmetries.
2//!
3//! A sector labels an irreducible representation of an abelian symmetry group.
4//! The [`Sector`] trait provides fusion (tensor-product selection rule),
5//! identity, and dual operations that drive block-sparse tensor construction.
6
7use std::fmt::Debug;
8use std::hash::Hash;
9
10/// Abelian symmetry sector.
11///
12/// For abelian groups the fusion of two sectors is unique and commutative.
13/// The following algebraic laws must hold for every implementation:
14///
15/// - **Identity**: `s.fuse(&S::identity()) == s`
16/// - **Inverse**: `s.fuse(&s.dual()) == S::identity()`
17/// - **Commutativity**: `s.fuse(&t) == t.fuse(&s)`
18///
19/// `Ord` is required so that index types can keep sectors in sorted order
20/// for binary search and merge operations.
21pub trait Sector: Clone + Eq + Ord + Hash + Debug {
22    /// Fuse two sectors (tensor-product selection rule).
23    fn fuse(&self, other: &Self) -> Self;
24
25    /// The identity (trivial) sector.
26    fn identity() -> Self;
27
28    /// The dual (conjugate) sector, mapping incoming ↔ outgoing.
29    fn dual(&self) -> Self;
30}
31
32// ---------------------------------------------------------------------------
33// Z2
34// ---------------------------------------------------------------------------
35
36/// Z₂ symmetry sector (values 0 or 1).
37///
38/// Fusion rule: (a + b) mod 2.
39/// Every element is self-dual.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct Z2Sector(u8);
42
43impl Z2Sector {
44    /// Create a Z₂ sector. Panics if `value` is not 0 or 1.
45    pub fn new(value: u8) -> Self {
46        assert!(value <= 1, "Z2Sector value must be 0 or 1, got {value}");
47        Self(value)
48    }
49
50    /// Return the inner value (0 or 1).
51    pub fn value(self) -> u8 {
52        self.0
53    }
54}
55
56impl Sector for Z2Sector {
57    fn fuse(&self, other: &Self) -> Self {
58        Self(self.0 ^ other.0)
59    }
60
61    fn identity() -> Self {
62        Self(0)
63    }
64
65    fn dual(&self) -> Self {
66        *self
67    }
68}
69
70// ---------------------------------------------------------------------------
71// U1
72// ---------------------------------------------------------------------------
73
74/// U(1) symmetry sector (integer charge).
75///
76/// Fusion rule: a + b.
77/// Dual: −a.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub struct U1Sector(pub i32);
80
81impl Sector for U1Sector {
82    fn fuse(&self, other: &Self) -> Self {
83        Self(
84            self.0
85                .checked_add(other.0)
86                .expect("U1Sector charge overflow"),
87        )
88    }
89
90    fn identity() -> Self {
91        Self(0)
92    }
93
94    fn dual(&self) -> Self {
95        Self(self.0.checked_neg().expect("U1Sector charge overflow"))
96    }
97}
98
99// ---------------------------------------------------------------------------
100// Direct-product symmetry via tuples
101// ---------------------------------------------------------------------------
102
103impl<A: Sector, B: Sector> Sector for (A, B) {
104    fn fuse(&self, other: &Self) -> Self {
105        (self.0.fuse(&other.0), self.1.fuse(&other.1))
106    }
107
108    fn identity() -> Self {
109        (A::identity(), B::identity())
110    }
111
112    fn dual(&self) -> Self {
113        (self.0.dual(), self.1.dual())
114    }
115}
116
117// ---------------------------------------------------------------------------
118// Tests
119// ---------------------------------------------------------------------------
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    /// Verify the three algebraic laws for any Sector implementation.
126    fn assert_sector_laws<S: Sector>(a: &S, b: &S) {
127        let id = S::identity();
128
129        // Identity
130        assert_eq!(a.fuse(&id), *a);
131        assert_eq!(id.fuse(a), *a);
132
133        // Inverse
134        assert_eq!(a.fuse(&a.dual()), id);
135        assert_eq!(a.dual().fuse(a), id);
136
137        // Commutativity
138        assert_eq!(a.fuse(b), b.fuse(a));
139    }
140
141    #[test]
142    fn z2_laws() {
143        let s0 = Z2Sector::new(0);
144        let s1 = Z2Sector::new(1);
145        assert_sector_laws(&s0, &s1);
146        assert_sector_laws(&s1, &s1);
147    }
148
149    #[test]
150    fn z2_fusion_table() {
151        let z0 = Z2Sector::new(0);
152        let z1 = Z2Sector::new(1);
153        assert_eq!(z0.fuse(&z0), z0);
154        assert_eq!(z0.fuse(&z1), z1);
155        assert_eq!(z1.fuse(&z0), z1);
156        assert_eq!(z1.fuse(&z1), z0);
157    }
158
159    #[test]
160    fn z2_dual() {
161        assert_eq!(Z2Sector::new(0).dual(), Z2Sector::new(0));
162        assert_eq!(Z2Sector::new(1).dual(), Z2Sector::new(1));
163    }
164
165    #[test]
166    fn z2_ord() {
167        assert!(Z2Sector::new(0) < Z2Sector::new(1));
168    }
169
170    #[test]
171    #[should_panic(expected = "Z2Sector value must be 0 or 1")]
172    fn z2_invalid_value() {
173        Z2Sector::new(2);
174    }
175
176    #[test]
177    fn u1_laws() {
178        let s0 = U1Sector(0);
179        let s1 = U1Sector(1);
180        let s_neg = U1Sector(-3);
181        assert_sector_laws(&s0, &s1);
182        assert_sector_laws(&s1, &s_neg);
183        assert_sector_laws(&s_neg, &s0);
184    }
185
186    #[test]
187    fn u1_fusion() {
188        assert_eq!(U1Sector(2).fuse(&U1Sector(3)), U1Sector(5));
189        assert_eq!(U1Sector(-1).fuse(&U1Sector(1)), U1Sector(0));
190    }
191
192    #[test]
193    fn u1_dual() {
194        assert_eq!(U1Sector(3).dual(), U1Sector(-3));
195        assert_eq!(U1Sector(0).dual(), U1Sector(0));
196    }
197
198    #[test]
199    fn u1_ord() {
200        assert!(U1Sector(-1) < U1Sector(0));
201        assert!(U1Sector(0) < U1Sector(1));
202    }
203
204    #[test]
205    fn tuple_laws() {
206        let a = (U1Sector(1), Z2Sector::new(0));
207        let b = (U1Sector(-2), Z2Sector::new(1));
208        assert_sector_laws(&a, &b);
209    }
210
211    #[test]
212    fn tuple_fusion() {
213        let a = (U1Sector(1), Z2Sector::new(1));
214        let b = (U1Sector(2), Z2Sector::new(1));
215        assert_eq!(a.fuse(&b), (U1Sector(3), Z2Sector::new(0)));
216    }
217
218    #[test]
219    fn tuple_identity_and_dual() {
220        let id = <(U1Sector, Z2Sector)>::identity();
221        assert_eq!(id, (U1Sector(0), Z2Sector::new(0)));
222
223        let s = (U1Sector(3), Z2Sector::new(1));
224        assert_eq!(s.dual(), (U1Sector(-3), Z2Sector::new(1)));
225    }
226
227    #[test]
228    fn tuple_ord() {
229        // Lexicographic: U1 compared first, then Z2
230        let a = (U1Sector(0), Z2Sector::new(1));
231        let b = (U1Sector(1), Z2Sector::new(0));
232        assert!(a < b);
233    }
234
235    #[test]
236    fn nested_tuple() {
237        // (U1 × Z2) × U1 — verifies blanket impl composes
238        let a = ((U1Sector(1), Z2Sector::new(0)), U1Sector(2));
239        let b = ((U1Sector(-1), Z2Sector::new(1)), U1Sector(3));
240        assert_sector_laws(&a, &b);
241    }
242}