File size: 6,536 Bytes
9f8cf99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
"""Shared utilities for dynamic surface code circuit builders.

These helpers centralize the Stim string assembly, coordinate bookkeeping, and
noisy layer construction used by the dynamic variants inspired by Morvan et al.
(2025). Each builder returns a Stim circuit string with qubit coordinates,
noisy entangling layers, detectors that stitch time-adjacent measurements, and a
terminal logical observable constructed from a boundary measurement.
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Iterable, List, Optional, Sequence, Tuple

from surface_code_in_stem.noise_models import NoiseModel, resolve_noise_model
from surface_code_in_stem.surface_code import adjacent_coords, prepare_coords

Coord = Tuple[float, float]


@dataclass
class DynamicLayout:
    """Container for the square-lattice surface code coordinates used here."""

    datas: List[Coord]
    x_measures: List[Coord]
    z_measures: List[Coord]
    coord_to_index: Dict[Coord, int]

    @classmethod
    def build(cls, distance: int) -> "DynamicLayout":
        datas, x_measures, z_measures, coord_to_index = prepare_coords(distance)
        return cls(datas=datas, x_measures=x_measures, z_measures=z_measures, coord_to_index=coord_to_index)


class StimStringBuilder:
    """Utility for assembling Stim circuit strings with detector bookkeeping."""

    def __init__(self, coord_lookup: Dict[int, Coord]):
        self.lines: List[str] = []
        self.measurement_count = 0
        self.coord_lookup = coord_lookup

    def add(self, line: str) -> None:
        if line.strip():
            self.lines.append(line.rstrip())

    def measure(self, qubits: Sequence[int]) -> List[int]:
        if not qubits:
            return []
        targets = " ".join(map(str, qubits))
        self.add(f"M {targets}")
        indices = list(range(self.measurement_count, self.measurement_count + len(qubits)))
        self.measurement_count += len(qubits)
        return indices

    def detector(self, coord: Coord, rec_indices: Sequence[int]) -> None:
        offsets = [self.measurement_count - idx for idx in rec_indices]
        rec_terms = " ".join(f"rec[-{offset}]" for offset in offsets)
        self.add(f"DETECTOR({coord[0]}, {coord[1]}) {rec_terms}")

    def observable(self, qubit_measure_indices: Sequence[int]) -> None:
        offsets = [self.measurement_count - idx for idx in qubit_measure_indices]
        rec_terms = " ".join(f"rec[-{offset}]" for offset in offsets)
        self.add(f"OBSERVABLE_INCLUDE(0) {rec_terms}")

    def qubit_coords(self) -> None:
        for index, coord in self.coord_lookup.items():
            self.add(f"QUBIT_COORDS({coord[0]},{coord[1]}) {index}")

    def build(self) -> str:
        return "\n".join(self.lines) + "\n"


def index_string(coords: Iterable[Coord], c2i: Dict[Coord, int]) -> str:
    return " ".join(str(c2i[c]) for c in coords)


def noisy_layer(
    builder: StimStringBuilder,
    gate: str,
    pairs: List[int],
    idle_qubits: Sequence[int],
    noise_model: NoiseModel,
    layer_id: str,
) -> None:
    if not pairs:
        return
    targets = " ".join(map(str, pairs))
    builder.add(f"{gate} {targets}")
    for line in noise_model.gate_noise(
        gate=gate, pair_targets=pairs, idle_targets=idle_qubits, layer_id=layer_id
    ):
        builder.add(line)
    builder.add("TICK")


def orientation_pairs(
    measures: List[Coord], orient: int, c2i: Dict[Coord, int], reorder: Sequence[int] | None = None
) -> List[int]:
    pairs: List[int] = []
    for measure in measures:
        adj = adjacent_coords(measure)
        if reorder is not None:
            adj = [adj[i] for i in reorder]
        if orient < len(adj) and adj[orient] in c2i:
            pairs.extend([c2i[measure], c2i[adj[orient]]])
    return pairs


def stabilizer_cycle(
    builder: StimStringBuilder,
    layout: DynamicLayout,
    p: float,
    orientations: Sequence[int],
    gate: str,
    noise_model: Optional[NoiseModel] = None,
    reset_data: bool = False,
    measure_data: bool = False,
    prev_meas: Dict[Coord, int] | None = None,
) -> Dict[Coord, int]:
    prev_meas = prev_meas or {}
    datas, x_measures, z_measures, c2i = (
        layout.datas,
        layout.x_measures,
        layout.z_measures,
        layout.coord_to_index,
    )
    noise_model = resolve_noise_model(p, noise_model)
    measure_qubits = x_measures + z_measures
    all_qubits = datas + measure_qubits

    reset_targets = measure_qubits + (datas if reset_data else [])
    if reset_targets:
        builder.add(f"R {index_string(reset_targets, c2i)}")
        for line in noise_model.reset_noise(qubits=[c2i[q] for q in reset_targets], layer_id="dynamic_reset"):
            builder.add(line)
        builder.add("TICK")

    builder.add(f"H {index_string(x_measures, c2i)}")
    for line in noise_model.gate_noise(gate="H", pair_targets=[], idle_targets=[c2i[q] for q in all_qubits], layer_id="dynamic_h_pre"):
        builder.add(line)
    builder.add("TICK")

    reorder = [0, 2, 1, 3]
    for orient in orientations:
        cz_pairs = orientation_pairs(z_measures, orient, c2i)
        cx_pairs = orientation_pairs(x_measures, orient, c2i, reorder=reorder)
        active_pair_targets = set(cz_pairs) | set(cx_pairs)
        idle_targets = [c2i[q] for q in all_qubits if c2i[q] not in active_pair_targets]
        noisy_layer(builder, gate, cz_pairs, idle_targets, noise_model, f"dynamic_z_orient_{orient}")
        noisy_layer(builder, gate, cx_pairs, idle_targets, noise_model, f"dynamic_x_orient_{orient}")

    builder.add(f"H {index_string(x_measures, c2i)}")
    for line in noise_model.gate_noise(gate="H", pair_targets=[], idle_targets=[c2i[q] for q in all_qubits], layer_id="dynamic_h_post"):
        builder.add(line)
    builder.add("TICK")

    measurement_targets = measure_qubits + (datas if measure_data else [])
    for line in noise_model.measurement_noise(qubits=[c2i[q] for q in measurement_targets], layer_id="dynamic_measure"):
        builder.add(line)
    record_indices = builder.measure([c2i[q] for q in measurement_targets])
    coord_order = measure_qubits + (datas if measure_data else [])
    coord_to_rec = {coord: idx for coord, idx in zip(coord_order, record_indices)}

    for coord in measure_qubits:
        current = coord_to_rec[coord]
        if coord in prev_meas:
            builder.detector(coord, [prev_meas[coord], current])
        else:
            builder.detector(coord, [current])

    return coord_to_rec