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