Skip to main content

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 the given anyons.
482    ///
483    /// For two anyons every allowed fusion channel `c` (with `N^c_{ab} > 0`) yields one
484    /// basis state. For `n > 2` anyons we enumerate the standard left-linear fusion-tree
485    /// basis: intermediate charges `e₁ ∈ a₀×a₁`, `e₂ ∈ e₁×a₂`, …, `e_{n-1} ∈ e_{n-2}×a_{n-1}`,
486    /// keeping every consistent assignment. Each assignment becomes a [`FusionTree`]
487    /// whose `internal` vector holds `(e₁, …, e_{n-1})` (the last entry is the total
488    /// charge / root).
489    fn generate_fusion_trees(
490        model: &dyn AnyonModel,
491        anyons: Vec<AnyonType>,
492    ) -> QuantRS2Result<Vec<FusionTree>> {
493        if anyons.len() < 2 {
494            return Ok(vec![FusionTree::new(anyons)]);
495        }
496
497        let mut trees = Vec::new();
498
499        if anyons.len() == 2 {
500            // Two anyons: enumerate all possible fusion channels (preserved exactly).
501            let a = anyons[0];
502            let b = anyons[1];
503            for c in model.anyon_types() {
504                if model.can_fuse(a, b, *c) {
505                    let mut tree = FusionTree::new(anyons.clone());
506                    tree.set_total_charge(*c);
507                    trees.push(tree);
508                }
509            }
510        } else {
511            // n > 2: enumerate the left-linear fusion-tree basis by sequentially
512            // fusing in each anyon and branching over every allowed intermediate
513            // charge. `partial` accumulates the chain of intermediate charges.
514            fn enumerate(
515                model: &dyn AnyonModel,
516                anyons: &[AnyonType],
517                running: AnyonType,
518                next: usize,
519                partial: &mut Vec<AnyonType>,
520                out: &mut Vec<Vec<AnyonType>>,
521            ) {
522                if next == anyons.len() {
523                    out.push(partial.clone());
524                    return;
525                }
526                let b = anyons[next];
527                for c in model.anyon_types() {
528                    if model.can_fuse(running, b, *c) {
529                        partial.push(*c);
530                        enumerate(model, anyons, *c, next + 1, partial, out);
531                        partial.pop();
532                    }
533                }
534            }
535
536            let mut internals: Vec<Vec<AnyonType>> = Vec::new();
537            let mut partial = Vec::new();
538            // The first fusion is a₀ × a₁; branch over its outcomes, then recurse.
539            for c in model.anyon_types() {
540                if model.can_fuse(anyons[0], anyons[1], *c) {
541                    partial.push(*c);
542                    enumerate(model, &anyons, *c, 2, &mut partial, &mut internals);
543                    partial.pop();
544                }
545            }
546
547            for internal in internals {
548                let n = anyons.len();
549                let structure = (0..n - 1).map(|i| (i, i + 1)).collect();
550                trees.push(FusionTree {
551                    external: anyons.clone(),
552                    internal,
553                    structure,
554                });
555            }
556        }
557
558        if trees.is_empty() {
559            // No allowed fusion outcome: fall back to a single default tree so callers
560            // always have a basis to work with.
561            trees.push(FusionTree::new(anyons));
562        }
563
564        Ok(trees)
565    }
566
567    /// Apply a braiding operation
568    pub fn braid(&mut self, op: &BraidingOperation) -> QuantRS2Result<()> {
569        // Get braiding matrix in fusion tree basis
570        let braid_matrix = self.compute_braiding_matrix(op)?;
571
572        // Apply to state
573        self.amplitudes = braid_matrix.dot(&self.amplitudes);
574
575        Ok(())
576    }
577
578    /// Compute braiding matrix in fusion tree basis
579    fn compute_braiding_matrix(&self, op: &BraidingOperation) -> QuantRS2Result<Array2<Complex64>> {
580        let n = self.fusion_trees.len();
581        let mut matrix = Array2::zeros((n, n));
582
583        // Simplified: diagonal R-matrix action
584        for (i, tree) in self.fusion_trees.iter().enumerate() {
585            if op.anyon1 < tree.external.len() && op.anyon2 < tree.external.len() {
586                let a = tree.external[op.anyon1];
587                let b = tree.external[op.anyon2];
588
589                // Find fusion channel
590                let c = if let Some(charge) = tree.get_fusion_outcome() {
591                    charge
592                } else if tree.internal.is_empty() {
593                    tree.total_charge()
594                } else {
595                    tree.internal[0]
596                };
597
598                let r_symbol = if op.over {
599                    self.model.r_symbol(a, b, c)
600                } else {
601                    self.model.r_symbol(a, b, c).conj()
602                };
603
604                matrix[(i, i)] = r_symbol;
605            } else {
606                // If indices are out of bounds, set diagonal to 1
607                matrix[(i, i)] = Complex64::new(1.0, 0.0);
608            }
609        }
610
611        Ok(matrix)
612    }
613
614    /// Measure topological charge
615    pub fn measure_charge(&self) -> (AnyonType, f64) {
616        // Find most probable total charge
617        let mut charge_probs: HashMap<u32, f64> = HashMap::new();
618
619        for (tree, &amp) in self.fusion_trees.iter().zip(&self.amplitudes) {
620            let charge = if let Some(c) = tree.get_fusion_outcome() {
621                c
622            } else {
623                tree.total_charge()
624            };
625            *charge_probs.entry(charge.id).or_insert(0.0) += amp.norm_sqr();
626        }
627
628        let (charge_id, prob) = charge_probs
629            .into_iter()
630            .max_by(|(_, p1), (_, p2)| p1.partial_cmp(p2).unwrap_or(std::cmp::Ordering::Equal))
631            .unwrap_or((0, 0.0));
632
633        let charge = self
634            .model
635            .anyon_types()
636            .iter()
637            .find(|a| a.id == charge_id)
638            .copied()
639            .unwrap_or(AnyonType::VACUUM);
640
641        (charge, prob)
642    }
643}
644
645/// Topological gate using anyon braiding
646#[derive(Debug, Clone)]
647pub struct TopologicalGate {
648    /// Sequence of braiding operations
649    pub braids: Vec<BraidingOperation>,
650    /// Target computational basis dimension
651    pub comp_dim: usize,
652}
653
654impl TopologicalGate {
655    /// Create a new topological gate
656    pub const fn new(braids: Vec<BraidingOperation>, comp_dim: usize) -> Self {
657        Self { braids, comp_dim }
658    }
659
660    /// Create a topological CNOT gate (using Ising anyons)
661    pub fn cnot() -> Self {
662        // Simplified braiding sequence for CNOT
663        let braids = vec![
664            BraidingOperation {
665                anyon1: 0,
666                anyon2: 1,
667                over: true,
668            },
669            BraidingOperation {
670                anyon1: 2,
671                anyon2: 3,
672                over: true,
673            },
674            BraidingOperation {
675                anyon1: 1,
676                anyon2: 2,
677                over: false,
678            },
679        ];
680
681        Self::new(braids, 4)
682    }
683
684    /// Compute the unitary matrix representation of this braiding sequence in the
685    /// fusion-tree basis of `model`.
686    ///
687    /// Each [`BraidingOperation`] is a generator `σ_i` (or its inverse when
688    /// `over == false`) of the braid group. Its matrix in the fusion-tree basis is the
689    /// diagonal action of the model's R-symbols `R^{ab}_c` on the fusion channel `c`
690    /// of the braided pair `(a, b)`. The full sequence is the ordered product
691    /// `B = B(braid_{k-1}) ··· B(braid_0)` (later braids act last, i.e. on the left).
692    ///
693    /// The anyons are materialised as `n = (max index used) + 1` copies of the model's
694    /// primary non-vacuum anyon (the smallest-id anyon with quantum dimension `> 1`,
695    /// falling back to the first non-vacuum type), which is the standard setting for
696    /// braiding-based gates (e.g. σ anyons for the Ising model).
697    ///
698    /// Returns an [`QuantRS2Error::UnsupportedOperation`] if the model exposes no
699    /// non-vacuum anyon to braid (so no generator matrices can be built) — never a
700    /// silent identity.
701    pub fn to_matrix(&self, model: &dyn AnyonModel) -> QuantRS2Result<Array2<Complex64>> {
702        // Determine how many anyons the braid indices reference.
703        let n_anyons = self
704            .braids
705            .iter()
706            .map(|b| b.anyon1.max(b.anyon2) + 1)
707            .max()
708            .unwrap_or(0)
709            .max(2);
710
711        // Pick the anyon species to braid: prefer a non-Abelian anyon (d > 1),
712        // otherwise the first non-vacuum type.
713        let species = model
714            .anyon_types()
715            .iter()
716            .filter(|a| a.id != AnyonType::VACUUM.id)
717            .min_by(|a, b| {
718                let da = model.quantum_dimension(**a);
719                let db = model.quantum_dimension(**b);
720                // Prefer larger quantum dimension (non-Abelian), then smaller id.
721                db.partial_cmp(&da)
722                    .unwrap_or(std::cmp::Ordering::Equal)
723                    .then(a.id.cmp(&b.id))
724            })
725            .copied()
726            .ok_or_else(|| {
727                QuantRS2Error::UnsupportedOperation(
728                    "anyon model exposes no non-vacuum anyon; cannot build braiding matrix"
729                        .to_string(),
730                )
731            })?;
732
733        let anyons = vec![species; n_anyons];
734
735        // Build the fusion-tree basis for these anyons.
736        let trees = TopologicalQC::generate_fusion_trees(model, anyons)?;
737        let dim = trees.len();
738        if dim == 0 {
739            return Err(QuantRS2Error::UnsupportedOperation(
740                "no valid fusion trees for the requested anyons; cannot build braiding matrix"
741                    .to_string(),
742            ));
743        }
744
745        // Start from the identity in the fusion-tree basis and apply each braid.
746        let mut result = Array2::<Complex64>::eye(dim);
747        for braid in &self.braids {
748            let b_mat = Self::braiding_generator_matrix(model, &trees, braid);
749            // Later braids act last (on the left of the accumulated product).
750            result = b_mat.dot(&result);
751        }
752
753        Ok(result)
754    }
755
756    /// Build the matrix of a single braid generator in the given fusion-tree basis.
757    ///
758    /// The action is diagonal in this basis: braiding anyons `(a, b)` that fuse to
759    /// channel `c` multiplies the amplitude by `R^{ab}_c` (or its conjugate for an
760    /// under-crossing). Trees whose indices fall outside the braided pair are left
761    /// invariant (diagonal entry `1`).
762    fn braiding_generator_matrix(
763        model: &dyn AnyonModel,
764        trees: &[FusionTree],
765        op: &BraidingOperation,
766    ) -> Array2<Complex64> {
767        let dim = trees.len();
768        let mut matrix = Array2::<Complex64>::zeros((dim, dim));
769
770        for (i, tree) in trees.iter().enumerate() {
771            if op.anyon1 < tree.external.len() && op.anyon2 < tree.external.len() {
772                let a = tree.external[op.anyon1];
773                let b = tree.external[op.anyon2];
774
775                let c = if let Some(charge) = tree.get_fusion_outcome() {
776                    charge
777                } else if tree.internal.is_empty() {
778                    tree.total_charge()
779                } else {
780                    tree.internal[0]
781                };
782
783                let r_symbol = if op.over {
784                    model.r_symbol(a, b, c)
785                } else {
786                    model.r_symbol(a, b, c).conj()
787                };
788
789                // If this pair cannot fuse to c the R-symbol is zero; fall back to a
790                // trivial (identity) action so the generator stays unitary on the
791                // physical subspace rather than annihilating the amplitude.
792                matrix[(i, i)] = if r_symbol.norm() > 1e-12 {
793                    r_symbol
794                } else {
795                    Complex64::new(1.0, 0.0)
796                };
797            } else {
798                matrix[(i, i)] = Complex64::new(1.0, 0.0);
799            }
800        }
801
802        matrix
803    }
804}
805
806/// Kitaev toric code model
807pub struct ToricCode {
808    /// Lattice size (L × L)
809    pub size: usize,
810    /// Vertex operators A_v
811    pub vertex_ops: Vec<Vec<usize>>,
812    /// Plaquette operators B_p
813    pub plaquette_ops: Vec<Vec<usize>>,
814}
815
816impl ToricCode {
817    /// Create a new toric code on L × L lattice
818    pub fn new(size: usize) -> Self {
819        let mut vertex_ops = Vec::new();
820        let mut plaquette_ops = Vec::new();
821
822        // Create vertex and plaquette operators
823        // (Simplified for demonstration)
824        for i in 0..size {
825            for j in 0..size {
826                // Vertex operator: X on all edges meeting vertex
827                let v_op = vec![
828                    2 * (i * size + j),     // Horizontal edge
829                    2 * (i * size + j) + 1, // Vertical edge
830                ];
831                vertex_ops.push(v_op);
832
833                // Plaquette operator: Z on all edges around plaquette
834                let p_op = vec![
835                    2 * (i * size + j),
836                    2 * (i * size + (j + 1) % size),
837                    2 * (((i + 1) % size) * size + j),
838                    2 * (i * size + j) + 1,
839                ];
840                plaquette_ops.push(p_op);
841            }
842        }
843
844        Self {
845            size,
846            vertex_ops,
847            plaquette_ops,
848        }
849    }
850
851    /// Get the number of physical qubits
852    pub const fn num_qubits(&self) -> usize {
853        2 * self.size * self.size
854    }
855
856    /// Get the number of logical qubits
857    pub const fn num_logical_qubits(&self) -> usize {
858        2 // Toric code encodes 2 logical qubits
859    }
860
861    /// Create anyonic excitations
862    pub fn create_anyons(&self, vertices: &[usize], plaquettes: &[usize]) -> Vec<AnyonType> {
863        let mut anyons = Vec::new();
864
865        // e anyons (vertex violations)
866        for _ in vertices {
867            anyons.push(AnyonType::new(1, "e"));
868        }
869
870        // m anyons (plaquette violations)
871        for _ in plaquettes {
872            anyons.push(AnyonType::new(2, "m"));
873        }
874
875        anyons
876    }
877}
878
879#[cfg(test)]
880mod tests {
881    use super::*;
882
883    #[test]
884    fn test_fibonacci_model() {
885        let model = FibonacciModel::new();
886
887        // Test quantum dimensions
888        assert_eq!(model.quantum_dimension(AnyonType::VACUUM), 1.0);
889        assert!((model.quantum_dimension(AnyonType::new(1, "τ")) - 1.618).abs() < 0.001);
890
891        // Test fusion rules
892        assert_eq!(
893            model.fusion_multiplicity(
894                AnyonType::VACUUM,
895                AnyonType::new(1, "τ"),
896                AnyonType::new(1, "τ")
897            ),
898            1
899        );
900
901        // Test total quantum dimension
902        // For Fibonacci anyons: D = sqrt(1^2 + φ^2) ≈ 2.058
903        let expected_dim = (1.0 + model.phi.powi(2)).sqrt();
904        assert!((model.total_quantum_dimension() - expected_dim).abs() < 0.001);
905    }
906
907    #[test]
908    fn test_ising_model() {
909        let model = IsingModel::new();
910
911        // Test quantum dimensions
912        assert_eq!(model.quantum_dimension(AnyonType::VACUUM), 1.0);
913        assert!((model.quantum_dimension(AnyonType::new(1, "σ")) - 1.414).abs() < 0.001);
914        assert_eq!(model.quantum_dimension(AnyonType::new(2, "ψ")), 1.0);
915
916        // Test fusion rules: σ × σ = 1 + ψ
917        assert_eq!(
918            model.fusion_multiplicity(
919                AnyonType::new(1, "σ"),
920                AnyonType::new(1, "σ"),
921                AnyonType::VACUUM
922            ),
923            1
924        );
925        assert_eq!(
926            model.fusion_multiplicity(
927                AnyonType::new(1, "σ"),
928                AnyonType::new(1, "σ"),
929                AnyonType::new(2, "ψ")
930            ),
931            1
932        );
933    }
934
935    #[test]
936    fn test_fusion_tree() {
937        let anyons = vec![
938            AnyonType::new(1, "τ"),
939            AnyonType::new(1, "τ"),
940            AnyonType::new(1, "τ"),
941        ];
942
943        let tree = FusionTree::new(anyons);
944        assert_eq!(tree.external.len(), 3);
945        assert_eq!(tree.internal.len(), 1);
946    }
947
948    #[test]
949    fn test_topological_qc() {
950        let model = Box::new(FibonacciModel::new());
951        let anyons = vec![AnyonType::new(1, "τ"), AnyonType::new(1, "τ")];
952
953        let qc = TopologicalQC::new(model, anyons).expect("Failed to create TopologicalQC");
954        // τ × τ = 1 + τ, so we should have 2 fusion trees
955        assert_eq!(qc.fusion_trees.len(), 2);
956
957        // Test charge measurement
958        let (charge, _prob) = qc.measure_charge();
959        assert!(charge.id == 0 || charge.id == 1); // Can be 1 or τ
960    }
961
962    #[test]
963    fn test_toric_code() {
964        let toric = ToricCode::new(4);
965
966        assert_eq!(toric.num_qubits(), 32); // 2 * 4 * 4
967        assert_eq!(toric.num_logical_qubits(), 2);
968
969        // Test anyon creation
970        let anyons = toric.create_anyons(&[0, 1], &[2]);
971        assert_eq!(anyons.len(), 3);
972    }
973
974    #[test]
975    fn test_topological_gate_to_matrix_is_real() {
976        // Site-5 proof: the braiding matrix of the cnot() sequence must be a genuine
977        // unitary that is NOT the identity (the old implementation returned eye()).
978        let model = IsingModel::new();
979        let gate = TopologicalGate::cnot();
980
981        let m = gate
982            .to_matrix(&model)
983            .expect("braiding matrix should be computable for the Ising model");
984
985        let dim = m.nrows();
986        assert!(dim >= 2, "fusion-tree space must be non-trivial, got {dim}");
987
988        // Unitarity: M† M ≈ I.
989        let mdag = m.mapv(|z| z.conj()).t().to_owned();
990        let prod = mdag.dot(&m);
991        let mut max_dev = 0.0_f64;
992        for i in 0..dim {
993            for j in 0..dim {
994                let expected = if i == j { 1.0 } else { 0.0 };
995                max_dev = max_dev.max((prod[(i, j)] - Complex64::new(expected, 0.0)).norm());
996            }
997        }
998        assert!(
999            max_dev < 1e-10,
1000            "braiding matrix is not unitary, max deviation = {max_dev}"
1001        );
1002
1003        // Not the identity: at least one off-diagonal or non-unit-phase diagonal entry.
1004        let identity = Array2::<Complex64>::eye(dim);
1005        let diff: f64 = m
1006            .iter()
1007            .zip(identity.iter())
1008            .map(|(a, b)| (a - b).norm_sqr())
1009            .sum::<f64>()
1010            .sqrt();
1011        assert!(
1012            diff > 1e-6,
1013            "braiding matrix collapsed to the identity (fabrication regression)"
1014        );
1015    }
1016
1017    #[test]
1018    fn test_topological_gate_to_matrix_inverse_braid() {
1019        // Over- and under-crossings must be inverses: σ_i · σ_i^{-1} = I.
1020        let model = IsingModel::new();
1021        let over = TopologicalGate::new(
1022            vec![BraidingOperation {
1023                anyon1: 0,
1024                anyon2: 1,
1025                over: true,
1026            }],
1027            2,
1028        );
1029        let under = TopologicalGate::new(
1030            vec![BraidingOperation {
1031                anyon1: 0,
1032                anyon2: 1,
1033                over: false,
1034            }],
1035            2,
1036        );
1037        let m_over = over.to_matrix(&model).expect("over braid");
1038        let m_under = under.to_matrix(&model).expect("under braid");
1039        let prod = m_under.dot(&m_over);
1040        let dim = prod.nrows();
1041        let mut dev = 0.0_f64;
1042        for i in 0..dim {
1043            for j in 0..dim {
1044                let exp = if i == j { 1.0 } else { 0.0 };
1045                dev = dev.max((prod[(i, j)] - Complex64::new(exp, 0.0)).norm());
1046            }
1047        }
1048        assert!(dev < 1e-10, "σ·σ⁻¹ should be identity, deviation {dev}");
1049    }
1050
1051    #[test]
1052    fn test_braiding_operation() {
1053        let model = Box::new(IsingModel::new());
1054        let anyons = vec![AnyonType::new(1, "σ"), AnyonType::new(1, "σ")];
1055
1056        let mut qc = TopologicalQC::new(model, anyons).expect("Failed to create TopologicalQC");
1057
1058        // Check initial normalization
1059        let initial_norm: f64 = qc.amplitudes.iter().map(|a| a.norm_sqr()).sum();
1060        assert!(
1061            (initial_norm - 1.0).abs() < 1e-10,
1062            "Initial state not normalized: {}",
1063            initial_norm
1064        );
1065
1066        // Apply braiding
1067        let braid = BraidingOperation {
1068            anyon1: 0,
1069            anyon2: 1,
1070            over: true,
1071        };
1072
1073        qc.braid(&braid)
1074            .expect("Failed to apply braiding operation");
1075
1076        // State should be normalized
1077        let norm: f64 = qc.amplitudes.iter().map(|a| a.norm_sqr()).sum();
1078        assert!(
1079            (norm - 1.0).abs() < 1e-10,
1080            "Final state not normalized: {}",
1081            norm
1082        );
1083    }
1084}