File size: 4,782 Bytes
4c17cfe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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(),
        }