quantrs2_core/
topological.rs

1//! Topological quantum computing primitives
2//!
3//! This module provides implementations of topological quantum computing concepts
4//! including anyons, braiding operations, fusion rules, and topological gates.
5
6use crate::error::{QuantRS2Error, QuantRS2Result};
7use ndarray::{Array1, Array2};
8use num_complex::Complex64;
9use std::collections::HashMap;
10use std::f64::consts::PI;
11use std::fmt;
12
13/// Type alias for fusion coefficients
14type FusionCoeff = Complex64;
15
16/// Anyon type label
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct AnyonType {
19    /// Unique identifier for the anyon type
20    pub id: u32,
21    /// String label (e.g., "1", "σ", "ψ")
22    pub label: &'static str,
23}
24
25impl AnyonType {
26    /// Create a new anyon type
27    pub const fn new(id: u32, label: &'static str) -> Self {
28        Self { id, label }
29    }
30
31    /// Vacuum (identity) anyon
32    pub const VACUUM: Self = Self::new(0, "1");
33}
34
35impl fmt::Display for AnyonType {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        write!(f, "{}", self.label)
38    }
39}
40
41/// Anyon model definition
42pub trait AnyonModel: Send + Sync {
43    /// Get all anyon types in this model
44    fn anyon_types(&self) -> &[AnyonType];
45
46    /// Get quantum dimension of an anyon
47    fn quantum_dimension(&self, anyon: AnyonType) -> f64;
48
49    /// Get topological spin of an anyon
50    fn topological_spin(&self, anyon: AnyonType) -> Complex64;
51
52    /// Check if two anyons can fuse into a third
53    fn can_fuse(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> bool;
54
55    /// Get fusion rules N^c_{ab}
56    fn fusion_multiplicity(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> u32;
57
58    /// Get F-symbols F^{abc}_d
59    fn f_symbol(
60        &self,
61        a: AnyonType,
62        b: AnyonType,
63        c: AnyonType,
64        d: AnyonType,
65        e: AnyonType,
66        f: AnyonType,
67    ) -> FusionCoeff;
68
69    /// Get R-symbols (braiding matrices) R^{ab}_c
70    fn r_symbol(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> FusionCoeff;
71
72    /// Get the name of this anyon model
73    fn name(&self) -> &str;
74
75    /// Check if the model is modular (all anyons have non-zero quantum dimension)
76    fn is_modular(&self) -> bool {
77        self.anyon_types()
78            .iter()
79            .all(|&a| self.quantum_dimension(a) > 0.0)
80    }
81
82    /// Get total quantum dimension
83    fn total_quantum_dimension(&self) -> f64 {
84        self.anyon_types()
85            .iter()
86            .map(|&a| self.quantum_dimension(a).powi(2))
87            .sum::<f64>()
88            .sqrt()
89    }
90}
91
92/// Fibonacci anyon model (simplest universal model)
93pub struct FibonacciModel {
94    anyons: Vec<AnyonType>,
95    phi: f64, // Golden ratio
96}
97
98impl FibonacciModel {
99    /// Create a new Fibonacci anyon model
100    pub fn new() -> Self {
101        let phi = (1.0 + 5.0_f64.sqrt()) / 2.0;
102        let anyons = vec![
103            AnyonType::new(0, "1"), // Vacuum
104            AnyonType::new(1, "τ"), // Fibonacci anyon
105        ];
106
107        Self { anyons, phi }
108    }
109}
110
111impl Default for FibonacciModel {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117impl AnyonModel for FibonacciModel {
118    fn anyon_types(&self) -> &[AnyonType] {
119        &self.anyons
120    }
121
122    fn quantum_dimension(&self, anyon: AnyonType) -> f64 {
123        match anyon.id {
124            0 => 1.0,      // Vacuum
125            1 => self.phi, // τ anyon
126            _ => 0.0,
127        }
128    }
129
130    fn topological_spin(&self, anyon: AnyonType) -> Complex64 {
131        match anyon.id {
132            0 => Complex64::new(1.0, 0.0),                   // Vacuum
133            1 => Complex64::from_polar(1.0, 4.0 * PI / 5.0), // τ anyon
134            _ => Complex64::new(0.0, 0.0),
135        }
136    }
137
138    fn can_fuse(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> bool {
139        self.fusion_multiplicity(a, b, c) > 0
140    }
141
142    fn fusion_multiplicity(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> u32 {
143        match (a.id, b.id, c.id) {
144            (0, x, y) | (x, 0, y) if x == y => 1, // 1 × a = a
145            (1, 1, 0) => 1,                       // τ × τ = 1
146            (1, 1, 1) => 1,                       // τ × τ = τ
147            _ => 0,
148        }
149    }
150
151    fn f_symbol(
152        &self,
153        a: AnyonType,
154        b: AnyonType,
155        c: AnyonType,
156        d: AnyonType,
157        e: AnyonType,
158        f: AnyonType,
159    ) -> FusionCoeff {
160        // Simplified F-symbols for Fibonacci anyons
161        // Only non-trivial case is F^{τττ}_τ
162        if a.id == 1 && b.id == 1 && c.id == 1 && d.id == 1 {
163            if e.id == 1 && f.id == 1 {
164                // F^{τττ}_τ[τ,τ] = φ^{-1}
165                Complex64::new(1.0 / self.phi, 0.0)
166            } else if e.id == 1 && f.id == 0 {
167                // F^{τττ}_τ[τ,1] = φ^{-1/2}
168                Complex64::new(1.0 / self.phi.sqrt(), 0.0)
169            } else if e.id == 0 && f.id == 1 {
170                // F^{τττ}_τ[1,τ] = φ^{-1/2}
171                Complex64::new(1.0 / self.phi.sqrt(), 0.0)
172            } else {
173                Complex64::new(0.0, 0.0)
174            }
175        } else {
176            // Most F-symbols are trivial (0 or 1)
177            if self.is_valid_fusion_tree(a, b, c, d, e, f) {
178                Complex64::new(1.0, 0.0)
179            } else {
180                Complex64::new(0.0, 0.0)
181            }
182        }
183    }
184
185    fn r_symbol(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> FusionCoeff {
186        // R^{ab}_c = θ_c / (θ_a θ_b)
187        if self.can_fuse(a, b, c) {
188            let theta_a = self.topological_spin(a);
189            let theta_b = self.topological_spin(b);
190            let theta_c = self.topological_spin(c);
191            let r = theta_c / (theta_a * theta_b);
192            // Ensure R-symbol has unit magnitude for unitary braiding
193            Complex64::from_polar(1.0, r.arg())
194        } else {
195            Complex64::new(0.0, 0.0)
196        }
197    }
198
199    fn name(&self) -> &str {
200        "Fibonacci"
201    }
202}
203
204impl FibonacciModel {
205    /// Check if a fusion tree is valid
206    fn is_valid_fusion_tree(
207        &self,
208        a: AnyonType,
209        b: AnyonType,
210        c: AnyonType,
211        d: AnyonType,
212        e: AnyonType,
213        f: AnyonType,
214    ) -> bool {
215        self.can_fuse(a, b, e)
216            && self.can_fuse(e, c, d)
217            && self.can_fuse(b, c, f)
218            && self.can_fuse(a, f, d)
219    }
220}
221
222/// Ising anyon model (used in some proposals for topological quantum computing)
223pub struct IsingModel {
224    anyons: Vec<AnyonType>,
225}
226
227impl IsingModel {
228    /// Create a new Ising anyon model
229    pub fn new() -> Self {
230        let anyons = vec![
231            AnyonType::new(0, "1"), // Vacuum
232            AnyonType::new(1, "σ"), // Ising anyon
233            AnyonType::new(2, "ψ"), // Fermion
234        ];
235
236        Self { anyons }
237    }
238}
239
240impl Default for IsingModel {
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246impl AnyonModel for IsingModel {
247    fn anyon_types(&self) -> &[AnyonType] {
248        &self.anyons
249    }
250
251    fn quantum_dimension(&self, anyon: AnyonType) -> f64 {
252        match anyon.id {
253            0 => 1.0,            // Vacuum
254            1 => 2.0_f64.sqrt(), // σ anyon
255            2 => 1.0,            // ψ fermion
256            _ => 0.0,
257        }
258    }
259
260    fn topological_spin(&self, anyon: AnyonType) -> Complex64 {
261        match anyon.id {
262            0 => Complex64::new(1.0, 0.0),             // Vacuum
263            1 => Complex64::from_polar(1.0, PI / 8.0), // σ anyon
264            2 => Complex64::new(-1.0, 0.0),            // ψ fermion
265            _ => Complex64::new(0.0, 0.0),
266        }
267    }
268
269    fn can_fuse(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> bool {
270        self.fusion_multiplicity(a, b, c) > 0
271    }
272
273    fn fusion_multiplicity(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> u32 {
274        match (a.id, b.id, c.id) {
275            // Vacuum fusion rules
276            (0, x, y) | (x, 0, y) if x == y => 1,
277            // σ × σ = 1 + ψ
278            (1, 1, 0) | (1, 1, 2) => 1,
279            // σ × ψ = σ
280            (1, 2, 1) | (2, 1, 1) => 1,
281            // ψ × ψ = 1
282            (2, 2, 0) => 1,
283            _ => 0,
284        }
285    }
286
287    fn f_symbol(
288        &self,
289        a: AnyonType,
290        b: AnyonType,
291        c: AnyonType,
292        d: AnyonType,
293        e: AnyonType,
294        f: AnyonType,
295    ) -> FusionCoeff {
296        // Ising model F-symbols
297        // Most non-trivial case is F^{σσσ}_σ
298        if a.id == 1 && b.id == 1 && c.id == 1 && d.id == 1 {
299            match (e.id, f.id) {
300                (0, 0) | (2, 2) => Complex64::new(0.5, 0.0),
301                (0, 2) | (2, 0) => Complex64::new(0.5, 0.0),
302                _ => Complex64::new(0.0, 0.0),
303            }
304        } else if self.is_valid_fusion_tree(a, b, c, d, e, f) {
305            Complex64::new(1.0, 0.0)
306        } else {
307            Complex64::new(0.0, 0.0)
308        }
309    }
310
311    fn r_symbol(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> FusionCoeff {
312        // Special cases for Ising model
313        match (a.id, b.id, c.id) {
314            // R^{σσ}_ψ = -1
315            (1, 1, 2) => Complex64::new(-1.0, 0.0),
316            // R^{ψψ}_1 = -1
317            (2, 2, 0) => Complex64::new(-1.0, 0.0),
318            // General case
319            _ => {
320                if self.can_fuse(a, b, c) {
321                    let theta_a = self.topological_spin(a);
322                    let theta_b = self.topological_spin(b);
323                    let theta_c = self.topological_spin(c);
324                    theta_c / (theta_a * theta_b)
325                } else {
326                    Complex64::new(0.0, 0.0)
327                }
328            }
329        }
330    }
331
332    fn name(&self) -> &str {
333        "Ising"
334    }
335}
336
337impl IsingModel {
338    /// Check if a fusion tree is valid
339    fn is_valid_fusion_tree(
340        &self,
341        a: AnyonType,
342        b: AnyonType,
343        c: AnyonType,
344        d: AnyonType,
345        e: AnyonType,
346        f: AnyonType,
347    ) -> bool {
348        self.can_fuse(a, b, e)
349            && self.can_fuse(e, c, d)
350            && self.can_fuse(b, c, f)
351            && self.can_fuse(a, f, d)
352    }
353}
354
355/// Anyon worldline in spacetime
356#[derive(Debug, Clone)]
357pub struct AnyonWorldline {
358    /// Anyon type
359    pub anyon_type: AnyonType,
360    /// Start position (x, y, t)
361    pub start: (f64, f64, f64),
362    /// End position (x, y, t)
363    pub end: (f64, f64, f64),
364    /// Intermediate points for braiding
365    pub path: Vec<(f64, f64, f64)>,
366}
367
368/// Braiding operation between two anyons
369#[derive(Debug, Clone)]
370pub struct BraidingOperation {
371    /// First anyon being braided
372    pub anyon1: usize,
373    /// Second anyon being braided
374    pub anyon2: usize,
375    /// Direction of braiding (true = over, false = under)
376    pub over: bool,
377}
378
379/// Fusion tree representation
380#[derive(Debug, Clone)]
381pub struct FusionTree {
382    /// External anyons (leaves)
383    pub external: Vec<AnyonType>,
384    /// Internal fusion channels
385    pub internal: Vec<AnyonType>,
386    /// Tree structure (pairs of indices to fuse)
387    pub structure: Vec<(usize, usize)>,
388}
389
390impl FusionTree {
391    /// Create a new fusion tree
392    pub fn new(external: Vec<AnyonType>) -> Self {
393        let n = external.len();
394        let internal = if n > 2 {
395            vec![AnyonType::VACUUM; n - 2]
396        } else {
397            vec![]
398        };
399        let structure = if n > 1 {
400            (0..n - 1).map(|i| (i, i + 1)).collect()
401        } else {
402            vec![]
403        };
404
405        Self {
406            external,
407            internal,
408            structure,
409        }
410    }
411
412    /// Get the total charge (root of the tree)
413    pub fn total_charge(&self) -> AnyonType {
414        if self.internal.is_empty() {
415            if self.external.is_empty() {
416                AnyonType::VACUUM
417            } else if self.external.len() == 1 {
418                self.external[0]
419            } else {
420                // For 2 external anyons with no internal, this should be set explicitly
421                AnyonType::VACUUM
422            }
423        } else {
424            *self.internal.last().unwrap()
425        }
426    }
427
428    /// Set the total charge for a 2-anyon tree
429    pub fn set_total_charge(&mut self, charge: AnyonType) {
430        if self.external.len() == 2 && self.internal.is_empty() {
431            // Store the charge as metadata (we'll use a hack for now)
432            // In a real implementation, we'd have a separate field
433            self.structure = vec![(charge.id as usize, charge.id as usize)];
434        }
435    }
436
437    /// Get the total charge for a 2-anyon tree
438    pub fn get_fusion_outcome(&self) -> Option<AnyonType> {
439        if self.external.len() == 2 && self.internal.is_empty() && !self.structure.is_empty() {
440            let charge_id = self.structure[0].0 as u32;
441            Some(AnyonType::new(
442                charge_id,
443                match charge_id {
444                    0 => "1",
445                    1 => "σ",
446                    2 => "ψ",
447                    _ => "τ",
448                },
449            ))
450        } else {
451            None
452        }
453    }
454}
455
456/// Topological quantum computer state
457pub struct TopologicalQC {
458    /// Anyon model being used
459    model: Box<dyn AnyonModel>,
460    /// Current fusion tree basis
461    fusion_trees: Vec<FusionTree>,
462    /// Amplitudes for each fusion tree
463    amplitudes: Array1<Complex64>,
464}
465
466impl TopologicalQC {
467    /// Create a new topological quantum computer
468    pub fn new(model: Box<dyn AnyonModel>, anyons: Vec<AnyonType>) -> QuantRS2Result<Self> {
469        // Generate all possible fusion trees
470        let fusion_trees = Self::generate_fusion_trees(&*model, anyons)?;
471        let n = fusion_trees.len();
472
473        if n == 0 {
474            return Err(QuantRS2Error::InvalidInput(
475                "No valid fusion trees for given anyons".to_string(),
476            ));
477        }
478
479        // Initialize in equal superposition
480        let amplitudes = Array1::from_elem(n, Complex64::new(1.0 / (n as f64).sqrt(), 0.0));
481
482        Ok(Self {
483            model,
484            fusion_trees,
485            amplitudes,
486        })
487    }
488
489    /// Generate all valid fusion trees for given anyons
490    fn generate_fusion_trees(
491        model: &dyn AnyonModel,
492        anyons: Vec<AnyonType>,
493    ) -> QuantRS2Result<Vec<FusionTree>> {
494        if anyons.len() < 2 {
495            return Ok(vec![FusionTree::new(anyons)]);
496        }
497
498        let mut trees = Vec::new();
499
500        // For two anyons, enumerate all possible fusion channels
501        if anyons.len() == 2 {
502            let a = anyons[0];
503            let b = anyons[1];
504
505            // Find all possible fusion outcomes
506            for c in model.anyon_types() {
507                if model.can_fuse(a, b, *c) {
508                    let mut tree = FusionTree::new(anyons.clone());
509                    tree.set_total_charge(*c);
510                    trees.push(tree);
511                }
512            }
513        } else {
514            // For simplicity, just create one tree for more than 2 anyons
515            trees.push(FusionTree::new(anyons.clone()));
516        }
517
518        if trees.is_empty() {
519            // If no valid fusion trees, create default
520            trees.push(FusionTree::new(anyons));
521        }
522
523        Ok(trees)
524    }
525
526    /// Apply a braiding operation
527    pub fn braid(&mut self, op: &BraidingOperation) -> QuantRS2Result<()> {
528        // Get braiding matrix in fusion tree basis
529        let braid_matrix = self.compute_braiding_matrix(op)?;
530
531        // Apply to state
532        self.amplitudes = braid_matrix.dot(&self.amplitudes);
533
534        Ok(())
535    }
536
537    /// Compute braiding matrix in fusion tree basis
538    fn compute_braiding_matrix(&self, op: &BraidingOperation) -> QuantRS2Result<Array2<Complex64>> {
539        let n = self.fusion_trees.len();
540        let mut matrix = Array2::zeros((n, n));
541
542        // Simplified: diagonal R-matrix action
543        for (i, tree) in self.fusion_trees.iter().enumerate() {
544            if op.anyon1 < tree.external.len() && op.anyon2 < tree.external.len() {
545                let a = tree.external[op.anyon1];
546                let b = tree.external[op.anyon2];
547
548                // Find fusion channel
549                let c = if let Some(charge) = tree.get_fusion_outcome() {
550                    charge
551                } else if tree.internal.is_empty() {
552                    tree.total_charge()
553                } else {
554                    tree.internal[0]
555                };
556
557                let r_symbol = if op.over {
558                    self.model.r_symbol(a, b, c)
559                } else {
560                    self.model.r_symbol(a, b, c).conj()
561                };
562
563                matrix[(i, i)] = r_symbol;
564            } else {
565                // If indices are out of bounds, set diagonal to 1
566                matrix[(i, i)] = Complex64::new(1.0, 0.0);
567            }
568        }
569
570        Ok(matrix)
571    }
572
573    /// Measure topological charge
574    pub fn measure_charge(&self) -> (AnyonType, f64) {
575        // Find most probable total charge
576        let mut charge_probs: HashMap<u32, f64> = HashMap::new();
577
578        for (tree, &amp) in self.fusion_trees.iter().zip(&self.amplitudes) {
579            let charge = if let Some(c) = tree.get_fusion_outcome() {
580                c
581            } else {
582                tree.total_charge()
583            };
584            *charge_probs.entry(charge.id).or_insert(0.0) += amp.norm_sqr();
585        }
586
587        let (charge_id, prob) = charge_probs
588            .into_iter()
589            .max_by(|(_, p1), (_, p2)| p1.partial_cmp(p2).unwrap())
590            .unwrap_or((0, 0.0));
591
592        let charge = self
593            .model
594            .anyon_types()
595            .iter()
596            .find(|a| a.id == charge_id)
597            .copied()
598            .unwrap_or(AnyonType::VACUUM);
599
600        (charge, prob)
601    }
602}
603
604/// Topological gate using anyon braiding
605#[derive(Debug, Clone)]
606pub struct TopologicalGate {
607    /// Sequence of braiding operations
608    pub braids: Vec<BraidingOperation>,
609    /// Target computational basis dimension
610    pub comp_dim: usize,
611}
612
613impl TopologicalGate {
614    /// Create a new topological gate
615    pub fn new(braids: Vec<BraidingOperation>, comp_dim: usize) -> Self {
616        Self { braids, comp_dim }
617    }
618
619    /// Create a topological CNOT gate (using Ising anyons)
620    pub fn cnot() -> Self {
621        // Simplified braiding sequence for CNOT
622        let braids = vec![
623            BraidingOperation {
624                anyon1: 0,
625                anyon2: 1,
626                over: true,
627            },
628            BraidingOperation {
629                anyon1: 2,
630                anyon2: 3,
631                over: true,
632            },
633            BraidingOperation {
634                anyon1: 1,
635                anyon2: 2,
636                over: false,
637            },
638        ];
639
640        Self::new(braids, 4)
641    }
642
643    /// Get the unitary matrix representation
644    pub fn to_matrix(&self, _model: &dyn AnyonModel) -> QuantRS2Result<Array2<Complex64>> {
645        // This would compute the full braiding matrix
646        // For now, return identity
647        Ok(Array2::eye(self.comp_dim))
648    }
649}
650
651/// Kitaev toric code model
652pub struct ToricCode {
653    /// Lattice size (L × L)
654    pub size: usize,
655    /// Vertex operators A_v
656    pub vertex_ops: Vec<Vec<usize>>,
657    /// Plaquette operators B_p  
658    pub plaquette_ops: Vec<Vec<usize>>,
659}
660
661impl ToricCode {
662    /// Create a new toric code on L × L lattice
663    pub fn new(size: usize) -> Self {
664        let mut vertex_ops = Vec::new();
665        let mut plaquette_ops = Vec::new();
666
667        // Create vertex and plaquette operators
668        // (Simplified for demonstration)
669        for i in 0..size {
670            for j in 0..size {
671                // Vertex operator: X on all edges meeting vertex
672                let v_op = vec![
673                    2 * (i * size + j),     // Horizontal edge
674                    2 * (i * size + j) + 1, // Vertical edge
675                ];
676                vertex_ops.push(v_op);
677
678                // Plaquette operator: Z on all edges around plaquette
679                let p_op = vec![
680                    2 * (i * size + j),
681                    2 * (i * size + (j + 1) % size),
682                    2 * (((i + 1) % size) * size + j),
683                    2 * (i * size + j) + 1,
684                ];
685                plaquette_ops.push(p_op);
686            }
687        }
688
689        Self {
690            size,
691            vertex_ops,
692            plaquette_ops,
693        }
694    }
695
696    /// Get the number of physical qubits
697    pub fn num_qubits(&self) -> usize {
698        2 * self.size * self.size
699    }
700
701    /// Get the number of logical qubits
702    pub fn num_logical_qubits(&self) -> usize {
703        2 // Toric code encodes 2 logical qubits
704    }
705
706    /// Create anyonic excitations
707    pub fn create_anyons(&self, vertices: &[usize], plaquettes: &[usize]) -> Vec<AnyonType> {
708        let mut anyons = Vec::new();
709
710        // e anyons (vertex violations)
711        for _ in vertices {
712            anyons.push(AnyonType::new(1, "e"));
713        }
714
715        // m anyons (plaquette violations)
716        for _ in plaquettes {
717            anyons.push(AnyonType::new(2, "m"));
718        }
719
720        anyons
721    }
722}
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727
728    #[test]
729    fn test_fibonacci_model() {
730        let model = FibonacciModel::new();
731
732        // Test quantum dimensions
733        assert_eq!(model.quantum_dimension(AnyonType::VACUUM), 1.0);
734        assert!((model.quantum_dimension(AnyonType::new(1, "τ")) - 1.618).abs() < 0.001);
735
736        // Test fusion rules
737        assert_eq!(
738            model.fusion_multiplicity(
739                AnyonType::VACUUM,
740                AnyonType::new(1, "τ"),
741                AnyonType::new(1, "τ")
742            ),
743            1
744        );
745
746        // Test total quantum dimension
747        // For Fibonacci anyons: D = sqrt(1^2 + φ^2) ≈ 2.058
748        let expected_dim = (1.0 + model.phi.powi(2)).sqrt();
749        assert!((model.total_quantum_dimension() - expected_dim).abs() < 0.001);
750    }
751
752    #[test]
753    fn test_ising_model() {
754        let model = IsingModel::new();
755
756        // Test quantum dimensions
757        assert_eq!(model.quantum_dimension(AnyonType::VACUUM), 1.0);
758        assert!((model.quantum_dimension(AnyonType::new(1, "σ")) - 1.414).abs() < 0.001);
759        assert_eq!(model.quantum_dimension(AnyonType::new(2, "ψ")), 1.0);
760
761        // Test fusion rules: σ × σ = 1 + ψ
762        assert_eq!(
763            model.fusion_multiplicity(
764                AnyonType::new(1, "σ"),
765                AnyonType::new(1, "σ"),
766                AnyonType::VACUUM
767            ),
768            1
769        );
770        assert_eq!(
771            model.fusion_multiplicity(
772                AnyonType::new(1, "σ"),
773                AnyonType::new(1, "σ"),
774                AnyonType::new(2, "ψ")
775            ),
776            1
777        );
778    }
779
780    #[test]
781    fn test_fusion_tree() {
782        let anyons = vec![
783            AnyonType::new(1, "τ"),
784            AnyonType::new(1, "τ"),
785            AnyonType::new(1, "τ"),
786        ];
787
788        let tree = FusionTree::new(anyons);
789        assert_eq!(tree.external.len(), 3);
790        assert_eq!(tree.internal.len(), 1);
791    }
792
793    #[test]
794    fn test_topological_qc() {
795        let model = Box::new(FibonacciModel::new());
796        let anyons = vec![AnyonType::new(1, "τ"), AnyonType::new(1, "τ")];
797
798        let qc = TopologicalQC::new(model, anyons).unwrap();
799        // τ × τ = 1 + τ, so we should have 2 fusion trees
800        assert_eq!(qc.fusion_trees.len(), 2);
801
802        // Test charge measurement
803        let (charge, _prob) = qc.measure_charge();
804        assert!(charge.id == 0 || charge.id == 1); // Can be 1 or τ
805    }
806
807    #[test]
808    fn test_toric_code() {
809        let toric = ToricCode::new(4);
810
811        assert_eq!(toric.num_qubits(), 32); // 2 * 4 * 4
812        assert_eq!(toric.num_logical_qubits(), 2);
813
814        // Test anyon creation
815        let anyons = toric.create_anyons(&[0, 1], &[2]);
816        assert_eq!(anyons.len(), 3);
817    }
818
819    #[test]
820    fn test_braiding_operation() {
821        let model = Box::new(IsingModel::new());
822        let anyons = vec![AnyonType::new(1, "σ"), AnyonType::new(1, "σ")];
823
824        let mut qc = TopologicalQC::new(model, anyons).unwrap();
825
826        // Check initial normalization
827        let initial_norm: f64 = qc.amplitudes.iter().map(|a| a.norm_sqr()).sum();
828        assert!(
829            (initial_norm - 1.0).abs() < 1e-10,
830            "Initial state not normalized: {}",
831            initial_norm
832        );
833
834        // Apply braiding
835        let braid = BraidingOperation {
836            anyon1: 0,
837            anyon2: 1,
838            over: true,
839        };
840
841        qc.braid(&braid).unwrap();
842
843        // State should be normalized
844        let norm: f64 = qc.amplitudes.iter().map(|a| a.norm_sqr()).sum();
845        assert!(
846            (norm - 1.0).abs() < 1e-10,
847            "Final state not normalized: {}",
848            norm
849        );
850    }
851}