Source code for surface_sim.circuit_blocks.util

from collections.abc import Collection, Generator, Sequence
from itertools import chain

from stim import Circuit

from ..detectors import Detectors, get_new_stab_dict_from_layout
from ..layouts.layout import Layout
from ..models import Model
from .decorators import (
    logical_measurement_x,
    logical_measurement_z,
    logical_noise,
    pauli_observable_x,
    pauli_observable_y,
    pauli_observable_z,
    qec_circuit,
    qubit_init_x,
    qubit_init_z,
    sq_gate,
    to_end_cycle_circuit,
    to_mid_cycle_circuit,
    tq_gate,
)


[docs] def qubit_coords(model: Model, *layouts: Layout) -> Circuit: """Returns a stim circuit that sets up the coordinates of the qubits.""" coord_dict: dict[str, Collection[int | float]] = {} for layout in layouts: coord_dict.update(layout.qubit_coords) return model.qubit_coords(coord_dict)
@sq_gate def idle_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuit blocks which in total correspond to a logical idling of the given model. Parameters ---------- model Noise model for the gates. layout Code layout. """ data_qubits = layout.data_qubits qubits = layout.qubits yield model.incoming_noise(data_qubits) yield model.tick() yield model.idle(qubits) yield model.tick()
[docs] def log_meas( model: Model, layout: Layout, detectors: Detectors, rot_basis: bool = False, anc_reset: bool = True, anc_detectors: Collection[str] | None = None, ) -> Circuit: """ Returns stim circuit corresponding to a logical measurement of the given model. It defines the observables for all the logical qubits in the layout. Parameters ---------- model Noise model for the gates. layout Code layout. detectors Detector definitions to use. rot_basis If ``True``, the memory experiment is performed in the X basis. If ``False``, the memory experiment is performed in the Z basis. By deafult ``False``. anc_reset If ``True``, ancillas are reset at the beginning of the QEC round. By default ``True``. anc_detectors List of ancilla qubits for which to define the detectors. If ``None``, adds all detectors. By default ``None``. Notes ----- It activates all the ancillas in ``detectors`` to always build the detectors. As this function should not be used when building encoded circuits with the iterating functions, it does not matter if the detectors are activated or not. """ anc_qubits = layout.anc_qubits if anc_detectors is None: anc_detectors = anc_qubits if set(anc_detectors) > set(anc_qubits): raise ValueError("Some of the given 'anc_qubits' are not ancilla qubits.") circuit = sum( log_meas_iterator(model=model, layout=layout, rot_basis=rot_basis), start=Circuit(), ) # activate detectors so that "Detectors.build_from_anc" always populates # the stim detector definitions. inactive_dets = set(anc_detectors).difference(detectors.detectors) detectors.activate_detectors(inactive_dets) # detectors and logical observables stab_type = "x_type" if rot_basis else "z_type" stabs = layout.get_qubits(role="anc", stab_type=stab_type) anc_support = layout.get_support(stabs) detectors_stim = detectors.build_from_data( model.meas_target, anc_support, anc_reset, reconstructable_stabs=stabs, anc_qubits=anc_detectors, ) circuit += detectors_stim log_op = "log_x" if rot_basis else "log_z" for logical_qubit in layout.logical_qubits: log_data_qubits = layout.logical_param(log_op, logical_qubit) targets = [model.meas_target(qubit, -1) for qubit in log_data_qubits] circuit.append("OBSERVABLE_INCLUDE", targets, 0) # deactivate detectors detectors.deactivate_detectors(layout.anc_qubits) return circuit
[docs] def log_meas_iterator( model: Model, layout: Layout, rot_basis: bool = False, ) -> Generator[Circuit]: """ Yields stim circuit blocks which in total correspond to a logical measurement of the given model without the definition of the detectors and observables. Parameters ---------- model Noise model for the gates. layout Code layout. rot_basis If ``True``, the memory experiment is performed in the X basis. If ``False``, the memory experiment is performed in the Z basis. By deafult ``False``. """ anc_qubits = layout.anc_qubits data_qubits = layout.data_qubits yield model.incoming_noise(data_qubits) yield model.tick() if rot_basis: yield model.hadamard(data_qubits) + model.idle(anc_qubits) yield model.tick() yield model.measure(data_qubits) + model.idle(anc_qubits) yield model.tick()
@logical_measurement_z def log_meas_z_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuit blocks which in total correspond to a logical Z measurement of the given model without the definition of the detectors and observables. Parameters ---------- model Noise model for the gates. layout Code layout. """ yield from log_meas_iterator(model=model, layout=layout, rot_basis=False) @logical_measurement_x def log_meas_x_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuit blocks which in total correspond to a logical X measurement of the given model without the definition of the detectors and observables. Parameters ---------- model Noise model for the gates. layout Code layout. """ yield from log_meas_iterator(model=model, layout=layout, rot_basis=True)
[docs] def init_qubits( model: Model, layout: Layout, detectors: Detectors, data_init: dict[str, int], rot_basis: bool = False, ) -> Circuit: """ Returns stim circuit corresponding to a logical initialization of the given model. By default, the logical measurement is in the Z basis. If rot_basis, the logical measurement is in the X basis. """ # activate detectors # the order of activating the detectors or applying the circuit # does not matter because this will be done in a layer of logical operations, # so no QEC round are run simultaneously anc_qubits = layout.anc_qubits stab_type = "z_type" if rot_basis else "x_type" gauge_dets = layout.get_qubits(role="anc", stab_type=stab_type) detectors.activate_detectors(anc_qubits, gauge_dets=gauge_dets) return sum( init_qubits_iterator( model=model, layout=layout, data_init=data_init, rot_basis=rot_basis, ), start=Circuit(), )
[docs] def init_qubits_iterator( model: Model, layout: Layout, data_init: dict[str, int], rot_basis: bool = False, ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization of the given model. By default, the logical initialization is in the Z basis. If rot_basis, the logical initialization is in the X basis. """ anc_qubits = layout.anc_qubits data_qubits = layout.data_qubits qubits = set(layout.qubits) yield model.reset(data_qubits) + model.idle(anc_qubits) yield model.tick() exc_qubits = set([q for q, s in data_init.items() if s and (q in data_qubits)]) if exc_qubits: init_circ = Circuit() init_circ += model.x_gate(exc_qubits) idle_qubits = qubits - exc_qubits yield init_circ + model.idle(idle_qubits) yield model.tick() if rot_basis: yield model.hadamard(data_qubits) + model.idle(anc_qubits) yield model.tick()
@qubit_init_z def init_qubits_z0_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |0> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 0 for q in layout.data_qubits} yield from init_qubits_iterator( model=model, layout=layout, data_init=data_init, rot_basis=False ) @qubit_init_z def init_qubits_z1_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |1> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 1 for q in layout.data_qubits} if layout.num_data_qubits % 2 == 0: # ensure that the bistring corresponds to the |1> state data_init[layout.data_qubits[-1]] = 0 yield from init_qubits_iterator( model=model, layout=layout, data_init=data_init, rot_basis=False ) @qubit_init_x def init_qubits_x0_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |+> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 0 for q in layout.data_qubits} yield from init_qubits_iterator( model=model, layout=layout, data_init=data_init, rot_basis=True ) @qubit_init_x def init_qubits_x1_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |-> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 1 for q in layout.data_qubits} if layout.num_data_qubits % 2 == 0: # ensure that the bistring corresponds to the |-> state data_init[layout.data_qubits[-1]] = 0 yield from init_qubits_iterator( model=model, layout=layout, data_init=data_init, rot_basis=True )
[docs] def log_x(model: Model, layout: Layout, detectors: Detectors) -> Circuit: """ Returns stim circuit corresponding to a logical X gate of the given model. """ # the stabilizer generators do not change when applying a logical X gate return sum(log_x_iterator(model=model, layout=layout), start=Circuit())
@sq_gate def log_x_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical X gate of the given model. """ anc_qubits = layout.anc_qubits data_qubits = layout.data_qubits qubits = anc_qubits + data_qubits if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_x_qubits = layout.logical_param("log_x", log_qubit_label) yield model.incoming_noise(data_qubits) yield model.tick() idle_qubits = set(qubits) - set(log_x_qubits) yield model.x_gate(log_x_qubits) + model.idle(idle_qubits) yield model.tick()
[docs] def log_z(model: Model, layout: Layout, detectors: Detectors) -> Circuit: """ Returns stim circuit corresponding to a logical Z gate of the given model. """ # the stabilizer generators do not change when applying a logical Z gate return sum(log_z_iterator(model=model, layout=layout), start=Circuit())
@sq_gate def log_z_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical Z gate of the given model. """ anc_qubits = layout.anc_qubits data_qubits = layout.data_qubits qubits = anc_qubits + data_qubits if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_z_qubits = layout.logical_param("log_z", log_qubit_label) yield model.incoming_noise(data_qubits) yield model.tick() idle_qubits = set(qubits) - set(log_z_qubits) yield model.z_gate(log_z_qubits) + model.idle(idle_qubits) yield model.tick()
[docs] def log_fold_trans_s(model: Model, layout: Layout, detectors: Detectors) -> Circuit: """Returns the stim circuit corresponding to a transversal logical S gate implemented following: https://quantum-journal.org/papers/q-2024-04-08-1310/ and https://doi.org/10.1088/1367-2630/17/8/083026 """ # update the stabilizer generators gate_label = f"log_fold_trans_s_{layout.logical_qubits[0]}" new_stabs, new_stabs_inv = get_new_stab_dict_from_layout(layout, gate_label) detectors.update(new_stabs, new_stabs_inv) return sum(log_fold_trans_s_iterator(model=model, layout=layout), start=Circuit())
@sq_gate def log_fold_trans_s_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """Yields the stim circuits corresponding to a transversal logical S gate implemented following: https://quantum-journal.org/papers/q-2024-04-08-1310/ and https://doi.org/10.1088/1367-2630/17/8/083026 """ if layout.code not in ["rotated_surface_code", "unrotated_surface_code"]: raise TypeError( "The given layout is not a rotated/unrotated surface code, " f"but a {layout.code}" ) data_qubits = layout.data_qubits anc_qubits = layout.anc_qubits gate_label = f"log_fold_trans_s_{layout.logical_qubits[0]}" cz_pairs: set[tuple[str, str]] = set() qubits_s_gate: set[str] = set() qubits_s_dag_gate: set[str] = set() for data_qubit in data_qubits: trans_s = layout.param(gate_label, data_qubit) if trans_s is None: raise ValueError( "The layout does not have the information to run a " f"transversal S gate on qubit {data_qubit}. " "Use the 'log_gates' module to set it up." ) # Using a set to avoid repetition of the cz gates. # Using tuple so that the object is hashable for the set. if trans_s["cz"] is not None: cz_pairs.add(tuple(sorted([data_qubit, trans_s["cz"]]))) if trans_s["local"] == "S": qubits_s_gate.add(data_qubit) elif trans_s["local"] == "S_DAG": qubits_s_dag_gate.add(data_qubit) yield model.incoming_noise(data_qubits) yield model.tick() # S, S_DAG gates int_qubits = list(chain.from_iterable(cz_pairs)) yield ( model.s_gate(qubits_s_gate) + model.s_dag_gate(qubits_s_dag_gate) + model.cphase(int_qubits) + model.idle(anc_qubits) ) yield model.tick()
[docs] def log_fold_trans_h(model: Model, layout: Layout, detectors: Detectors) -> Circuit: """Returns the stim circuit corresponding to a transversal logical H gate implemented following the circuit show in: https://arxiv.org/pdf/2406.17653 """ # update the stabilizer generators gate_label = f"log_fold_trans_h_{layout.logical_qubits[0]}" new_stabs, new_stabs_inv = get_new_stab_dict_from_layout(layout, gate_label) detectors.update(new_stabs, new_stabs_inv) return sum(log_fold_trans_h_iterator(model=model, layout=layout), start=Circuit())
@sq_gate def log_fold_trans_h_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """Yields the stim circuits corresponding to a fold-transversal logical H gate implemented following the circuit show in: https://arxiv.org/pdf/2406.17653 """ if layout.code not in ["unrotated_surface_code"]: raise TypeError( f"The given layout is not an unrotated surface code, but a {layout.code}" ) data_qubits = layout.data_qubits qubits = set(layout.qubits) gate_label = f"log_fold_trans_h_{layout.logical_qubits[0]}" swap_pairs: set[tuple[str, str]] = set() qubits_h_gate: set[str] = set() for data_qubit in data_qubits: trans_h = layout.param(gate_label, data_qubit) if trans_h is None: raise ValueError( "The layout does not have the information to run a " f"transversal H gate on qubit {data_qubit}. " "Use the 'log_gates' module to set it up." ) # Using a set to avoid repetition of the swap gates. # Using tuple so that the object is hashable for the set. if trans_h["swap"] is not None: swap_pairs.add(tuple(sorted([data_qubit, trans_h["swap"]]))) if trans_h["local"] == "H": qubits_h_gate.add(data_qubit) yield model.incoming_noise(data_qubits) yield model.tick() # H gates idle_qubits = qubits - qubits_h_gate yield model.hadamard(qubits_h_gate) + model.idle(idle_qubits) yield model.tick() # long-range SWAP gates int_qubits = list(chain.from_iterable(swap_pairs)) idle_qubits = qubits - set(int_qubits) yield model.swap(int_qubits) + model.idle(idle_qubits) yield model.tick()
[docs] def log_trans_cnot( model: Model, layout_c: Layout, layout_t: Layout, detectors: Detectors ) -> Circuit: """Returns the stim circuit corresponding to a transversal logical CNOT gate. Parameters ---------- model Noise model for the gates. layout_c Code layout for the control of the logical CNOT. layout_t Code layout for the target of the logical CNOT. detectors Detector definitions to use. """ # update the stabilizer generators gate_label = ( f"log_trans_cnot_{layout_c.logical_qubits[0]}_{layout_t.logical_qubits[0]}" ) new_stabs, new_stabs_inv = get_new_stab_dict_from_layout(layout_c, gate_label) new_stabs_2, new_stabs_2_inv = get_new_stab_dict_from_layout(layout_t, gate_label) new_stabs.update(new_stabs_2) new_stabs_inv.update(new_stabs_2_inv) detectors.update(new_stabs, new_stabs_inv) return sum( log_trans_cnot_iterator(model=model, layout_c=layout_c, layout_t=layout_t), start=Circuit(), )
@tq_gate def log_trans_cnot_iterator( model: Model, layout_c: Layout, layout_t: Layout ) -> Generator[Circuit]: """Returns the stim circuit corresponding to a transversal logical CNOT gate. Parameters ---------- model Noise model for the gates. layout_c Code layout for the control of the logical CNOT. layout_t Code layout for the target of the logical CNOT. detectors Detector definitions to use. """ if layout_c.code not in ["rotated_surface_code", "unrotated_surface_code"]: raise TypeError( "The given layout is not a rotated/unrotated surface code, " f"but a {layout_c.code}" ) if layout_t.code not in ["rotated_surface_code", "unrotated_surface_code"]: raise TypeError( "The given layout is not a rotated/unrotated surface code, " f"but a {layout_t.code}" ) data_qubits_c = layout_c.data_qubits data_qubits_t = layout_t.data_qubits qubits = set(layout_c.qubits + layout_t.qubits) gate_label = ( f"log_trans_cnot_{layout_c.logical_qubits[0]}_{layout_t.logical_qubits[0]}" ) cz_pairs: set[tuple[str, str]] = set() qubits_h_gate = set(data_qubits_t) for data_qubit in data_qubits_c: trans_cnot = layout_c.param(gate_label, data_qubit) if trans_cnot is None: raise ValueError( "The layout does not have the information to run " f"{gate_label} gate on qubit {data_qubit}. " "Use the 'log_gates' module to set it up." ) cz_pairs.add((data_qubit, trans_cnot["cz"])) yield model.incoming_noise(data_qubits_c) + model.incoming_noise(data_qubits_t) yield model.tick() # CNOT is decomposed into H CZ H idle_qubits = qubits - qubits_h_gate yield model.hadamard(qubits_h_gate) + model.idle(idle_qubits) yield model.tick() # long-range CZ gates int_qubits = list(chain.from_iterable(cz_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cphase(int_qubits) + model.idle(idle_qubits) yield model.tick() # CNOT is decomposed into H CZ H idle_qubits = qubits - qubits_h_gate yield model.hadamard(qubits_h_gate) + model.idle(idle_qubits) yield model.tick() def log_meas_xzzx( model: Model, layout: Layout, detectors: Detectors, rot_basis: bool = False, anc_reset: bool = True, anc_detectors: Collection[str] | None = None, ) -> Circuit: """ Returns stim circuit corresponding to a logical measurement of the given model. It defines the observables for all the logical qubits in the layout. Parameters ---------- model Noise model for the gates. layout Code layout. detectors Detector definitions to use. rot_basis If ``True``, the memory experiment is performed in the X basis. If ``False``, the memory experiment is performed in the Z basis. By deafult ``False``. anc_reset If ``True``, ancillas are reset at the beginning of the QEC round. By default ``True``. anc_detectors List of ancilla qubits for which to define the detectors. If ``None``, adds all detectors. By default ``None``. Notes ----- It activates all the ancillas in ``detectors`` to always build the detectors. As this function should not be used when building encoded circuits with the iterating functions, it does not matter if the detectors are activated or not. """ if layout.code != "rotated_surface_code": raise TypeError( f"The given layout is not a rotated surface code, but a {layout.code}" ) if anc_detectors is None: anc_detectors = layout.anc_qubits if set(anc_detectors) > set(layout.anc_qubits): raise ValueError("Some of the given 'anc_qubits' are not ancilla qubits.") circuit = sum( log_meas_xzzx_iterator(model=model, layout=layout, rot_basis=rot_basis), start=Circuit(), ) # activate detectors so that "Detectors.build_from_anc" always populates # the stim detector definitions. inactive_dets = set(anc_detectors).difference(detectors.detectors) detectors.activate_detectors(inactive_dets) # detectors and logical observables stab_type = "x_type" if rot_basis else "z_type" stabs = layout.get_qubits(role="anc", stab_type=stab_type) anc_support = layout.get_support(stabs) detectors_stim = detectors.build_from_data( model.meas_target, anc_support, anc_reset, reconstructable_stabs=stabs, anc_qubits=anc_detectors, ) circuit += detectors_stim log_op = "log_x" if rot_basis else "log_z" for logical_qubit in layout.logical_qubits: log_data_qubits = layout.logical_param(log_op, logical_qubit) targets = [model.meas_target(qubit, -1) for qubit in log_data_qubits] circuit.append("OBSERVABLE_INCLUDE", targets, 0) detectors.deactivate_detectors(layout.anc_qubits) return circuit def log_meas_xzzx_iterator( model: Model, layout: Layout, rot_basis: bool = False, ) -> Generator[Circuit]: """ Yields stim circuit blocks which in total correspond to a logical measurement of the given model without the definition of the detectors and observables. Parameters ---------- model Noise model for the gates. layout Code layout. rot_basis If ``True``, the memory experiment is performed in the X basis. If ``False``, the memory experiment is performed in the Z basis. By deafult ``False``. """ anc_qubits = layout.anc_qubits data_qubits = layout.data_qubits qubits = set(layout.qubits) stab_type = "x_type" if rot_basis else "z_type" stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) yield model.incoming_noise(data_qubits) yield model.tick() rot_qubits: set[str] = set() for direction in ("north_west", "south_east"): neighbors = layout.get_neighbors(stab_qubits, direction=direction) rot_qubits.update(neighbors) idle_qubits = qubits - rot_qubits yield model.hadamard(rot_qubits) + model.idle(idle_qubits) yield model.tick() yield model.measure(data_qubits) + model.idle(anc_qubits) yield model.tick() @logical_measurement_z def log_meas_z_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuit blocks which in total correspond to a logical Z measurement of the given model without the definition of the detectors and observables. """ yield from log_meas_xzzx_iterator(model=model, layout=layout, rot_basis=False) @logical_measurement_x def log_meas_x_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuit blocks which in total correspond to a logical X measurement of the given model without the definition of the detectors and observables. """ yield from log_meas_xzzx_iterator(model=model, layout=layout, rot_basis=True) def log_x_xzzx(model: Model, layout: Layout, detectors: Detectors) -> Circuit: """ Returns stim circuit corresponding to a logical X gate of the given model. """ # the stabilizer generators do not change when applying a logical X gate return sum(log_x_xzzx_iterator(model=model, layout=layout), start=Circuit()) @sq_gate def log_x_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical X gate of the given model. """ anc_qubits = layout.anc_qubits data_qubits = layout.data_qubits if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_x_qubits = layout.logical_param("log_x", log_qubit_label) yield model.incoming_noise(data_qubits) yield model.tick() # apply log X rot_qubits: list[str] = [] stab_qubits = layout.get_qubits(role="anc", stab_type="z_type") for direction in ("north_west", "south_east"): rot_qubits += layout.get_neighbors(stab_qubits, direction=direction) pauli_z = set(d for d in log_x_qubits if d in rot_qubits) pauli_x = set(log_x_qubits) - pauli_z yield model.x_gate(pauli_x) + model.z_gate(pauli_z) + model.idle(anc_qubits) yield model.tick() def log_z_xzzx(model: Model, layout: Layout, detectors: Detectors) -> Circuit: """ Returns stim circuit corresponding to a logical Z gate of the given model. """ # the stabilizer generators do not change when applying a logical Z gate return sum(log_z_xzzx_iterator(model=model, layout=layout), start=Circuit()) @sq_gate def log_z_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical Z gate of the given model. """ anc_qubits = layout.anc_qubits data_qubits = layout.data_qubits if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_z_qubits = layout.logical_param("log_z", log_qubit_label) yield model.incoming_noise(data_qubits) yield model.tick() # apply log Z rot_qubits: list[str] = [] stab_qubits = layout.get_qubits(role="anc", stab_type="z_type") for direction in ("north_west", "south_east"): rot_qubits += layout.get_neighbors(stab_qubits, direction=direction) pauli_x = set(d for d in log_z_qubits if d in rot_qubits) pauli_z = set(log_z_qubits) - pauli_x yield model.x_gate(pauli_x) + model.z_gate(pauli_z) + model.idle(anc_qubits) yield model.tick() def init_qubits_xzzx( model: Model, layout: Layout, detectors: Detectors, data_init: dict[str, int], rot_basis: bool = False, ) -> Circuit: """ Returns stim circuit corresponding to a logical initialization of the given model. By default, the logical initialization is in the Z basis. If rot_basis, the logical initialization is in the X basis. """ # activate detectors # the order of activating the detectors or applying the circuit # does not matter because this will be done in a layer of logical operations, # so no QEC round are run simultaneously anc_qubits = layout.anc_qubits stab_type = "z_type" if rot_basis else "x_type" gauge_dets = layout.get_qubits(role="anc", stab_type=stab_type) detectors.activate_detectors(anc_qubits, gauge_dets=gauge_dets) return sum( init_qubits_xzzx_iterator( model=model, layout=layout, data_init=data_init, rot_basis=rot_basis ), start=Circuit(), ) def init_qubits_xzzx_iterator( model: Model, layout: Layout, data_init: dict[str, int], rot_basis: bool = False, ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization of the given model. By default, the logical measurement is in the Z basis. If rot_basis, the logical measurement is in the X basis. """ if layout.code not in ("rotated_surface_code", "rotated_surface_stability"): raise TypeError( f"The given layout is not a rotated surface code, but a {layout.code}" ) qubits = set(layout.qubits) yield model.reset(layout.data_qubits) + model.idle(layout.anc_qubits) yield model.tick() init_circ = Circuit() exc_qubits = set( [q for q, s in data_init.items() if s and (q in layout.data_qubits)] ) if exc_qubits: init_circ += model.x_gate(exc_qubits) idle_qubits = qubits - exc_qubits yield init_circ + model.idle(idle_qubits) yield model.tick() stab_type = "x_type" if rot_basis else "z_type" stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) rot_qubits: set[str] = set() for direction in ("north_west", "south_east"): neighbors = layout.get_neighbors(stab_qubits, direction=direction) rot_qubits.update(neighbors) idle_qubits = qubits - rot_qubits yield model.hadamard(rot_qubits) + model.idle(idle_qubits) yield model.tick() @qubit_init_z def init_qubits_z0_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |0> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 0 for q in layout.data_qubits} yield from init_qubits_xzzx_iterator( model=model, layout=layout, data_init=data_init, rot_basis=False ) @qubit_init_z def init_qubits_z1_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |1> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 1 for q in layout.data_qubits} if layout.num_data_qubits % 2 == 0: # ensure that the bistring corresponds to the |1> state data_init[layout.data_qubits[-1]] = 0 yield from init_qubits_xzzx_iterator( model=model, layout=layout, data_init=data_init, rot_basis=False ) @qubit_init_x def init_qubits_x0_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |+> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 0 for q in layout.data_qubits} yield from init_qubits_xzzx_iterator( model=model, layout=layout, data_init=data_init, rot_basis=True ) @qubit_init_x def init_qubits_x1_xzzx_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical initialization in the |-> state of the given model. Notes ----- The ``data_init`` bitstring used for the initialization is not important when doing stabilizer simulation. """ data_init = {q: 1 for q in layout.data_qubits} if layout.num_data_qubits % 2 == 0: # ensure that the bistring corresponds to the |-> state data_init[layout.data_qubits[-1]] = 0 yield from init_qubits_xzzx_iterator( model=model, layout=layout, data_init=data_init, rot_basis=True ) @qec_circuit def general_qec_round_iterator_cnots( model: Model, layout: Layout, anc_reset: bool = True ) -> Generator[Circuit]: """ Yields stim circuits corresponds to a QEC round for the given model and layout. Notes ----- This implementation follows the interaction order in the layout. The format for this interaction order must follow the one described in ``surface_sim.layouts.overwrite_interaction_order``. This implementation can be used for any CSS code, and uses: CNOT, RX, RZ, MX, and MZ. """ data_qubits = layout.data_qubits anc_qubits = layout.anc_qubits qubits = set(layout.qubits) int_order = layout.interaction_order num_steps = len(int_order[anc_qubits[0]]) x_stabs = layout.get_qubits(role="anc", stab_type="x_type") z_stabs = layout.get_qubits(role="anc", stab_type="z_type") yield model.incoming_noise(data_qubits) yield model.tick() if anc_reset: resets = model.reset_x(x_stabs) + model.reset_z(z_stabs) yield resets + model.idle(data_qubits) yield model.tick() # CNOT gates for step in range(num_steps): int_qubits: list[str] = [] # X ancillas int_pairs = [(x, int_order[x][step]) for x in x_stabs] int_pairs = [pair for pair in int_pairs if pair[1] is not None] int_qubits += list(chain.from_iterable(int_pairs)) # Z ancillas int_pairs = [(int_order[z][step], z) for z in z_stabs] int_pairs = [pair for pair in int_pairs if pair[0] is not None] int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cnot(int_qubits) + model.idle(idle_qubits) yield model.tick() meas = model.measure_x(x_stabs) + model.measure_z(z_stabs) yield meas + model.idle(data_qubits) yield model.tick() @to_mid_cycle_circuit def to_mid_cycle_iterator( model: Model, layout: Layout, anc_reset: bool = True ) -> Generator[Circuit]: """ Yields stim circuit blocks which as a whole correspond to the first half of a QEC round of the given model without the detectors. Parameters ---------- model Noise model for the gates. layout Code layout. anc_reset If ``True``, ancillas are reset at the beginning of the QEC round. By default ``True``. Notes ----- This implementation follows: https://doi.org/10.1103/PhysRevApplied.8.034021 """ if layout.code not in ( "rotated_surface_code", "rotated_surface_stability", "unrotated_surface_code", "repetition_code", "repetition_stability", ): raise TypeError( "The given layout is not a rotated surface, unrotated surface code, " f"or repetition code, but a {layout.code}." ) data_qubits = layout.data_qubits anc_qubits = layout.anc_qubits qubits = set(layout.qubits) int_order = layout.interaction_order stab_types = list(int_order.keys()) x_stabs = layout.get_qubits(role="anc", stab_type="x_type") yield model.incoming_noise(data_qubits) yield model.tick() if anc_reset: yield model.reset(anc_qubits) + model.idle(data_qubits) yield model.tick() # a directions = [int_order["x_type"][0], int_order["x_type"][3]] rot_qubits = set(anc_qubits) rot_qubits.update(layout.get_neighbors(x_stabs, direction=directions[0])) rot_qubits.update(layout.get_neighbors(x_stabs, direction=directions[1])) idle_qubits = qubits - rot_qubits yield model.hadamard(rot_qubits) + model.idle(idle_qubits) yield model.tick() # b int_qubits: list[str] = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][0] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cphase(int_qubits) + model.idle(idle_qubits) yield model.tick() # c yield model.hadamard(data_qubits) + model.idle(anc_qubits) yield model.tick() # d int_qubits = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][1] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cphase(int_qubits) + model.idle(idle_qubits) yield model.tick() @to_end_cycle_circuit def to_end_cycle_iterator(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuit blocks which as a whole correspond to the second half of a QEC round of the given model without the detectors. Parameters ---------- model Noise model for the gates. layout Code layout. Notes ----- This implementation follows: https://doi.org/10.1103/PhysRevApplied.8.034021 """ if layout.code not in ( "rotated_surface_code", "rotated_surface_stability", "unrotated_surface_code", "repetition_code", "repetition_stability", ): raise TypeError( "The given layout is not a rotated surface, unrotated surface code, " f"or repetition code, but a {layout.code}." ) data_qubits = layout.data_qubits anc_qubits = layout.anc_qubits qubits = set(layout.qubits) int_order = layout.interaction_order stab_types = list(int_order.keys()) x_stabs = layout.get_qubits(role="anc", stab_type="x_type") # e int_qubits: list[str] = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][2] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cphase(int_qubits) + model.idle(idle_qubits) yield model.tick() # f yield model.hadamard(data_qubits) + model.idle(anc_qubits) yield model.tick() # g int_qubits = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][3] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cphase(int_qubits) + model.idle(idle_qubits) yield model.tick() # h directions = [int_order["x_type"][0], int_order["x_type"][3]] rot_qubits = set(anc_qubits) rot_qubits.update(layout.get_neighbors(x_stabs, direction=directions[0])) rot_qubits.update(layout.get_neighbors(x_stabs, direction=directions[1])) idle_qubits = qubits - rot_qubits yield model.hadamard(rot_qubits) + model.idle(idle_qubits) yield model.tick() # i yield model.measure(anc_qubits) + model.idle(data_qubits) yield model.tick() @qec_circuit def qec_round_iterator( model: Model, layout: Layout, anc_reset: bool = True, ) -> Generator[Circuit]: """ Yields stim circuit blocks which as a whole correspond to a QEC round of the given model without the detectors. Parameters ---------- model Noise model for the gates. layout Code layout. anc_reset If ``True``, ancillas are reset at the beginning of the QEC round. By default ``True``. Notes ----- This implementation follows: https://doi.org/10.1103/PhysRevApplied.8.034021 """ yield from to_mid_cycle_iterator(model, layout, anc_reset=anc_reset) yield from to_end_cycle_iterator(model, layout) @to_mid_cycle_circuit def to_mid_cycle_iterator_cnots( model: Model, layout: Layout, anc_reset: bool = True, ) -> Generator[Circuit]: """ Yields stim circuit blocks which as a whole correspond to the first half of a QEC round of the given model without the detectors. Parameters ---------- model Noise model for the gates. layout Code layout. anc_reset If ``True``, ancillas are reset at the beginning of the QEC round. By default ``True``. Notes ----- This implementation uses the following instructions: CNOT, RZ, RX, MZ, MX. Note that if ``anc_reset = False``, then the ancillas are not reset in the first round and stim assumes that, if not specified, they are reset in the Z-basis, which is the incorrect basis for the X-type ancillas. See the initialization iterators from the dodecahedron code. """ if layout.code not in ( "rotated_surface_code", "rotated_surface_stability", "unrotated_surface_code", "repetition_code", "repetition_stability", ): raise TypeError( "The given layout is not a rotated surface, unrotated surface code, " f"or repetition code, but a {layout.code}." ) data_qubits = layout.data_qubits x_stabs = layout.get_qubits(role="anc", stab_type="x_type") z_stabs = layout.get_qubits(role="anc", stab_type="z_type") qubits = set(layout.qubits) int_order = layout.interaction_order stab_types = list(int_order.keys()) yield model.incoming_noise(data_qubits) yield model.tick() if anc_reset: resets = model.reset_x(x_stabs) + model.reset_z(z_stabs) yield resets + model.idle(data_qubits) yield model.tick() # a int_qubits: list[str] = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][0] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) if stab_type == "z_type": int_pairs = [(b, a) for (a, b) in int_pairs] int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cnot(int_qubits) + model.idle(idle_qubits) yield model.tick() # b int_qubits = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][1] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) if stab_type == "z_type": int_pairs = [(b, a) for (a, b) in int_pairs] int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cnot(int_qubits) + model.idle(idle_qubits) yield model.tick() @to_end_cycle_circuit def to_end_cycle_iterator_cnots(model: Model, layout: Layout) -> Generator[Circuit]: """ Yields stim circuit blocks which as a whole correspond to the second half of a QEC round of the given model without the detectors. Parameters ---------- model Noise model for the gates. layout Code layout. Notes ----- This implementation uses the following instructions: CNOT, RZ, RX, MZ, MX. """ if layout.code not in ( "rotated_surface_code", "rotated_surface_stability", "unrotated_surface_code", "repetition_code", "repetition_stability", ): raise TypeError( "The given layout is not a rotated surface, unrotated surface code, " f"or repetition code, but a {layout.code}." ) data_qubits = layout.data_qubits x_stabs = layout.get_qubits(role="anc", stab_type="x_type") z_stabs = layout.get_qubits(role="anc", stab_type="z_type") qubits = set(layout.qubits) int_order = layout.interaction_order stab_types = list(int_order.keys()) # c int_qubits: list[str] = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][2] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) if stab_type == "z_type": int_pairs = [(b, a) for (a, b) in int_pairs] int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cnot(int_qubits) + model.idle(idle_qubits) yield model.tick() # d int_qubits = [] for stab_type in stab_types: stab_qubits = layout.get_qubits(role="anc", stab_type=stab_type) ord_dir = int_order[stab_type][3] int_pairs = layout.get_neighbors(stab_qubits, direction=ord_dir, as_pairs=True) if stab_type == "z_type": int_pairs = [(b, a) for (a, b) in int_pairs] int_qubits += list(chain.from_iterable(int_pairs)) idle_qubits = qubits - set(int_qubits) yield model.cnot(int_qubits) + model.idle(idle_qubits) yield model.tick() # e yield model.measure_x(x_stabs) + model.measure_z(z_stabs) + model.idle(data_qubits) yield model.tick() @qec_circuit def qec_round_iterator_cnots( model: Model, layout: Layout, anc_reset: bool = True, ) -> Generator[Circuit]: """ Yields stim circuit blocks which as a whole correspond to a QEC round of the given model without the detectors. Parameters ---------- model Noise model for the gates. layout Code layout. anc_reset If ``True``, ancillas are reset at the beginning of the QEC round. By default ``True``. Notes ----- This implementation uses the following instructions: CNOT, RZ, RX, MZ, MX. Note that if ``anc_reset = False``, then the ancillas are not reset in the first round and stim assumes that, if not specified, they are reset in the Z-basis, which is the incorrect basis for the X-type ancillas. See the initialization iterators from the dodecahedron code. """ yield from to_mid_cycle_iterator_cnots(model, layout, anc_reset=anc_reset) yield from to_end_cycle_iterator_cnots(model, layout) @logical_noise def log_x_error_iterator( model: Model, layout: Layout, prob: float | int = 0 ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical X error with probability ``prob``. """ if not isinstance(prob, (float, int)): raise TypeError(f"'prob' must be a float, but {type(prob)} was given.") if prob > 1 or prob < 0: raise ValueError("'prob' must be a physical error probability.") if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_x_qubits = layout.logical_param("log_x", log_qubit_label) log_x_inds = layout.get_inds(log_x_qubits) circuit = Circuit( f"CORRELATED_ERROR({prob}) " + " ".join([f"X{i}" for i in log_x_inds]) ) yield circuit @logical_noise def log_y_error_iterator( model: Model, layout: Layout, prob: float | int = 0 ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical Y error with probability ``prob``. """ if not isinstance(prob, (float, int)): raise TypeError(f"'prob' must be a float, but {type(prob)} was given.") if prob > 1 or prob < 0: raise ValueError("'prob' must be a physical error probability.") if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_x_qubits = layout.logical_param("log_x", log_qubit_label) log_x_inds = layout.get_inds(log_x_qubits) log_z_qubits = layout.logical_param("log_z", log_qubit_label) log_z_inds = layout.get_inds(log_z_qubits) circuit = Circuit( f"CORRELATED_ERROR({prob}) " + " ".join([f"X{i}" for i in log_x_inds] + [f"Z{i}" for i in log_z_inds]) ) yield circuit @logical_noise def log_z_error_iterator( model: Model, layout: Layout, prob: float | int = 0 ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical Z error with probability ``prob``. """ if not isinstance(prob, (float, int)): raise TypeError(f"'prob' must be a float, but {type(prob)} was given.") if prob > 1 or prob < 0: raise ValueError("'prob' must be a physical error probability.") if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_z_qubits = layout.logical_param("log_z", log_qubit_label) log_z_inds = layout.get_inds(log_z_qubits) circuit = Circuit( f"CORRELATED_ERROR({prob}) " + " ".join([f"Z{i}" for i in log_z_inds]) ) yield circuit @logical_noise def log_depolarize1_error_iterator( model: Model, layout: Layout, prob: float | int = 0 ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a logical depolarizing error with probability ``prob``. """ if not isinstance(prob, (float, int)): raise TypeError(f"'prob' must be a float, but {type(prob)} was given.") if prob > 1 or prob < 0: raise ValueError("'prob' must be a physical error probability.") if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_x_qubits = layout.logical_param("log_x", log_qubit_label) log_x_inds = layout.get_inds(log_x_qubits) log_z_qubits = layout.logical_param("log_z", log_qubit_label) log_z_inds = layout.get_inds(log_z_qubits) # probabilities: # p/3 = p/3 # p/3 = (1 - p/3)*A --> A = p/(3 - p) # p/3 = (1 - p/3)*(1 - A)*B --> B = p/(3 - 2p) circuit = Circuit( f"CORRELATED_ERROR({prob / 3}) " + " ".join([f"X{i}" for i in log_x_inds]) + f"\nELSE_CORRELATED_ERROR({prob / (3 - prob)}) " + " ".join([f"X{i}" for i in log_x_inds] + [f"Z{i}" for i in log_z_inds]) + f"\nELSE_CORRELATED_ERROR({prob / (3 - 2 * prob)}) " + " ".join([f"Z{i}" for i in log_z_inds]) ) yield circuit def general_grow_code_iterator( model: Model, layout: Layout, reset_x_coords: Collection[tuple[int, int]], reset_z_coords: Collection[tuple[int, int]], cnot_layers_coords: Sequence[Sequence[tuple[int, int]]], physical_reset_op: str | None, primitive_gates: str, block: str = "whole", ): """ Yields stim blocks corresponding to the growth of the code following the given RX, RZ, and CNOT operatons. Parameters ---------- model Noise model for the gates. layout Code layout. reset_x_coords Generalized coordinates for the data qubits that need to be reset in the X basis. reset_z_coords Generalized coordinates for the data qubits that need to be reset in the Z basis. cnot_layers_coords List of CNOT layers that need to be applied at each step of the encoding circuit. The data qubits are specified by the generalized coordinates. physical_reset_op Reset operation to be applied to the physical qubit that will grow to a unrotated surface code. If ``None``, this circuit grows an already existing code to one of higher distance. primitive_gates Set of primitive gates to use. The available options are: (1) ``"cnot"``, which uses RZ, RX, CNOT gates, and (2) ``"cz"``, which uses RZ, H, and CZ gates. block Block to add. The available options are: (1) ``"whole"`` (default) add the whole growth circuit, (2) ``"resets"`` only adds the resets, and (3) ``"unitary"`` only add unitary gates. """ if primitive_gates not in ["cnot", "cz"]: raise ValueError(f"'{primitive_gates}' is not available as primitive gate set.") if block not in ["whole", "resets", "unitary"]: raise ValueError(f"'{block}' is not available as 'block'.") gate_label = f"encoding_{layout.logical_qubits[0]}" l: dict[tuple[int, int], str] = {} for data_qubit in layout.data_qubits: glabel = layout.param(gate_label, data_qubit)["label"] if glabel is None: raise ValueError( "The layout does not have the information to run " f"an encoding circuit on qubit {data_qubit}. " "Use the 'log_gates' module to set it up." ) l[glabel] = data_qubit qubits = set(layout.qubits) reversed = layout.param(gate_label, l[(0, 0)]).get("reversed", False) # step 1: resets circ = Circuit() reset_x = [l[c] for c in reset_x_coords] reset_z = [l[c] for c in reset_z_coords] if reversed: reset_x, reset_z = reset_z, reset_x hadamards_prev = hadamards_curr = set(reset_x) if block in ("whole", "resets"): if primitive_gates == "cnot": circ += model.reset_x(reset_x) + model.reset_z(reset_z) elif primitive_gates == "cz": circ += model.reset_z(reset_z + reset_x) exec_qubits = reset_x + reset_z if physical_reset_op is not None: circ += model.__getattribute__(physical_reset_op)([l[(0, 0)]]) exec_qubits.append(l[(0, 0)]) yield circ + model.idle(qubits - set(exec_qubits)) yield model.tick() if block == "resets": return # steps 2, 3, ...: cnot gates for cnot_pairs_coords in cnot_layers_coords: cnot_pairs = [l[c] for c in cnot_pairs_coords] if reversed: cnot_pairs = list( chain.from_iterable( (j, i) for i, j in zip(cnot_pairs[::2], cnot_pairs[1::2]) ) ) hadamards_curr = set(cnot_pairs[1::2]) # apply hadamards between step prev and curr if primitive_gates == "cz": hadamards = hadamards_prev.symmetric_difference(hadamards_curr) yield model.hadamard(hadamards) + model.idle(qubits - hadamards) yield model.tick() hadamards_prev = hadamards_curr circ = Circuit() if primitive_gates == "cnot": circ += model.cnot(cnot_pairs) elif primitive_gates == "cz": circ += model.cphase(cnot_pairs) circ += model.idle(qubits - set(cnot_pairs)) yield circ yield model.tick() if primitive_gates == "cz": yield model.hadamard(hadamards_curr) + model.idle(qubits - hadamards_curr) yield model.tick() @pauli_observable_x def pauli_observable_x_iterator( model: Model, layout: Layout, observable_ind: int = 0 ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a Pauli-X observable definition for the given observable index. """ if not isinstance(observable_ind, int): raise TypeError( f"'observable_ind' must be an int, but {type(observable_ind)} was given." ) if observable_ind < 0: raise ValueError("'observable_ind' must be a positive integer.") if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_x_qubits = layout.logical_param("log_x", log_qubit_label) log_x_inds = layout.get_inds(log_x_qubits) circuit = Circuit( f"OBSERVABLE_INCLUDE({observable_ind}) " + " ".join([f"X{i}" for i in log_x_inds]) ) yield circuit @pauli_observable_y def pauli_observable_y_iterator( model: Model, layout: Layout, observable_ind: int = 0 ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a Pauli-Y observable definition for the given observable index. """ if not isinstance(observable_ind, int): raise TypeError( f"'observable_ind' must be an int, but {type(observable_ind)} was given." ) if observable_ind < 0: raise ValueError("'observable_ind' must be a positive integer.") if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_x_qubits = layout.logical_param("log_x", log_qubit_label) log_x_inds = layout.get_inds(log_x_qubits) log_z_qubits = layout.logical_param("log_z", log_qubit_label) log_z_inds = layout.get_inds(log_z_qubits) circuit = Circuit( f"OBSERVABLE_INCLUDE({observable_ind}) " + " ".join([f"X{i}" for i in log_x_inds]) + " " + " ".join([f"Z{i}" for i in log_z_inds]) ) yield circuit @pauli_observable_z def pauli_observable_z_iterator( model: Model, layout: Layout, observable_ind: int = 0 ) -> Generator[Circuit]: """ Yields stim circuits corresponding to a Pauli-Z observable definition for the given observable index. """ if not isinstance(observable_ind, int): raise TypeError( f"'observable_ind' must be an int, but {type(observable_ind)} was given." ) if observable_ind < 0: raise ValueError("'observable_ind' must be a positive integer.") if len(layout.logical_qubits) != 1: raise ValueError( "This function only works for layouts with one logical qubit, " f"but the given layout has {len(layout.logical_qubits)} logical qubits." ) log_qubit_label = layout.logical_qubits[0] log_z_qubits = layout.logical_param("log_z", log_qubit_label) log_z_inds = layout.get_inds(log_z_qubits) circuit = Circuit( f"OBSERVABLE_INCLUDE({observable_ind}) " + " ".join([f"Z{i}" for i in log_z_inds]) ) yield circuit