|
| 1 | +# Noisy Circuit Dataset (Surface Code, d=3) |
| 2 | + |
| 3 | +Circuit-level surface-code memory experiments generated with Stim for **Belief Propagation (BP) decoding** demonstrations. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +| Parameter | Value | |
| 8 | +|-----------|-------| |
| 9 | +| Code | Rotated surface code | |
| 10 | +| Distance | d = 3 | |
| 11 | +| Noise model | i.i.d. depolarizing | |
| 12 | +| Error rate | p = 0.01 | |
| 13 | +| Task | Z-memory experiment | |
| 14 | +| Rounds | 3, 5, 7 | |
| 15 | + |
| 16 | +### Noise Application Points |
| 17 | +- Clifford gates (`after_clifford_depolarization`) |
| 18 | +- Data qubits between rounds (`before_round_data_depolarization`) |
| 19 | +- Resets (`after_reset_flip_probability`) |
| 20 | +- Measurements (`before_measure_flip_probability`) |
| 21 | + |
| 22 | +## Files |
| 23 | + |
| 24 | +| File | Description | |
| 25 | +|------|-------------| |
| 26 | +| `sc_d3_r3_p0010_z.stim` | 3 rounds, p=0.01, Z-memory | |
| 27 | +| `sc_d3_r5_p0010_z.stim` | 5 rounds, p=0.01, Z-memory | |
| 28 | +| `sc_d3_r7_p0010_z.stim` | 7 rounds, p=0.01, Z-memory | |
| 29 | +| `sc_d3_layout.png` | Qubit layout visualization | |
| 30 | +| `parity_check_matrix.png` | BP parity check matrix H | |
| 31 | +| `syndrome_stats.png` | Detection event statistics | |
| 32 | +| `single_syndrome.png` | Example syndrome pattern | |
| 33 | + |
| 34 | +## Qubit Layout |
| 35 | + |
| 36 | +The surface code layout showing qubit positions (data + ancilla): |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | +## Using This Dataset for BP Decoding |
| 41 | + |
| 42 | +### Step 1: Load Circuit and Extract Detector Error Model (DEM) |
| 43 | + |
| 44 | +The Detector Error Model is the key input for BP decoding. It describes which errors trigger which detectors. |
| 45 | + |
| 46 | +```python |
| 47 | +import stim |
| 48 | +import numpy as np |
| 49 | + |
| 50 | +# Load circuit |
| 51 | +circuit = stim.Circuit.from_file("datasets/noisy_circuits/sc_d3_r3_p0010_z.stim") |
| 52 | + |
| 53 | +# Extract DEM - this is what BP needs |
| 54 | +dem = circuit.detector_error_model(decompose_errors=True) |
| 55 | +print(f"Detectors: {dem.num_detectors}") # 24 |
| 56 | +print(f"Error mechanisms: {dem.num_errors}") # 286 |
| 57 | +print(f"Observables: {dem.num_observables}") # 1 |
| 58 | +``` |
| 59 | + |
| 60 | +### Step 2: Build Parity Check Matrix H |
| 61 | + |
| 62 | +BP operates on the parity check matrix where `H[i,j] = 1` means error `j` triggers detector `i`. |
| 63 | + |
| 64 | +```python |
| 65 | +def build_parity_check_matrix(dem): |
| 66 | + """Convert DEM to parity check matrix H and prior probabilities.""" |
| 67 | + errors = [] |
| 68 | + for inst in dem.flattened(): |
| 69 | + if inst.type == 'error': |
| 70 | + prob = inst.args_copy()[0] |
| 71 | + dets = [t.val for t in inst.targets_copy() if t.is_relative_detector_id()] |
| 72 | + obs = [t.val for t in inst.targets_copy() if t.is_logical_observable_id()] |
| 73 | + errors.append({'prob': prob, 'detectors': dets, 'observables': obs}) |
| 74 | + |
| 75 | + n_detectors = dem.num_detectors |
| 76 | + n_errors = len(errors) |
| 77 | + |
| 78 | + # Parity check matrix |
| 79 | + H = np.zeros((n_detectors, n_errors), dtype=np.uint8) |
| 80 | + # Prior error probabilities (for BP initialization) |
| 81 | + priors = np.zeros(n_errors) |
| 82 | + # Which errors flip the logical observable |
| 83 | + obs_flip = np.zeros(n_errors, dtype=np.uint8) |
| 84 | + |
| 85 | + for j, e in enumerate(errors): |
| 86 | + priors[j] = e['prob'] |
| 87 | + for d in e['detectors']: |
| 88 | + H[d, j] = 1 |
| 89 | + if e['observables']: |
| 90 | + obs_flip[j] = 1 |
| 91 | + |
| 92 | + return H, priors, obs_flip |
| 93 | + |
| 94 | +H, priors, obs_flip = build_parity_check_matrix(dem) |
| 95 | +print(f"H shape: {H.shape}") # (24, 286) |
| 96 | +``` |
| 97 | + |
| 98 | +The parity check matrix structure: |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | +### Step 3: Sample Syndromes (Detection Events) |
| 103 | + |
| 104 | +```python |
| 105 | +# Compile sampler |
| 106 | +sampler = circuit.compile_detector_sampler() |
| 107 | + |
| 108 | +# Sample detection events + observable flip |
| 109 | +n_shots = 1000 |
| 110 | +samples = sampler.sample(n_shots, append_observables=True) |
| 111 | + |
| 112 | +# Split into syndrome and observable |
| 113 | +syndromes = samples[:, :-1] # shape: (n_shots, n_detectors) |
| 114 | +actual_obs_flips = samples[:, -1] # shape: (n_shots,) |
| 115 | + |
| 116 | +print(f"Syndrome shape: {syndromes.shape}") |
| 117 | +print(f"Example syndrome: {syndromes[0]}") |
| 118 | +``` |
| 119 | + |
| 120 | +### Step 4: BP Decoding (Pseudocode) |
| 121 | + |
| 122 | +```python |
| 123 | +def bp_decode(H, syndrome, priors, max_iter=50, damping=0.5): |
| 124 | + """ |
| 125 | + Belief Propagation decoder (min-sum variant). |
| 126 | + |
| 127 | + Args: |
| 128 | + H: Parity check matrix (n_detectors, n_errors) |
| 129 | + syndrome: Detection events (n_detectors,) |
| 130 | + priors: Prior error probabilities (n_errors,) |
| 131 | + max_iter: Maximum BP iterations |
| 132 | + damping: Message damping factor |
| 133 | + |
| 134 | + Returns: |
| 135 | + estimated_errors: Most likely error pattern (n_errors,) |
| 136 | + soft_output: Log-likelihood ratios (n_errors,) |
| 137 | + """ |
| 138 | + n_checks, n_vars = H.shape |
| 139 | + |
| 140 | + # Initialize LLRs from priors: LLR = log((1-p)/p) |
| 141 | + llr_prior = np.log((1 - priors) / priors) |
| 142 | + |
| 143 | + # Messages: check-to-variable and variable-to-check |
| 144 | + # ... BP message passing iterations ... |
| 145 | + |
| 146 | + # Hard decision |
| 147 | + estimated_errors = (soft_output < 0).astype(int) |
| 148 | + |
| 149 | + return estimated_errors, soft_output |
| 150 | + |
| 151 | +# Decode each syndrome |
| 152 | +for i in range(n_shots): |
| 153 | + syndrome = syndromes[i] |
| 154 | + estimated_errors, _ = bp_decode(H, syndrome, priors) |
| 155 | + |
| 156 | + # Predict observable flip |
| 157 | + predicted_obs_flip = np.dot(estimated_errors, obs_flip) % 2 |
| 158 | + |
| 159 | + # Check if decoding succeeded |
| 160 | + success = (predicted_obs_flip == actual_obs_flips[i]) |
| 161 | +``` |
| 162 | + |
| 163 | +### Step 5: Evaluate Decoder Performance |
| 164 | + |
| 165 | +After decoding, compare predicted vs actual observable flips to measure logical error rate. |
| 166 | + |
| 167 | +```python |
| 168 | +def evaluate_decoder(decoder_fn, circuit, n_shots=10000): |
| 169 | + """Evaluate decoder logical error rate.""" |
| 170 | + dem = circuit.detector_error_model(decompose_errors=True) |
| 171 | + H, priors, obs_flip = build_parity_check_matrix(dem) |
| 172 | + |
| 173 | + sampler = circuit.compile_detector_sampler() |
| 174 | + samples = sampler.sample(n_shots, append_observables=True) |
| 175 | + syndromes = samples[:, :-1] |
| 176 | + actual_obs = samples[:, -1] |
| 177 | + |
| 178 | + errors = 0 |
| 179 | + for i in range(n_shots): |
| 180 | + est_errors, _ = decoder_fn(H, syndromes[i], priors) |
| 181 | + pred_obs = np.dot(est_errors, obs_flip) % 2 |
| 182 | + if pred_obs != actual_obs[i]: |
| 183 | + errors += 1 |
| 184 | + |
| 185 | + return errors / n_shots |
| 186 | + |
| 187 | +# logical_error_rate = evaluate_decoder(bp_decode, circuit) |
| 188 | +``` |
| 189 | + |
| 190 | +## Syndrome Statistics |
| 191 | + |
| 192 | +Detection event frequencies across 1000 shots (left) and baseline observable flip rate without decoding (right): |
| 193 | + |
| 194 | + |
| 195 | + |
| 196 | +## Example Syndrome |
| 197 | + |
| 198 | +A single syndrome sample showing which detectors fired (red = triggered): |
| 199 | + |
| 200 | + |
| 201 | + |
| 202 | +## Regenerating the Dataset |
| 203 | + |
| 204 | +```bash |
| 205 | +# Install the package with uv |
| 206 | +uv sync |
| 207 | + |
| 208 | +# Generate circuits using the CLI |
| 209 | +uv run generate-noisy-circuits \ |
| 210 | + --distance 3 \ |
| 211 | + --p 0.01 \ |
| 212 | + --rounds 3 5 7 \ |
| 213 | + --task z \ |
| 214 | + --output datasets/noisy_circuits |
| 215 | +``` |
| 216 | + |
| 217 | +## Extending the Dataset |
| 218 | + |
| 219 | +```bash |
| 220 | +# Different error rates |
| 221 | +uv run generate-noisy-circuits --p 0.005 --rounds 3 5 7 |
| 222 | + |
| 223 | +# Different distances |
| 224 | +uv run generate-noisy-circuits --distance 5 --rounds 5 7 9 |
| 225 | + |
| 226 | +# X-memory experiment |
| 227 | +uv run generate-noisy-circuits --task x --rounds 3 5 7 |
| 228 | +``` |
| 229 | + |
| 230 | +## References |
| 231 | + |
| 232 | +- [Stim Documentation](https://github.com/quantumlib/Stim) |
| 233 | +- [BP+OSD Decoder Paper](https://arxiv.org/abs/2005.07016) |
| 234 | +- [Surface Code Decoding Review](https://quantum-journal.org/papers/q-2024-10-10-1498/) |
0 commit comments