1use 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
13type FusionCoeff = Complex64;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct AnyonType {
19 pub id: u32,
21 pub label: &'static str,
23}
24
25impl AnyonType {
26 pub const fn new(id: u32, label: &'static str) -> Self {
28 Self { id, label }
29 }
30
31 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
41pub trait AnyonModel: Send + Sync {
43 fn anyon_types(&self) -> &[AnyonType];
45
46 fn quantum_dimension(&self, anyon: AnyonType) -> f64;
48
49 fn topological_spin(&self, anyon: AnyonType) -> Complex64;
51
52 fn can_fuse(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> bool;
54
55 fn fusion_multiplicity(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> u32;
57
58 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 fn r_symbol(&self, a: AnyonType, b: AnyonType, c: AnyonType) -> FusionCoeff;
71
72 fn name(&self) -> &str;
74
75 fn is_modular(&self) -> bool {
77 self.anyon_types()
78 .iter()
79 .all(|&a| self.quantum_dimension(a) > 0.0)
80 }
81
82 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
92pub struct FibonacciModel {
94 anyons: Vec<AnyonType>,
95 phi: f64, }
97
98impl FibonacciModel {
99 pub fn new() -> Self {
101 let phi = f64::midpoint(1.0, 5.0_f64.sqrt());
102 let anyons = vec![
103 AnyonType::new(0, "1"), AnyonType::new(1, "τ"), ];
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, 1 => self.phi, _ => 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), 1 => Complex64::from_polar(1.0, 4.0 * PI / 5.0), _ => 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, 1, 0 | 1) => 1, _ => 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 if a.id == 1 && b.id == 1 && c.id == 1 && d.id == 1 {
162 if e.id == 1 && f.id == 1 {
163 Complex64::new(1.0 / self.phi, 0.0)
165 } else if e.id == 1 && f.id == 0 {
166 Complex64::new(1.0 / self.phi.sqrt(), 0.0)
168 } else if e.id == 0 && f.id == 1 {
169 Complex64::new(1.0 / self.phi.sqrt(), 0.0)
171 } else {
172 Complex64::new(0.0, 0.0)
173 }
174 } else {
175 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 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 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 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
221pub struct IsingModel {
223 anyons: Vec<AnyonType>,
224}
225
226impl IsingModel {
227 pub fn new() -> Self {
229 let anyons = vec![
230 AnyonType::new(0, "1"), AnyonType::new(1, "σ"), AnyonType::new(2, "ψ"), ];
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, 1 => 2.0_f64.sqrt(), _ => 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), 1 => Complex64::from_polar(1.0, PI / 8.0), 2 => Complex64::new(-1.0, 0.0), _ => 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 (0, x, y) | (x, 0, y) if x == y => 1,
275 (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 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 match (a.id, b.id, c.id) {
307 (1, 1, 2) | (2, 2, 0) => Complex64::new(-1.0, 0.0),
309 _ => {
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 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#[derive(Debug, Clone)]
348pub struct AnyonWorldline {
349 pub anyon_type: AnyonType,
351 pub start: (f64, f64, f64),
353 pub end: (f64, f64, f64),
355 pub path: Vec<(f64, f64, f64)>,
357}
358
359#[derive(Debug, Clone)]
361pub struct BraidingOperation {
362 pub anyon1: usize,
364 pub anyon2: usize,
366 pub over: bool,
368}
369
370#[derive(Debug, Clone)]
372pub struct FusionTree {
373 pub external: Vec<AnyonType>,
375 pub internal: Vec<AnyonType>,
377 pub structure: Vec<(usize, usize)>,
379}
380
381impl FusionTree {
382 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 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 AnyonType::VACUUM
413 }
414 } else {
415 self.internal.last().copied().unwrap_or(AnyonType::VACUUM)
417 }
418 }
419
420 pub fn set_total_charge(&mut self, charge: AnyonType) {
422 if self.external.len() == 2 && self.internal.is_empty() {
423 self.structure = vec![(charge.id as usize, charge.id as usize)];
426 }
427 }
428
429 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
448pub struct TopologicalQC {
450 model: Box<dyn AnyonModel>,
452 fusion_trees: Vec<FusionTree>,
454 amplitudes: Array1<Complex64>,
456}
457
458impl TopologicalQC {
459 pub fn new(model: Box<dyn AnyonModel>, anyons: Vec<AnyonType>) -> QuantRS2Result<Self> {
461 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 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 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 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 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 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 trees.push(FusionTree::new(anyons));
562 }
563
564 Ok(trees)
565 }
566
567 pub fn braid(&mut self, op: &BraidingOperation) -> QuantRS2Result<()> {
569 let braid_matrix = self.compute_braiding_matrix(op)?;
571
572 self.amplitudes = braid_matrix.dot(&self.amplitudes);
574
575 Ok(())
576 }
577
578 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 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 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 matrix[(i, i)] = Complex64::new(1.0, 0.0);
608 }
609 }
610
611 Ok(matrix)
612 }
613
614 pub fn measure_charge(&self) -> (AnyonType, f64) {
616 let mut charge_probs: HashMap<u32, f64> = HashMap::new();
618
619 for (tree, &) 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#[derive(Debug, Clone)]
647pub struct TopologicalGate {
648 pub braids: Vec<BraidingOperation>,
650 pub comp_dim: usize,
652}
653
654impl TopologicalGate {
655 pub const fn new(braids: Vec<BraidingOperation>, comp_dim: usize) -> Self {
657 Self { braids, comp_dim }
658 }
659
660 pub fn cnot() -> Self {
662 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 pub fn to_matrix(&self, model: &dyn AnyonModel) -> QuantRS2Result<Array2<Complex64>> {
702 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 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 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 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 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 result = b_mat.dot(&result);
751 }
752
753 Ok(result)
754 }
755
756 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 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
806pub struct ToricCode {
808 pub size: usize,
810 pub vertex_ops: Vec<Vec<usize>>,
812 pub plaquette_ops: Vec<Vec<usize>>,
814}
815
816impl ToricCode {
817 pub fn new(size: usize) -> Self {
819 let mut vertex_ops = Vec::new();
820 let mut plaquette_ops = Vec::new();
821
822 for i in 0..size {
825 for j in 0..size {
826 let v_op = vec![
828 2 * (i * size + j), 2 * (i * size + j) + 1, ];
831 vertex_ops.push(v_op);
832
833 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 pub const fn num_qubits(&self) -> usize {
853 2 * self.size * self.size
854 }
855
856 pub const fn num_logical_qubits(&self) -> usize {
858 2 }
860
861 pub fn create_anyons(&self, vertices: &[usize], plaquettes: &[usize]) -> Vec<AnyonType> {
863 let mut anyons = Vec::new();
864
865 for _ in vertices {
867 anyons.push(AnyonType::new(1, "e"));
868 }
869
870 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 assert_eq!(model.quantum_dimension(AnyonType::VACUUM), 1.0);
889 assert!((model.quantum_dimension(AnyonType::new(1, "τ")) - 1.618).abs() < 0.001);
890
891 assert_eq!(
893 model.fusion_multiplicity(
894 AnyonType::VACUUM,
895 AnyonType::new(1, "τ"),
896 AnyonType::new(1, "τ")
897 ),
898 1
899 );
900
901 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 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 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 assert_eq!(qc.fusion_trees.len(), 2);
956
957 let (charge, _prob) = qc.measure_charge();
959 assert!(charge.id == 0 || charge.id == 1); }
961
962 #[test]
963 fn test_toric_code() {
964 let toric = ToricCode::new(4);
965
966 assert_eq!(toric.num_qubits(), 32); assert_eq!(toric.num_logical_qubits(), 2);
968
969 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 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 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 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 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 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 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 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}