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