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}