1use std::collections::VecDeque;
39
40use crate::error::{QuantumError, QuantumResult};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum StabKind {
45 X,
47 Z,
49}
50
51#[derive(Debug, Clone)]
53pub struct Stabilizer {
54 pub kind: StabKind,
56 pub support: Vec<usize>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct PauliError {
65 pub x: Vec<bool>,
67 pub z: Vec<bool>,
69}
70
71impl PauliError {
72 #[must_use]
74 pub fn identity(num_qubits: usize) -> Self {
75 Self {
76 x: vec![false; num_qubits],
77 z: vec![false; num_qubits],
78 }
79 }
80
81 pub fn single_x(num_qubits: usize, q: usize) -> QuantumResult<Self> {
86 if q >= num_qubits {
87 return Err(QuantumError::QubitIndexOutOfRange {
88 index: q,
89 n_qubits: num_qubits,
90 });
91 }
92 let mut e = Self::identity(num_qubits);
93 e.x[q] = true;
94 Ok(e)
95 }
96
97 pub fn single_z(num_qubits: usize, q: usize) -> QuantumResult<Self> {
102 if q >= num_qubits {
103 return Err(QuantumError::QubitIndexOutOfRange {
104 index: q,
105 n_qubits: num_qubits,
106 });
107 }
108 let mut e = Self::identity(num_qubits);
109 e.z[q] = true;
110 Ok(e)
111 }
112
113 #[must_use]
115 pub fn weight(&self) -> usize {
116 self.x
117 .iter()
118 .zip(self.z.iter())
119 .filter(|(a, b)| **a || **b)
120 .count()
121 }
122
123 pub fn compose(&self, other: &Self) -> QuantumResult<Self> {
129 if self.x.len() != other.x.len() {
130 return Err(QuantumError::DimensionMismatch {
131 expected: self.x.len(),
132 got: other.x.len(),
133 });
134 }
135 let x = self
136 .x
137 .iter()
138 .zip(other.x.iter())
139 .map(|(a, b)| a ^ b)
140 .collect();
141 let z = self
142 .z
143 .iter()
144 .zip(other.z.iter())
145 .map(|(a, b)| a ^ b)
146 .collect();
147 Ok(Self { x, z })
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct Syndrome {
154 pub bits: Vec<bool>,
156}
157
158impl Syndrome {
159 #[must_use]
161 pub fn is_trivial(&self) -> bool {
162 self.bits.iter().all(|b| !b)
163 }
164
165 #[must_use]
167 pub fn weight(&self) -> usize {
168 self.bits.iter().filter(|b| **b).count()
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub struct SurfaceCodeConfig {
175 pub distance: usize,
177}
178
179#[derive(Debug, Clone)]
182struct MatchingGraph {
183 num_nodes: usize,
184 boundary: usize,
185 adj: Vec<Vec<(usize, usize)>>,
187}
188
189#[derive(Debug, Clone)]
191pub struct SurfaceCode {
192 distance: usize,
193 num_data: usize,
194 stabilizers: Vec<Stabilizer>,
195 x_stab_indices: Vec<usize>,
197 z_stab_indices: Vec<usize>,
198 logical_x_support: Vec<usize>,
200 logical_z_support: Vec<usize>,
202 graph_z: MatchingGraph,
204 graph_x: MatchingGraph,
206}
207
208impl SurfaceCode {
209 pub fn new(distance: usize) -> QuantumResult<Self> {
214 if distance == 0 || distance.is_multiple_of(2) {
215 return Err(QuantumError::InvalidParameter {
216 name: "distance must be a positive odd integer".into(),
217 });
218 }
219 let d = distance;
220 let num_data = d * d;
221 let stabilizers = build_stabilizers(d);
222
223 let x_stab_indices: Vec<usize> = stabilizers
224 .iter()
225 .enumerate()
226 .filter(|(_, s)| s.kind == StabKind::X)
227 .map(|(i, _)| i)
228 .collect();
229 let z_stab_indices: Vec<usize> = stabilizers
230 .iter()
231 .enumerate()
232 .filter(|(_, s)| s.kind == StabKind::Z)
233 .map(|(i, _)| i)
234 .collect();
235
236 let logical_z_support: Vec<usize> = (0..d).collect();
238 let logical_x_support: Vec<usize> = (0..d).map(|r| r * d).collect();
240
241 let graph_z = build_matching_graph(&stabilizers, &z_stab_indices, num_data);
242 let graph_x = build_matching_graph(&stabilizers, &x_stab_indices, num_data);
243
244 Ok(Self {
245 distance: d,
246 num_data,
247 stabilizers,
248 x_stab_indices,
249 z_stab_indices,
250 logical_x_support,
251 logical_z_support,
252 graph_z,
253 graph_x,
254 })
255 }
256
257 pub fn from_config(cfg: SurfaceCodeConfig) -> QuantumResult<Self> {
262 Self::new(cfg.distance)
263 }
264
265 #[must_use]
267 pub fn distance(&self) -> usize {
268 self.distance
269 }
270
271 #[must_use]
273 pub fn num_data_qubits(&self) -> usize {
274 self.num_data
275 }
276
277 #[must_use]
279 pub fn num_stabilizers(&self) -> usize {
280 self.stabilizers.len()
281 }
282
283 #[must_use]
285 pub fn num_x_stabilizers(&self) -> usize {
286 self.x_stab_indices.len()
287 }
288
289 #[must_use]
291 pub fn num_z_stabilizers(&self) -> usize {
292 self.z_stab_indices.len()
293 }
294
295 #[must_use]
297 pub fn stabilizers(&self) -> &[Stabilizer] {
298 &self.stabilizers
299 }
300
301 #[must_use]
303 pub fn logical_x_support(&self) -> &[usize] {
304 &self.logical_x_support
305 }
306
307 #[must_use]
309 pub fn logical_z_support(&self) -> &[usize] {
310 &self.logical_z_support
311 }
312
313 pub fn syndrome(&self, error: &PauliError) -> QuantumResult<Syndrome> {
323 if error.x.len() != self.num_data || error.z.len() != self.num_data {
324 return Err(QuantumError::DimensionMismatch {
325 expected: self.num_data,
326 got: error.x.len(),
327 });
328 }
329 let bits = self
330 .stabilizers
331 .iter()
332 .map(|s| {
333 let component = match s.kind {
334 StabKind::Z => &error.x,
335 StabKind::X => &error.z,
336 };
337 let overlap = s.support.iter().filter(|&&q| component[q]).count();
338 !overlap.is_multiple_of(2)
339 })
340 .collect();
341 Ok(Syndrome { bits })
342 }
343
344 pub fn decode(&self, syndrome: &Syndrome) -> QuantumResult<PauliError> {
352 if syndrome.bits.len() != self.stabilizers.len() {
353 return Err(QuantumError::DimensionMismatch {
354 expected: self.stabilizers.len(),
355 got: syndrome.bits.len(),
356 });
357 }
358
359 let z_defects: Vec<usize> = self
361 .z_stab_indices
362 .iter()
363 .enumerate()
364 .filter(|&(_, &g)| syndrome.bits[g])
365 .map(|(l, _)| l)
366 .collect();
367 let x_defects: Vec<usize> = self
368 .x_stab_indices
369 .iter()
370 .enumerate()
371 .filter(|&(_, &g)| syndrome.bits[g])
372 .map(|(l, _)| l)
373 .collect();
374
375 let x_correction = match_defects(&self.graph_z, &z_defects, self.num_data);
376 let z_correction = match_defects(&self.graph_x, &x_defects, self.num_data);
377
378 Ok(PauliError {
379 x: x_correction,
380 z: z_correction,
381 })
382 }
383
384 pub fn residual_after_correction(&self, error: &PauliError) -> QuantumResult<PauliError> {
392 let syndrome = self.syndrome(error)?;
393 let correction = self.decode(&syndrome)?;
394 error.compose(&correction)
395 }
396
397 pub fn logical_error(&self, error: &PauliError) -> QuantumResult<(bool, bool)> {
409 if error.x.len() != self.num_data || error.z.len() != self.num_data {
410 return Err(QuantumError::DimensionMismatch {
411 expected: self.num_data,
412 got: error.x.len(),
413 });
414 }
415 let x_flip = !self
416 .logical_z_support
417 .iter()
418 .filter(|&&q| error.x[q])
419 .count()
420 .is_multiple_of(2);
421 let z_flip = !self
422 .logical_x_support
423 .iter()
424 .filter(|&&q| error.z[q])
425 .count()
426 .is_multiple_of(2);
427 Ok((x_flip, z_flip))
428 }
429}
430
431fn build_stabilizers(d: usize) -> Vec<Stabilizer> {
433 let mut stabs = Vec::new();
434 for a in 0..=d {
436 for b in 0..=d {
437 let mut support = Vec::new();
438 for (dr, dc) in [(-1i64, -1i64), (-1, 0), (0, -1), (0, 0)] {
439 let r = a as i64 + dr;
440 let c = b as i64 + dc;
441 if r >= 0 && r < d as i64 && c >= 0 && c < d as i64 {
442 support.push((r as usize) * d + (c as usize));
443 }
444 }
445 let kind = if (a + b).is_multiple_of(2) {
446 StabKind::Z
447 } else {
448 StabKind::X
449 };
450 let keep = match support.len() {
451 4 => true,
453 2 => {
455 let on_top_or_bottom = a == 0 || a == d;
456 let on_left_or_right = b == 0 || b == d;
457 match kind {
458 StabKind::X => on_top_or_bottom,
459 StabKind::Z => on_left_or_right,
460 }
461 }
462 _ => false,
464 };
465 if keep {
466 stabs.push(Stabilizer { kind, support });
467 }
468 }
469 }
470 stabs
471}
472
473fn build_matching_graph(
475 stabilizers: &[Stabilizer],
476 local_indices: &[usize],
477 num_data: usize,
478) -> MatchingGraph {
479 let num = local_indices.len();
480 let boundary = num;
481 let mut adj: Vec<Vec<(usize, usize)>> = vec![Vec::new(); num + 1];
482
483 for q in 0..num_data {
484 let touching: Vec<usize> = local_indices
486 .iter()
487 .enumerate()
488 .filter(|&(_, &g)| stabilizers[g].support.contains(&q))
489 .map(|(l, _)| l)
490 .collect();
491 match touching.as_slice() {
492 [s, t] => {
493 adj[*s].push((*t, q));
494 adj[*t].push((*s, q));
495 }
496 [s] => {
497 adj[*s].push((boundary, q));
498 adj[boundary].push((*s, q));
499 }
500 _ => {}
503 }
504 }
505
506 MatchingGraph {
507 num_nodes: num + 1,
508 boundary,
509 adj,
510 }
511}
512
513type BfsResult = (Vec<usize>, Vec<Option<(usize, usize)>>);
516
517fn bfs(graph: &MatchingGraph, source: usize) -> BfsResult {
521 let n = graph.num_nodes;
522 let mut dist = vec![usize::MAX; n];
523 let mut pred: Vec<Option<(usize, usize)>> = vec![None; n];
524 let mut queue = VecDeque::new();
525 dist[source] = 0;
526 queue.push_back(source);
527 while let Some(u) = queue.pop_front() {
528 if u == graph.boundary {
529 continue; }
531 for &(v, q) in &graph.adj[u] {
532 if dist[v] == usize::MAX {
533 dist[v] = dist[u] + 1;
534 pred[v] = Some((u, q));
535 queue.push_back(v);
536 }
537 }
538 }
539 (dist, pred)
540}
541
542fn path_qubits(pred: &[Option<(usize, usize)>], source: usize, target: usize) -> Vec<usize> {
544 let mut qubits = Vec::new();
545 let mut cur = target;
546 while cur != source {
547 match pred[cur] {
548 Some((prev, q)) => {
549 qubits.push(q);
550 cur = prev;
551 }
552 None => break, }
554 }
555 qubits
556}
557
558fn match_defects(graph: &MatchingGraph, defects: &[usize], num_data: usize) -> Vec<bool> {
562 let mut correction = vec![false; num_data];
563 if defects.is_empty() {
564 return correction;
565 }
566 let k = defects.len();
567
568 let searches: Vec<BfsResult> = defects.iter().map(|&d| bfs(graph, d)).collect();
570
571 if k > 20 {
576 for (i, &_d) in defects.iter().enumerate() {
577 let path = path_qubits(&searches[i].1, defects[i], graph.boundary);
578 for q in path {
579 correction[q] ^= true;
580 }
581 }
582 return correction;
583 }
584
585 let pair_dist = |i: usize, j: usize| searches[i].0[defects[j]];
586 let bnd_dist = |i: usize| searches[i].0[graph.boundary];
587
588 let size = 1usize << k;
589 let mut dp = vec![usize::MAX; size];
590 let mut partner = vec![usize::MAX; size];
593 dp[0] = 0;
594
595 for mask in 1..size {
596 let i = mask.trailing_zeros() as usize;
597 let rest = mask & !(1 << i);
598
599 let bd = bnd_dist(i);
601 if bd != usize::MAX && dp[rest] != usize::MAX {
602 let cost = dp[rest] + bd;
603 if cost < dp[mask] {
604 dp[mask] = cost;
605 partner[mask] = usize::MAX;
606 }
607 }
608
609 let mut jbits = rest;
611 while jbits != 0 {
612 let j = jbits.trailing_zeros() as usize;
613 jbits &= jbits - 1;
614 let pd = pair_dist(i, j);
615 let sub = rest & !(1 << j);
616 if pd != usize::MAX && dp[sub] != usize::MAX {
617 let cost = dp[sub] + pd;
618 if cost < dp[mask] {
619 dp[mask] = cost;
620 partner[mask] = j;
621 }
622 }
623 }
624 }
625
626 let mut mask = size - 1;
628 while mask != 0 {
629 let i = mask.trailing_zeros() as usize;
630 let p = partner[mask];
631 if p == usize::MAX {
632 let path = path_qubits(&searches[i].1, defects[i], graph.boundary);
633 for q in path {
634 correction[q] ^= true;
635 }
636 mask &= !(1 << i);
637 } else {
638 let path = path_qubits(&searches[i].1, defects[i], defects[p]);
639 for q in path {
640 correction[q] ^= true;
641 }
642 mask &= !((1 << i) | (1 << p));
643 }
644 }
645
646 correction
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652
653 #[test]
654 fn distance_three_layout_counts() {
655 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
656 assert_eq!(code.distance(), 3);
657 assert_eq!(code.num_data_qubits(), 9); assert_eq!(code.num_stabilizers(), 8); assert_eq!(code.num_x_stabilizers(), 4); assert_eq!(code.num_z_stabilizers(), 4);
661 let weight4 = code
663 .stabilizers()
664 .iter()
665 .filter(|s| s.support.len() == 4)
666 .count();
667 let weight2 = code
668 .stabilizers()
669 .iter()
670 .filter(|s| s.support.len() == 2)
671 .count();
672 assert_eq!(weight4, 4); assert_eq!(weight2, 4); }
675
676 #[test]
677 fn css_stabilizers_commute() {
678 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
680 for sx in code.stabilizers().iter().filter(|s| s.kind == StabKind::X) {
681 for sz in code.stabilizers().iter().filter(|s| s.kind == StabKind::Z) {
682 let overlap = sx.support.iter().filter(|q| sz.support.contains(q)).count();
683 assert!(overlap.is_multiple_of(2), "overlap={overlap}");
684 }
685 }
686 }
687
688 #[test]
689 fn logical_operators_anticommute() {
690 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
691 let overlap = code
693 .logical_x_support()
694 .iter()
695 .filter(|q| code.logical_z_support().contains(q))
696 .count();
697 assert_eq!(overlap, 1);
698 assert_eq!(code.logical_x_support().len(), 3);
700 assert_eq!(code.logical_z_support().len(), 3);
701 }
702
703 #[test]
704 fn invalid_distance_rejected() {
705 assert!(SurfaceCode::new(0).is_err()); assert!(SurfaceCode::new(2).is_err()); assert!(SurfaceCode::new(4).is_err()); assert!(SurfaceCode::new(3).is_ok()); }
710
711 #[test]
712 fn zero_error_gives_empty_syndrome_and_no_correction() {
713 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
714 let err = PauliError::identity(code.num_data_qubits());
715 let syn = code.syndrome(&err).expect("syndrome for identity error");
716 assert!(syn.is_trivial());
717 let corr = code.decode(&syn).expect("decode trivial syndrome");
718 assert_eq!(corr.weight(), 0);
719 }
720
721 #[test]
722 fn single_x_on_bulk_qubit_fires_two_z_stabilizers() {
723 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
724 let err = PauliError::single_x(code.num_data_qubits(), 4)
726 .expect("valid single-X error on qubit 4");
727 let syn = code
728 .syndrome(&err)
729 .expect("syndrome for single-X error on qubit 4");
730 assert_eq!(syn.weight(), 2, "central X error should fire two defects");
731 for (i, &fired) in syn.bits.iter().enumerate() {
733 if fired {
734 assert_eq!(code.stabilizers()[i].kind, StabKind::Z);
735 }
736 }
737 let residual = code
739 .residual_after_correction(&err)
740 .expect("residual after correcting X error on qubit 4");
741 assert_eq!(
742 code.logical_error(&residual)
743 .expect("logical error check on residual after X correction"),
744 (false, false)
745 );
746 }
747
748 #[test]
749 fn single_z_on_bulk_qubit_fires_two_x_stabilizers() {
750 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
751 let err = PauliError::single_z(code.num_data_qubits(), 4)
752 .expect("valid single-Z error on qubit 4");
753 let syn = code
754 .syndrome(&err)
755 .expect("syndrome for single-Z error on qubit 4");
756 assert_eq!(syn.weight(), 2);
757 for (i, &fired) in syn.bits.iter().enumerate() {
758 if fired {
759 assert_eq!(code.stabilizers()[i].kind, StabKind::X);
760 }
761 }
762 let residual = code
763 .residual_after_correction(&err)
764 .expect("residual after correcting Z error on qubit 4");
765 assert_eq!(
766 code.logical_error(&residual)
767 .expect("logical error check on residual after Z correction"),
768 (false, false)
769 );
770 }
771
772 #[test]
773 fn every_weight_one_error_is_corrected() {
774 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
775 let n = code.num_data_qubits();
776 for q in 0..n {
777 let ex = PauliError::single_x(n, q).expect("valid single-X error on qubit q");
779 let rx = code
780 .residual_after_correction(&ex)
781 .expect("residual after correcting single-X error");
782 assert_eq!(
783 code.logical_error(&rx)
784 .expect("logical error check on single-X residual"),
785 (false, false),
786 "X error on qubit {q} not corrected"
787 );
788 let ez = PauliError::single_z(n, q).expect("valid single-Z error on qubit q");
790 let rz = code
791 .residual_after_correction(&ez)
792 .expect("residual after correcting single-Z error");
793 assert_eq!(
794 code.logical_error(&rz)
795 .expect("logical error check on single-Z residual"),
796 (false, false),
797 "Z error on qubit {q} not corrected"
798 );
799 let mut ey =
801 PauliError::single_x(n, q).expect("valid single-X component of Y error on qubit q");
802 ey.z[q] = true;
803 let ry = code
804 .residual_after_correction(&ey)
805 .expect("residual after correcting single-Y error");
806 assert_eq!(
807 code.logical_error(&ry)
808 .expect("logical error check on single-Y residual"),
809 (false, false),
810 "Y error on qubit {q} not corrected"
811 );
812 }
813 }
814
815 #[test]
816 fn corrected_residual_has_trivial_syndrome() {
817 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
819 let n = code.num_data_qubits();
820 for q in 0..n {
821 let err = PauliError::single_x(n, q).expect("valid single-X error on qubit q");
822 let residual = code
823 .residual_after_correction(&err)
824 .expect("residual after correcting error on qubit q");
825 let syn = code
826 .syndrome(&residual)
827 .expect("syndrome for residual error");
828 assert!(syn.is_trivial(), "residual syndrome non-trivial for q={q}");
829 }
830 }
831
832 #[test]
833 fn boundary_x_error_fires_single_defect() {
834 let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
838 let err = PauliError::single_x(code.num_data_qubits(), 0)
839 .expect("valid single-X error on corner qubit 0");
840 let syn = code
841 .syndrome(&err)
842 .expect("syndrome for boundary X error on qubit 0");
843 assert_eq!(syn.weight(), 1);
844 let residual = code
845 .residual_after_correction(&err)
846 .expect("residual after correcting boundary X error");
847 assert_eq!(
848 code.logical_error(&residual)
849 .expect("logical error check on boundary residual"),
850 (false, false)
851 );
852 }
853
854 #[test]
855 fn distance_five_layout_counts() {
856 let code = SurfaceCode::new(5).expect("valid distance-5 surface code");
858 assert_eq!(code.num_data_qubits(), 25);
859 assert_eq!(code.num_stabilizers(), 24);
860 assert_eq!(code.num_x_stabilizers(), 12);
861 assert_eq!(code.num_z_stabilizers(), 12);
862 }
863}