| """Floquet (honeycomb) dynamic code circuit builder. |
| |
| Implements the Hastings-Haah honeycomb Floquet code using a three-round |
| schedule of XX, YY, ZZ paired measurements on a honeycomb lattice. |
| |
| Each round measures one color of edges on the honeycomb, creating a set |
| of dynamically generated stabilizers (check operators that span 6 qubits |
| around each hexagon) and a logical qubit whose effective stabilizers |
| rotate in a well-defined way between rounds. |
| |
| Reference: Hastings and Haah (2021), "Dynamically Generated Logical Qubits" |
| https://arxiv.org/abs/2107.02194 |
| """ |
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| from typing import Dict, List, Optional, Tuple |
|
|
| import stim |
|
|
| from surface_code_in_stem.noise_models import NoiseModel, resolve_noise_model |
|
|
| Coord = Tuple[float, float] |
|
|
|
|
| @dataclass |
| class HoneycombLayout: |
| """Coordinate layout for a honeycomb Floquet code. |
| |
| Qubits sit on vertices of a honeycomb lattice. Edges are grouped into |
| three colors (0=red, 1=green, 2=blue) corresponding to XX, YY, ZZ |
| measurement rounds respectively. |
| """ |
| qubits: List[Coord] |
| coord_to_index: Dict[Coord, int] |
| edge_pairs: List[List[Tuple[Coord, Coord]]] |
| ancilla_coords: List[Coord] |
| logical_x_qubits: List[Coord] |
| logical_z_qubits: List[Coord] |
|
|
| @classmethod |
| def build(cls, distance: int) -> "HoneycombLayout": |
| """Build a honeycomb layout for given distance. |
| |
| Creates a d×d patch of the honeycomb lattice with boundary conditions |
| supporting one logical qubit. |
| |
| Args: |
| distance: Code distance (must be even for standard honeycomb) |
| """ |
| |
| |
| |
| |
|
|
| qubits = [] |
| coord_to_index: Dict[Coord, int] = {} |
|
|
| |
| rows = distance |
| cols = distance |
|
|
| for row in range(rows): |
| for col in range(cols): |
| |
| q0 = (float(2 * col), float(2 * row)) |
| q1 = (float(2 * col + 1), float(2 * row)) |
| for q in [q0, q1]: |
| if q not in coord_to_index: |
| coord_to_index[q] = len(qubits) |
| qubits.append(q) |
|
|
| |
| |
| |
| |
|
|
| edge_pairs: List[List[Tuple[Coord, Coord]]] = [[], [], []] |
|
|
| for row in range(rows): |
| for col in range(cols): |
| q0 = (float(2 * col), float(2 * row)) |
| q1 = (float(2 * col + 1), float(2 * row)) |
|
|
| |
| if q0 in coord_to_index and q1 in coord_to_index: |
| edge_pairs[2].append((q0, q1)) |
|
|
| |
| q_down = (float(2 * col + 1), float(2 * (row + 1))) |
| if q1 in coord_to_index and q_down in coord_to_index: |
| edge_pairs[0].append((q1, q_down)) |
|
|
| |
| q_right_down = (float(2 * (col + 1)), float(2 * row)) |
| if q1 in coord_to_index and q_right_down in coord_to_index: |
| edge_pairs[1].append((q1, q_right_down)) |
|
|
| |
| ancilla_coords = [] |
|
|
| |
| logical_z_qubits = [(float(0), float(2 * r)) for r in range(rows) if (0.0, float(2 * r)) in coord_to_index] |
| logical_x_qubits = [(float(2 * c), float(0)) for c in range(cols) if (float(2 * c), 0.0) in coord_to_index] |
|
|
| return cls( |
| qubits=qubits, |
| coord_to_index=coord_to_index, |
| edge_pairs=edge_pairs, |
| ancilla_coords=ancilla_coords, |
| logical_x_qubits=logical_x_qubits, |
| logical_z_qubits=logical_z_qubits, |
| ) |
|
|
|
|
| def floquet_honeycomb_code( |
| distance: int, |
| rounds: int, |
| p: float, |
| noise_model: Optional[NoiseModel] = None, |
| ) -> stim.Circuit: |
| """Build a Floquet honeycomb code circuit. |
| |
| Uses a three-round schedule of paired measurements: |
| - Round 0 mod 3: Measure XX on type-0 edges |
| - Round 1 mod 3: Measure YY on type-1 edges |
| - Round 2 mod 3: Measure ZZ on type-2 edges |
| |
| Detectors are formed by comparing each edge measurement to its |
| previous appearance (every 3 rounds). |
| |
| Args: |
| distance: Code distance |
| rounds: Number of measurement rounds (should be multiple of 3 for clean boundaries) |
| p: Physical error rate |
| noise_model: Optional noise model (defaults to IID depolarizing with rate p) |
| |
| Returns: |
| stim.Circuit with detectors and logical observable |
| """ |
| noise_model = resolve_noise_model(p, noise_model) |
| layout = HoneycombLayout.build(distance) |
|
|
| c2i = layout.coord_to_index |
| all_data = list(c2i.keys()) |
| n_data = len(all_data) |
|
|
| |
| ancilla_offset = n_data |
| ancilla_map: Dict[Tuple[Coord, Coord], int] = {} |
|
|
| all_edge_pairs: List[Tuple[Coord, Coord]] = [] |
| for group in layout.edge_pairs: |
| all_edge_pairs.extend(group) |
|
|
| for i, pair in enumerate(all_edge_pairs): |
| ancilla_map[pair] = ancilla_offset + i |
|
|
| n_ancilla = len(ancilla_map) |
| total_qubits = n_data + n_ancilla |
|
|
| |
| lines: List[str] = [] |
|
|
| |
| for coord, idx in c2i.items(): |
| lines.append(f"QUBIT_COORDS({coord[0]},{coord[1]}) {idx}") |
| for pair, anc_idx in ancilla_map.items(): |
| mid_x = (pair[0][0] + pair[1][0]) / 2 |
| mid_y = (pair[0][1] + pair[1][1]) / 2 |
| lines.append(f"QUBIT_COORDS({mid_x},{mid_y}) {anc_idx}") |
|
|
| |
| all_data_idx = list(c2i.values()) |
| all_anc_idx = list(ancilla_map.values()) |
| all_qubit_idx = all_data_idx + all_anc_idx |
|
|
| |
| round_paulis = ["XX", "YY", "ZZ"] |
|
|
| |
| prev_anc_meas: Dict[Tuple[Coord, Coord], int] = {} |
| meas_count = 0 |
|
|
| def add_lines(new_lines: List[str]) -> None: |
| lines.extend(new_lines) |
|
|
| |
| lines.append(f"R {' '.join(map(str, all_qubit_idx))}") |
| for ln in noise_model.reset_noise(qubits=list(all_qubit_idx), layer_id="init_reset"): |
| lines.append(ln) |
| lines.append("TICK") |
|
|
| for r in range(rounds): |
| round_type = r % 3 |
| pauli = round_paulis[round_type] |
| edge_group = layout.edge_pairs[round_type] |
|
|
| if not edge_group: |
| continue |
|
|
| |
| anc_qubits = [ancilla_map[pair] for pair in edge_group if pair in ancilla_map] |
|
|
| |
| if anc_qubits: |
| lines.append(f"R {' '.join(map(str, anc_qubits))}") |
| for ln in noise_model.reset_noise(qubits=anc_qubits, layer_id=f"round_{r}_reset"): |
| lines.append(ln) |
| lines.append("TICK") |
|
|
| |
| if pauli == "XX": |
| |
| for pair in edge_group: |
| if pair not in ancilla_map: |
| continue |
| q0, q1 = c2i[pair[0]], c2i[pair[1]] |
| anc = ancilla_map[pair] |
| active = {q0, q1, anc} |
| idle = [q for q in all_qubit_idx if q not in active] |
| lines.append(f"H {anc}") |
| for ln in noise_model.gate_noise(gate="H", pair_targets=[], idle_targets=list(idle), layer_id=f"round_{r}_h_pre"): |
| lines.append(ln) |
| lines.append("TICK") |
| lines.append(f"CX {anc} {q0} {anc} {q1}") |
| for ln in noise_model.gate_noise(gate="CX", pair_targets=[anc, q0, anc, q1], idle_targets=list(idle), layer_id=f"round_{r}_cx"): |
| lines.append(ln) |
| lines.append("TICK") |
| lines.append(f"H {anc}") |
| for ln in noise_model.gate_noise(gate="H", pair_targets=[], idle_targets=list(idle), layer_id=f"round_{r}_h_post"): |
| lines.append(ln) |
| lines.append("TICK") |
|
|
| elif pauli == "ZZ": |
| |
| for pair in edge_group: |
| if pair not in ancilla_map: |
| continue |
| q0, q1 = c2i[pair[0]], c2i[pair[1]] |
| anc = ancilla_map[pair] |
| active = {q0, q1, anc} |
| idle = [q for q in all_qubit_idx if q not in active] |
| lines.append(f"CX {q0} {anc} {q1} {anc}") |
| for ln in noise_model.gate_noise(gate="CX", pair_targets=[q0, anc, q1, anc], idle_targets=list(idle), layer_id=f"round_{r}_cx"): |
| lines.append(ln) |
| lines.append("TICK") |
|
|
| elif pauli == "YY": |
| |
| for pair in edge_group: |
| if pair not in ancilla_map: |
| continue |
| q0, q1 = c2i[pair[0]], c2i[pair[1]] |
| anc = ancilla_map[pair] |
| active = {q0, q1, anc} |
| idle = [q for q in all_qubit_idx if q not in active] |
| |
| lines.append(f"S_DAG {q0} {q1}") |
| lines.append("TICK") |
| lines.append(f"H {anc}") |
| lines.append("TICK") |
| lines.append(f"CX {anc} {q0} {anc} {q1}") |
| for ln in noise_model.gate_noise(gate="CX", pair_targets=[anc, q0, anc, q1], idle_targets=list(idle), layer_id=f"round_{r}_cx"): |
| lines.append(ln) |
| lines.append("TICK") |
| lines.append(f"H {anc}") |
| lines.append("TICK") |
| |
| lines.append(f"S {q0} {q1}") |
| lines.append("TICK") |
|
|
| |
| if anc_qubits: |
| for ln in noise_model.measurement_noise(qubits=list(anc_qubits), layer_id=f"round_{r}_meas"): |
| lines.append(ln) |
| lines.append(f"M {' '.join(map(str, anc_qubits))}") |
|
|
| |
| curr_indices = list(range(meas_count, meas_count + len(anc_qubits))) |
| meas_count += len(anc_qubits) |
|
|
| for pair, curr_idx in zip(edge_group, curr_indices): |
| if pair in ancilla_map: |
| curr_offset = meas_count - curr_idx |
| mid_x = (pair[0][0] + pair[1][0]) / 2 |
| mid_y = (pair[0][1] + pair[1][1]) / 2 |
| if pair in prev_anc_meas: |
| prev_idx = prev_anc_meas[pair] |
| prev_offset = meas_count - prev_idx |
| lines.append( |
| f"DETECTOR({mid_x},{mid_y}) rec[-{curr_offset}] rec[-{prev_offset}]" |
| ) |
| elif r >= 3: |
| lines.append( |
| f"DETECTOR({mid_x},{mid_y}) rec[-{curr_offset}]" |
| ) |
| prev_anc_meas[pair] = curr_idx |
|
|
| |
| logical_z = layout.logical_z_qubits |
| if logical_z: |
| final_meas_targets = [c2i[q] for q in logical_z if q in c2i] |
| if final_meas_targets: |
| lines.append(f"M {' '.join(map(str, final_meas_targets))}") |
| obs_offsets = " ".join( |
| f"rec[-{meas_count + len(final_meas_targets) - i}]" |
| for i in range(len(final_meas_targets)) |
| ) |
| meas_count += len(final_meas_targets) |
| lines.append(f"OBSERVABLE_INCLUDE(0) {obs_offsets}") |
|
|
| return stim.Circuit("\n".join(lines) + "\n") |
|
|