quantrs2_core/
error_correction.rs

1//! Quantum error correction codes and decoders
2//!
3//! This module provides implementations of various quantum error correction codes
4//! including stabilizer codes, surface codes, and color codes, along with
5//! efficient decoder algorithms.
6
7use crate::error::{QuantRS2Error, QuantRS2Result};
8use ndarray::Array2;
9use num_complex::Complex64;
10use std::collections::HashMap;
11use std::fmt;
12
13/// Pauli operator representation
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum Pauli {
16    I,
17    X,
18    Y,
19    Z,
20}
21
22impl Pauli {
23    /// Get matrix representation
24    pub fn matrix(&self) -> Array2<Complex64> {
25        match self {
26            Pauli::I => Array2::from_shape_vec(
27                (2, 2),
28                vec![
29                    Complex64::new(1.0, 0.0),
30                    Complex64::new(0.0, 0.0),
31                    Complex64::new(0.0, 0.0),
32                    Complex64::new(1.0, 0.0),
33                ],
34            )
35            .unwrap(),
36            Pauli::X => Array2::from_shape_vec(
37                (2, 2),
38                vec![
39                    Complex64::new(0.0, 0.0),
40                    Complex64::new(1.0, 0.0),
41                    Complex64::new(1.0, 0.0),
42                    Complex64::new(0.0, 0.0),
43                ],
44            )
45            .unwrap(),
46            Pauli::Y => Array2::from_shape_vec(
47                (2, 2),
48                vec![
49                    Complex64::new(0.0, 0.0),
50                    Complex64::new(0.0, -1.0),
51                    Complex64::new(0.0, 1.0),
52                    Complex64::new(0.0, 0.0),
53                ],
54            )
55            .unwrap(),
56            Pauli::Z => Array2::from_shape_vec(
57                (2, 2),
58                vec![
59                    Complex64::new(1.0, 0.0),
60                    Complex64::new(0.0, 0.0),
61                    Complex64::new(0.0, 0.0),
62                    Complex64::new(-1.0, 0.0),
63                ],
64            )
65            .unwrap(),
66        }
67    }
68
69    /// Multiply two Pauli operators
70    pub fn multiply(&self, other: &Pauli) -> (Complex64, Pauli) {
71        use Pauli::*;
72        match (self, other) {
73            (I, p) | (p, I) => (Complex64::new(1.0, 0.0), *p),
74            (X, X) | (Y, Y) | (Z, Z) => (Complex64::new(1.0, 0.0), I),
75            (X, Y) => (Complex64::new(0.0, 1.0), Z),
76            (Y, X) => (Complex64::new(0.0, -1.0), Z),
77            (Y, Z) => (Complex64::new(0.0, 1.0), X),
78            (Z, Y) => (Complex64::new(0.0, -1.0), X),
79            (Z, X) => (Complex64::new(0.0, 1.0), Y),
80            (X, Z) => (Complex64::new(0.0, -1.0), Y),
81        }
82    }
83}
84
85/// Multi-qubit Pauli operator
86#[derive(Debug, Clone, PartialEq)]
87pub struct PauliString {
88    /// Phase factor (±1, ±i)
89    pub phase: Complex64,
90    /// Pauli operators for each qubit
91    pub paulis: Vec<Pauli>,
92}
93
94impl PauliString {
95    /// Create a new Pauli string
96    pub fn new(paulis: Vec<Pauli>) -> Self {
97        Self {
98            phase: Complex64::new(1.0, 0.0),
99            paulis,
100        }
101    }
102
103    /// Create identity on n qubits
104    pub fn identity(n: usize) -> Self {
105        Self::new(vec![Pauli::I; n])
106    }
107
108    /// Get the weight (number of non-identity operators)
109    pub fn weight(&self) -> usize {
110        self.paulis.iter().filter(|&&p| p != Pauli::I).count()
111    }
112
113    /// Multiply two Pauli strings
114    pub fn multiply(&self, other: &PauliString) -> QuantRS2Result<PauliString> {
115        if self.paulis.len() != other.paulis.len() {
116            return Err(QuantRS2Error::InvalidInput(
117                "Pauli strings must have same length".to_string(),
118            ));
119        }
120
121        let mut phase = self.phase * other.phase;
122        let mut paulis = Vec::with_capacity(self.paulis.len());
123
124        for (p1, p2) in self.paulis.iter().zip(&other.paulis) {
125            let (factor, result) = p1.multiply(p2);
126            phase *= factor;
127            paulis.push(result);
128        }
129
130        Ok(PauliString { phase, paulis })
131    }
132
133    /// Check if two Pauli strings commute
134    pub fn commutes_with(&self, other: &PauliString) -> QuantRS2Result<bool> {
135        if self.paulis.len() != other.paulis.len() {
136            return Err(QuantRS2Error::InvalidInput(
137                "Pauli strings must have same length".to_string(),
138            ));
139        }
140
141        let mut commutation_count = 0;
142        for (p1, p2) in self.paulis.iter().zip(&other.paulis) {
143            if *p1 != Pauli::I && *p2 != Pauli::I && p1 != p2 {
144                commutation_count += 1;
145            }
146        }
147
148        Ok(commutation_count % 2 == 0)
149    }
150}
151
152impl fmt::Display for PauliString {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        let phase_str = if self.phase == Complex64::new(1.0, 0.0) {
155            "+".to_string()
156        } else if self.phase == Complex64::new(-1.0, 0.0) {
157            "-".to_string()
158        } else if self.phase == Complex64::new(0.0, 1.0) {
159            "+i".to_string()
160        } else {
161            "-i".to_string()
162        };
163
164        write!(f, "{}", phase_str)?;
165        for p in &self.paulis {
166            write!(f, "{:?}", p)?;
167        }
168        Ok(())
169    }
170}
171
172/// Stabilizer code definition
173#[derive(Debug, Clone)]
174pub struct StabilizerCode {
175    /// Number of physical qubits
176    pub n: usize,
177    /// Number of logical qubits
178    pub k: usize,
179    /// Minimum distance
180    pub d: usize,
181    /// Stabilizer generators
182    pub stabilizers: Vec<PauliString>,
183    /// Logical X operators
184    pub logical_x: Vec<PauliString>,
185    /// Logical Z operators
186    pub logical_z: Vec<PauliString>,
187}
188
189impl StabilizerCode {
190    /// Create a new stabilizer code
191    pub fn new(
192        n: usize,
193        k: usize,
194        d: usize,
195        stabilizers: Vec<PauliString>,
196        logical_x: Vec<PauliString>,
197        logical_z: Vec<PauliString>,
198    ) -> QuantRS2Result<Self> {
199        // Validate code parameters
200        // Note: For surface codes and other topological codes,
201        // the number of stabilizers may be less than n-k due to dependencies
202        if stabilizers.len() > n - k {
203            return Err(QuantRS2Error::InvalidInput(format!(
204                "Too many stabilizers: got {}, maximum is {}",
205                stabilizers.len(),
206                n - k
207            )));
208        }
209
210        if logical_x.len() != k || logical_z.len() != k {
211            return Err(QuantRS2Error::InvalidInput(
212                "Number of logical operators must equal k".to_string(),
213            ));
214        }
215
216        // Check that stabilizers commute
217        for i in 0..stabilizers.len() {
218            for j in i + 1..stabilizers.len() {
219                if !stabilizers[i].commutes_with(&stabilizers[j])? {
220                    return Err(QuantRS2Error::InvalidInput(
221                        "Stabilizers must commute".to_string(),
222                    ));
223                }
224            }
225        }
226
227        Ok(Self {
228            n,
229            k,
230            d,
231            stabilizers,
232            logical_x,
233            logical_z,
234        })
235    }
236
237    /// Create the 3-qubit repetition code
238    pub fn repetition_code() -> Self {
239        let stabilizers = vec![
240            PauliString::new(vec![Pauli::Z, Pauli::Z, Pauli::I]),
241            PauliString::new(vec![Pauli::I, Pauli::Z, Pauli::Z]),
242        ];
243
244        let logical_x = vec![PauliString::new(vec![Pauli::X, Pauli::X, Pauli::X])];
245        let logical_z = vec![PauliString::new(vec![Pauli::Z, Pauli::I, Pauli::I])];
246
247        Self::new(3, 1, 1, stabilizers, logical_x, logical_z).unwrap()
248    }
249
250    /// Create the 5-qubit perfect code
251    pub fn five_qubit_code() -> Self {
252        let stabilizers = vec![
253            PauliString::new(vec![Pauli::X, Pauli::Z, Pauli::Z, Pauli::X, Pauli::I]),
254            PauliString::new(vec![Pauli::I, Pauli::X, Pauli::Z, Pauli::Z, Pauli::X]),
255            PauliString::new(vec![Pauli::X, Pauli::I, Pauli::X, Pauli::Z, Pauli::Z]),
256            PauliString::new(vec![Pauli::Z, Pauli::X, Pauli::I, Pauli::X, Pauli::Z]),
257        ];
258
259        let logical_x = vec![PauliString::new(vec![
260            Pauli::X,
261            Pauli::X,
262            Pauli::X,
263            Pauli::X,
264            Pauli::X,
265        ])];
266        let logical_z = vec![PauliString::new(vec![
267            Pauli::Z,
268            Pauli::Z,
269            Pauli::Z,
270            Pauli::Z,
271            Pauli::Z,
272        ])];
273
274        Self::new(5, 1, 3, stabilizers, logical_x, logical_z).unwrap()
275    }
276
277    /// Create the 7-qubit Steane code
278    pub fn steane_code() -> Self {
279        let stabilizers = vec![
280            PauliString::new(vec![
281                Pauli::I,
282                Pauli::I,
283                Pauli::I,
284                Pauli::X,
285                Pauli::X,
286                Pauli::X,
287                Pauli::X,
288            ]),
289            PauliString::new(vec![
290                Pauli::I,
291                Pauli::X,
292                Pauli::X,
293                Pauli::I,
294                Pauli::I,
295                Pauli::X,
296                Pauli::X,
297            ]),
298            PauliString::new(vec![
299                Pauli::X,
300                Pauli::I,
301                Pauli::X,
302                Pauli::I,
303                Pauli::X,
304                Pauli::I,
305                Pauli::X,
306            ]),
307            PauliString::new(vec![
308                Pauli::I,
309                Pauli::I,
310                Pauli::I,
311                Pauli::Z,
312                Pauli::Z,
313                Pauli::Z,
314                Pauli::Z,
315            ]),
316            PauliString::new(vec![
317                Pauli::I,
318                Pauli::Z,
319                Pauli::Z,
320                Pauli::I,
321                Pauli::I,
322                Pauli::Z,
323                Pauli::Z,
324            ]),
325            PauliString::new(vec![
326                Pauli::Z,
327                Pauli::I,
328                Pauli::Z,
329                Pauli::I,
330                Pauli::Z,
331                Pauli::I,
332                Pauli::Z,
333            ]),
334        ];
335
336        let logical_x = vec![PauliString::new(vec![
337            Pauli::X,
338            Pauli::X,
339            Pauli::X,
340            Pauli::X,
341            Pauli::X,
342            Pauli::X,
343            Pauli::X,
344        ])];
345        let logical_z = vec![PauliString::new(vec![
346            Pauli::Z,
347            Pauli::Z,
348            Pauli::Z,
349            Pauli::Z,
350            Pauli::Z,
351            Pauli::Z,
352            Pauli::Z,
353        ])];
354
355        Self::new(7, 1, 3, stabilizers, logical_x, logical_z).unwrap()
356    }
357
358    /// Get syndrome for a given error
359    pub fn syndrome(&self, error: &PauliString) -> QuantRS2Result<Vec<bool>> {
360        if error.paulis.len() != self.n {
361            return Err(QuantRS2Error::InvalidInput(
362                "Error must act on all physical qubits".to_string(),
363            ));
364        }
365
366        let mut syndrome = Vec::with_capacity(self.stabilizers.len());
367        for stabilizer in &self.stabilizers {
368            syndrome.push(!stabilizer.commutes_with(error)?);
369        }
370
371        Ok(syndrome)
372    }
373}
374
375/// Surface code lattice
376#[derive(Debug, Clone)]
377pub struct SurfaceCode {
378    /// Number of rows in the lattice
379    pub rows: usize,
380    /// Number of columns in the lattice
381    pub cols: usize,
382    /// Qubit positions (row, col) -> qubit index
383    pub qubit_map: HashMap<(usize, usize), usize>,
384    /// Stabilizer plaquettes
385    pub x_stabilizers: Vec<Vec<usize>>,
386    pub z_stabilizers: Vec<Vec<usize>>,
387}
388
389impl SurfaceCode {
390    /// Create a new surface code
391    pub fn new(rows: usize, cols: usize) -> Self {
392        let mut qubit_map = HashMap::new();
393        let mut qubit_index = 0;
394
395        // Place qubits on the lattice
396        for r in 0..rows {
397            for c in 0..cols {
398                qubit_map.insert((r, c), qubit_index);
399                qubit_index += 1;
400            }
401        }
402
403        let mut x_stabilizers = Vec::new();
404        let mut z_stabilizers = Vec::new();
405
406        // Create X stabilizers (vertex operators)
407        for r in 0..rows - 1 {
408            for c in 0..cols - 1 {
409                if (r + c) % 2 == 0 {
410                    let stabilizer = vec![
411                        qubit_map[&(r, c)],
412                        qubit_map[&(r, c + 1)],
413                        qubit_map[&(r + 1, c)],
414                        qubit_map[&(r + 1, c + 1)],
415                    ];
416                    x_stabilizers.push(stabilizer);
417                }
418            }
419        }
420
421        // Create Z stabilizers (plaquette operators)
422        for r in 0..rows - 1 {
423            for c in 0..cols - 1 {
424                if (r + c) % 2 == 1 {
425                    let stabilizer = vec![
426                        qubit_map[&(r, c)],
427                        qubit_map[&(r, c + 1)],
428                        qubit_map[&(r + 1, c)],
429                        qubit_map[&(r + 1, c + 1)],
430                    ];
431                    z_stabilizers.push(stabilizer);
432                }
433            }
434        }
435
436        Self {
437            rows,
438            cols,
439            qubit_map,
440            x_stabilizers,
441            z_stabilizers,
442        }
443    }
444
445    /// Get the code distance
446    pub fn distance(&self) -> usize {
447        self.rows.min(self.cols)
448    }
449
450    /// Convert to stabilizer code representation
451    pub fn to_stabilizer_code(&self) -> StabilizerCode {
452        let n = self.qubit_map.len();
453        let mut stabilizers = Vec::new();
454
455        // Add X stabilizers
456        for x_stab in &self.x_stabilizers {
457            let mut paulis = vec![Pauli::I; n];
458            for &qubit in x_stab {
459                paulis[qubit] = Pauli::X;
460            }
461            stabilizers.push(PauliString::new(paulis));
462        }
463
464        // Add Z stabilizers
465        for z_stab in &self.z_stabilizers {
466            let mut paulis = vec![Pauli::I; n];
467            for &qubit in z_stab {
468                paulis[qubit] = Pauli::Z;
469            }
470            stabilizers.push(PauliString::new(paulis));
471        }
472
473        // Create logical operators (simplified - just use boundary chains)
474        let mut logical_x_paulis = vec![Pauli::I; n];
475        let mut logical_z_paulis = vec![Pauli::I; n];
476
477        // Logical X: horizontal chain on top boundary
478        for c in 0..self.cols {
479            if let Some(&qubit) = self.qubit_map.get(&(0, c)) {
480                logical_x_paulis[qubit] = Pauli::X;
481            }
482        }
483
484        // Logical Z: vertical chain on left boundary
485        for r in 0..self.rows {
486            if let Some(&qubit) = self.qubit_map.get(&(r, 0)) {
487                logical_z_paulis[qubit] = Pauli::Z;
488            }
489        }
490
491        let logical_x = vec![PauliString::new(logical_x_paulis)];
492        let logical_z = vec![PauliString::new(logical_z_paulis)];
493
494        StabilizerCode::new(n, 1, self.distance(), stabilizers, logical_x, logical_z).unwrap()
495    }
496}
497
498/// Syndrome decoder interface
499pub trait SyndromeDecoder {
500    /// Decode syndrome to find most likely error
501    fn decode(&self, syndrome: &[bool]) -> QuantRS2Result<PauliString>;
502}
503
504/// Lookup table decoder
505pub struct LookupDecoder {
506    /// Syndrome to error mapping
507    syndrome_table: HashMap<Vec<bool>, PauliString>,
508}
509
510impl LookupDecoder {
511    /// Create decoder for a stabilizer code
512    pub fn new(code: &StabilizerCode) -> QuantRS2Result<Self> {
513        let mut syndrome_table = HashMap::new();
514
515        // Generate all correctable errors (up to weight floor(d/2))
516        let max_weight = (code.d - 1) / 2;
517        let all_errors = Self::generate_pauli_errors(code.n, max_weight);
518
519        for error in all_errors {
520            let syndrome = code.syndrome(&error)?;
521
522            // Only keep lowest weight error for each syndrome
523            syndrome_table
524                .entry(syndrome)
525                .and_modify(|e: &mut PauliString| {
526                    if error.weight() < e.weight() {
527                        *e = error.clone();
528                    }
529                })
530                .or_insert(error);
531        }
532
533        Ok(Self { syndrome_table })
534    }
535
536    /// Generate all Pauli errors up to given weight
537    fn generate_pauli_errors(n: usize, max_weight: usize) -> Vec<PauliString> {
538        let mut errors = vec![PauliString::identity(n)];
539
540        for weight in 1..=max_weight {
541            let weight_errors = Self::generate_weight_k_errors(n, weight);
542            errors.extend(weight_errors);
543        }
544
545        errors
546    }
547
548    /// Generate all weight-k Pauli errors
549    fn generate_weight_k_errors(n: usize, k: usize) -> Vec<PauliString> {
550        let mut errors = Vec::new();
551        let paulis = [Pauli::X, Pauli::Y, Pauli::Z];
552
553        // Generate all combinations of k positions
554        let positions = Self::combinations(n, k);
555
556        for pos_set in positions {
557            // For each position set, try all Pauli combinations
558            let pauli_combinations = Self::cartesian_power(&paulis, k);
559
560            for pauli_combo in pauli_combinations {
561                let mut error_paulis = vec![Pauli::I; n];
562                for (i, &pos) in pos_set.iter().enumerate() {
563                    error_paulis[pos] = pauli_combo[i];
564                }
565                errors.push(PauliString::new(error_paulis));
566            }
567        }
568
569        errors
570    }
571
572    /// Generate all k-combinations from n elements
573    fn combinations(n: usize, k: usize) -> Vec<Vec<usize>> {
574        let mut result = Vec::new();
575        let mut combo = (0..k).collect::<Vec<_>>();
576
577        loop {
578            result.push(combo.clone());
579
580            // Find rightmost element that can be incremented
581            let mut i = k;
582            while i > 0 && (i == k || combo[i] == n - k + i) {
583                i -= 1;
584            }
585
586            if i == 0 && combo[0] == n - k {
587                break;
588            }
589
590            // Increment and reset following elements
591            combo[i] += 1;
592            for j in i + 1..k {
593                combo[j] = combo[j - 1] + 1;
594            }
595        }
596
597        result
598    }
599
600    /// Generate Cartesian power of a set
601    fn cartesian_power<T: Clone>(set: &[T], k: usize) -> Vec<Vec<T>> {
602        if k == 0 {
603            return vec![vec![]];
604        }
605
606        let mut result = Vec::new();
607        let smaller = Self::cartesian_power(set, k - 1);
608
609        for item in set {
610            for mut combo in smaller.clone() {
611                combo.push(item.clone());
612                result.push(combo);
613            }
614        }
615
616        result
617    }
618}
619
620impl SyndromeDecoder for LookupDecoder {
621    fn decode(&self, syndrome: &[bool]) -> QuantRS2Result<PauliString> {
622        self.syndrome_table
623            .get(syndrome)
624            .cloned()
625            .ok_or_else(|| QuantRS2Error::InvalidInput("Unknown syndrome".to_string()))
626    }
627}
628
629/// Minimum Weight Perfect Matching decoder for surface codes
630pub struct MWPMDecoder {
631    surface_code: SurfaceCode,
632}
633
634impl MWPMDecoder {
635    /// Create MWPM decoder for surface code
636    pub fn new(surface_code: SurfaceCode) -> Self {
637        Self { surface_code }
638    }
639
640    /// Find minimum weight matching for syndrome
641    pub fn decode_syndrome(
642        &self,
643        x_syndrome: &[bool],
644        z_syndrome: &[bool],
645    ) -> QuantRS2Result<PauliString> {
646        let n = self.surface_code.qubit_map.len();
647        let mut error_paulis = vec![Pauli::I; n];
648
649        // Decode X errors using Z syndrome
650        let z_defects = self.find_defects(z_syndrome, &self.surface_code.z_stabilizers);
651        let x_corrections = self.minimum_weight_matching(&z_defects, Pauli::X)?;
652
653        for (qubit, pauli) in x_corrections {
654            error_paulis[qubit] = pauli;
655        }
656
657        // Decode Z errors using X syndrome
658        let x_defects = self.find_defects(x_syndrome, &self.surface_code.x_stabilizers);
659        let z_corrections = self.minimum_weight_matching(&x_defects, Pauli::Z)?;
660
661        for (qubit, pauli) in z_corrections {
662            if error_paulis[qubit] != Pauli::I {
663                // Combine X and Z to get Y
664                error_paulis[qubit] = Pauli::Y;
665            } else {
666                error_paulis[qubit] = pauli;
667            }
668        }
669
670        Ok(PauliString::new(error_paulis))
671    }
672
673    /// Find stabilizer defects from syndrome
674    fn find_defects(&self, syndrome: &[bool], stabilizers: &[Vec<usize>]) -> Vec<usize> {
675        syndrome
676            .iter()
677            .enumerate()
678            .filter_map(|(i, &s)| if s { Some(i) } else { None })
679            .collect()
680    }
681
682    /// Simple minimum weight matching (for demonstration)
683    fn minimum_weight_matching(
684        &self,
685        defects: &[usize],
686        error_type: Pauli,
687    ) -> QuantRS2Result<Vec<(usize, Pauli)>> {
688        // This is a simplified version - real implementation would use blossom algorithm
689        let mut corrections = Vec::new();
690
691        if defects.len() % 2 != 0 {
692            return Err(QuantRS2Error::InvalidInput(
693                "Odd number of defects".to_string(),
694            ));
695        }
696
697        // Simple greedy pairing
698        let mut paired = vec![false; defects.len()];
699
700        for i in 0..defects.len() {
701            if paired[i] {
702                continue;
703            }
704
705            // Find nearest unpaired defect
706            let mut min_dist = usize::MAX;
707            let mut min_j = i;
708
709            for j in i + 1..defects.len() {
710                if !paired[j] {
711                    let dist = self.defect_distance(defects[i], defects[j]);
712                    if dist < min_dist {
713                        min_dist = dist;
714                        min_j = j;
715                    }
716                }
717            }
718
719            if min_j != i {
720                paired[i] = true;
721                paired[min_j] = true;
722
723                // Add correction path
724                let path = self.shortest_path(defects[i], defects[min_j])?;
725                for qubit in path {
726                    corrections.push((qubit, error_type));
727                }
728            }
729        }
730
731        Ok(corrections)
732    }
733
734    /// Manhattan distance between defects
735    fn defect_distance(&self, defect1: usize, defect2: usize) -> usize {
736        // This is simplified - would need proper defect coordinates
737        (defect1 as isize - defect2 as isize).unsigned_abs()
738    }
739
740    /// Find shortest path between defects
741    fn shortest_path(&self, start: usize, end: usize) -> QuantRS2Result<Vec<usize>> {
742        // Simplified path - in practice would use proper graph traversal
743        let path = if start < end {
744            (start..=end).collect()
745        } else {
746            (end..=start).rev().collect()
747        };
748
749        Ok(path)
750    }
751}
752
753/// Color code
754#[derive(Debug, Clone)]
755pub struct ColorCode {
756    /// Number of physical qubits
757    pub n: usize,
758    /// Face coloring (red, green, blue)
759    pub faces: Vec<(Vec<usize>, Color)>,
760    /// Vertex to qubit mapping
761    pub vertex_map: HashMap<(i32, i32), usize>,
762}
763
764#[derive(Debug, Clone, Copy, PartialEq, Eq)]
765enum Color {
766    Red,
767    Green,
768    Blue,
769}
770
771impl ColorCode {
772    /// Create a triangular color code
773    pub fn triangular(size: usize) -> Self {
774        let mut vertex_map = HashMap::new();
775        let mut qubit_index = 0;
776
777        // Create hexagonal lattice vertices
778        for i in 0..size as i32 {
779            for j in 0..size as i32 {
780                vertex_map.insert((i, j), qubit_index);
781                qubit_index += 1;
782            }
783        }
784
785        let mut faces = Vec::new();
786
787        // Create colored faces
788        for i in 0..size as i32 - 1 {
789            for j in 0..size as i32 - 1 {
790                // Red face
791                if let (Some(&q1), Some(&q2), Some(&q3)) = (
792                    vertex_map.get(&(i, j)),
793                    vertex_map.get(&(i + 1, j)),
794                    vertex_map.get(&(i, j + 1)),
795                ) {
796                    faces.push((vec![q1, q2, q3], Color::Red));
797                }
798
799                // Green face
800                if let (Some(&q1), Some(&q2), Some(&q3)) = (
801                    vertex_map.get(&(i + 1, j)),
802                    vertex_map.get(&(i + 1, j + 1)),
803                    vertex_map.get(&(i, j + 1)),
804                ) {
805                    faces.push((vec![q1, q2, q3], Color::Green));
806                }
807            }
808        }
809
810        Self {
811            n: vertex_map.len(),
812            faces,
813            vertex_map,
814        }
815    }
816
817    /// Convert to stabilizer code
818    pub fn to_stabilizer_code(&self) -> StabilizerCode {
819        let mut x_stabilizers = Vec::new();
820        let mut z_stabilizers = Vec::new();
821
822        for (qubits, color) in &self.faces {
823            // X-type stabilizer
824            let mut x_paulis = vec![Pauli::I; self.n];
825            for &q in qubits {
826                x_paulis[q] = Pauli::X;
827            }
828            x_stabilizers.push(PauliString::new(x_paulis));
829
830            // Z-type stabilizer
831            let mut z_paulis = vec![Pauli::I; self.n];
832            for &q in qubits {
833                z_paulis[q] = Pauli::Z;
834            }
835            z_stabilizers.push(PauliString::new(z_paulis));
836        }
837
838        let mut stabilizers = x_stabilizers;
839        stabilizers.extend(z_stabilizers);
840
841        // Simplified logical operators
842        let logical_x = vec![PauliString::new(vec![Pauli::X; self.n])];
843        let logical_z = vec![PauliString::new(vec![Pauli::Z; self.n])];
844
845        StabilizerCode::new(
846            self.n,
847            1,
848            3, // minimum distance
849            stabilizers,
850            logical_x,
851            logical_z,
852        )
853        .unwrap()
854    }
855}
856
857#[cfg(test)]
858mod tests {
859    use super::*;
860
861    #[test]
862    fn test_pauli_multiplication() {
863        let (phase, result) = Pauli::X.multiply(&Pauli::Y);
864        assert_eq!(result, Pauli::Z);
865        assert_eq!(phase, Complex64::new(0.0, 1.0));
866    }
867
868    #[test]
869    fn test_pauli_string_commutation() {
870        let ps1 = PauliString::new(vec![Pauli::X, Pauli::I]);
871        let ps2 = PauliString::new(vec![Pauli::Z, Pauli::I]);
872        assert!(!ps1.commutes_with(&ps2).unwrap());
873
874        let ps3 = PauliString::new(vec![Pauli::X, Pauli::I]);
875        let ps4 = PauliString::new(vec![Pauli::I, Pauli::Z]);
876        assert!(ps3.commutes_with(&ps4).unwrap());
877    }
878
879    #[test]
880    fn test_repetition_code() {
881        let code = StabilizerCode::repetition_code();
882        assert_eq!(code.n, 3);
883        assert_eq!(code.k, 1);
884        assert_eq!(code.d, 1);
885
886        // Test syndrome for X error on first qubit
887        let error = PauliString::new(vec![Pauli::X, Pauli::I, Pauli::I]);
888        let syndrome = code.syndrome(&error).unwrap();
889        // X error anti-commutes with Z stabilizer on first two qubits
890        assert_eq!(syndrome, vec![true, false]);
891    }
892
893    #[test]
894    fn test_steane_code() {
895        let code = StabilizerCode::steane_code();
896        assert_eq!(code.n, 7);
897        assert_eq!(code.k, 1);
898        assert_eq!(code.d, 3);
899
900        // Test that stabilizers commute
901        for i in 0..code.stabilizers.len() {
902            for j in i + 1..code.stabilizers.len() {
903                assert!(code.stabilizers[i]
904                    .commutes_with(&code.stabilizers[j])
905                    .unwrap());
906            }
907        }
908    }
909
910    #[test]
911    fn test_surface_code() {
912        let surface = SurfaceCode::new(3, 3);
913        assert_eq!(surface.distance(), 3);
914
915        let code = surface.to_stabilizer_code();
916        assert_eq!(code.n, 9);
917        // For a 3x3 lattice, we have 2 X stabilizers and 2 Z stabilizers
918        assert_eq!(code.stabilizers.len(), 4);
919    }
920
921    #[test]
922    fn test_lookup_decoder() {
923        let code = StabilizerCode::repetition_code();
924        let decoder = LookupDecoder::new(&code).unwrap();
925
926        // Test decoding trivial syndrome (no error)
927        let trivial_syndrome = vec![false, false];
928        let decoded = decoder.decode(&trivial_syndrome).unwrap();
929        assert_eq!(decoded.weight(), 0); // Should be identity
930
931        // Test single bit flip error
932        let error = PauliString::new(vec![Pauli::X, Pauli::I, Pauli::I]);
933        let syndrome = code.syndrome(&error).unwrap();
934
935        // The decoder should be able to decode this syndrome
936        if let Ok(decoded_error) = decoder.decode(&syndrome) {
937            // Decoder should find a low-weight error
938            assert!(decoded_error.weight() <= 1);
939        }
940    }
941}