1use crate::quantum_ops::{content_hash, DecodeReceipt, GrantRef};
164use ed25519_dalek::SigningKey;
165
166pub const QEC_FRAC: u32 = 12;
168const SCALE: i64 = 1 << QEC_FRAC;
169const L0: i64 = SCALE;
171const MSG_MAX: i64 = 64 * SCALE;
173const ALPHA_NUM: i64 = 7;
175const GAMMA_MIN: i64 = -(6 * SCALE) / 10;
179const GAMMA_MAX: i64 = (9 * SCALE) / 10;
180
181fn splitmix64(state: &mut u64) -> u64 {
182 *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
183 let mut z = *state;
184 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
185 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
186 z ^ (z >> 31)
187}
188
189#[derive(Clone, Debug)]
197pub struct Gf2Basis {
198 words: usize,
199 rows: Vec<Vec<u64>>,
201 pivots: Vec<usize>,
203}
204
205fn bit_get(row: &[u64], i: usize) -> bool {
206 (row[i >> 6] >> (i & 63)) & 1 == 1
207}
208fn bit_set(row: &mut [u64], i: usize) {
209 row[i >> 6] |= 1u64 << (i & 63);
210}
211fn bit_xor(dst: &mut [u64], src: &[u64]) {
212 for (d, s) in dst.iter_mut().zip(src) {
213 *d ^= *s;
214 }
215}
216fn is_zero(row: &[u64]) -> bool {
217 row.iter().all(|&w| w == 0)
218}
219
220#[cfg(feature = "quantum")]
226fn rref(rows_cols: &[Vec<usize>], n: usize) -> Vec<(usize, Vec<usize>)> {
227 let mut m: Vec<Vec<bool>> = rows_cols
228 .iter()
229 .map(|cols| {
230 let mut r = vec![false; n];
231 for &c in cols {
232 r[c] = true;
233 }
234 r
235 })
236 .collect();
237 let mut pivots: Vec<usize> = Vec::new();
238 let mut r = 0usize;
239 for c in 0..n {
240 let Some(sel) = (r..m.len()).find(|&i| m[i][c]) else { continue };
241 m.swap(r, sel);
242 for i in 0..m.len() {
243 if i != r && m[i][c] {
244 for j in 0..n {
245 let v = m[r][j];
246 m[i][j] ^= v;
247 }
248 }
249 }
250 pivots.push(c);
251 r += 1;
252 if r == m.len() {
253 break;
254 }
255 }
256 m.truncate(pivots.len());
257 pivots
258 .into_iter()
259 .zip(m)
260 .map(|(p, row)| (p, (0..n).filter(|&j| row[j]).collect()))
261 .collect()
262}
263
264impl Gf2Basis {
265 pub fn reduces_to_zero(&self, cols: &[usize], ncols: usize) -> bool {
267 let mut v = vec![0u64; ncols.div_ceil(64).max(1)];
268 for &c in cols {
269 bit_set(&mut v, c);
270 }
271 for (bi, br) in self.rows.iter().enumerate() {
272 if bit_get(&v, self.pivots[bi]) {
273 bit_xor(&mut v, br);
274 }
275 }
276 is_zero(&v)
277 }
278
279 pub fn from_rows(rows_cols: &[Vec<usize>], ncols: usize) -> Gf2Basis {
281 let words = ncols.div_ceil(64).max(1);
282 let mut basis: Vec<Vec<u64>> = Vec::new();
283 let mut pivots: Vec<usize> = Vec::new();
284 for cols in rows_cols {
285 let mut v = vec![0u64; words];
286 for &c in cols {
287 bit_set(&mut v, c);
288 }
289 for (bi, br) in basis.iter().enumerate() {
291 if bit_get(&v, pivots[bi]) {
292 bit_xor(&mut v, br);
293 }
294 }
295 if let Some(p) = Self::lowest_set(&v) {
296 for br in basis.iter_mut() {
298 if bit_get(br, p) {
299 bit_xor(br, &v);
300 }
301 }
302 basis.push(v);
303 pivots.push(p);
304 }
305 }
306 Gf2Basis { words, rows: basis, pivots }
307 }
308
309 fn lowest_set(v: &[u64]) -> Option<usize> {
310 for (w, &word) in v.iter().enumerate() {
311 if word != 0 {
312 return Some(w * 64 + word.trailing_zeros() as usize);
313 }
314 }
315 None
316 }
317
318 pub fn rank(&self) -> usize {
320 self.rows.len()
321 }
322
323 pub fn contains(&self, cols: &[usize]) -> bool {
326 let mut v = vec![0u64; self.words];
327 for &c in cols {
328 bit_set(&mut v, c);
329 }
330 for (bi, br) in self.rows.iter().enumerate() {
331 if bit_get(&v, self.pivots[bi]) {
332 bit_xor(&mut v, br);
333 }
334 }
335 is_zero(&v)
336 }
337}
338
339#[derive(Clone, Debug)]
348pub struct CssCode {
349 pub id: String,
350 pub n: usize,
351 pub checks: Vec<Vec<usize>>,
353 pub stabs: Vec<Vec<usize>>,
357 stab_basis: Gf2Basis,
358 pub k: usize,
360}
361
362impl CssCode {
363 fn new(id: String, n: usize, checks: Vec<Vec<usize>>, stab: Vec<Vec<usize>>) -> CssCode {
364 let stab_basis = Gf2Basis::from_rows(&stab, n);
365 let check_basis = Gf2Basis::from_rows(&checks, n);
366 let k = n.saturating_sub(check_basis.rank() + stab_basis.rank());
367 CssCode { id, n, checks, stabs: stab, stab_basis, k }
368 }
369
370 pub fn check_matrix_bytes(&self) -> Vec<u8> {
372 let mut b = Vec::new();
373 b.extend_from_slice(b"wai:qec-checks\x01");
374 b.extend_from_slice(&(self.n as u64).to_le_bytes());
375 for row in &self.checks {
376 b.extend_from_slice(&(row.len() as u32).to_le_bytes());
377 for &c in row {
378 b.extend_from_slice(&(c as u32).to_le_bytes());
379 }
380 }
381 b
382 }
383
384 pub fn is_trivial(&self, residual_cols: &[usize]) -> bool {
387 self.stab_basis.contains(residual_cols)
388 }
389
390 pub fn toric(l: usize) -> CssCode {
396 let n = 2 * l * l;
397 let h = |i: usize, j: usize| (i % l) * l + (j % l); let v = |i: usize, j: usize| l * l + (i % l) * l + (j % l); let mut plaq = Vec::with_capacity(l * l); let mut star = Vec::with_capacity(l * l); for i in 0..l {
402 for j in 0..l {
403 let mut p = vec![h(i, j), h(i + 1, j), v(i, j), v(i, j + 1)];
405 p.sort_unstable();
406 p.dedup();
407 plaq.push(p);
408 let mut s = vec![h(i, j), h(i, j + l - 1), v(i, j), v(i + l - 1, j)];
410 s.sort_unstable();
411 s.dedup();
412 star.push(s);
413 }
414 }
415 CssCode::new(format!("toric:L{l}"), n, plaq, star)
416 }
417
418 pub fn surface(d: usize) -> CssCode {
433 assert!(d >= 3 && d % 2 == 1, "rotated surface code needs an odd distance >= 3");
434 let n = d * d;
435 let q = |r: usize, c: usize| r * d + c;
436 let mut z_checks: Vec<Vec<usize>> = Vec::new();
437 let mut x_stabs: Vec<Vec<usize>> = Vec::new();
438
439 for r in 0..d - 1 {
441 for c in 0..d - 1 {
442 let face = vec![q(r, c), q(r, c + 1), q(r + 1, c), q(r + 1, c + 1)];
443 if (r + c) % 2 == 0 {
444 z_checks.push(face);
445 } else {
446 x_stabs.push(face);
447 }
448 }
449 }
450 for c in (0..d - 1).step_by(2) {
452 x_stabs.push(vec![q(0, c), q(0, c + 1)]);
453 }
454 for c in (1..d - 1).step_by(2) {
455 x_stabs.push(vec![q(d - 1, c), q(d - 1, c + 1)]);
456 }
457 for r in (1..d - 1).step_by(2) {
459 z_checks.push(vec![q(r, 0), q(r + 1, 0)]);
460 }
461 for r in (0..d - 1).step_by(2) {
462 z_checks.push(vec![q(r, d - 1), q(r + 1, d - 1)]);
463 }
464 for v in z_checks.iter_mut().chain(x_stabs.iter_mut()) {
465 v.sort_unstable();
466 v.dedup();
467 }
468 CssCode::new(format!("surface:d{d}"), n, z_checks, x_stabs)
469 }
470
471 pub fn color_steane() -> CssCode {
486 let faces = vec![vec![3, 4, 5, 6], vec![1, 2, 5, 6], vec![0, 2, 4, 6]];
487 CssCode::new("color:steane".into(), 7, faces.clone(), faces)
488 }
489
490 pub fn is_self_dual(&self) -> bool {
497 let a = Gf2Basis::from_rows(&self.checks, self.n);
498 let b = Gf2Basis::from_rows(&self.stabs, self.n);
499 if a.rank() != b.rank() {
500 return false;
501 }
502 self.checks.iter().all(|r| b.reduces_to_zero(r, self.n))
504 && self.stabs.iter().all(|r| a.reduces_to_zero(r, self.n))
505 }
506
507 pub fn has_transversal_hadamard(&self) -> bool {
512 self.is_self_dual()
513 }
514
515 #[cfg(feature = "quantum")]
528 pub fn encode_zero_circuit(&self) -> crate::quantum::Circuit {
529 let mut c = crate::quantum::Circuit::new(self.n as u8);
530 for (pivot, cols) in rref(&self.stabs, self.n) {
531 c.h(pivot as u8);
532 for col in cols {
533 if col != pivot {
534 c.cx(pivot as u8, col as u8);
535 }
536 }
537 }
538 c
539 }
540
541 pub fn bivariate_bicycle(l: usize, m: usize, a: &[(usize, usize)], b: &[(usize, usize)]) -> CssCode {
548 let lm = l * m;
549 let n = 2 * lm;
550 let cell = |i: usize, j: usize| (i % l) * m + (j % m);
551 let build = |monos: &[(usize, usize)]| -> Vec<Vec<usize>> {
554 let mut rows = vec![Vec::new(); lm];
555 for i in 0..l {
556 for j in 0..m {
557 let col = cell(i, j);
558 for &(px, py) in monos {
559 let row = cell(i + px, j + py);
560 rows[row].push(col);
561 }
562 }
563 }
564 for r in rows.iter_mut() {
565 r.sort_unstable();
566 r.dedup();
567 }
568 rows
569 };
570 let a_rows = build(a);
571 let b_rows = build(b);
572 let transpose = |rows: &[Vec<usize>]| -> Vec<Vec<usize>> {
574 let mut t = vec![Vec::new(); lm];
575 for (r, cols) in rows.iter().enumerate() {
576 for &c in cols {
577 t[c].push(r);
578 }
579 }
580 for x in t.iter_mut() {
581 x.sort_unstable();
582 }
583 t
584 };
585 let at = transpose(&a_rows);
586 let bt = transpose(&b_rows);
587
588 let hx: Vec<Vec<usize>> = (0..lm)
590 .map(|r| {
591 let mut row: Vec<usize> = a_rows[r].clone();
592 row.extend(b_rows[r].iter().map(|&c| c + lm));
593 row.sort_unstable();
594 row
595 })
596 .collect();
597 let hz: Vec<Vec<usize>> = (0..lm)
599 .map(|r| {
600 let mut row: Vec<usize> = bt[r].clone();
601 row.extend(at[r].iter().map(|&c| c + lm));
602 row.sort_unstable();
603 row
604 })
605 .collect();
606
607 let code = CssCode::new(String::new(), n, hz, hx);
609 CssCode { id: format!("bb:[[{},{}]]", code.n, code.k), ..code }
610 }
611
612 pub fn gross() -> CssCode {
614 CssCode::bivariate_bicycle(6, 6, &[(3, 0), (0, 1), (0, 2)], &[(0, 3), (1, 0), (2, 0)])
615 }
616}
617
618struct Tanner {
623 n: usize,
624 n_edges: usize,
625 e_var: Vec<u32>, chk_range: Vec<(u32, u32)>, var_edges: Vec<Vec<u32>>, }
629
630impl Tanner {
631 fn build(checks: &[Vec<usize>], n: usize) -> Tanner {
632 let mut e_var = Vec::new();
633 let mut chk_range = Vec::with_capacity(checks.len());
634 let mut var_edges = vec![Vec::new(); n];
635 for cols in checks {
636 let start = e_var.len() as u32;
637 for &v in cols {
638 var_edges[v].push(e_var.len() as u32);
639 e_var.push(v as u32);
640 }
641 chk_range.push((start, e_var.len() as u32));
642 }
643 let n_edges = e_var.len();
644 Tanner { n, n_edges, e_var, chk_range, var_edges }
645 }
646}
647
648#[allow(clippy::too_many_arguments)]
652fn bp_leg(
653 t: &Tanner,
654 syndrome: &[bool],
655 gamma: &[i64],
656 max_iter: u32,
657 mu_v2c: &mut [i64],
658 mu_c2v: &mut [i64],
659) -> (Vec<bool>, bool, u32, u64) {
660 let mut hard = vec![false; t.n];
661 let mut messages = 0u64;
662 let mut iters = 0;
663 for _ in 0..max_iter {
664 iters += 1;
665 for (&(a, b), &s) in t.chk_range.iter().zip(syndrome) {
667 let (mut min1, mut min2) = (i64::MAX, i64::MAX);
668 let mut arg = a;
669 let mut neg_parity = false;
670 for e in a..b {
671 let x = mu_v2c[e as usize];
672 if x < 0 {
673 neg_parity = !neg_parity;
674 }
675 let mag = x.abs();
676 if mag < min1 {
677 min2 = min1;
678 min1 = mag;
679 arg = e;
680 } else if mag < min2 {
681 min2 = mag;
682 }
683 }
684 for e in a..b {
685 let x = mu_v2c[e as usize];
686 let base = if e == arg { min2 } else { min1 };
687 let mag = (base * ALPHA_NUM) >> 3;
688 let is_neg = neg_parity ^ (x < 0) ^ s;
689 mu_c2v[e as usize] = if is_neg { -mag } else { mag };
690 }
691 }
692 for v in 0..t.n {
694 let mut total = L0;
695 for &e in &t.var_edges[v] {
696 total += mu_c2v[e as usize];
697 }
698 hard[v] = total < 0;
699 let g = gamma[v];
700 for &e in &t.var_edges[v] {
701 let target = total - mu_c2v[e as usize];
702 let nv = if g == 0 {
703 target
704 } else {
705 (g * mu_v2c[e as usize] + (SCALE - g) * target) >> QEC_FRAC
706 };
707 mu_v2c[e as usize] = nv.clamp(-MSG_MAX, MSG_MAX);
708 }
709 }
710 messages += 2 * t.n_edges as u64;
711 let mut ok = true;
713 for (&(a, b), &s) in t.chk_range.iter().zip(syndrome) {
714 let mut par = false;
715 for e in a..b {
716 par ^= hard[t.e_var[e as usize] as usize];
717 }
718 if par != s {
719 ok = false;
720 break;
721 }
722 }
723 if ok {
724 return (hard, true, iters, messages);
725 }
726 }
727 (hard, false, iters, messages)
728}
729
730struct DecodeCore {
735 correction: Vec<bool>,
736 converged: bool,
737 legs: u32,
738 iters: u32,
739 messages: u64,
740}
741
742fn relay_bp(
743 t: &Tanner,
744 syndrome: &[bool],
745 seed: u64,
746 max_iter: u32,
747 max_legs: u32,
748) -> DecodeCore {
749 let mut mu_v2c = vec![L0; t.n_edges];
750 let mut mu_c2v = vec![0i64; t.n_edges];
751 let gamma0 = vec![0i64; t.n];
752 let (mut hard, mut conv, mut it, mut msg) =
753 bp_leg(t, syndrome, &gamma0, max_iter, &mut mu_v2c, &mut mu_c2v);
754 let mut legs = 1u32;
755 let (mut tot_it, mut tot_msg) = (it, msg);
756 while !conv && legs < max_legs {
757 let mut gamma = vec![0i64; t.n];
758 let mut st = seed
759 .wrapping_mul(0x100_0001)
760 .wrapping_add(legs as u64)
761 .wrapping_add(0xD15EA5E);
762 let span = (GAMMA_MAX - GAMMA_MIN) as u64 + 1;
763 for gv in gamma.iter_mut() {
764 let r = splitmix64(&mut st);
765 *gv = GAMMA_MIN + (r % span) as i64;
766 }
767 let out = bp_leg(t, syndrome, &gamma, max_iter, &mut mu_v2c, &mut mu_c2v);
768 hard = out.0;
769 conv = out.1;
770 it = out.2;
771 msg = out.3;
772 tot_it += it;
773 tot_msg += msg;
774 legs += 1;
775 }
776 let _ = it;
777 let _ = msg;
778 DecodeCore { correction: hard, converged: conv, legs, iters: tot_it, messages: tot_msg }
779}
780
781#[derive(Clone, Copy, Debug)]
787pub struct DecodeConfig {
788 pub p_fx: i64,
790 pub max_iter: u32,
792 pub max_legs: u32,
794}
795
796impl DecodeConfig {
797 pub fn from_p(p: f64) -> DecodeConfig {
798 DecodeConfig { p_fx: (p * SCALE as f64) as i64, max_iter: 32, max_legs: 12 }
799 }
800}
801
802#[derive(Clone, Debug)]
805pub struct DecodeResult {
806 pub code_id: String,
807 pub n: usize,
808 pub error: Vec<u32>, pub correction: Vec<u32>, pub syndrome: Vec<u32>, pub converged: bool,
812 pub logical_success: bool,
813 pub legs: u32,
814 pub iters: u32,
815 pub messages: u64,
816 syndrome_bits: Vec<bool>,
817 correction_bits: Vec<bool>,
818 check_matrix_hash: [u8; 32],
819}
820
821fn sample_error(n: usize, p_fx: i64, seed: u64) -> Vec<bool> {
823 let mut e = vec![false; n];
824 let mut st = seed.wrapping_mul(0x2545_F491_4F6C_DD1D).wrapping_add(1);
825 for ei in e.iter_mut() {
826 let r = (splitmix64(&mut st) >> (64 - QEC_FRAC)) as i64; if r < p_fx {
828 *ei = true;
829 }
830 }
831 e
832}
833
834fn syndrome_of(checks: &[Vec<usize>], e: &[bool]) -> Vec<bool> {
835 checks
836 .iter()
837 .map(|row| row.iter().fold(false, |acc, &q| acc ^ e[q]))
838 .collect()
839}
840
841fn bits_to_ids(bits: &[bool]) -> Vec<u32> {
842 bits.iter()
843 .enumerate()
844 .filter(|&(_, &b)| b)
845 .map(|(i, _)| i as u32)
846 .collect()
847}
848
849fn canonical_bits(tag: &[u8], bits: &[bool]) -> Vec<u8> {
850 let mut out = tag.to_vec();
851 out.extend_from_slice(&(bits.len() as u64).to_le_bytes());
852 for chunk in bits.chunks(8) {
853 let mut byte = 0u8;
854 for (i, &b) in chunk.iter().enumerate() {
855 if b {
856 byte |= 1 << i;
857 }
858 }
859 out.push(byte);
860 }
861 out
862}
863
864pub fn decode_once(code: &CssCode, cfg: &DecodeConfig, seed: u64) -> DecodeResult {
866 let t = Tanner::build(&code.checks, code.n);
867 let error = sample_error(code.n, cfg.p_fx, seed);
868 let syndrome_bits = syndrome_of(&code.checks, &error);
869 let core = relay_bp(&t, &syndrome_bits, seed, cfg.max_iter, cfg.max_legs);
870 let residual: Vec<usize> = (0..code.n)
872 .filter(|&i| error[i] ^ core.correction[i])
873 .collect();
874 let logical_success = core.converged && code.is_trivial(&residual);
875 DecodeResult {
876 code_id: code.id.clone(),
877 n: code.n,
878 error: bits_to_ids(&error),
879 correction: bits_to_ids(&core.correction),
880 syndrome: bits_to_ids(&syndrome_bits),
881 converged: core.converged,
882 logical_success,
883 legs: core.legs,
884 iters: core.iters,
885 messages: core.messages,
886 syndrome_bits,
887 correction_bits: core.correction.clone(),
888 check_matrix_hash: content_hash(&code.check_matrix_bytes()),
889 }
890}
891
892impl DecodeResult {
893 pub fn seal(
897 &self,
898 signer: &SigningKey,
899 signer_id: impl Into<String>,
900 joules_micro: u64,
901 grant: GrantRef,
902 ) -> DecodeReceipt {
903 DecodeReceipt::seal(
904 signer,
905 signer_id,
906 self.code_id.clone(),
907 "relay-bp.min-sum",
908 self.check_matrix_hash,
909 content_hash(&canonical_bits(b"wai:qec-syndrome\x01", &self.syndrome_bits)),
910 content_hash(&canonical_bits(b"wai:qec-correction\x01", &self.correction_bits)),
911 self.converged,
912 self.legs,
913 self.messages,
914 joules_micro,
915 grant,
916 None,
917 )
918 }
919
920 pub fn correction_bytes(&self) -> Vec<u8> {
922 canonical_bits(b"wai:qec-correction\x01", &self.correction_bits)
923 }
924}
925
926#[derive(Clone, Debug, PartialEq, Eq)]
929pub struct BenchResult {
930 pub trials: u32,
931 pub plain_logical_failures: u32,
932 pub relay_logical_failures: u32,
933 pub relay_total_messages: u64,
934}
935
936pub fn logical_z_support(code: &CssCode, max_weight: usize) -> Option<Vec<usize>> {
969 let n = code.n;
970 let check_basis = Gf2Basis::from_rows(&code.checks, n);
971 for w in 1..=max_weight.min(n) {
972 let mut combo: Vec<usize> = (0..w).collect();
973 loop {
974 let mut bits = vec![false; n];
975 for &q in &combo {
976 bits[q] = true;
977 }
978 if syndrome_of(&code.stabs, &bits).iter().all(|b| !b) && !check_basis.contains(&combo) {
980 return Some(combo);
981 }
982 if !next_combination(&mut combo, n) {
983 break;
984 }
985 }
986 }
987 None
988}
989
990fn next_combination(c: &mut [usize], n: usize) -> bool {
993 let w = c.len();
994 let mut i = w;
995 while i > 0 {
996 i -= 1;
997 if c[i] < n - (w - i) {
998 c[i] += 1;
999 for j in i + 1..w {
1000 c[j] = c[j - 1] + 1;
1001 }
1002 return true;
1003 }
1004 }
1005 false
1006}
1007
1008pub fn x_distance(code: &CssCode, max_weight: usize) -> Option<usize> {
1021 let n = code.n;
1022 for w in 1..=max_weight.min(n) {
1023 let mut combo: Vec<usize> = (0..w).collect();
1024 loop {
1025 let mut bits = vec![false; n];
1026 for &q in &combo {
1027 bits[q] = true;
1028 }
1029 if syndrome_of(&code.checks, &bits).iter().all(|b| !b) && !code.is_trivial(&combo) {
1031 return Some(w);
1032 }
1033 if !next_combination(&mut combo, n) {
1034 break;
1035 }
1036 }
1037 }
1038 None
1039}
1040
1041fn uf_decode_graph(
1058 nodes: usize,
1059 boundary: usize,
1060 edges: &[(usize, usize)],
1061 weights: &[u32],
1062 syndrome: &[bool],
1063) -> Vec<bool> {
1064 let mut parent: Vec<usize> = (0..nodes).collect();
1065 fn find(parent: &mut Vec<usize>, mut x: usize) -> usize {
1066 while parent[x] != x {
1067 parent[x] = parent[parent[x]];
1068 x = parent[x];
1069 }
1070 x
1071 }
1072 let mut syn: Vec<bool> = (0..nodes).map(|i| syndrome.get(i).copied().unwrap_or(false)).collect();
1073 syn[boundary] = false;
1074 let mut grown: Vec<u32> = vec![0; edges.len()];
1075 let maxw = weights.iter().copied().max().unwrap_or(1).max(1) as usize;
1076
1077 for _ in 0..(4 * nodes * maxw + 8) {
1078 let mut parity = vec![false; nodes];
1079 let mut touches = vec![false; nodes];
1080 for v in 0..nodes {
1081 let r = find(&mut parent, v);
1082 if syn[v] {
1083 parity[r] ^= true;
1084 }
1085 if v == boundary {
1086 touches[r] = true;
1087 }
1088 }
1089 if !(0..nodes).any(|v| find(&mut parent, v) == v && parity[v] && !touches[v]) {
1090 break;
1091 }
1092 let mut to_union: Vec<usize> = Vec::new();
1093 for (ei, &(a, b)) in edges.iter().enumerate() {
1094 if grown[ei] >= 2 * weights.get(ei).copied().unwrap_or(1).max(1) {
1095 continue;
1096 }
1097 let (ra, rb) = (find(&mut parent, a), find(&mut parent, b));
1098 let odd_a = parity[ra] && !touches[ra];
1099 let odd_b = parity[rb] && !touches[rb];
1100 let inc = if ra == rb { u8::from(odd_a) } else { u8::from(odd_a) + u8::from(odd_b) };
1101 if inc > 0 {
1102 let cap = 2 * weights.get(ei).copied().unwrap_or(1).max(1);
1103 grown[ei] = grown[ei].saturating_add(inc as u32);
1104 if grown[ei] >= cap {
1105 to_union.push(ei);
1106 }
1107 }
1108 }
1109 for ei in to_union {
1110 let (a, b) = edges[ei];
1111 let (ra, rb) = (find(&mut parent, a), find(&mut parent, b));
1112 if ra != rb {
1113 parent[ra] = rb;
1114 }
1115 }
1116 }
1117
1118 let mut adj: Vec<Vec<(usize, usize)>> = vec![Vec::new(); nodes];
1119 for (ei, &(a, b)) in edges.iter().enumerate() {
1120 if grown[ei] >= 2 * weights.get(ei).copied().unwrap_or(1).max(1) {
1121 adj[a].push((b, ei));
1122 adj[b].push((a, ei));
1123 }
1124 }
1125 let mut picked = vec![false; edges.len()];
1126 let mut seen = vec![false; nodes];
1127 for root in std::iter::once(boundary).chain(0..nodes) {
1128 if seen[root] {
1129 continue;
1130 }
1131 let mut order = vec![root];
1132 let mut parent_edge: Vec<Option<(usize, usize)>> = vec![None; nodes];
1133 seen[root] = true;
1134 let mut i = 0;
1135 while i < order.len() {
1136 let v = order[i];
1137 i += 1;
1138 for &(w, ei) in &adj[v] {
1139 if !seen[w] {
1140 seen[w] = true;
1141 parent_edge[w] = Some((v, ei));
1142 order.push(w);
1143 }
1144 }
1145 }
1146 for &v in order.iter().rev() {
1147 if v == root || !syn[v] {
1148 continue;
1149 }
1150 if let Some((u, ei)) = parent_edge[v] {
1151 picked[ei] ^= true;
1152 syn[v] = false;
1153 syn[u] ^= true;
1154 }
1155 }
1156 }
1157 picked
1158}
1159
1160pub fn union_find_correct(code: &CssCode, syndrome: &[bool]) -> Option<Vec<bool>> {
1174 let m = code.checks.len();
1175 let boundary = m;
1176 let mut owners: Vec<Vec<usize>> = vec![Vec::new(); code.n];
1177 for (ci, chk) in code.checks.iter().enumerate() {
1178 for &q in chk {
1179 owners[q].push(ci);
1180 }
1181 }
1182 let mut edges: Vec<(usize, usize)> = Vec::new();
1183 let mut edge_qubit: Vec<usize> = Vec::new();
1184 for (q, own) in owners.iter().enumerate() {
1185 match own.len() {
1186 0 => {}
1187 1 => {
1188 edges.push((own[0], boundary));
1189 edge_qubit.push(q);
1190 }
1191 2 => {
1192 edges.push((own[0], own[1]));
1193 edge_qubit.push(q);
1194 }
1195 _ => return None,
1196 }
1197 }
1198 let weights = vec![1u32; edges.len()];
1199 let picked = uf_decode_graph(m + 1, boundary, &edges, &weights, syndrome);
1200 let mut correction = vec![false; code.n];
1201 for (ei, &on) in picked.iter().enumerate() {
1202 if on {
1203 correction[edge_qubit[ei]] ^= true;
1204 }
1205 }
1206 Some(correction)
1207}
1208
1209fn phenom_correction(code: &CssCode, cfg: &PhenomConfig, detector_bits: &[bool]) -> Option<Vec<bool>> {
1213 let m = code.checks.len();
1214 let t_rounds = cfg.rounds.max(1) as usize;
1215 let layers = t_rounds + 1;
1216 let mut owners: Vec<Vec<usize>> = vec![Vec::new(); code.n];
1217 for (ci, chk) in code.checks.iter().enumerate() {
1218 for &q in chk {
1219 owners[q].push(ci);
1220 }
1221 }
1222 if owners.iter().any(|o| o.len() > 2) {
1223 return None;
1224 }
1225 let boundary = layers * m;
1227 let node = |c: usize, t: usize| t * m + c;
1228 let cost = |rate_fx: i64| -> u32 {
1232 let r = rate_fx as f64 / 4096.0;
1233 if r <= 0.0 { 0 } else { (-r.ln()).round().clamp(1.0, 12.0) as u32 }
1234 };
1235 let w_space = cost(cfg.p_data_fx);
1236 let w_time = cost(cfg.q_meas_fx);
1237 let mut edges: Vec<(usize, usize)> = Vec::new();
1238 let mut weights: Vec<u32> = Vec::new();
1239 let mut space_qubit: Vec<Option<usize>> = Vec::new();
1241 if w_space > 0 {
1242 for t in 0..layers {
1243 for (q, own) in owners.iter().enumerate() {
1244 match own.len() {
1245 1 => {
1246 edges.push((node(own[0], t), boundary));
1247 weights.push(w_space);
1248 space_qubit.push(Some(q));
1249 }
1250 2 => {
1251 edges.push((node(own[0], t), node(own[1], t)));
1252 weights.push(w_space);
1253 space_qubit.push(Some(q));
1254 }
1255 _ => {}
1256 }
1257 }
1258 }
1259 }
1260 if w_time > 0 {
1262 for t in 0..layers - 1 {
1263 for c in 0..m {
1264 edges.push((node(c, t), node(c, t + 1)));
1265 weights.push(w_time);
1266 space_qubit.push(None);
1267 }
1268 }
1269 }
1270
1271 let picked = uf_decode_graph(boundary + 1, boundary, &edges, &weights, detector_bits);
1272 let mut correction = vec![false; code.n];
1273 for (ei, &on) in picked.iter().enumerate() {
1274 if on && let Some(q) = space_qubit[ei] {
1275 correction[q] ^= true;
1276 }
1277 }
1278
1279 Some(correction)
1280}
1281
1282#[derive(Clone, Copy, Debug)]
1293pub struct PhenomConfig {
1294 pub rounds: u32,
1296 pub p_data_fx: i64,
1298 pub q_meas_fx: i64,
1300}
1301
1302impl PhenomConfig {
1303 pub fn from_p(rounds: u32, p_data: f64, q_meas: f64) -> PhenomConfig {
1304 PhenomConfig {
1305 rounds,
1306 p_data_fx: (p_data * 4096.0) as i64,
1307 q_meas_fx: (q_meas * 4096.0) as i64,
1308 }
1309 }
1310}
1311
1312#[derive(Clone, Debug)]
1314pub struct PhenomResult {
1315 pub rounds: u32,
1316 pub detectors: Vec<u32>,
1318 pub data_error: Vec<u32>,
1320 pub correction: Vec<u32>,
1321 pub logical_success: bool,
1322}
1323
1324pub fn decode_phenomenological(
1327 code: &CssCode,
1328 cfg: &PhenomConfig,
1329 seed: u64,
1330) -> Option<PhenomResult> {
1331 let m = code.checks.len();
1332 let t_rounds = cfg.rounds.max(1) as usize;
1333 let layers = t_rounds + 1; let mut owners: Vec<Vec<usize>> = vec![Vec::new(); code.n];
1337 for (ci, chk) in code.checks.iter().enumerate() {
1338 for &q in chk {
1339 owners[q].push(ci);
1340 }
1341 }
1342 if owners.iter().any(|o| o.len() > 2) {
1343 return None;
1344 }
1345
1346 let mut st = seed ^ 0x51ED_2701_A55A_1234;
1348 let draw = |rate: i64, st: &mut u64| -> bool {
1349 *st = st
1350 .wrapping_mul(6364136223846793005)
1351 .wrapping_add(1442695040888963407);
1352 (((*st >> 40) & 0xFFF) as i64) < rate
1353 };
1354 let mut cum_err = vec![false; code.n];
1355 let mut s_prev = vec![false; m];
1356 let mut detector_bits = vec![false; layers * m];
1357 for t in 0..layers {
1358 if t < t_rounds {
1359 for q in 0..code.n {
1360 if draw(cfg.p_data_fx, &mut st) {
1361 cum_err[q] ^= true;
1362 }
1363 }
1364 }
1365 let s_true = syndrome_of(&code.checks, &cum_err);
1366 let mut s_obs = s_true.clone();
1367 if t < t_rounds {
1368 for c in 0..m {
1369 if draw(cfg.q_meas_fx, &mut st) {
1370 s_obs[c] ^= true;
1371 }
1372 }
1373 }
1374 for c in 0..m {
1375 detector_bits[t * m + c] = s_obs[c] ^ s_prev[c];
1376 }
1377 s_prev = s_obs;
1378 }
1379
1380 let correction = phenom_correction(code, cfg, &detector_bits)?;
1381 let residual: Vec<usize> = (0..code.n).filter(|&i| cum_err[i] ^ correction[i]).collect();
1382 Some(PhenomResult {
1383 rounds: cfg.rounds,
1384 detectors: bits_to_ids(&detector_bits),
1385 data_error: bits_to_ids(&cum_err),
1386 correction: bits_to_ids(&correction),
1387 logical_success: code.is_trivial(&residual),
1388 })
1389}
1390
1391#[derive(Clone, Copy, Debug)]
1413pub struct CircuitConfig {
1414 pub rounds: u32,
1415 pub p_data_fx: i64,
1417 pub q_meas_fx: i64,
1419 pub hook_fx: i64,
1421}
1422
1423impl CircuitConfig {
1424 pub fn from_p(rounds: u32, p_data: f64, q_meas: f64, hook: f64) -> CircuitConfig {
1425 CircuitConfig {
1426 rounds,
1427 p_data_fx: (p_data * 4096.0) as i64,
1428 q_meas_fx: (q_meas * 4096.0) as i64,
1429 hook_fx: (hook * 4096.0) as i64,
1430 }
1431 }
1432 fn phenom(&self) -> PhenomConfig {
1433 PhenomConfig { rounds: self.rounds, p_data_fx: self.p_data_fx, q_meas_fx: self.q_meas_fx }
1434 }
1435}
1436
1437#[derive(Clone, Debug)]
1440pub struct CircuitResult {
1441 pub rounds: u32,
1442 pub data_error: Vec<u32>,
1443 pub correction: Vec<u32>,
1444 pub hook_events: Vec<u32>,
1445 pub logical_success: bool,
1446}
1447
1448pub fn decode_circuit_level(
1451 code: &CssCode,
1452 cfg: &CircuitConfig,
1453 seed: u64,
1454) -> Option<CircuitResult> {
1455 let m = code.checks.len();
1456 let t_rounds = cfg.rounds.max(1) as usize;
1457 let layers = t_rounds + 1;
1458
1459 let mut owners: Vec<Vec<usize>> = vec![Vec::new(); code.n];
1460 for (ci, chk) in code.checks.iter().enumerate() {
1461 for &q in chk {
1462 owners[q].push(ci);
1463 }
1464 }
1465 if owners.iter().any(|o| o.len() > 2) {
1466 return None;
1467 }
1468
1469 let mut st = seed ^ 0x51ED_2701_A55A_1234;
1473 let draw = |rate: i64, st: &mut u64| -> bool {
1474 *st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1475 (((*st >> 40) & 0xFFF) as i64) < rate
1476 };
1477 let pick = |bound: usize, st: &mut u64| -> usize {
1478 *st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1479 if bound == 0 { 0 } else { ((*st >> 33) as usize) % bound }
1480 };
1481
1482 let mut cum_err = vec![false; code.n];
1483 let mut s_prev = vec![false; m];
1484 let mut detector_bits = vec![false; layers * m];
1485 let mut hook_events: Vec<u32> = Vec::new();
1486
1487 for t in 0..layers {
1488 if t < t_rounds {
1489 for q in 0..code.n {
1490 if draw(cfg.p_data_fx, &mut st) {
1491 cum_err[q] ^= true;
1492 }
1493 }
1494 if cfg.hook_fx > 0 {
1497 for stab in &code.stabs {
1498 if !stab.is_empty() && draw(cfg.hook_fx, &mut st) {
1499 let j = pick(stab.len(), &mut st);
1500 for &q in &stab[j..] {
1501 cum_err[q] ^= true;
1502 }
1503 hook_events.push((stab.len() - j) as u32);
1504 }
1505 }
1506 }
1507 }
1508 let s_true = syndrome_of(&code.checks, &cum_err);
1509 let mut s_obs = s_true.clone();
1510 if t < t_rounds {
1511 for c in 0..m {
1512 if draw(cfg.q_meas_fx, &mut st) {
1513 s_obs[c] ^= true;
1514 }
1515 }
1516 }
1517 for c in 0..m {
1518 detector_bits[t * m + c] = s_obs[c] ^ s_prev[c];
1519 }
1520 s_prev = s_obs;
1521 }
1522
1523 let correction = phenom_correction(code, &cfg.phenom(), &detector_bits)?;
1524 let residual: Vec<usize> = (0..code.n).filter(|&i| cum_err[i] ^ correction[i]).collect();
1525 Some(CircuitResult {
1526 rounds: cfg.rounds,
1527 data_error: bits_to_ids(&cum_err),
1528 correction: bits_to_ids(&correction),
1529 hook_events,
1530 logical_success: code.is_trivial(&residual),
1531 })
1532}
1533
1534#[derive(Clone, Debug, PartialEq, Eq)]
1536pub struct DemMechanism {
1537 pub detectors: Vec<u32>,
1539 pub flips_observable: bool,
1541 pub probability_ppm: u32,
1543}
1544
1545pub fn to_dem(code: &CssCode, cfg: &CircuitConfig, max_logical_weight: usize) -> Option<String> {
1567 let mechs = dem_mechanisms(code, cfg, max_logical_weight)?;
1568 let m = code.checks.len();
1569 let layers = cfg.rounds.max(1) as usize + 1;
1570 let mut s = String::new();
1571 s.push_str("# detector error model emitted by wai-quantum\n");
1572 s.push_str(&format!("# code={} rounds={} detectors={}\n", code.id, cfg.rounds, layers * m));
1573 for t in 0..layers {
1574 for c in 0..m {
1575 s.push_str(&format!("detector({c}, {t}) D{}\n", t * m + c));
1576 }
1577 }
1578 for mech in &mechs {
1579 let p = mech.probability_ppm as f64 / 1.0e6;
1580 let mut line = format!("error({p:.9})");
1581 for d in &mech.detectors {
1582 line.push_str(&format!(" D{d}"));
1583 }
1584 if mech.flips_observable {
1585 line.push_str(" L0");
1586 }
1587 s.push('\n');
1588 s.truncate(s.len() - 1);
1589 s.push_str(&line);
1590 s.push('\n');
1591 }
1592 Some(s)
1593}
1594
1595pub fn dem_mechanisms(
1598 code: &CssCode,
1599 cfg: &CircuitConfig,
1600 max_logical_weight: usize,
1601) -> Option<Vec<DemMechanism>> {
1602 let m = code.checks.len();
1603 let t_rounds = cfg.rounds.max(1) as usize;
1604
1605 let mut owners: Vec<Vec<usize>> = vec![Vec::new(); code.n];
1606 for (ci, chk) in code.checks.iter().enumerate() {
1607 for &q in chk {
1608 owners[q].push(ci);
1609 }
1610 }
1611 if owners.iter().any(|o| o.len() > 2) {
1612 return None;
1613 }
1614 let logical = logical_z_support(code, max_logical_weight)?;
1615 let flips = |qs: &[usize]| qs.iter().filter(|q| logical.contains(q)).count() % 2 == 1;
1616 let ppm = |fx: i64| ((fx as f64 / 4096.0) * 1.0e6).round().max(0.0) as u32;
1617
1618 let mut out: Vec<DemMechanism> = Vec::new();
1619 if cfg.p_data_fx > 0 {
1621 for t in 0..t_rounds {
1622 for q in 0..code.n {
1623 let mut ds: Vec<u32> = owners[q].iter().map(|&c| (t * m + c) as u32).collect();
1624 ds.sort_unstable();
1625 out.push(DemMechanism {
1626 detectors: ds,
1627 flips_observable: flips(&[q]),
1628 probability_ppm: ppm(cfg.p_data_fx),
1629 });
1630 }
1631 }
1632 }
1633 if cfg.q_meas_fx > 0 {
1635 for t in 0..t_rounds {
1636 for c in 0..m {
1637 out.push(DemMechanism {
1638 detectors: vec![(t * m + c) as u32, ((t + 1) * m + c) as u32],
1639 flips_observable: false,
1640 probability_ppm: ppm(cfg.q_meas_fx),
1641 });
1642 }
1643 }
1644 }
1645 if cfg.hook_fx > 0 {
1647 for t in 0..t_rounds {
1648 for stab in &code.stabs {
1649 if stab.is_empty() {
1650 continue;
1651 }
1652 let each = ppm(cfg.hook_fx) / stab.len() as u32;
1653 for j in 0..stab.len() {
1654 let qs = &stab[j..];
1655 let mut set: Vec<u32> = Vec::new();
1656 for &q in qs {
1657 for &c in &owners[q] {
1658 let d = (t * m + c) as u32;
1659 if let Some(pos) = set.iter().position(|&x| x == d) {
1660 set.remove(pos); } else {
1662 set.push(d);
1663 }
1664 }
1665 }
1666 set.sort_unstable();
1667 let obs = flips(qs);
1671 if set.is_empty() && !obs {
1672 continue;
1673 }
1674 out.push(DemMechanism {
1675 detectors: set,
1676 flips_observable: obs,
1677 probability_ppm: each,
1678 });
1679 }
1680 }
1681 }
1682 }
1683 Some(out)
1684}
1685
1686#[derive(Clone, Debug, PartialEq, Eq)]
1688pub struct DemModel {
1689 pub num_detectors: usize,
1690 pub mechanisms: Vec<DemMechanism>,
1691}
1692
1693pub fn from_dem(src: &str) -> Result<DemModel, String> {
1702 let mut mechanisms = Vec::new();
1703 let mut max_det: i64 = -1;
1704 for (lineno, raw) in src.lines().enumerate() {
1705 let line = raw.split('#').next().unwrap_or("").trim();
1706 if line.is_empty() {
1707 continue;
1708 }
1709 let head = line.split(['(', ' ']).next().unwrap_or("");
1710 match head {
1711 "detector" | "shift_detectors" | "logical_observable" | "observable_include" => {
1712 for tok in line.split_whitespace() {
1713 if let Some(d) = tok.strip_prefix('D')
1714 && let Ok(i) = d.trim_end_matches(',').parse::<i64>()
1715 {
1716 max_det = max_det.max(i);
1717 }
1718 }
1719 }
1720 "error" => {
1721 let open = line.find('(').ok_or_else(|| format!("line {}: error without probability", lineno + 1))?;
1722 let close = line[open..].find(')').ok_or_else(|| format!("line {}: unclosed probability", lineno + 1))? + open;
1723 let p: f64 = line[open + 1..close]
1724 .trim()
1725 .parse()
1726 .map_err(|_| format!("line {}: bad probability", lineno + 1))?;
1727 if !(0.0..=1.0).contains(&p) {
1728 return Err(format!("line {}: probability {p} outside [0,1]", lineno + 1));
1729 }
1730 let mut detectors = Vec::new();
1731 let mut flips = false;
1732 for tok in line[close + 1..].split_whitespace() {
1733 if let Some(d) = tok.strip_prefix('D') {
1734 let i: u32 = d.parse().map_err(|_| format!("line {}: bad detector {tok}", lineno + 1))?;
1735 max_det = max_det.max(i as i64);
1736 detectors.push(i);
1737 } else if let Some(l) = tok.strip_prefix('L') {
1738 let _: u32 = l.parse().map_err(|_| format!("line {}: bad observable {tok}", lineno + 1))?;
1739 flips = !flips;
1740 } else {
1741 return Err(format!("line {}: unexpected target {tok}", lineno + 1));
1742 }
1743 }
1744 detectors.sort_unstable();
1745 if detectors.is_empty() && !flips {
1746 continue; }
1748 mechanisms.push(DemMechanism {
1749 detectors,
1750 flips_observable: flips,
1751 probability_ppm: (p * 1.0e6).round().clamp(0.0, 1.0e6) as u32,
1752 });
1753 }
1754 other => return Err(format!("line {}: unsupported DEM instruction `{other}`", lineno + 1)),
1755 }
1756 }
1757 Ok(DemModel { num_detectors: (max_det + 1).max(0) as usize, mechanisms })
1758}
1759
1760#[derive(Clone, Debug)]
1762pub struct DemDecode {
1763 pub mechanisms: Vec<u32>,
1765 pub observable_flipped: bool,
1767}
1768
1769pub fn decode_dem(model: &DemModel, detectors: &[bool]) -> Result<DemDecode, String> {
1780 let nd = model.num_detectors;
1781 let boundary = nd;
1782 let mut edges: Vec<(usize, usize)> = Vec::new();
1783 let mut weights: Vec<u32> = Vec::new();
1784 let mut index: Vec<usize> = Vec::new();
1785 for (i, m) in model.mechanisms.iter().enumerate() {
1786 let w = {
1787 let p = m.probability_ppm as f64 / 1.0e6;
1788 if p <= 0.0 { continue } else { (-p.ln()).round().clamp(1.0, 12.0) as u32 }
1789 };
1790 match m.detectors.len() {
1791 0 => {}
1792 1 => {
1793 edges.push((m.detectors[0] as usize, boundary));
1794 weights.push(w);
1795 index.push(i);
1796 }
1797 2 => {
1798 edges.push((m.detectors[0] as usize, m.detectors[1] as usize));
1799 weights.push(w);
1800 index.push(i);
1801 }
1802 k => {
1803 return Err(format!(
1804 "mechanism {i} touches {k} detectors; matching needs at most two — \
1805 decompose the hyperedge or use a decoder that accepts one"
1806 ));
1807 }
1808 }
1809 }
1810 let picked = uf_decode_graph(nd + 1, boundary, &edges, &weights, detectors);
1811 let mut used = Vec::new();
1812 let mut flipped = false;
1813 for (ei, &on) in picked.iter().enumerate() {
1814 if on {
1815 let mi = index[ei];
1816 used.push(mi as u32);
1817 flipped ^= model.mechanisms[mi].flips_observable;
1818 }
1819 }
1820 Ok(DemDecode { mechanisms: used, observable_flipped: flipped })
1821}
1822
1823pub fn benchmark_circuit_level(
1825 code: &CssCode,
1826 cfg: &CircuitConfig,
1827 trials: u32,
1828 seed: u64,
1829) -> (u32, u32) {
1830 let mut fails = 0;
1831 for tix in 0..trials {
1832 let s = seed.wrapping_add(tix as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
1833 match decode_circuit_level(code, cfg, s) {
1834 Some(r) if r.logical_success => {}
1835 _ => fails += 1,
1836 }
1837 }
1838 (trials, fails)
1839}
1840
1841pub fn benchmark_phenomenological(
1843 code: &CssCode,
1844 cfg: &PhenomConfig,
1845 trials: u32,
1846 seed: u64,
1847) -> (u32, u32) {
1848 let mut fails = 0;
1849 for tix in 0..trials {
1850 let s = seed.wrapping_add(tix as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
1851 match decode_phenomenological(code, cfg, s) {
1852 Some(r) if r.logical_success => {}
1853 _ => fails += 1,
1854 }
1855 }
1856 (trials, fails)
1857}
1858
1859pub fn decode_once_uf(code: &CssCode, cfg: &DecodeConfig, seed: u64) -> DecodeResult {
1862 let error = sample_error(code.n, cfg.p_fx, seed);
1863 let syndrome_bits = syndrome_of(&code.checks, &error);
1864 let (correction, converged) = match union_find_correct(code, &syndrome_bits) {
1865 Some(c) => (c, true),
1866 None => (vec![false; code.n], false),
1867 };
1868 let residual: Vec<usize> = (0..code.n).filter(|&i| error[i] ^ correction[i]).collect();
1869 let logical_success = converged && code.is_trivial(&residual);
1870 DecodeResult {
1871 code_id: code.id.clone(),
1872 n: code.n,
1873 error: bits_to_ids(&error),
1874 correction: bits_to_ids(&correction),
1875 syndrome: bits_to_ids(&syndrome_bits),
1876 converged,
1877 logical_success,
1878 legs: 0,
1879 iters: 0,
1880 messages: 0,
1881 syndrome_bits,
1882 correction_bits: correction,
1883 check_matrix_hash: content_hash(&code.check_matrix_bytes()),
1884 }
1885}
1886
1887pub fn benchmark_uf(code: &CssCode, p: f64, trials: u32, seed: u64) -> (u32, u32) {
1889 let cfg = DecodeConfig::from_p(p);
1890 let mut fails = 0;
1891 for tix in 0..trials {
1892 let s = seed.wrapping_add(tix as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
1893 if !decode_once_uf(code, &cfg, s).logical_success {
1894 fails += 1;
1895 }
1896 }
1897 (trials, fails)
1898}
1899
1900pub fn benchmark(code: &CssCode, p: f64, trials: u32, seed: u64, max_legs: u32) -> BenchResult {
1901 let base = DecodeConfig::from_p(p);
1902 let plain = DecodeConfig { max_legs: 1, ..base };
1903 let relay = DecodeConfig { max_legs, ..base };
1904 let mut pf = 0;
1905 let mut rf = 0;
1906 let mut msg = 0u64;
1907 for tix in 0..trials {
1908 let s = seed.wrapping_add(tix as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
1909 if !decode_once(code, &plain, s).logical_success {
1910 pf += 1;
1911 }
1912 let r = decode_once(code, &relay, s);
1913 if !r.logical_success {
1914 rf += 1;
1915 }
1916 msg += r.messages;
1917 }
1918 BenchResult { trials, plain_logical_failures: pf, relay_logical_failures: rf, relay_total_messages: msg }
1919}
1920
1921#[cfg(test)]
1922mod tests {
1923 use super::*;
1924
1925 fn key(s: u8) -> SigningKey {
1926 SigningKey::from_bytes(&[s; 32])
1927 }
1928
1929 #[test]
1932 fn steane_is_a_distance_three_colour_code() {
1933 let c = CssCode::color_steane();
1934 assert_eq!(c.n, 7);
1935 assert_eq!(c.k, 1, "one logical qubit");
1936 assert_eq!(x_distance(&c, 5), Some(3), "distance must be found, not claimed");
1937 assert!(c.is_self_dual(), "a colour code's two stabilizer families coincide");
1938 assert!(c.has_transversal_hadamard());
1939 }
1940
1941 #[test]
1945 fn the_surface_code_has_no_transversal_hadamard() {
1946 let s = CssCode::surface(3);
1947 assert!(!s.is_self_dual(), "surface X and Z families sit on different supports");
1948 assert!(!s.has_transversal_hadamard());
1949 }
1950
1951 #[cfg(feature = "quantum")]
1955 #[test]
1956 fn the_encoder_prepares_a_codeword() {
1957 for code in [CssCode::color_steane(), CssCode::surface(3)] {
1958 let sv = code.encode_zero_circuit().simulate().unwrap();
1959 for (i, a) in sv.amps.iter().enumerate() {
1962 if a.norm2() == 0 {
1963 continue;
1964 }
1965 for chk in &code.checks {
1966 let par = chk.iter().filter(|&&q| (i >> q) & 1 == 1).count() % 2;
1967 assert_eq!(par, 0, "{} left basis state {i:b} outside the code", code.id);
1968 }
1969 }
1970 }
1971 }
1972
1973 #[cfg(feature = "quantum_info")]
1978 #[test]
1979 fn transversal_hadamard_implements_a_logical_hadamard() {
1980 use crate::quantum::ONE;
1981 use crate::quantum_info::{expect_pauli, Pauli};
1982
1983 let code = CssCode::color_steane();
1984 let zl: Vec<(u8, Pauli)> = (0..7).map(|q| (q, Pauli::Z)).collect();
1985 let xl: Vec<(u8, Pauli)> = (0..7).map(|q| (q, Pauli::X)).collect();
1986 let near = |got: i64, want: i64, what: &str| {
1987 assert!((got - want).abs() < 1 << 13, "{what}: got {got}, want {want}");
1988 };
1989
1990 let mut c = code.encode_zero_circuit();
1992 let sv = c.simulate().unwrap();
1993 near(expect_pauli(&sv, &zl), ONE, "<Z_L> on |0>_L");
1994 near(expect_pauli(&sv, &xl), 0, "<X_L> on |0>_L");
1995
1996 for q in 0..7 {
1998 c.h(q);
1999 }
2000 let out = c.simulate().unwrap();
2001
2002 near(expect_pauli(&out, &xl), ONE, "<X_L> after transversal H");
2004 near(expect_pauli(&out, &zl), 0, "<Z_L> after transversal H");
2005
2006 for chk in &code.checks {
2008 let zs: Vec<(u8, Pauli)> = chk.iter().map(|&q| (q as u8, Pauli::Z)).collect();
2009 let xs: Vec<(u8, Pauli)> = chk.iter().map(|&q| (q as u8, Pauli::X)).collect();
2010 near(expect_pauli(&out, &zs), ONE, "Z stabilizer after transversal H");
2011 near(expect_pauli(&out, &xs), ONE, "X stabilizer after transversal H");
2012 }
2013 }
2014
2015 #[cfg(feature = "quantum_info")]
2018 #[test]
2019 fn transversal_hadamard_breaks_the_surface_code() {
2020 use crate::quantum::ONE;
2021 use crate::quantum_info::{expect_pauli, Pauli};
2022
2023 let code = CssCode::surface(3);
2024 let mut c = code.encode_zero_circuit();
2025 for q in 0..code.n as u8 {
2026 c.h(q);
2027 }
2028 let out = c.simulate().unwrap();
2029 let violated = code.checks.iter().any(|chk| {
2030 let zs: Vec<(u8, Pauli)> = chk.iter().map(|&q| (q as u8, Pauli::Z)).collect();
2031 (expect_pauli(&out, &zs) - ONE).abs() >= 1 << 13
2032 });
2033 assert!(violated, "transversal H must knock the surface code out of its code space");
2034 }
2035
2036 #[test]
2037 fn gf2_membership() {
2038 let basis = Gf2Basis::from_rows(&[vec![0, 1], vec![1, 2]], 3);
2040 assert_eq!(basis.rank(), 2);
2041 assert!(basis.contains(&[0, 1]));
2042 assert!(basis.contains(&[1, 2]));
2043 assert!(basis.contains(&[0, 2]));
2044 assert!(basis.contains(&[])); assert!(!basis.contains(&[0]));
2046 assert!(!basis.contains(&[2]));
2047 }
2048
2049
2050 #[test]
2055 fn surface_code_is_a_valid_css_code() {
2056 for d in [3usize, 5, 7] {
2057 let code = CssCode::surface(d);
2058 assert_eq!(code.n, d * d, "d={d} qubit count");
2059 assert_eq!(code.k, 1, "d={d} must encode exactly one logical qubit");
2060 let x: Vec<&Vec<usize>> = code.stabs.iter().collect();
2061 for zc in &code.checks {
2062 for xs in &x {
2063 let overlap = zc.iter().filter(|q| xs.contains(q)).count();
2064 assert_eq!(overlap % 2, 0, "d={d}: X{xs:?} and Z{zc:?} anticommute");
2065 }
2066 }
2067 assert_eq!(code.checks.len(), (d * d - 1) / 2, "d={d} Z-check count");
2069 assert_eq!(x.len(), (d * d - 1) / 2, "d={d} X-stabilizer count");
2070 for v in code.checks.iter().chain(x.iter().copied()) {
2072 assert!(v.len() == 4 || v.len() == 2, "d={d}: weight {} stabilizer", v.len());
2073 }
2074 }
2075 }
2076
2077 #[test]
2081 fn surface_code_decodes() {
2082 let d3 = CssCode::surface(3);
2083 let r = benchmark(&d3, 0.01, 500, 7, 4);
2084 let relay = r.relay_logical_failures as f64 / r.trials as f64;
2085 assert!(relay <= 0.05, "d=3 at p=0.01 should rarely fail: {relay}");
2086 assert!(
2087 r.relay_logical_failures <= r.plain_logical_failures,
2088 "relay {} must not do worse than plain BP {}",
2089 r.relay_logical_failures,
2090 r.plain_logical_failures
2091 );
2092 }
2093
2094 #[test]
2104 fn relay_beats_plain_bp_on_qldpc() {
2105 let bb = CssCode::gross();
2106 let r = benchmark(&bb, 0.06, 400, 11, 16);
2107 assert!(
2108 r.relay_logical_failures < r.plain_logical_failures,
2109 "relay {} should beat plain BP {} on a qLDPC code",
2110 r.relay_logical_failures,
2111 r.plain_logical_failures
2112 );
2113 }
2114
2115 #[test]
2116 #[ignore]
2117 fn probe_surface_threshold() {
2118 println!("\n-- relay legs on the code Relay-BP was designed for: BB [[72,12]] --");
2119 let bb = CssCode::gross();
2120 println!(" p legs=1(plain) legs=4 legs=16");
2121 for &pp in &[0.02f64, 0.04, 0.06] {
2122 let f = |legs: u32| {
2123 let r = benchmark(&bb, pp, 400, 11, legs);
2124 r.relay_logical_failures as f64 / r.trials as f64
2125 };
2126 let plain = {
2127 let r = benchmark(&bb, pp, 400, 11, 4);
2128 r.plain_logical_failures as f64 / r.trials as f64
2129 };
2130 println!(" {pp:<8} {plain:<13.4} {:<9.4} {:<9.4}", f(4), f(16));
2131 }
2132 println!("\n-- the same sweep on topological codes --");
2133 println!(" p surf d=3 surf d=5 toric L=3 toric L=5");
2134 for &pp in &[0.001f64, 0.005, 0.01] {
2135 let sf = |d: usize| {
2136 let r = benchmark(&CssCode::surface(d), pp, 1500, 11, 16);
2137 r.relay_logical_failures as f64 / r.trials as f64
2138 };
2139 let tf = |l: usize| {
2140 let r = benchmark(&CssCode::toric(l), pp, 1500, 11, 16);
2141 r.relay_logical_failures as f64 / r.trials as f64
2142 };
2143 println!(" {pp:<8} {:<9.4} {:<9.4} {:<9.4} {:<9.4}", sf(3), sf(5), tf(3), tf(5));
2144 }
2145 }
2146
2147
2148
2149 #[test]
2153 fn union_find_makes_distance_count() {
2154 let p = 0.01;
2155 let rate = |d: usize| {
2156 let (t, f) = benchmark_uf(&CssCode::surface(d), p, 1500, 11);
2157 f as f64 / t as f64
2158 };
2159 let (r3, r5) = (rate(3), rate(5));
2160 assert!(r5 < r3, "d=5 ({r5}) must beat d=3 ({r3}) below threshold");
2161 assert!(r5 <= 0.005, "d=5 at p={p} should almost never fail: {r5}");
2162 }
2163
2164 #[test]
2166 fn union_find_beats_bp_on_topological_codes() {
2167 let p = 0.02;
2168 for d in [5usize, 7] {
2169 let code = CssCode::surface(d);
2170 let bp = {
2171 let r = benchmark(&code, p, 1000, 11, 16);
2172 r.relay_logical_failures as f64 / r.trials as f64
2173 };
2174 let (t, f) = benchmark_uf(&code, p, 1000, 11);
2175 let uf = f as f64 / t as f64;
2176 assert!(uf < bp, "d={d}: union-find {uf} should beat Relay-BP {bp}");
2177 }
2178 }
2179
2180 #[test]
2183 fn union_find_refuses_non_matching_codes() {
2184 assert!(
2185 union_find_correct(&CssCode::gross(), &vec![false; CssCode::gross().checks.len()]).is_none(),
2186 "a bivariate-bicycle qubit sits in three checks; UF must decline"
2187 );
2188 assert!(union_find_correct(&CssCode::surface(5), &vec![false; 12]).is_some());
2189 }
2190
2191 #[test]
2193 fn union_find_corrects_every_single_qubit_error() {
2194 for d in [3usize, 5] {
2195 let code = CssCode::surface(d);
2196 for q in 0..code.n {
2197 let mut err = vec![false; code.n];
2198 err[q] = true;
2199 let syn = syndrome_of(&code.checks, &err);
2200 let corr = union_find_correct(&code, &syn).expect("matching graph");
2201 let residual: Vec<usize> = (0..code.n).filter(|&i| err[i] ^ corr[i]).collect();
2202 assert!(
2203 code.is_trivial(&residual),
2204 "d={d}: single error on qubit {q} left a logical residual {residual:?}"
2205 );
2206 }
2207 }
2208 }
2209
2210
2211
2212 #[test]
2217 fn measurement_noise_alone_never_causes_a_logical_error() {
2218 for d in [3usize, 5] {
2219 let code = CssCode::surface(d);
2220 let cfg = PhenomConfig::from_p(d as u32, 0.0, 0.05);
2221 for t in 0..300u64 {
2222 let r = decode_phenomenological(&code, &cfg, t.wrapping_mul(0x9E37_79B9))
2223 .expect("matching graph");
2224 assert!(
2225 r.logical_success,
2226 "d={d} seed={t}: pure measurement noise produced a logical failure \
2227 (correction {:?} on an empty data error)",
2228 r.correction
2229 );
2230 }
2231 }
2232 }
2233
2234 #[test]
2237 fn phenomenological_distance_helps() {
2238 let p = 0.005;
2239 let rate = |d: usize| {
2240 let cfg = PhenomConfig::from_p(d as u32, p, p);
2241 let (t, f) = benchmark_phenomenological(&CssCode::surface(d), &cfg, 1200, 11);
2242 f as f64 / t as f64
2243 };
2244 let (r3, r5) = (rate(3), rate(5));
2245 assert!(r5 < r3, "d=5 ({r5}) must beat d=3 ({r3}) under noisy measurement");
2246 }
2247
2248 #[test]
2251 fn perfect_measurement_reduces_to_single_shot() {
2252 for d in [3usize, 5] {
2253 let code = CssCode::surface(d);
2254 let cfg = PhenomConfig::from_p(1, 0.02, 0.0);
2255 let (t1, f1) = benchmark_phenomenological(&code, &cfg, 1200, 11);
2256 let (t2, f2) = benchmark_uf(&code, 0.02, 1200, 11);
2257 let (a, b) = (f1 as f64 / t1 as f64, f2 as f64 / t2 as f64);
2258 assert!((a - b).abs() < 0.02, "d={d}: spacetime {a} vs single-shot {b}");
2259 }
2260 }
2261
2262 #[test]
2263 fn phenomenological_refuses_non_matching_codes() {
2264 let cfg = PhenomConfig::from_p(3, 0.01, 0.01);
2265 assert!(decode_phenomenological(&CssCode::gross(), &cfg, 1).is_none());
2266 }
2267
2268
2269 #[test]
2272 fn surface_code_distance_is_d() {
2273 for d in [3usize, 5] {
2274 let code = CssCode::surface(d);
2275 assert_eq!(
2276 x_distance(&code, d + 1),
2277 Some(d),
2278 "surface d={d} should have X-distance exactly {d}"
2279 );
2280 }
2281 }
2282
2283 #[test]
2286 fn toric_code_distance_is_l() {
2287 let code = CssCode::toric(3);
2288 assert_eq!(code.k, 2, "toric encodes two logical qubits");
2289 assert_eq!(x_distance(&code, 4), Some(3));
2290 }
2291
2292 #[test]
2295 fn union_find_corrects_every_error_below_the_distance() {
2296 for d in [3usize, 5] {
2297 let code = CssCode::surface(d);
2298 let w = (d - 1) / 2; let mut combo: Vec<usize> = (0..w).collect();
2300 let mut checked = 0u32;
2301 loop {
2302 let mut err = vec![false; code.n];
2303 for &q in &combo {
2304 err[q] = true;
2305 }
2306 let syn = syndrome_of(&code.checks, &err);
2307 let corr = union_find_correct(&code, &syn).expect("matching graph");
2308 let residual: Vec<usize> =
2309 (0..code.n).filter(|&i| err[i] ^ corr[i]).collect();
2310 assert!(
2311 code.is_trivial(&residual),
2312 "d={d}: weight-{w} error {combo:?} decoded to a logical residual {residual:?}"
2313 );
2314 checked += 1;
2315 if !next_combination(&mut combo, code.n) {
2316 break;
2317 }
2318 }
2319 assert!(checked > 0, "d={d}: nothing checked");
2320 }
2321 }
2322
2323
2324
2325 #[test]
2330 fn hooks_off_is_exactly_phenomenological() {
2331 for &pp in &[0.005f64, 0.02] {
2332 for d in [3usize, 5] {
2333 let code = CssCode::surface(d);
2334 let ph = PhenomConfig::from_p(d as u32, pp, pp);
2335 let cl = CircuitConfig::from_p(d as u32, pp, pp, 0.0);
2336 let (_, a) = benchmark_phenomenological(&code, &ph, 600, 11);
2337 let (_, b) = benchmark_circuit_level(&code, &cl, 600, 11);
2338 assert_eq!(a, b, "p={pp} d={d}: hook-free circuit level must equal phenomenological");
2339 }
2340 }
2341 }
2342
2343 #[test]
2347 fn hook_errors_cost_accuracy() {
2348 let (p, d) = (0.005, 5usize);
2349 let code = CssCode::surface(d);
2350 let off = CircuitConfig::from_p(d as u32, p, p, 0.0);
2351 let on = CircuitConfig::from_p(d as u32, p, p, p);
2352 let (_, f_off) = benchmark_circuit_level(&code, &off, 1500, 11);
2353 let (_, f_on) = benchmark_circuit_level(&code, &on, 1500, 11);
2354 assert!(f_on > f_off, "hooks on ({f_on}) should fail more than hooks off ({f_off})");
2355 }
2356
2357 #[test]
2360 fn hook_weight_is_bounded_by_the_stabilizer() {
2361 let code = CssCode::surface(5);
2362 let maxw = code.stabs.iter().map(|s| s.len()).max().unwrap();
2363 let cfg = CircuitConfig::from_p(5, 0.0, 0.0, 0.2);
2364 let mut seen = 0;
2365 for t in 0..200u64 {
2366 let r = decode_circuit_level(&code, &cfg, t.wrapping_mul(0x9E37_79B9)).unwrap();
2367 for &w in &r.hook_events {
2368 assert!(w >= 1 && w as usize <= maxw, "hook weight {w} outside 1..={maxw}");
2369 seen += 1;
2370 }
2371 }
2372 assert!(seen > 0, "the probe never fired a hook");
2373 }
2374
2375 #[test]
2378 fn noiseless_circuit_never_fails() {
2379 let code = CssCode::surface(5);
2380 let cfg = CircuitConfig::from_p(5, 0.0, 0.0, 0.0);
2381 for t in 0..200u64 {
2382 let r = decode_circuit_level(&code, &cfg, t).unwrap();
2383 assert!(r.logical_success && r.data_error.is_empty() && r.correction.is_empty());
2384 }
2385 }
2386
2387 #[test]
2388 fn circuit_level_refuses_non_matching_codes() {
2389 let cfg = CircuitConfig::from_p(3, 0.01, 0.01, 0.01);
2390 assert!(decode_circuit_level(&CssCode::gross(), &cfg, 1).is_none());
2391 }
2392
2393
2394 #[test]
2399 fn logical_z_is_a_valid_observable() {
2400 for d in [3usize, 5] {
2401 let code = CssCode::surface(d);
2402 let lz = logical_z_support(&code, d + 1).expect("a logical Z exists");
2403 assert_eq!(lz.len(), d, "d={d}: minimum-weight logical Z should have weight {d}");
2404 let mut bits = vec![false; code.n];
2406 for &q in &lz { bits[q] = true; }
2407 assert!(syndrome_of(&code.stabs, &bits).iter().all(|b| !b));
2408 let dx = x_distance(&code, d + 1).expect("a logical X exists");
2410 assert_eq!(dx, d);
2411 let mut combo: Vec<usize> = (0..d).collect();
2413 let mut found = false;
2414 loop {
2415 let mut e = vec![false; code.n];
2416 for &q in &combo { e[q] = true; }
2417 if syndrome_of(&code.checks, &e).iter().all(|b| !b) && !code.is_trivial(&combo) {
2418 let overlap = combo.iter().filter(|q| lz.contains(q)).count();
2419 assert_eq!(overlap % 2, 1, "d={d}: logical Z {lz:?} misses logical X {combo:?}");
2420 found = true;
2421 break;
2422 }
2423 if !next_combination(&mut combo, code.n) { break; }
2424 }
2425 assert!(found, "d={d}: no minimum-weight logical X found");
2426 }
2427 }
2428
2429
2430 #[test]
2439 fn dem_encodes_the_codes_logical_structure() {
2440 for d in [3usize, 5] {
2441 let code = CssCode::surface(d);
2442 let cfg = CircuitConfig::from_p(2, 0.001, 0.001, 0.0);
2443 let mechs = dem_mechanisms(&code, &cfg, d + 1).expect("matching graph");
2444
2445 let mut combo: Vec<usize> = (0..d).collect();
2447 let logical_x = loop {
2448 let mut e = vec![false; code.n];
2449 for &q in &combo { e[q] = true; }
2450 if syndrome_of(&code.checks, &e).iter().all(|b| !b) && !code.is_trivial(&combo) {
2451 break combo.clone();
2452 }
2453 assert!(next_combination(&mut combo, code.n), "d={d}: no logical X of weight {d}");
2454 };
2455
2456 let mut detectors: Vec<u32> = Vec::new();
2458 let mut obs = false;
2459 for &q in &logical_x {
2460 let mech = &mechs[q]; obs ^= mech.flips_observable;
2462 for &dt in &mech.detectors {
2463 if let Some(pos) = detectors.iter().position(|&x| x == dt) {
2464 detectors.remove(pos);
2465 } else {
2466 detectors.push(dt);
2467 }
2468 }
2469 }
2470 assert!(detectors.is_empty(), "d={d}: logical X {logical_x:?} lit detectors {detectors:?}");
2471 assert!(obs, "d={d}: logical X {logical_x:?} did not flip the observable");
2472 }
2473 }
2474
2475 #[test]
2478 fn dem_says_stabilizers_are_harmless() {
2479 let d = 5usize;
2480 let code = CssCode::surface(d);
2481 let cfg = CircuitConfig::from_p(2, 0.001, 0.001, 0.0);
2482 let mechs = dem_mechanisms(&code, &cfg, d + 1).unwrap();
2483 for stab in &code.stabs {
2484 let mut detectors: Vec<u32> = Vec::new();
2485 let mut obs = false;
2486 for &q in stab {
2487 let mech = &mechs[q];
2488 obs ^= mech.flips_observable;
2489 for &dt in &mech.detectors {
2490 if let Some(pos) = detectors.iter().position(|&x| x == dt) {
2491 detectors.remove(pos);
2492 } else {
2493 detectors.push(dt);
2494 }
2495 }
2496 }
2497 assert!(detectors.is_empty(), "stabilizer {stab:?} lit detectors {detectors:?}");
2498 assert!(!obs, "stabilizer {stab:?} flipped the observable");
2499 }
2500 }
2501
2502 #[test]
2512 fn no_dem_mechanism_is_an_invisible_logical() {
2513 for d in [3usize, 5] {
2514 let code = CssCode::surface(d);
2515 for hook in [0.0, 0.002] {
2516 let cfg = CircuitConfig::from_p(2, 0.001, 0.001, hook);
2517 for m in dem_mechanisms(&code, &cfg, d + 1).unwrap() {
2518 assert!(
2519 !(m.detectors.is_empty() && m.flips_observable),
2520 "d={d} hook={hook}: undetectable mechanism flips the observable"
2521 );
2522 assert!(
2523 !m.detectors.is_empty(),
2524 "d={d} hook={hook}: a mechanism with no detectors should have been dropped"
2525 );
2526 }
2527 }
2528 }
2529 }
2530
2531 #[test]
2532 fn dem_text_is_wellformed() {
2533 let code = CssCode::surface(3);
2534 let cfg = CircuitConfig::from_p(2, 0.002, 0.002, 0.001);
2535 let dem = to_dem(&code, &cfg, 4).unwrap();
2536 let n_det = 3 * code.checks.len(); assert_eq!(dem.lines().filter(|l| l.starts_with("detector(")).count(), n_det);
2538 let errs: Vec<&str> = dem.lines().filter(|l| l.starts_with("error(")).collect();
2539 assert!(!errs.is_empty());
2540 for l in &errs {
2541 let p: f64 = l[6..l.find(')').unwrap()].parse().expect("probability parses");
2542 assert!(p > 0.0 && p < 1.0, "probability {p} out of range in {l}");
2543 assert!(l.contains(" D"), "mechanism with no detectors: {l}");
2544 for tok in l.split_whitespace().skip(1) {
2545 let idx: u32 = tok.trim_start_matches(['D', 'L']).parse().expect("target index");
2546 if tok.starts_with('D') {
2547 assert!((idx as usize) < n_det, "detector {idx} out of range");
2548 } else {
2549 assert_eq!(idx, 0, "only one observable is defined");
2550 }
2551 }
2552 }
2553 assert!(dem.contains("logical") || dem.contains("L0"), "no observable referenced");
2554 }
2555
2556
2557
2558
2559 #[test]
2561 fn dem_round_trips() {
2562 for d in [3usize, 5] {
2563 let code = CssCode::surface(d);
2564 let cfg = CircuitConfig::from_p(2, 0.002, 0.003, 0.0);
2565 let text = to_dem(&code, &cfg, d + 1).unwrap();
2566 let parsed = from_dem(&text).expect("our own output must parse");
2567 let native = dem_mechanisms(&code, &cfg, d + 1).unwrap();
2568 assert_eq!(parsed.mechanisms.len(), native.len(), "d={d}: mechanism count");
2569 assert_eq!(parsed.mechanisms, native, "d={d}: mechanisms changed through the text");
2570 assert_eq!(parsed.num_detectors, (cfg.rounds as usize + 1) * code.checks.len());
2571 }
2572 }
2573
2574 #[test]
2579 fn decoding_through_the_dem_matches_the_native_decoder() {
2580 for d in [3usize, 5] {
2581 let code = CssCode::surface(d);
2582 let cfg = CircuitConfig::from_p(d as u32, 0.01, 0.01, 0.0);
2583 let phen = PhenomConfig { rounds: cfg.rounds, p_data_fx: cfg.p_data_fx, q_meas_fx: cfg.q_meas_fx };
2584 let model = from_dem(&to_dem(&code, &cfg, d + 1).unwrap()).unwrap();
2585 let logical = logical_z_support(&code, d + 1).unwrap();
2586 let m = code.checks.len();
2587 let n_det = (cfg.rounds as usize + 1) * m;
2588
2589 let (mut native_fail, mut dem_fail) = (0u32, 0u32);
2590 let trials = 600u32;
2591 for tix in 0..trials {
2592 let seed = (tix as u64).wrapping_add(7).wrapping_mul(0x9E37_79B9_7F4A_7C15);
2593 let r = decode_phenomenological(&code, &phen, seed).unwrap();
2594 if !r.logical_success {
2595 native_fail += 1;
2596 }
2597 let mut bits = vec![false; n_det];
2598 for &dd in &r.detectors {
2599 bits[dd as usize] = true;
2600 }
2601 let truth = r.data_error.iter().filter(|q| logical.contains(&(**q as usize))).count() % 2 == 1;
2602 let pred = decode_dem(&model, &bits).unwrap().observable_flipped;
2603 if pred != truth {
2604 dem_fail += 1;
2605 }
2606 }
2607 let (a, b) = (native_fail as f64 / trials as f64, dem_fail as f64 / trials as f64);
2608 assert!(
2609 b <= a + 0.02,
2610 "d={d}: decoding through the DEM ({b}) lost accuracy vs native ({a})"
2611 );
2612 }
2613 }
2614
2615 #[test]
2617 fn dem_ingest_refuses_what_it_cannot_honour() {
2618 let hyper = "error(0.001) D0 D1 D2\n";
2620 let model = from_dem(hyper).unwrap();
2621 let e = decode_dem(&model, &[false; 3]).unwrap_err();
2622 assert!(e.contains("three or more") || e.contains("at most two"), "{e}");
2623
2624 assert!(from_dem("repeat 5 {\n").unwrap_err().contains("unsupported"));
2626 assert!(from_dem("error(2.0) D0\n").unwrap_err().contains("outside"));
2627 assert!(from_dem("error(0.1) X3\n").unwrap_err().contains("unexpected target"));
2628 }
2629
2630 #[test]
2633 fn ingests_a_hand_written_dem() {
2634 let src = "\
2635# hand written
2636detector(0, 0) D0
2637detector(1, 0) D1
2638error(0.01) D0 D1
2639error(0.02) D0 L0
2640error(0.02) D1 L0
2641";
2642 let model = from_dem(src).expect("parses");
2643 assert_eq!(model.num_detectors, 2);
2644 assert_eq!(model.mechanisms.len(), 3);
2645 let r = decode_dem(&model, &[true, true]).unwrap();
2648 assert!(!r.observable_flipped, "the D0-D1 mechanism should explain it");
2649 let r = decode_dem(&model, &[true, false]).unwrap();
2651 assert!(r.observable_flipped);
2652 }
2653
2654
2655
2656 #[test]
2657 #[ignore]
2658 fn probe_perf() {
2659 use std::time::Instant;
2660 println!("\n decoder throughput (surface code, p=0.01)");
2661 for d in [3usize, 5, 7, 9, 11] {
2662 let code = CssCode::surface(d);
2663 let t0 = Instant::now();
2664 let (trials, _) = benchmark_uf(&code, 0.01, 500, 11);
2665 let el = t0.elapsed();
2666 println!(" d={d:<3} n={:<4} checks={:<4} {:>8.1} shots/s ({:?}/shot)",
2667 code.n, code.checks.len(),
2668 trials as f64 / el.as_secs_f64(), el / trials);
2669 }
2670 println!("\n space-time decoder (d rounds, p=q=0.01)");
2671 for d in [3usize, 5, 7] {
2672 let code = CssCode::surface(d);
2673 let cfg = PhenomConfig::from_p(d as u32, 0.01, 0.01);
2674 let t0 = Instant::now();
2675 let (trials, _) = benchmark_phenomenological(&code, &cfg, 200, 11);
2676 let el = t0.elapsed();
2677 println!(" d={d:<3} detectors={:<5} {:>8.1} shots/s", (d + 1) * code.checks.len(),
2678 trials as f64 / el.as_secs_f64());
2679 }
2680 println!("\n code construction + distance verification");
2681 for d in [3usize, 5] {
2682 let t0 = Instant::now(); let c = CssCode::surface(d); let build = t0.elapsed();
2683 let t1 = Instant::now(); let dd = x_distance(&c, d + 1); let dist = t1.elapsed();
2684 println!(" d={d}: build {build:?}, distance {dd:?} in {dist:?}");
2685 }
2686 }
2687
2688 #[test]
2689 #[ignore]
2690 fn probe_dem_equivalence() {
2691 println!("\n same shots, decoded natively vs through the exported DEM");
2692 println!(" d p native via DEM");
2693 for d in [3usize, 5] {
2694 for &pp in &[0.005f64, 0.01, 0.02] {
2695 let code = CssCode::surface(d);
2696 let cfg = CircuitConfig::from_p(d as u32, pp, pp, 0.0);
2697 let phen = PhenomConfig { rounds: cfg.rounds, p_data_fx: cfg.p_data_fx, q_meas_fx: cfg.q_meas_fx };
2698 let model = from_dem(&to_dem(&code, &cfg, d + 1).unwrap()).unwrap();
2699 let logical = logical_z_support(&code, d + 1).unwrap();
2700 let n_det = (cfg.rounds as usize + 1) * code.checks.len();
2701 let (mut a, mut b) = (0u32, 0u32);
2702 let trials = 1500u32;
2703 for tix in 0..trials {
2704 let seed = (tix as u64).wrapping_add(7).wrapping_mul(0x9E37_79B9_7F4A_7C15);
2705 let r = decode_phenomenological(&code, &phen, seed).unwrap();
2706 if !r.logical_success { a += 1; }
2707 let mut bits = vec![false; n_det];
2708 for &dd in &r.detectors { bits[dd as usize] = true; }
2709 let truth = r.data_error.iter().filter(|q| logical.contains(&(**q as usize))).count() % 2 == 1;
2710 if decode_dem(&model, &bits).unwrap().observable_flipped != truth { b += 1; }
2711 }
2712 println!(" {d} {pp:<7} {:<8.4} {:<8.4}", a as f64/trials as f64, b as f64/trials as f64);
2713 }
2714 }
2715 }
2716
2717 #[test]
2718 #[ignore]
2719 fn probe_print_dem() {
2720 let code = CssCode::surface(3);
2721 let cfg = CircuitConfig::from_p(1, 0.002, 0.002, 0.001);
2722 let dem = to_dem(&code, &cfg, 4).unwrap();
2723 for l in dem.lines().take(22) { println!("{l}"); }
2724 println!("... ({} lines total)", dem.lines().count());
2725 }
2726
2727 #[test]
2728 #[ignore]
2729 fn probe_dem_shapes() {
2730 let code = CssCode::surface(5);
2731 let cfg = CircuitConfig::from_p(2, 0.001, 0.001, 0.001);
2732 let mechs = dem_mechanisms(&code, &cfg, 6).unwrap();
2733 let mut hist = std::collections::BTreeMap::new();
2734 for m in &mechs { *hist.entry(m.detectors.len()).or_insert(0u32) += 1; }
2735 println!("\n detector-set size histogram: {hist:?}");
2736 let empty = mechs.iter().filter(|m| m.detectors.is_empty()).count();
2737 println!(" mechanisms with NO detectors: {empty} (of {})", mechs.len());
2738 let empty_obs = mechs.iter().filter(|m| m.detectors.is_empty() && m.flips_observable).count();
2739 println!(" empty AND flips observable (would be a silent logical): {empty_obs}");
2740 println!(" X stabilizer weights: {:?}", {
2741 let mut w: Vec<usize> = code.stabs.iter().map(|s| s.len()).collect(); w.sort_unstable(); w.dedup(); w });
2742 for stab in code.stabs.iter().take(3) {
2744 print!(" stab {stab:?}: ");
2745 for j in 0..stab.len() {
2746 let qs = &stab[j..];
2747 let mut set: Vec<usize> = Vec::new();
2748 for &q in qs {
2749 for (ci, chk) in code.checks.iter().enumerate() {
2750 if chk.contains(&q) {
2751 if let Some(pos) = set.iter().position(|&x| x == ci) { set.remove(pos); } else { set.push(ci); }
2752 }
2753 }
2754 }
2755 print!("j={j}->{} ", set.len());
2756 }
2757 println!();
2758 }
2759 }
2760
2761 #[test]
2762 #[ignore]
2763 fn probe_circuit_level() {
2764 println!("\n hook=0 must reproduce phenomenological exactly");
2765 for &pp in &[0.005f64, 0.02] {
2766 for d in [3usize, 5] {
2767 let ph = PhenomConfig::from_p(d as u32, pp, pp);
2768 let cl = CircuitConfig::from_p(d as u32, pp, pp, 0.0);
2769 let (_, a) = benchmark_phenomenological(&CssCode::surface(d), &ph, 800, 11);
2770 let (_, b) = benchmark_circuit_level(&CssCode::surface(d), &cl, 800, 11);
2771 println!(" p={pp} d={d}: phenom {a} circuit(hook=0) {b} {}",
2772 if a == b { "IDENTICAL" } else { "DIFFER" });
2773 }
2774 }
2775 println!("\n hook errors lower the threshold (surface code, q=p, hook=p)");
2776 println!(" p d=3 d=5 d=7");
2777 for &pp in &[0.001f64, 0.002, 0.005, 0.01] {
2778 let r = |d: usize| {
2779 let cfg = CircuitConfig::from_p(d as u32, pp, pp, pp);
2780 let (t, f) = benchmark_circuit_level(&CssCode::surface(d), &cfg, 1200, 11);
2781 f as f64 / t as f64
2782 };
2783 println!(" {pp:<8} {:<9.4} {:<9.4} {:<9.4}", r(3), r(5), r(7));
2784 }
2785 println!("\n same p, hooks off vs on (d=5)");
2786 for &pp in &[0.002f64, 0.005] {
2787 let off = { let c=CircuitConfig::from_p(5,pp,pp,0.0); let (t,f)=benchmark_circuit_level(&CssCode::surface(5),&c,1200,11); f as f64/t as f64 };
2788 let on = { let c=CircuitConfig::from_p(5,pp,pp,pp); let (t,f)=benchmark_circuit_level(&CssCode::surface(5),&c,1200,11); f as f64/t as f64 };
2789 println!(" p={pp:<7} hooks off {:<8.4} hooks on {:<8.4}", off, on);
2790 }
2791 }
2792
2793 #[test]
2794 #[ignore]
2795 fn probe_phenomenological() {
2796 println!("\n surface code, phenomenological noise (data p, measurement q=p), d rounds");
2797 println!(" p d=3 d=5 d=7");
2798 for &pp in &[0.001f64, 0.003, 0.005, 0.01, 0.02] {
2799 let r = |d: usize| {
2800 let cfg = PhenomConfig::from_p(d as u32, pp, pp);
2801 let (t, f) = benchmark_phenomenological(&CssCode::surface(d), &cfg, 1200, 11);
2802 f as f64 / t as f64
2803 };
2804 println!(" {pp:<8} {:<9.4} {:<9.4} {:<9.4}", r(3), r(5), r(7));
2805 }
2806 println!("\n sanity: q=0 (perfect measurement) should track the single-shot decoder");
2807 for &pp in &[0.005f64, 0.02] {
2808 let st = |d: usize| {
2809 let cfg = PhenomConfig::from_p(1, pp, 0.0);
2810 let (t, f) = benchmark_phenomenological(&CssCode::surface(d), &cfg, 1200, 11);
2811 f as f64 / t as f64
2812 };
2813 let ss = |d: usize| { let (t,f)=benchmark_uf(&CssCode::surface(d), pp, 1200, 11); f as f64/t as f64 };
2814 println!(" p={pp:<7} d=3 spacetime {:<8.4} single-shot {:<8.4} | d=5 spacetime {:<8.4} single-shot {:<8.4}",
2815 st(3), ss(3), st(5), ss(5));
2816 }
2817 }
2818
2819 #[test]
2820 #[ignore]
2821 fn probe_union_find_vs_bp() {
2822 println!("\n surface code — logical failure rate");
2823 println!(" p d=3 BP d=3 UF d=5 BP d=5 UF d=7 BP d=7 UF");
2824 for &pp in &[0.001f64, 0.005, 0.01, 0.02, 0.05] {
2825 let bp = |d: usize| {
2826 let r = benchmark(&CssCode::surface(d), pp, 2000, 11, 16);
2827 r.relay_logical_failures as f64 / r.trials as f64
2828 };
2829 let uf = |d: usize| {
2830 let (t, f) = benchmark_uf(&CssCode::surface(d), pp, 2000, 11);
2831 f as f64 / t as f64
2832 };
2833 println!(" {pp:<8} {:<9.4} {:<9.4} {:<9.4} {:<9.4} {:<9.4} {:<9.4}",
2834 bp(3), uf(3), bp(5), uf(5), bp(7), uf(7));
2835 }
2836 println!("\n toric code — UF");
2837 for &pp in &[0.005f64, 0.02] {
2838 let uf = |l: usize| { let (t,f)=benchmark_uf(&CssCode::toric(l), pp, 2000, 11); f as f64/t as f64 };
2839 println!(" p={pp:<7} L=3 {:<8.4} L=5 {:<8.4}", uf(3), uf(5));
2840 }
2841 }
2842
2843 #[test]
2844 fn toric_code_shape() {
2845 let c = CssCode::toric(5);
2846 assert_eq!(c.n, 50);
2847 assert_eq!(c.checks.len(), 25);
2848 assert_eq!(c.k, 2, "toric code encodes 2 logical qubits");
2849 assert!(c.checks.iter().all(|r| r.len() == 4));
2851 }
2852
2853 #[test]
2854 fn gross_code_is_72_12() {
2855 let c = CssCode::gross();
2856 assert_eq!(c.n, 72);
2857 assert_eq!(c.k, 12, "the gross-style BB code encodes 12 logical qubits");
2858 assert!(c.checks.iter().all(|r| r.len() == 6));
2860 }
2861
2862 #[test]
2863 fn css_orthogonality_gross() {
2864 let c = CssCode::gross();
2867 let hx = CssCode::bivariate_bicycle(6, 6, &[(3, 0), (0, 1), (0, 2)], &[(0, 3), (1, 0), (2, 0)]);
2869 for zc in &c.checks {
2870 let zset: std::collections::HashSet<usize> = zc.iter().copied().collect();
2871 for xs in raw_hx(&hx) {
2872 let overlap = xs.iter().filter(|q| zset.contains(q)).count();
2873 assert_eq!(overlap % 2, 0, "CSS orthogonality violated");
2874 }
2875 }
2876 }
2877
2878 fn raw_hx(_c: &CssCode) -> Vec<Vec<usize>> {
2880 let (l, m) = (6usize, 6usize);
2882 let a = [(3usize, 0usize), (0, 1), (0, 2)];
2883 let b = [(0usize, 3usize), (1, 0), (2, 0)];
2884 let lm = l * m;
2885 let cell = |i: usize, j: usize| (i % l) * m + (j % m);
2886 let build = |monos: &[(usize, usize)]| -> Vec<Vec<usize>> {
2887 let mut rows = vec![Vec::new(); lm];
2888 for i in 0..l {
2889 for j in 0..m {
2890 let col = cell(i, j);
2891 for &(px, py) in monos {
2892 rows[cell(i + px, j + py)].push(col);
2893 }
2894 }
2895 }
2896 rows
2897 };
2898 let ar = build(&a);
2899 let br = build(&b);
2900 (0..lm)
2901 .map(|r| {
2902 let mut row = ar[r].clone();
2903 row.extend(br[r].iter().map(|&c| c + lm));
2904 row.sort_unstable();
2905 row
2906 })
2907 .collect()
2908 }
2909
2910 #[test]
2911 fn decode_recovers_low_weight_errors() {
2912 let c = CssCode::toric(5);
2914 let cfg = DecodeConfig::from_p(0.02);
2915 let mut fails = 0;
2916 for s in 0..200u64 {
2917 if !decode_once(&c, &cfg, s.wrapping_mul(0x9E37_79B9)).logical_success {
2918 fails += 1;
2919 }
2920 }
2921 assert!(fails < 20, "too many logical failures at p=0.02: {fails}/200");
2922 }
2923
2924 #[test]
2925 fn decode_is_deterministic() {
2926 let c = CssCode::gross();
2927 let cfg = DecodeConfig::from_p(0.03);
2928 let a = decode_once(&c, &cfg, 12345);
2929 let b = decode_once(&c, &cfg, 12345);
2930 assert_eq!(a.correction, b.correction);
2931 assert_eq!(a.messages, b.messages);
2932 assert_eq!(a.legs, b.legs);
2933 }
2934
2935 #[test]
2936 fn relay_beats_plain_bp() {
2937 let c = CssCode::gross();
2940 let bench = benchmark(&c, 0.04, 150, 0xBEEF, 12);
2941 assert!(
2942 bench.relay_logical_failures <= bench.plain_logical_failures,
2943 "relay {} must not exceed plain {}",
2944 bench.relay_logical_failures, bench.plain_logical_failures
2945 );
2946 }
2947
2948 #[test]
2949 fn decode_seals_verifying_receipt() {
2950 let c = CssCode::gross();
2951 let cfg = DecodeConfig::from_p(0.03);
2952 let r = decode_once(&c, &cfg, 7);
2953 let rec = r.seal(&key(1), "did:key:lab", 500_000, GrantRef::unbounded("quantum.decode"));
2954 assert!(rec.verify());
2955 assert!(rec.correction_matches(&r.correction_bytes()));
2956 assert_eq!(rec.code_id, "bb:[[72,12]]");
2957 assert_eq!(rec.work_messages, r.messages);
2958 }
2959}
2960
2961