qubasic 0.9.0__tar.gz → 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {qubasic-0.9.0 → qubasic-0.10.0}/CHANGELOG.md +15 -0
  2. {qubasic-0.9.0/qubasic.egg-info → qubasic-0.10.0}/PKG-INFO +20 -3
  3. {qubasic-0.9.0 → qubasic-0.10.0}/README.md +19 -2
  4. {qubasic-0.9.0 → qubasic-0.10.0}/pyproject.toml +1 -1
  5. {qubasic-0.9.0 → qubasic-0.10.0/qubasic.egg-info}/PKG-INFO +20 -3
  6. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic.egg-info/SOURCES.txt +1 -0
  7. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/__init__.py +1 -1
  8. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/noise_mixin.py +8 -1
  9. qubasic-0.10.0/qubasic_core/qec.py +434 -0
  10. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/qol.py +2 -1
  11. qubasic-0.10.0/qubasic_core/resources.py +173 -0
  12. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/terminal.py +4 -2
  13. {qubasic-0.9.0 → qubasic-0.10.0}/tests/test_qubasic.py +84 -0
  14. qubasic-0.9.0/qubasic_core/qec.py +0 -220
  15. {qubasic-0.9.0 → qubasic-0.10.0}/LICENSE +0 -0
  16. {qubasic-0.9.0 → qubasic-0.10.0}/MANIFEST.in +0 -0
  17. {qubasic-0.9.0 → qubasic-0.10.0}/examples/bell.qb +0 -0
  18. {qubasic-0.9.0 → qubasic-0.10.0}/examples/grover3.qb +0 -0
  19. {qubasic-0.9.0 → qubasic-0.10.0}/examples/locc_teleport.qb +0 -0
  20. {qubasic-0.9.0 → qubasic-0.10.0}/examples/sweep_rx.qb +0 -0
  21. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic.egg-info/dependency_links.txt +0 -0
  22. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic.egg-info/entry_points.txt +0 -0
  23. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic.egg-info/requires.txt +0 -0
  24. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic.egg-info/top_level.txt +0 -0
  25. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/__main__.py +0 -0
  26. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/algorithms.py +0 -0
  27. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/algos2.py +0 -0
  28. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/analysis.py +0 -0
  29. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/backend.py +0 -0
  30. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/benchmarking.py +0 -0
  31. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/bosonic.py +0 -0
  32. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/classic.py +0 -0
  33. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/cli.py +0 -0
  34. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/control_flow.py +0 -0
  35. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/debug.py +0 -0
  36. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/demos.py +0 -0
  37. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/display.py +0 -0
  38. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/dynamics.py +0 -0
  39. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/engine.py +0 -0
  40. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/engine_state.py +0 -0
  41. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/errors.py +0 -0
  42. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/exec_context.py +0 -0
  43. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/executor.py +0 -0
  44. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/expression.py +0 -0
  45. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/file_io.py +0 -0
  46. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/gates.py +0 -0
  47. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/help_text.py +0 -0
  48. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/io_protocol.py +0 -0
  49. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/locc.py +0 -0
  50. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/locc_commands.py +0 -0
  51. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/locc_display.py +0 -0
  52. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/locc_engine.py +0 -0
  53. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/locc_execution.py +0 -0
  54. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/memory.py +0 -0
  55. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/mock_backend.py +0 -0
  56. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/parser.py +0 -0
  57. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/patterns.py +0 -0
  58. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/pauliprop.py +0 -0
  59. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/profiler.py +0 -0
  60. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/program_mgmt.py +0 -0
  61. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/protocol.py +0 -0
  62. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/qudits.py +0 -0
  63. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/scope.py +0 -0
  64. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/screen.py +0 -0
  65. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/state_display.py +0 -0
  66. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/statements.py +0 -0
  67. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/strings.py +0 -0
  68. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/subs.py +0 -0
  69. {qubasic-0.9.0 → qubasic-0.10.0}/qubasic_core/sweep.py +0 -0
  70. {qubasic-0.9.0 → qubasic-0.10.0}/setup.cfg +0 -0
  71. {qubasic-0.9.0 → qubasic-0.10.0}/tests/test_features.py +0 -0
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 (2026-06-19)
4
+
5
+ Completes the frontier set: fault-tolerant QEC extras and a compilation/resources
6
+ group. All offline on Qiskit/Aer/numpy.
7
+
8
+ ### Added
9
+ - Surface code: `QEC SURFACE [d]`, a rotated surface code at odd distance d (validated to correct every weight-1 error).
10
+ - Union-find / matching decoder: exact minimum-weight matching of syndrome defects, scalable and dependency-free, selected with the `UF` flag on `LOGICAL_ERROR_RATE`. It tracks the optimal lookup decoder on the repetition and surface codes; the lookup decoder remains the default and the right choice for non-topological codes like Steane and Shor.
11
+ - Magic-state distillation: `DISTILL <p>` runs the 15-to-1 protocol (via the [15,11,3] Hamming detection), with cubic output-error suppression ~35 p^3.
12
+ - Lattice surgery: `LATTICE <a> <b>` performs the joint Zbar_A Zbar_B measurement that merges two repetition patches (the building block of a lattice-surgery CNOT).
13
+ - Fault-tolerant resource estimation: `RESOURCES <target_pL> <physical_p>` reports the surface-code distance, physical qubits per logical, T-count, T-factory overhead, and an approximate runtime.
14
+ - Calibrated device models: `DEVICE linear|ring|heavyhex|all [n] [T1us] [T2us]` builds a per-qubit thermal-relaxation noise model and coupling map for offline hardware-realistic simulation.
15
+ - `NOISE crosstalk <p>`: a correlated two-qubit ZZ crosstalk channel on entangling gates.
16
+ - `OPTIMIZE [level]`: transpiles the program and reports the depth and gate-count reduction.
17
+
3
18
  ## 0.9.0 (2026-06-19)
4
19
 
5
20
  Frontier and experimental methods. All offline on Qiskit/Aer/numpy; nothing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qubasic
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Quantum BASIC Interactive Terminal
5
5
  Author-email: "Charles C. Norton" <machineelv@gmail.com>
6
6
  License-Expression: MIT
@@ -514,11 +514,18 @@ CHANNEL AD = [[1,0],[0,0.95]] ; [[0,0.31],[0,0]] Define a Kraus channel
514
514
  ## Error correction
515
515
 
516
516
  ```
517
- QEC STEANE Show a code (REP [d], STEANE, SHOR) and its stabilizers
518
- LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate at physical p
517
+ QEC STEANE Show a code (REP [d], STEANE, SHOR, SURFACE [d]) and its stabilizers
518
+ LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate (optimal lookup decoder)
519
+ LOGICAL_ERROR_RATE SURFACE 0.02 UF Same, with the union-find / matching decoder
519
520
  THRESHOLD REP 0.0 0.5 11 Sweep p across distances 3/5/7 (crossing at 0.5)
521
+ DISTILL 0.02 15-to-1 magic-state distillation (output error ~35 p^3)
522
+ LATTICE 0 1 Lattice-surgery joint Zbar-Zbar measurement of two patches
520
523
  ```
521
524
 
525
+ Codes: repetition (any odd distance), Steane [[7,1,3]], Shor [[9,1,3]], rotated
526
+ surface. Decoders: an optimal minimum-weight lookup table (all codes) and a
527
+ scalable union-find / matching decoder (topological codes, via the `UF` flag).
528
+
522
529
  ## Benchmarking and verification
523
530
 
524
531
  ```
@@ -554,6 +561,16 @@ BOSONIC 1 25 Continuous-variable Fock modes
554
561
  DISPLACE 0 1.0 ...DISPLACE, SQUEEZE, CAT, BS, BSTATE
555
562
  ```
556
563
 
564
+ ## Compilation and resources
565
+
566
+ ```
567
+ RESOURCES 1e-12 0.001 Fault-tolerant estimate (surface-code distance, qubits, runtime)
568
+ DEVICE linear 5 Calibrated offline device model (per-qubit T1/T2 + coupling map)
569
+ DEVICE ring 5 80 60 ...with custom T1/T2 in microseconds (also: heavyhex, all, OFF)
570
+ OPTIMIZE 3 Transpile the program and report the depth/gate reduction
571
+ NOISE crosstalk 0.01 Correlated two-qubit ZZ crosstalk on entangling gates
572
+ ```
573
+
557
574
  ## Noise models
558
575
 
559
576
  ```
@@ -481,11 +481,18 @@ CHANNEL AD = [[1,0],[0,0.95]] ; [[0,0.31],[0,0]] Define a Kraus channel
481
481
  ## Error correction
482
482
 
483
483
  ```
484
- QEC STEANE Show a code (REP [d], STEANE, SHOR) and its stabilizers
485
- LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate at physical p
484
+ QEC STEANE Show a code (REP [d], STEANE, SHOR, SURFACE [d]) and its stabilizers
485
+ LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate (optimal lookup decoder)
486
+ LOGICAL_ERROR_RATE SURFACE 0.02 UF Same, with the union-find / matching decoder
486
487
  THRESHOLD REP 0.0 0.5 11 Sweep p across distances 3/5/7 (crossing at 0.5)
488
+ DISTILL 0.02 15-to-1 magic-state distillation (output error ~35 p^3)
489
+ LATTICE 0 1 Lattice-surgery joint Zbar-Zbar measurement of two patches
487
490
  ```
488
491
 
492
+ Codes: repetition (any odd distance), Steane [[7,1,3]], Shor [[9,1,3]], rotated
493
+ surface. Decoders: an optimal minimum-weight lookup table (all codes) and a
494
+ scalable union-find / matching decoder (topological codes, via the `UF` flag).
495
+
489
496
  ## Benchmarking and verification
490
497
 
491
498
  ```
@@ -521,6 +528,16 @@ BOSONIC 1 25 Continuous-variable Fock modes
521
528
  DISPLACE 0 1.0 ...DISPLACE, SQUEEZE, CAT, BS, BSTATE
522
529
  ```
523
530
 
531
+ ## Compilation and resources
532
+
533
+ ```
534
+ RESOURCES 1e-12 0.001 Fault-tolerant estimate (surface-code distance, qubits, runtime)
535
+ DEVICE linear 5 Calibrated offline device model (per-qubit T1/T2 + coupling map)
536
+ DEVICE ring 5 80 60 ...with custom T1/T2 in microseconds (also: heavyhex, all, OFF)
537
+ OPTIMIZE 3 Transpile the program and report the depth/gate reduction
538
+ NOISE crosstalk 0.01 Correlated two-qubit ZZ crosstalk on entangling gates
539
+ ```
540
+
524
541
  ## Noise models
525
542
 
526
543
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qubasic"
7
- version = "0.9.0"
7
+ version = "0.10.0"
8
8
  description = "Quantum BASIC Interactive Terminal"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qubasic
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Quantum BASIC Interactive Terminal
5
5
  Author-email: "Charles C. Norton" <machineelv@gmail.com>
6
6
  License-Expression: MIT
@@ -514,11 +514,18 @@ CHANNEL AD = [[1,0],[0,0.95]] ; [[0,0.31],[0,0]] Define a Kraus channel
514
514
  ## Error correction
515
515
 
516
516
  ```
517
- QEC STEANE Show a code (REP [d], STEANE, SHOR) and its stabilizers
518
- LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate at physical p
517
+ QEC STEANE Show a code (REP [d], STEANE, SHOR, SURFACE [d]) and its stabilizers
518
+ LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate (optimal lookup decoder)
519
+ LOGICAL_ERROR_RATE SURFACE 0.02 UF Same, with the union-find / matching decoder
519
520
  THRESHOLD REP 0.0 0.5 11 Sweep p across distances 3/5/7 (crossing at 0.5)
521
+ DISTILL 0.02 15-to-1 magic-state distillation (output error ~35 p^3)
522
+ LATTICE 0 1 Lattice-surgery joint Zbar-Zbar measurement of two patches
520
523
  ```
521
524
 
525
+ Codes: repetition (any odd distance), Steane [[7,1,3]], Shor [[9,1,3]], rotated
526
+ surface. Decoders: an optimal minimum-weight lookup table (all codes) and a
527
+ scalable union-find / matching decoder (topological codes, via the `UF` flag).
528
+
522
529
  ## Benchmarking and verification
523
530
 
524
531
  ```
@@ -554,6 +561,16 @@ BOSONIC 1 25 Continuous-variable Fock modes
554
561
  DISPLACE 0 1.0 ...DISPLACE, SQUEEZE, CAT, BS, BSTATE
555
562
  ```
556
563
 
564
+ ## Compilation and resources
565
+
566
+ ```
567
+ RESOURCES 1e-12 0.001 Fault-tolerant estimate (surface-code distance, qubits, runtime)
568
+ DEVICE linear 5 Calibrated offline device model (per-qubit T1/T2 + coupling map)
569
+ DEVICE ring 5 80 60 ...with custom T1/T2 in microseconds (also: heavyhex, all, OFF)
570
+ OPTIMIZE 3 Transpile the program and report the depth/gate reduction
571
+ NOISE crosstalk 0.01 Correlated two-qubit ZZ crosstalk on entangling gates
572
+ ```
573
+
557
574
  ## Noise models
558
575
 
559
576
  ```
@@ -55,6 +55,7 @@ qubasic_core/protocol.py
55
55
  qubasic_core/qec.py
56
56
  qubasic_core/qol.py
57
57
  qubasic_core/qudits.py
58
+ qubasic_core/resources.py
58
59
  qubasic_core/scope.py
59
60
  qubasic_core/screen.py
60
61
  qubasic_core/state_display.py
@@ -28,7 +28,7 @@ __all__ = [
28
28
  'GATE_TABLE', 'GATE_ALIASES',
29
29
  ]
30
30
 
31
- __version__ = '0.9.0'
31
+ __version__ = '0.10.0'
32
32
 
33
33
  def __getattr__(name):
34
34
  """Lazy import heavy modules on first access."""
@@ -106,10 +106,17 @@ class NoiseMixin:
106
106
  p1 = float(parts[2]) if len(parts) > 2 else 0.01
107
107
  nm.add_all_qubit_quantum_error(reset_error(p0, p1), _1q)
108
108
  self.io.writeln(f"NOISE reset p0={p0} p1={p1}")
109
+ elif ntype == 'crosstalk':
110
+ # Correlated (non-product) two-qubit error after each 2-qubit gate:
111
+ # a ZZ Pauli with probability p, otherwise identity.
112
+ p = float(parts[1]) if len(parts) > 1 else 0.01
113
+ err = pauli_error([('ZZ', p), ('II', 1 - p)])
114
+ nm.add_all_qubit_quantum_error(err, _2q)
115
+ self.io.writeln(f"NOISE crosstalk (correlated ZZ on 2-qubit gates) p={p}")
109
116
  else:
110
117
  self.io.writeln(f"?UNKNOWN NOISE TYPE: {ntype}")
111
118
  self.io.writeln(" Types: depolarizing, amplitude_damping, phase_flip, thermal,")
112
- self.io.writeln(" readout, combined, pauli, reset")
119
+ self.io.writeln(" readout, combined, pauli, reset, crosstalk")
113
120
  return
114
121
  self._noise_model = nm
115
122
  self._noise_spec = rest.strip() # for SAVE round-tripping
@@ -0,0 +1,434 @@
1
+ """QUBASIC quantum error correction.
2
+
3
+ Stabilizer codes with an optimal (minimum-weight lookup) decoder, logical error
4
+ rates, and threshold sweeps, computed by classical Pauli-frame simulation (no
5
+ statevector needed, so it is exact and fast). Built-in codes: the repetition
6
+ (bit-flip) code at any odd distance, the [[7,1,3]] Steane code, and the [[9,1,3]]
7
+ Shor code.
8
+
9
+ QEC LIST List the built-in codes
10
+ QEC STEANE Show a code's stabilizers and logical operators
11
+ LOGICAL_ERROR_RATE STEANE 0.05 Monte-Carlo logical error rate at physical rate p
12
+ THRESHOLD REP 0.0 0.5 11 Sweep p and compare code distances (find the crossing)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ import itertools
19
+
20
+ import numpy as np
21
+
22
+ # Phase-free single-qubit Pauli multiplication (we only need the group structure).
23
+ _PMUL = {
24
+ ('I', 'I'): 'I', ('I', 'X'): 'X', ('I', 'Y'): 'Y', ('I', 'Z'): 'Z',
25
+ ('X', 'I'): 'X', ('X', 'X'): 'I', ('X', 'Y'): 'Z', ('X', 'Z'): 'Y',
26
+ ('Y', 'I'): 'Y', ('Y', 'X'): 'Z', ('Y', 'Y'): 'I', ('Y', 'Z'): 'X',
27
+ ('Z', 'I'): 'Z', ('Z', 'X'): 'Y', ('Z', 'Y'): 'X', ('Z', 'Z'): 'I',
28
+ }
29
+
30
+
31
+ def _anticommute(a: str, b: str) -> int:
32
+ """1 if Pauli strings a and b anticommute, else 0."""
33
+ cnt = 0
34
+ for pa, pb in zip(a, b):
35
+ if pa != 'I' and pb != 'I' and pa != pb:
36
+ cnt ^= 1
37
+ return cnt
38
+
39
+
40
+ def _pmul(a: str, b: str) -> str:
41
+ return ''.join(_PMUL[(pa, pb)] for pa, pb in zip(a, b))
42
+
43
+
44
+ def _weight(p: str) -> int:
45
+ return sum(1 for c in p if c != 'I')
46
+
47
+
48
+ class QECMixin:
49
+ """Stabilizer codes, decoding, and logical error rates for QBasicTerminal.
50
+
51
+ Requires: TerminalProtocol — uses self._eval_with_vars(), self._seed, self.io.
52
+ """
53
+
54
+ def _qec_code(self, name: str, distance: int = 3) -> dict:
55
+ """Return a code descriptor: data qubit count, stabilizers, logical X/Z,
56
+ distance, and the error alphabet the decoder corrects."""
57
+ name = name.upper()
58
+ if name in ('REP', 'REPETITION', 'BITFLIP'):
59
+ d = distance if distance % 2 == 1 else distance + 1
60
+ stab = []
61
+ for i in range(d - 1):
62
+ s = ['I'] * d
63
+ s[i] = 'Z'; s[i + 1] = 'Z'
64
+ stab.append(''.join(s))
65
+ return {'n': d, 'stab': stab, 'lx': 'X' * d, 'lz': 'Z' + 'I' * (d - 1),
66
+ 'd': d, 'alphabet': 'IX', 'name': f'repetition d={d}'}
67
+ if name == 'STEANE':
68
+ Hm = [[0, 0, 0, 1, 1, 1, 1], [0, 1, 1, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0, 1]]
69
+ stab = [''.join('X' if b else 'I' for b in row) for row in Hm]
70
+ stab += [''.join('Z' if b else 'I' for b in row) for row in Hm]
71
+ return {'n': 7, 'stab': stab, 'lx': 'X' * 7, 'lz': 'Z' * 7,
72
+ 'd': 3, 'alphabet': 'IXYZ', 'name': 'Steane [[7,1,3]]'}
73
+ if name == 'SHOR':
74
+ stab = []
75
+ for b in range(3):
76
+ base = b * 3
77
+ for a, c in ((base, base + 1), (base + 1, base + 2)):
78
+ s = ['I'] * 9; s[a] = 'Z'; s[c] = 'Z'; stab.append(''.join(s))
79
+ stab.append('XXXXXX' + 'III')
80
+ stab.append('III' + 'XXXXXX')
81
+ return {'n': 9, 'stab': stab, 'lx': 'X' * 9, 'lz': 'ZIIZIIZII',
82
+ 'd': 3, 'alphabet': 'IXYZ', 'name': 'Shor [[9,1,3]]'}
83
+ if name in ('SURFACE', 'SURF'):
84
+ return self._surface_code(distance)
85
+ raise ValueError(f"unknown code '{name}' (try REP, STEANE, SHOR, SURFACE; QEC LIST)")
86
+
87
+ def _surface_code(self, d: int) -> dict:
88
+ """Rotated surface code of odd distance d (d^2 data qubits on a d x d grid).
89
+
90
+ Bulk faces are weight-4 stabilizers alternating X/Z by checkerboard parity;
91
+ boundary faces are weight-2, X on the top/bottom edges and Z on the
92
+ left/right edges. Logical X is an X string along the top row, logical Z a
93
+ Z string down the left column. The construction is validated by the
94
+ caller's checks (it must correct every weight-1 error)."""
95
+ if d % 2 == 0:
96
+ d += 1
97
+ n = d * d
98
+
99
+ def idx(r, c):
100
+ return r * d + c
101
+
102
+ stab = []
103
+ for r in range(-1, d):
104
+ for c in range(-1, d):
105
+ corners = [(r, c), (r, c + 1), (r + 1, c), (r + 1, c + 1)]
106
+ qs = [idx(i, j) for (i, j) in corners if 0 <= i < d and 0 <= j < d]
107
+ if len(qs) < 2:
108
+ continue
109
+ typ = 'Z' if (r + c) % 2 == 0 else 'X'
110
+ if len(qs) == 4:
111
+ stab.append((typ, qs))
112
+ else:
113
+ on_tb = (r == -1 or r == d - 1)
114
+ on_lr = (c == -1 or c == d - 1)
115
+ if on_tb and not on_lr and typ == 'X':
116
+ stab.append((typ, qs))
117
+ elif on_lr and not on_tb and typ == 'Z':
118
+ stab.append((typ, qs))
119
+ stab_strs = []
120
+ for typ, qs in stab:
121
+ s = ['I'] * n
122
+ for q in qs:
123
+ s[q] = typ
124
+ stab_strs.append(''.join(s))
125
+ # Logical X runs down a column (between the top/bottom X boundaries);
126
+ # logical Z runs along a row (between the left/right Z boundaries). They
127
+ # cross at qubit 0 and so anticommute.
128
+ lx = ['I'] * n
129
+ for r in range(d):
130
+ lx[idx(r, 0)] = 'X'
131
+ lz = ['I'] * n
132
+ for c in range(d):
133
+ lz[idx(0, c)] = 'Z'
134
+ return {'n': n, 'stab': stab_strs, 'lx': ''.join(lx), 'lz': ''.join(lz),
135
+ 'd': d, 'alphabet': 'IXYZ', 'name': f'rotated surface d={d}'}
136
+
137
+ def _qec_decoder(self, code: dict) -> dict:
138
+ """Build the minimum-weight lookup decoder: syndrome -> recovery Pauli.
139
+
140
+ Enumerates every error over the code's alphabet, keeping the lowest-weight
141
+ representative per syndrome. Optimal for these small codes."""
142
+ n = code['n']
143
+ stab = code['stab']
144
+ alphabet = code['alphabet']
145
+ table: dict = {}
146
+ for combo in itertools.product(alphabet, repeat=n):
147
+ err = ''.join(combo)
148
+ synd = tuple(_anticommute(err, s) for s in stab)
149
+ w = _weight(err)
150
+ if synd not in table or w < table[synd][1]:
151
+ table[synd] = (err, w)
152
+ return {synd: err for synd, (err, _w) in table.items()}
153
+
154
+ def _random_pauli_error(self, code: dict, p: float, rng) -> str:
155
+ """Sample an i.i.d. error per qubit: X for a bit-flip code, depolarizing else."""
156
+ n = code['n']
157
+ out = []
158
+ if code['alphabet'] == 'IX':
159
+ for _ in range(n):
160
+ out.append('X' if rng.random() < p else 'I')
161
+ else:
162
+ for _ in range(n):
163
+ r = rng.random()
164
+ if r < p:
165
+ out.append('XYZ'[rng.integers(3)])
166
+ else:
167
+ out.append('I')
168
+ return ''.join(out)
169
+
170
+ def _logical_error_rate(self, code: dict, p: float, trials: int, rng, uf: bool = False) -> float:
171
+ decoder = None if uf else self._qec_decoder(code)
172
+ stab, lx, lz = code['stab'], code['lx'], code['lz']
173
+ fails = 0
174
+ for _ in range(trials):
175
+ err = self._random_pauli_error(code, p, rng)
176
+ synd = tuple(_anticommute(err, s) for s in stab)
177
+ recovery = self._qec_matching_decode(code, synd) if uf \
178
+ else decoder.get(synd, 'I' * code['n'])
179
+ residual = _pmul(err, recovery)
180
+ if _anticommute(residual, lx) or _anticommute(residual, lz):
181
+ fails += 1
182
+ return fails / trials
183
+
184
+ def _qec_matching_decode(self, code: dict, syndrome) -> str:
185
+ """Union-find / matching decoder: minimum-weight matching of syndrome
186
+ defects on the matching graph, correcting the connecting qubits.
187
+
188
+ Scalable alternative to the exponential lookup table (it never enumerates
189
+ all errors). Defects are matched to each other or to the boundary by exact
190
+ minimum-weight matching over shortest paths, and the path qubits flipped.
191
+ """
192
+ import functools
193
+ from collections import deque
194
+ n = code['n']
195
+ stabs = code['stab']
196
+ xr = ['I'] * n
197
+ zr = ['I'] * n
198
+ for etype, dtype, rec in (('X', 'Z', xr), ('Z', 'X', zr)):
199
+ det = [i for i, s in enumerate(stabs) if set(s) - {'I'} == {dtype}]
200
+ defects = tuple(i for i in det if syndrome[i])
201
+ if not defects:
202
+ continue
203
+ adj = {i: [] for i in det}
204
+ adj['B'] = []
205
+ for q in range(n):
206
+ touch = [i for i in det if stabs[i][q] == dtype]
207
+ if len(touch) == 2:
208
+ adj[touch[0]].append((touch[1], q)); adj[touch[1]].append((touch[0], q))
209
+ elif len(touch) == 1:
210
+ adj[touch[0]].append(('B', q)); adj['B'].append((touch[0], q))
211
+
212
+ def bfs(src):
213
+ prev = {src: (None, None)}
214
+ dq = deque([src])
215
+ while dq:
216
+ u = dq.popleft()
217
+ for v, q in adj.get(u, []):
218
+ if v not in prev:
219
+ prev[v] = (u, q); dq.append(v)
220
+ return prev
221
+
222
+ prevs = {s: bfs(s) for s in defects}
223
+
224
+ def path_qubits(src, tgt):
225
+ prev = prevs[src]
226
+ qs, node = [], tgt
227
+ while node != src and prev.get(node, (None, None))[0] is not None:
228
+ par, q = prev[node]
229
+ qs.append(q); node = par
230
+ return qs
231
+
232
+ def dist(src, tgt):
233
+ return len(path_qubits(src, tgt)) if tgt in prevs[src] else 10 ** 9
234
+
235
+ @functools.lru_cache(maxsize=None)
236
+ def solve(rem):
237
+ if not rem:
238
+ return (0, ())
239
+ i, rest = rem[0], rem[1:]
240
+ best_c, best_m = dist(i, 'B') + solve(rest)[0], ((i, 'B'),) + solve(rest)[1]
241
+ for k, j in enumerate(rest):
242
+ sub = solve(rest[:k] + rest[k + 1:])
243
+ c = dist(i, j) + sub[0]
244
+ if c < best_c:
245
+ best_c, best_m = c, ((i, j),) + sub[1]
246
+ return (best_c, best_m)
247
+
248
+ _, matches = solve(defects)
249
+ for a, b in matches:
250
+ for q in path_qubits(a, b):
251
+ rec[q] = etype
252
+ out = []
253
+ for q in range(n):
254
+ x, z = xr[q] == 'X', zr[q] == 'Z'
255
+ out.append('Y' if (x and z) else 'X' if x else 'Z' if z else 'I')
256
+ return ''.join(out)
257
+
258
+ def cmd_distill(self, rest: str) -> None:
259
+ """DISTILL <p> [trials] — 15-to-1 magic-state (T-state) distillation.
260
+
261
+ Models the protocol's error detection by the [15,11,3] Hamming code:
262
+ 15 noisy T-states (Z error rate p), accept on a trivial syndrome, and a
263
+ logical output error occurs only for an undetected nonzero codeword. The
264
+ output error rate scales as ~35 p^3, well below the input p."""
265
+ parts = rest.split()
266
+ if not parts:
267
+ self.io.writeln("?USAGE: DISTILL <p> [trials]")
268
+ return
269
+ try:
270
+ p = float(self._eval_with_vars(parts[0], {}))
271
+ trials = int(parts[1]) if len(parts) > 1 else 200000
272
+ # [15,11,3] Hamming parity-check matrix: columns are 1..15 in binary.
273
+ H = np.array([[ (c >> b) & 1 for c in range(1, 16)] for b in range(4)])
274
+ rng = np.random.default_rng(self._seed)
275
+ accepted = 0
276
+ errors = 0
277
+ for _ in range(trials):
278
+ e = (rng.random(15) < p).astype(int)
279
+ synd = H.dot(e) % 2
280
+ if not synd.any(): # accept: trivial syndrome
281
+ accepted += 1
282
+ if e.any(): # nonzero codeword -> logical error
283
+ errors += 1
284
+ acc = accepted / trials
285
+ p_out = errors / accepted if accepted else 0.0
286
+ self.io.writeln(f"\n 15-to-1 magic-state distillation:")
287
+ self.io.writeln(f" input error p = {p:g}")
288
+ self.io.writeln(f" acceptance rate = {acc:.4f}")
289
+ self.io.writeln(f" output error p_out = {p_out:.3e} (~35 p^3 = {35 * p ** 3:.3e})")
290
+ self.io.writeln(f" suppression p/p_out= {p / p_out:.1f}x" if p_out > 0 else
291
+ " output error: none observed")
292
+ self.variables['_DISTILL_POUT'] = p_out
293
+ except Exception as e:
294
+ self.io.writeln(f"?DISTILL ERROR: {e}")
295
+
296
+ def cmd_lattice(self, rest: str) -> None:
297
+ """LATTICE <stateA> <stateB> — lattice-surgery logical ZZ measurement.
298
+
299
+ Encodes two distance-3 repetition logical qubits in the given logical
300
+ states (0, 1, +, -), then performs a merge that measures the joint
301
+ operator Zbar_A Zbar_B (the basis of a lattice-surgery CNOT) via an
302
+ ancilla, and reports the logical parity outcome."""
303
+ parts = rest.split()
304
+ if len(parts) < 2:
305
+ self.io.writeln("?USAGE: LATTICE <stateA> <stateB> (states: 0, 1, +, -)")
306
+ return
307
+ from qiskit import QuantumCircuit, transpile
308
+ from qiskit_aer import AerSimulator
309
+ try:
310
+ sa, sb = parts[0], parts[1]
311
+ # 3 data qubits per patch (A: 0-2, B: 3-5) + 1 merge ancilla (6).
312
+ qc = QuantumCircuit(7, 1)
313
+
314
+ def encode(base, state):
315
+ if state in ('1',):
316
+ qc.x(base)
317
+ elif state in ('+', '-'):
318
+ qc.h(base)
319
+ if state == '-':
320
+ qc.z(base)
321
+ qc.cx(base, base + 1); qc.cx(base, base + 2) # repetition encode
322
+ encode(0, sa); encode(3, sb)
323
+ # Merge: measure Zbar_A Zbar_B = Z_{A0} Z_{B0} via an ancilla.
324
+ anc = 6
325
+ qc.h(anc)
326
+ qc.cz(anc, 0)
327
+ qc.cz(anc, 3)
328
+ qc.h(anc)
329
+ qc.measure(anc, 0)
330
+ backend = AerSimulator(noise_model=self._noise_model) if self._noise_model else AerSimulator()
331
+ counts = backend.run(transpile(qc, backend), shots=max(500, self.shots)).result().get_counts()
332
+ par = '+1 (even)' if counts.get('0', 0) >= counts.get('1', 0) else '-1 (odd)'
333
+ self.io.writeln(f"\n Lattice surgery: merge of |{sa}>_L (A) and |{sb}>_L (B)")
334
+ self.io.writeln(f" joint Zbar_A Zbar_B measurement -> {par} {dict(counts)}")
335
+ self.io.writeln(f" (a deterministic joint parity for computational inputs; the "
336
+ f"building block of a lattice-surgery CNOT)")
337
+ except Exception as e:
338
+ self.io.writeln(f"?LATTICE ERROR: {e}")
339
+
340
+ # ── Commands ────────────────────────────────────────────────────────
341
+
342
+ def cmd_qec(self, rest: str) -> None:
343
+ """QEC LIST | QEC <code> [distance] — show a stabilizer code.
344
+
345
+ Lists the built-in codes, or prints a code's stabilizer generators and
346
+ logical operators (and verifies they form a valid code)."""
347
+ arg = rest.strip().upper()
348
+ if not arg or arg == 'LIST':
349
+ self.io.writeln("\n Built-in QEC codes:")
350
+ self.io.writeln(" REP [d] repetition / bit-flip code, odd distance d (default 3)")
351
+ self.io.writeln(" STEANE [[7,1,3]] CSS code")
352
+ self.io.writeln(" SHOR [[9,1,3]] code")
353
+ self.io.writeln(" SURFACE [d] rotated surface code, odd distance d (default 3)")
354
+ self.io.writeln(" Decoders: optimal lookup (default), union-find (LOGICAL_ERROR_RATE ... UF)")
355
+ return
356
+ parts = arg.split()
357
+ try:
358
+ d = int(parts[1]) if len(parts) > 1 else 3
359
+ code = self._qec_code(parts[0], d)
360
+ except Exception as e:
361
+ self.io.writeln(f"?QEC ERROR: {e}")
362
+ return
363
+ self.io.writeln(f"\n {code['name']}: {code['n']} data qubits, distance {code['d']}")
364
+ self.io.writeln(f" Stabilizers ({len(code['stab'])}):")
365
+ for s in code['stab']:
366
+ self.io.writeln(f" {s}")
367
+ self.io.writeln(f" Logical X: {code['lx']}")
368
+ self.io.writeln(f" Logical Z: {code['lz']}")
369
+ # Validity self-check: logicals commute with every stabilizer and
370
+ # anticommute with each other.
371
+ ok = bool(all(not _anticommute(code['lx'], s) for s in code['stab'])
372
+ and all(not _anticommute(code['lz'], s) for s in code['stab'])
373
+ and _anticommute(code['lx'], code['lz']))
374
+ self.io.writeln(f" Valid code (logicals normalize the stabilizer group): {ok}")
375
+
376
+ def cmd_logical_error_rate(self, rest: str) -> None:
377
+ """LOGICAL_ERROR_RATE <code> [distance] <p> [trials] — Monte-Carlo logical error rate.
378
+
379
+ Injects i.i.d. physical errors at rate p, extracts the syndrome, decodes
380
+ with the optimal lookup decoder, applies the recovery, and reports the
381
+ fraction of trials left with a logical error."""
382
+ parts = rest.split()
383
+ if len(parts) < 2:
384
+ self.io.writeln("?USAGE: LOGICAL_ERROR_RATE <code> [distance] <p> [trials]")
385
+ return
386
+ try:
387
+ name = parts[0]
388
+ idx = 1
389
+ distance = 3
390
+ if len(parts) > 2 and parts[1].isdigit() and float(parts[1]) >= 1:
391
+ distance = int(parts[1]); idx = 2
392
+ uf = any(t.upper() in ('UF', 'UNION-FIND', 'MATCHING') for t in parts)
393
+ tail = [t for t in parts[idx:] if t.upper() not in ('UF', 'UNION-FIND', 'MATCHING')]
394
+ p = float(self._eval_with_vars(tail[0], {}))
395
+ trials = int(tail[1]) if len(tail) > 1 else 20000
396
+ code = self._qec_code(name, distance)
397
+ rng = np.random.default_rng(self._seed)
398
+ ler = self._logical_error_rate(code, p, trials, rng, uf=uf)
399
+ self.io.writeln(f"\n {code['name']}: physical p={p}, {trials} trials"
400
+ f"{' (union-find decoder)' if uf else ' (lookup decoder)'}")
401
+ self.io.writeln(f" Logical error rate = {ler:.6f}")
402
+ self.variables['_LER'] = ler
403
+ except Exception as e:
404
+ self.io.writeln(f"?LOGICAL_ERROR_RATE ERROR: {e}")
405
+
406
+ def cmd_threshold(self, rest: str) -> None:
407
+ """THRESHOLD <code> <p1> <p2> <steps> [d1 d2 ...] — sweep physical error rate.
408
+
409
+ Reports the logical error rate across [p1, p2] for several code distances
410
+ (default 3, 5, 7 for the repetition code), so the threshold crossing where
411
+ larger codes start helping is visible."""
412
+ parts = rest.split()
413
+ if len(parts) < 4:
414
+ self.io.writeln("?USAGE: THRESHOLD <code> <p1> <p2> <steps> [distances]")
415
+ return
416
+ try:
417
+ name = parts[0]
418
+ p1 = float(self._eval_with_vars(parts[1], {}))
419
+ p2 = float(self._eval_with_vars(parts[2], {}))
420
+ steps = int(parts[3])
421
+ distances = [int(x) for x in parts[4:]] if len(parts) > 4 else [3, 5, 7]
422
+ ps = [p1 + (p2 - p1) * i / max(1, steps - 1) for i in range(steps)]
423
+ codes = [self._qec_code(name, d) for d in distances]
424
+ rng = np.random.default_rng(self._seed)
425
+ trials = 8000
426
+ self.io.writeln(f"\n Threshold sweep ({name.upper()}), {trials} trials/point:")
427
+ self.io.writeln(" p " + ''.join(f" d={c['d']:<8}" for c in codes))
428
+ for p in ps:
429
+ row = f" {p:5.3f} "
430
+ for c in codes:
431
+ row += f" {self._logical_error_rate(c, p, trials, rng):<8.4f}"
432
+ self.io.writeln(row)
433
+ except Exception as e:
434
+ self.io.writeln(f"?THRESHOLD ERROR: {e}")
@@ -98,12 +98,13 @@ _ALL_COMMANDS = [
98
98
  'MINIMIZE', 'GRADIENT', 'FIDELITY', 'TOMOGRAPHY', 'COUPLING', 'BASIS',
99
99
  'LOADQASM', 'SET_DENSITY', 'SAVEPNG', 'PTOMOGRAPHY', 'RB', 'MEASURE',
100
100
  'HAMILTONIAN', 'EVOLVE', 'LINDBLAD', 'CHANNEL', 'APPLYCHANNEL',
101
- 'QEC', 'LOGICAL_ERROR_RATE', 'THRESHOLD',
101
+ 'QEC', 'LOGICAL_ERROR_RATE', 'THRESHOLD', 'DISTILL', 'LATTICE',
102
102
  'XEB', 'QVOLUME', 'RBINT', 'MIRROR', 'GST', 'CONCURRENCE', 'NEGATIVITY',
103
103
  'IQPE', 'AMPEST', 'AMPLIFY', 'QWALK', 'GRAPHSTATE', 'FEATUREMAP', 'QKERNEL',
104
104
  'SHOR', 'HHL', 'PAULIPROP',
105
105
  'QUDIT', 'QX', 'QZ', 'QF', 'QSUM', 'QSTATE', 'QMEASURE',
106
106
  'BOSONIC', 'DISPLACE', 'SQUEEZE', 'CAT', 'BS', 'BSTATE',
107
+ 'RESOURCES', 'DEVICE', 'OPTIMIZE',
107
108
  # Gates
108
109
  'H', 'X', 'Y', 'Z', 'S', 'T', 'SDG', 'TDG', 'SX', 'ID',
109
110
  'RX', 'RY', 'RZ', 'P', 'U', 'CX', 'CZ', 'CY', 'CH',