quantum-qec / app /surface_code.py
9059Rohith
Initial Quantum-QEC OpenEnv submission-ready project
4c17cfe
Raw History Blame Contribute Delete
4.78 kB
from __future__ import annotations
import math
from collections import deque
from typing import Dict, List, Tuple
import numpy as np
class SurfaceCode:
"""
Lightweight stabilizer-style simulator.
Uses binary error frames (X/Z) plus parity-check matrices to expose a
syndrome-based control problem while staying within small CPU/RAM limits.
"""
def __init__(self, distance: int):
self.distance = int(distance)
self.n_qubits = 2 * (self.distance ** 2) - 2 * self.distance + 1
self.grid_cols = math.ceil(math.sqrt(self.n_qubits))
self.grid_rows = math.ceil(self.n_qubits / self.grid_cols)
self.x_err = np.zeros(self.n_qubits, dtype=np.uint8)
self.z_err = np.zeros(self.n_qubits, dtype=np.uint8)
self.hx, self.hz = self._build_parity_checks()
def _neighbors(self, idx: int) -> List[int]:
r = idx // self.grid_cols
c = idx % self.grid_cols
out: List[int] = [idx]
cand = [
(r - 1, c),
(r + 1, c),
(r, c - 1),
(r, c + 1),
]
for rr, cc in cand:
if 0 <= rr < self.grid_rows and 0 <= cc < self.grid_cols:
j = rr * self.grid_cols + cc
if j < self.n_qubits:
out.append(j)
return out
def _build_parity_checks(self) -> Tuple[np.ndarray, np.ndarray]:
x_rows: List[np.ndarray] = []
z_rows: List[np.ndarray] = []
for i in range(self.n_qubits - 1):
row_x = np.zeros(self.n_qubits, dtype=np.uint8)
row_z = np.zeros(self.n_qubits, dtype=np.uint8)
for j in self._neighbors(i):
row_x[j] = 1
for j in self._neighbors((i + self.distance) % self.n_qubits):
row_z[j] = 1
x_rows.append(row_x)
z_rows.append(row_z)
return np.vstack(x_rows), np.vstack(z_rows)
def apply_error(self, qubit_idx: int, error_type: str) -> None:
if qubit_idx < 0 or qubit_idx >= self.n_qubits:
return
if error_type == "X":
self.x_err[qubit_idx] ^= 1
elif error_type == "Z":
self.z_err[qubit_idx] ^= 1
elif error_type == "Y":
self.x_err[qubit_idx] ^= 1
self.z_err[qubit_idx] ^= 1
def measure_syndromes(self) -> List[int]:
x_syn = (self.hx @ self.z_err) % 2
z_syn = (self.hz @ self.x_err) % 2
return np.concatenate([x_syn, z_syn]).astype(int).tolist()
def apply_correction(self, corrections: Dict[int, str]) -> None:
for q, op in corrections.items():
self.apply_error(int(q), op)
def _spans_vertical(self, active: np.ndarray) -> bool:
top = [i for i in range(min(self.grid_cols, self.n_qubits)) if active[i] == 1]
if not top:
return False
last_row_start = (self.grid_rows - 1) * self.grid_cols
bottom = {
i
for i in range(last_row_start, min(last_row_start + self.grid_cols, self.n_qubits))
if active[i] == 1
}
if not bottom:
return False
seen = set(top)
queue = deque(top)
while queue:
cur = queue.popleft()
if cur in bottom:
return True
for nxt in self._neighbors(cur):
if active[nxt] == 1 and nxt not in seen:
seen.add(nxt)
queue.append(nxt)
return False
def _spans_horizontal(self, active: np.ndarray) -> bool:
left = [i for i in range(0, self.n_qubits, self.grid_cols) if active[i] == 1]
if not left:
return False
right = {
i
for i in range(self.grid_cols - 1, self.n_qubits, self.grid_cols)
if active[i] == 1
}
if not right:
return False
seen = set(left)
queue = deque(left)
while queue:
cur = queue.popleft()
if cur in right:
return True
for nxt in self._neighbors(cur):
if active[nxt] == 1 and nxt not in seen:
seen.add(nxt)
queue.append(nxt)
return False
def check_logical_failure(self) -> bool:
return self._spans_vertical(self.z_err) or self._spans_horizontal(self.x_err)
def get_fidelity_estimate(self) -> float:
weight = int(self.x_err.sum() + self.z_err.sum())
ratio = weight / max(1, 2 * self.n_qubits)
return max(0.0, 1.0 - ratio)
@property
def error_frame(self) -> Dict[str, List[int]]:
return {
"x": self.x_err.astype(int).tolist(),
"z": self.z_err.astype(int).tolist(),
}