Gyanateet Dutta
Fix Space loading: direct Streamlit, lazy imports, ReNova page, fix deps
9f8cf99
Raw
History Blame Contribute Delete
6.54 kB
"""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