atlas_embeddings/foundations/
resonance.rs

1//! # Chapter 0.3: Resonance and Equivalence
2//!
3//! The stationary configuration of our action functional takes on many values—one
4//! for each cell in the 12,288-cell complex. However, these values are not all
5//! distinct: they fall into **equivalence classes** called **resonance classes**.
6//!
7//! The 96 vertices of the Atlas correspond to the 96 resonance classes.
8//!
9//! ## Overview
10//!
11//! This chapter answers: **Why 96 vertices?**
12//!
13//! The answer lies in understanding equivalence relations on configurations.
14//! While there are 12,288 cells, the stationary configuration's values cluster
15//! into exactly 96 distinct classes when we account for:
16//!
17//! 1. Gauge symmetries (phase equivalence)
18//! 2. Geometric symmetries (cell permutations)
19//! 3. Structural constraints (boundary relations)
20//!
21//! These 96 classes are what we call the **Atlas vertices**.
22
23use num_rational::Ratio;
24
25// # 0.3.1 Resonance Condition
26
27// ## 0.3.1 Resonance Condition
28//
29// Two configurations are **resonant** if they produce the same physical behavior
30// despite having different mathematical representations.
31//
32// ### Definition 0.3.1 (Resonance Equivalence)
33//
34// Two configurations φ, ψ are **resonant** (φ ~ ψ) if:
35//
36// $$ S[\phi] = S[\psi] $$
37//
38// and they are related by a symmetry of the action functional.
39//
40// ### Why Equivalence?
41//
42// In physics, many different mathematical descriptions can represent the same
43// physical state. Examples:
44//
45// - **Gauge symmetry**: In electromagnetism, potentials A and A + ∇χ produce
46//   the same electromagnetic field
47// - **Phase equivalence**: In quantum mechanics, ψ and e^{iθ}ψ represent the
48//   same state (global phase is unobservable)
49// - **Coordinate choice**: Different coordinate systems describe the same geometry
50//
51// Our action functional has similar symmetries. Configurations that differ by
52// these symmetries are physically equivalent—they should be considered the
53// same resonance class.
54//
55// ### Mathematical Structure
56//
57// Resonance defines an **equivalence relation** on configurations:
58//
59// 1. **Reflexive**: φ ~ φ (every configuration is resonant with itself)
60// 2. **Symmetric**: φ ~ ψ implies ψ ~ φ
61// 3. **Transitive**: φ ~ ψ and ψ ~ χ implies φ ~ χ
62//
63// The equivalence classes [φ] = {ψ : ψ ~ φ} are the **resonance classes**.
64
65/// A resonance class: an equivalence class of configurations.
66///
67/// Represents a set of configurations that are equivalent under the action
68/// functional's symmetries.
69///
70/// In the simplified model, we represent a resonance class by a representative
71/// configuration.
72#[derive(Debug, Clone)]
73pub struct ResonanceClass {
74    /// A representative element of this equivalence class
75    representative: Ratio<i64>,
76    /// Size of the equivalence class (how many configurations map to this class)
77    class_size: usize,
78}
79
80impl ResonanceClass {
81    /// Create a new resonance class with the given representative.
82    #[must_use]
83    pub const fn new(representative: Ratio<i64>, class_size: usize) -> Self {
84        Self { representative, class_size }
85    }
86
87    /// Get the representative value for this class.
88    #[must_use]
89    pub const fn representative(&self) -> &Ratio<i64> {
90        &self.representative
91    }
92
93    /// Get the size of this equivalence class.
94    #[must_use]
95    pub const fn size(&self) -> usize {
96        self.class_size
97    }
98}
99
100/// Check if two values are resonant (equivalent under symmetries).
101///
102/// This is a simplified version. The full theory involves checking gauge
103/// and geometric symmetries.
104///
105/// # Examples
106///
107/// ```
108/// use atlas_embeddings::foundations::resonance::are_resonant;
109/// use num_rational::Ratio;
110///
111/// let a = Ratio::new(1, 2);
112/// let b = Ratio::new(1, 2);
113/// assert!(are_resonant(&a, &b)); // Same value
114///
115/// let c = Ratio::new(2, 3);
116/// // Different values might still be resonant under symmetries
117/// ```
118#[must_use]
119pub fn are_resonant(a: &Ratio<i64>, b: &Ratio<i64>) -> bool {
120    // Simplified: exact equality
121    // Full theory would check gauge and symmetry equivalence
122    a == b
123}
124
125// # 0.3.2 The 96 Resonance Classes
126
127// ## 0.3.2 The 96 Resonance Classes
128//
129// When we partition the 12,288 cell values of the stationary configuration
130// into resonance classes, we get exactly **96 classes**.
131//
132// ### The Discovery
133//
134// This number—96—is not input to our construction. It emerges as output:
135//
136// 1. Start with 12,288-cell complex
137// 2. Find stationary configuration of action functional
138// 3. Group values into resonance classes
139// 4. Count: **exactly 96 classes**
140//
141// This is the **first empirical fact** about the Atlas.
142//
143// ### Why 96?
144//
145// The number 96 factors as:
146//
147// $$ 96 = 2^5 \cdot 3 = 32 \cdot 3 $$
148//
149// This reflects the structure of the label system (next section):
150// - 2⁵ from binary coordinates (e₁, e₂, e₃, e₆, e₇)
151// - 3 from ternary coordinate (d₄₅)
152//
153// But more fundamentally, 96 appears in E₈ theory:
154// - E₈ has 240 roots
155// - 96 is the number of roots in a certain orbit
156// - This hints at the Atlas → E₈ connection (Chapter 3)
157//
158// ### Distribution Across the Complex
159//
160// The 12,288 cells are not evenly distributed among the 96 classes:
161// - Some classes are large (many cells)
162// - Some classes are small (few cells)
163//
164// The distribution is determined by the symmetry structure of the complex.
165
166/// Partition configurations into resonance classes.
167///
168/// Given a collection of values from the stationary configuration, group them
169/// into equivalence classes.
170///
171/// # Returns
172///
173/// A vector of [`ResonanceClass`] objects, one for each equivalence class.
174///
175/// # Examples
176///
177/// ```
178/// use atlas_embeddings::foundations::resonance::partition_into_classes;
179/// use num_rational::Ratio;
180///
181/// let values = vec![
182///     Ratio::new(1, 2),
183///     Ratio::new(1, 2),
184///     Ratio::new(2, 3),
185///     Ratio::new(1, 2),
186///     Ratio::new(2, 3),
187/// ];
188///
189/// let classes = partition_into_classes(&values);
190/// assert_eq!(classes.len(), 2); // Two distinct values
191/// ```
192#[must_use]
193pub fn partition_into_classes(values: &[Ratio<i64>]) -> Vec<ResonanceClass> {
194    let mut class_map: std::collections::HashMap<Ratio<i64>, usize> =
195        std::collections::HashMap::new();
196
197    // Count occurrences of each value
198    for &value in values {
199        *class_map.entry(value).or_insert(0) += 1;
200    }
201
202    // Convert to resonance classes
203    class_map
204        .into_iter()
205        .map(|(rep, count)| ResonanceClass::new(rep, count))
206        .collect()
207}
208
209/// Verify that the stationary configuration has exactly 96 resonance classes.
210///
211/// This is the key empirical result that defines the Atlas.
212///
213/// # Examples
214///
215/// ```
216/// use atlas_embeddings::foundations::resonance::verify_96_classes;
217///
218/// // In the actual construction, this would verify the stationary configuration
219/// // For now, we demonstrate the expected result
220/// let num_classes = 96;
221/// assert!(verify_96_classes(num_classes));
222/// ```
223#[must_use]
224pub const fn verify_96_classes(num_classes: usize) -> bool {
225    num_classes == 96
226}
227
228// # 0.3.3 The Label System
229
230// ## 0.3.3 The Label System
231//
232// Each resonance class is labeled by a **6-tuple** encoding its structure.
233//
234// ### Definition 0.3.2 (Atlas Label)
235//
236// An **Atlas label** is a 6-tuple:
237//
238// $$ (e_1, e_2, e_3, d_{45}, e_6, e_7) $$
239//
240// where:
241// - **e₁, e₂, e₃ ∈ {0, 1}**: Binary coordinates (first three)
242// - **d₄₅ ∈ {-1, 0, +1}**: Ternary coordinate (encodes difference e₄ - e₅)
243// - **e₆, e₇ ∈ {0, 1}**: Binary coordinates (last two)
244//
245// This gives 2³ × 3 × 2² = 8 × 3 × 4 = 96 possible labels, and **all 96 occur**.
246//
247// ### Why 6-tuple, Not 8-tuple?
248//
249// We work in 8 dimensions (preparing for E₈), but use only 6 coordinates. Why?
250//
251// **Gauge symmetry**: The individual values of e₄ and e₅ are not observable—only
252// their difference d₄₅ = e₄ - e₅ matters. This is a fundamental symmetry of
253// the action functional.
254//
255// Attempting to specify e₄ and e₅ independently would:
256// - Violate gauge symmetry
257// - Introduce redundancy (many labels for same class)
258// - Obscure the natural structure
259//
260// The 6-tuple is the **minimal** label system respecting all symmetries.
261//
262// ### Connection to Binary/Ternary Structure
263//
264// The label structure reflects the factorization 96 = 2⁵ × 3:
265//
266// - **Binary part** (2⁵ = 32): Five coordinates e₁, e₂, e₃, e₆, e₇
267// - **Ternary part** (3): One coordinate d₄₅
268//
269// This will connect to the categorical operations in Chapter 4:
270// - G₂ construction uses the ternary structure (ℤ/3)
271// - Other constructions use the binary structure
272
273/// An Atlas label: 6-tuple identifying a resonance class.
274///
275/// # Coordinate System
276///
277/// - `e1, e2, e3`: Binary coordinates (0 or 1)
278/// - `d45`: Ternary coordinate (-1, 0, or +1) representing e₄ - e₅
279/// - `e6, e7`: Binary coordinates (0 or 1)
280///
281/// # Examples
282///
283/// ```
284/// use atlas_embeddings::foundations::resonance::AtlasLabel;
285///
286/// // Create label (0,0,0,0,0,0) - the "origin" vertex
287/// let origin = AtlasLabel::new(0, 0, 0, 0, 0, 0);
288/// assert!(origin.is_valid());
289///
290/// // Create label with ternary coordinate
291/// let label = AtlasLabel::new(1, 0, 1, 1, 0, 1);
292/// assert!(label.is_valid());
293/// assert_eq!(label.d45(), 1);
294///
295/// // Invalid ternary value
296/// let invalid = AtlasLabel::new(0, 0, 0, 2, 0, 0);
297/// assert!(!invalid.is_valid());
298/// ```
299#[allow(clippy::large_stack_arrays)]
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
301pub struct AtlasLabel {
302    /// First coordinate (binary: 0 or 1)
303    pub e1: i8,
304    /// Second coordinate (binary: 0 or 1)
305    pub e2: i8,
306    /// Third coordinate (binary: 0 or 1)
307    pub e3: i8,
308    /// Difference d₄₅ = e₄ - e₅ (ternary: -1, 0, or +1)
309    pub d45: i8,
310    /// Sixth coordinate (binary: 0 or 1)
311    pub e6: i8,
312    /// Seventh coordinate (binary: 0 or 1)
313    pub e7: i8,
314}
315
316impl AtlasLabel {
317    /// Create a new Atlas label.
318    ///
319    /// # Arguments
320    ///
321    /// * `e1, e2, e3, e6, e7` - Binary coordinates (should be 0 or 1)
322    /// * `d45` - Ternary coordinate (should be -1, 0, or +1)
323    ///
324    /// # Note
325    ///
326    /// This constructor does not validate inputs. Use [`is_valid`](Self::is_valid)
327    /// to check validity.
328    #[must_use]
329    #[allow(clippy::too_many_arguments)]
330    pub const fn new(e1: i8, e2: i8, e3: i8, d45: i8, e6: i8, e7: i8) -> Self {
331        Self { e1, e2, e3, d45, e6, e7 }
332    }
333
334    /// Check if this label is valid.
335    ///
336    /// Validity conditions:
337    /// - Binary coordinates in {0, 1}
338    /// - Ternary coordinate in {-1, 0, +1}
339    #[must_use]
340    pub const fn is_valid(&self) -> bool {
341        // Check binary coordinates
342        let binary_valid = (self.e1 == 0 || self.e1 == 1)
343            && (self.e2 == 0 || self.e2 == 1)
344            && (self.e3 == 0 || self.e3 == 1)
345            && (self.e6 == 0 || self.e6 == 1)
346            && (self.e7 == 0 || self.e7 == 1);
347
348        // Check ternary coordinate
349        let ternary_valid = self.d45 == -1 || self.d45 == 0 || self.d45 == 1;
350
351        binary_valid && ternary_valid
352    }
353
354    /// Get the ternary coordinate d₄₅ = e₄ - e₅.
355    #[must_use]
356    pub const fn d45(&self) -> i8 {
357        self.d45
358    }
359
360    /// Count the number of 1's in the binary coordinates.
361    ///
362    /// This is used in determining adjacency and other properties.
363    #[must_use]
364    #[allow(clippy::cast_sign_loss)]
365    pub const fn binary_weight(&self) -> usize {
366        (self.e1 + self.e2 + self.e3 + self.e6 + self.e7) as usize
367    }
368}
369
370/// Generate all 96 valid Atlas labels.
371///
372/// Enumerates all 2³ × 3 × 2² = 96 combinations of valid coordinates.
373///
374/// # Returns
375///
376/// A vector of all 96 [`AtlasLabel`] values.
377///
378/// # Examples
379///
380/// ```
381/// use atlas_embeddings::foundations::resonance::generate_all_labels;
382///
383/// let labels = generate_all_labels();
384/// assert_eq!(labels.len(), 96);
385///
386/// // All labels should be valid
387/// assert!(labels.iter().all(|label| label.is_valid()));
388///
389/// // All labels should be distinct
390/// use std::collections::HashSet;
391/// let unique: HashSet<_> = labels.iter().collect();
392/// assert_eq!(unique.len(), 96);
393/// ```
394#[must_use]
395pub fn generate_all_labels() -> Vec<AtlasLabel> {
396    let mut labels = Vec::with_capacity(96);
397
398    // Iterate over all combinations
399    for e1 in [0, 1] {
400        for e2 in [0, 1] {
401            for e3 in [0, 1] {
402                for d45 in [-1, 0, 1] {
403                    for e6 in [0, 1] {
404                        for e7 in [0, 1] {
405                            labels.push(AtlasLabel::new(e1, e2, e3, d45, e6, e7));
406                        }
407                    }
408                }
409            }
410        }
411    }
412
413    labels
414}
415
416// # 0.3.4 Extension to E₈ Coordinates
417
418// ## 0.3.4 Extension to E₈ Coordinates
419//
420// The 6-tuple labels naturally extend to 8-tuples in the E₈ lattice.
421//
422// ### Proposition 0.3.3 (Extension to 8D)
423//
424// Each 6-tuple (e₁,e₂,e₃,d₄₅,e₆,e₇) extends uniquely to an 8-tuple
425// (e₁,e₂,e₃,e₄,e₅,e₆,e₇,e₈) satisfying:
426//
427// 1. **d₄₅ constraint**: e₄ - e₅ = d₄₅
428// 2. **Parity constraint**: e₁ + e₂ + e₃ + e₄ + e₅ + e₆ + e₇ + e₈ ≡ 0 (mod 2)
429//
430// ### The Extension Algorithm
431//
432// Given (e₁,e₂,e₃,d₄₅,e₆,e₇):
433//
434// 1. **Recover e₄ and e₅ from d₄₅**:
435//    - We know e₄ - e₅ = d₄₅
436//    - We need e₄, e₅ ∈ {0, 1}
437//    - Solution depends on d₄₅:
438//      - If d₄₅ = -1: (e₄, e₅) = (0, 1)
439//      - If d₄₅ = 0: Choose based on parity of e₁+e₂+e₃+e₆+e₇
440//      - If d₄₅ = +1: (e₄, e₅) = (1, 0)
441//
442// 2. **Compute e₈ from parity**:
443//    - e₈ = (e₁ + e₂ + e₃ + e₄ + e₅ + e₆ + e₇) mod 2
444//    - This ensures the sum is even
445//
446// ### Why This Extension?
447//
448// The extension is **canonical**—determined by mathematical constraints, not
449// arbitrary choices. It ensures:
450//
451// - Compatibility with E₈ lattice structure
452// - Preservation of symmetries
453// - Uniqueness (no ambiguity)
454//
455// This extension is central to the embedding theorem (Chapter 3).
456//
457// ### Preview: The Embedding
458//
459// The extended 8-tuples turn out to be **roots of E₈**—vectors of norm² = 2
460// in the E₈ root system. This is not obvious from the construction; it is a
461// discovery.
462//
463// The Atlas embeds into E₈ via this extension, which is why E₈ structure
464// underlies all exceptional groups.
465
466/// Extend an Atlas label to 8 coordinates.
467///
468/// Recovers (e₄, e₅) from d₄₅ and computes e₈ from parity constraint.
469///
470/// # Algorithm
471///
472/// 1. Determine (e₄, e₅) from d₄₅ using constraints
473/// 2. Compute e₈ to ensure even parity
474///
475/// # Returns
476///
477/// An 8-tuple (e₁, e₂, e₃, e₄, e₅, e₆, e₇, e₈) extending the input label.
478///
479/// # Examples
480///
481/// ```
482/// use atlas_embeddings::foundations::resonance::{AtlasLabel, extend_to_8d};
483///
484/// // Label with d45 = 1 means e4 - e5 = 1
485/// let label = AtlasLabel::new(0, 0, 0, 1, 0, 0);
486/// let extended = extend_to_8d(&label);
487///
488/// assert_eq!(extended[3] - extended[4], 1); // e4 - e5 = 1
489///
490/// // Check parity: sum should be even
491/// let sum: i8 = extended.iter().sum();
492/// assert_eq!(sum % 2, 0);
493/// ```
494///
495/// # Panics
496///
497/// Panics if `label.d45` is not one of -1, 0, or +1.
498#[must_use]
499pub fn extend_to_8d(label: &AtlasLabel) -> [i8; 8] {
500    let (e4, e5) = match label.d45 {
501        -1 => (0, 1), // e4 - e5 = -1
502        1 => (1, 0),  // e4 - e5 = 1
503        0 => {
504            // e4 = e5, choose based on parity
505            let partial_sum = label.e1 + label.e2 + label.e3 + label.e6 + label.e7;
506            if partial_sum % 2 == 0 {
507                (0, 0)
508            } else {
509                (1, 1)
510            }
511        },
512        _ => panic!("Invalid d45 value: {}", label.d45),
513    };
514
515    // Compute e8 to ensure even parity
516    let e8 = (label.e1 + label.e2 + label.e3 + e4 + e5 + label.e6 + label.e7) % 2;
517
518    [label.e1, label.e2, label.e3, e4, e5, label.e6, label.e7, e8]
519}
520
521// ## 0.3.5 Summary
522//
523// We have established the structure of resonance classes:
524//
525// 1. **Resonance equivalence**: Configurations equivalent under symmetries
526// 2. **96 classes**: The stationary configuration has exactly 96 resonance classes
527// 3. **Label system**: Each class is labeled by a 6-tuple (e₁,e₂,e₃,d₄₅,e₆,e₇)
528// 4. **Extension to 8D**: Labels extend uniquely to 8-tuples in E₈
529//
530// These 96 resonance classes **are** the Atlas vertices. The graph structure
531// (edges) comes from the adjacency structure of the action functional.
532//
533// In the next section, we introduce the categorical framework that will allow
534// us to construct all exceptional groups from the Atlas.
535//
536// ---
537//
538// **Navigation**:
539// - Previous: [§0.2 Action Functionals](super::action)
540// - Next: [§0.4 Categorical Preliminaries](super::categories)
541// - Up: [Chapter 0: Foundations](super)
542
543#[cfg(test)]
544mod tests {
545    use super::*;
546    use std::collections::HashSet;
547
548    #[test]
549    fn test_resonance_class_creation() {
550        let class = ResonanceClass::new(Ratio::new(1, 2), 128);
551        assert_eq!(*class.representative(), Ratio::new(1, 2));
552        assert_eq!(class.size(), 128);
553    }
554
555    #[test]
556    fn test_partition_into_classes() {
557        let values = vec![Ratio::new(1, 2), Ratio::new(1, 2), Ratio::new(2, 3), Ratio::new(1, 2)];
558
559        let classes = partition_into_classes(&values);
560        assert_eq!(classes.len(), 2); // Two distinct values
561
562        // Check total count
563        let total: usize = classes.iter().map(ResonanceClass::size).sum();
564        assert_eq!(total, 4);
565    }
566
567    #[test]
568    fn test_atlas_label_validity() {
569        // Valid labels
570        assert!(AtlasLabel::new(0, 0, 0, 0, 0, 0).is_valid());
571        assert!(AtlasLabel::new(1, 1, 1, -1, 1, 1).is_valid());
572        assert!(AtlasLabel::new(0, 1, 0, 1, 1, 0).is_valid());
573
574        // Invalid: binary coordinate out of range
575        assert!(!AtlasLabel::new(2, 0, 0, 0, 0, 0).is_valid());
576        assert!(!AtlasLabel::new(0, 0, 0, 0, 0, -1).is_valid());
577
578        // Invalid: ternary coordinate out of range
579        assert!(!AtlasLabel::new(0, 0, 0, 2, 0, 0).is_valid());
580        assert!(!AtlasLabel::new(0, 0, 0, -2, 0, 0).is_valid());
581    }
582
583    #[test]
584    fn test_generate_all_labels() {
585        let labels = generate_all_labels();
586
587        // Exactly 96 labels
588        assert_eq!(labels.len(), 96);
589
590        // All valid
591        assert!(labels.iter().all(AtlasLabel::is_valid));
592
593        // All distinct
594        let unique: HashSet<_> = labels.iter().collect();
595        assert_eq!(unique.len(), 96);
596
597        // Verify count: 2^3 * 3 * 2^2 = 8 * 3 * 4 = 96
598        assert_eq!(labels.len(), 8 * 3 * 4);
599    }
600
601    #[test]
602    fn test_extension_to_8d() {
603        // Test d45 = 1: should give e4 - e5 = 1
604        let label = AtlasLabel::new(0, 0, 0, 1, 0, 0);
605        let extended = extend_to_8d(&label);
606        assert_eq!(extended[3] - extended[4], 1);
607
608        // Test d45 = -1: should give e4 - e5 = -1
609        let label = AtlasLabel::new(0, 0, 0, -1, 0, 0);
610        let extended = extend_to_8d(&label);
611        assert_eq!(extended[3] - extended[4], -1);
612
613        // Test d45 = 0: e4 should equal e5
614        let label = AtlasLabel::new(0, 0, 0, 0, 0, 0);
615        let extended = extend_to_8d(&label);
616        assert_eq!(extended[3], extended[4]);
617    }
618
619    #[test]
620    fn test_extension_parity() {
621        let labels = generate_all_labels();
622
623        for label in &labels {
624            let extended = extend_to_8d(label);
625            let sum: i8 = extended.iter().sum();
626
627            // All extended coordinates should have even parity
628            assert_eq!(sum % 2, 0, "Label {label:?} has odd parity");
629        }
630    }
631
632    #[test]
633    fn test_verify_96_classes() {
634        assert!(verify_96_classes(96));
635        assert!(!verify_96_classes(95));
636        assert!(!verify_96_classes(97));
637    }
638
639    #[test]
640    fn test_atlas_label_binary_weight() {
641        let label = AtlasLabel::new(1, 1, 0, 0, 1, 1);
642        assert_eq!(label.binary_weight(), 4); // Four 1's
643
644        let label = AtlasLabel::new(0, 0, 0, 0, 0, 0);
645        assert_eq!(label.binary_weight(), 0); // All 0's
646
647        let label = AtlasLabel::new(1, 1, 1, 0, 1, 1);
648        assert_eq!(label.binary_weight(), 5); // Five 1's
649    }
650}