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