from collections.abc import Collection, Generator
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 qec_circuit, qubit_init_x, qubit_init_z, sq_gate
from .util import general_qec_round_iterator_cnots as general_qec_round_iterator
# methods to have in this script
from .util import (
idle_iterator,
log_meas,
log_meas_iterator,
log_meas_x_iterator,
log_meas_z_iterator,
qubit_coords,
)
__all__ = [
"qubit_coords",
"idle_iterator",
"log_meas",
"log_meas_iterator",
"log_meas_z_iterator",
"log_meas_x_iterator",
"init_qubits",
"init_qubits_iterator",
"init_qubits_z0_iterator",
"init_qubits_z1_iterator",
"init_qubits_x0_iterator",
"init_qubits_x1_iterator",
"log_fold_trans_h",
"log_fold_trans_h_iterator",
"log_fold_trans_s",
"log_fold_trans_s_iterator",
"log_fold_trans_swap_r",
"log_fold_trans_swap_r_iterator",
"log_fold_trans_swap_s",
"log_fold_trans_swap_s_iterator",
"log_fold_trans_swap_a",
"log_fold_trans_swap_a_iterator",
"log_fold_trans_swap_b",
"log_fold_trans_swap_b_iterator",
"log_fold_trans_swap_c",
"log_fold_trans_swap_c_iterator",
"qec_round",
"qec_round_iterator",
"gate_to_iterator",
]
[docs]
def qec_round(
model: Model,
layout: Layout,
detectors: Detectors,
anc_reset: bool = True,
anc_detectors: Collection[str] | None = None,
) -> Circuit:
"""
Returns stim circuit corresponding to a QEC round
of the given model.
Parameters
----------
model
Noise model for the gates.
layout
Code layout.
detectors
Detector object to use for their definition.
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
-----
This implementation follows the interaction order specified in the layout.
This implementation uses CNOTs, and resets and measurements in the X basis.
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.
"""
circuit = sum(
qec_round_iterator(model=model, layout=layout, anc_reset=anc_reset),
start=Circuit(),
)
# add detectors
anc_qubits = layout.anc_qubits
if anc_detectors is None:
anc_detectors = anc_qubits
if set(anc_detectors) > set(anc_qubits):
raise ValueError("Elements in 'anc_detectors' are not ancilla qubits.")
# 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)
circuit += detectors.build_from_anc(
model.meas_target, anc_reset, anc_qubits=anc_detectors
)
return circuit
@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 the interaction order specified in the layout.
This implementation uses CNOTs, and resets and measurements in the X basis.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not an small stellated dodecahedron code, "
f"but a {layout.code}"
)
yield from general_qec_round_iterator(
model=model, layout=layout, anc_reset=anc_reset
)
[docs]
def log_fold_trans_s(model: Model, layout: Layout, detectors: Detectors) -> Circuit:
"""
Returns the stim circuit corresponding to a transversal logical S-like gate.
See the corresponding setting function for more information.
"""
# update the stabilizer generators
gate_label = "log_fold_trans_s"
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-like gate.
See the corresponding setting function for more information.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not a small stellated dodecahedron code, "
f"but a {layout.code}"
)
data_qubits = layout.data_qubits
anc_qubits = layout.anc_qubits
gate_label = "log_fold_trans_s"
cz_pairs = set()
qubits_s_gate = set()
qubits_s_dag_gate = 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-like 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-like gate.
See the corresponding setting function for more information.
"""
# update the stabilizer generators
gate_label = "log_fold_trans_h"
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 transversal logical H-like gate.
See the corresponding setting function for more information.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not a small stellated dodecahedron code, "
f"but a {layout.code}"
)
data_qubits = layout.data_qubits
qubits = set(layout.qubits)
gate_label = "log_fold_trans_h"
swap_pairs = set()
qubits_h_gate = 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_fold_trans_swap_r(
model: Model, layout: Layout, detectors: Detectors
) -> Circuit:
"""
Returns the stim circuit corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_r` gate.
See the corresponding setting function for more information.
"""
# update the stabilizer generators
gate_label = "log_fold_trans_swap_r"
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_swap_r_iterator(model=model, layout=layout), start=Circuit()
)
@sq_gate
def log_fold_trans_swap_r_iterator(model: Model, layout: Layout) -> Generator[Circuit]:
"""
Yields the stim circuits corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_r` gate.
See the corresponding setting function for more information.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not a small stellated dodecahedron code, "
f"but a {layout.code}"
)
data_qubits = layout.data_qubits
qubits = set(layout.qubits)
gate_label = "log_fold_trans_swap_r"
swap_1_pairs = set()
swap_2_pairs = set()
for data_qubit in data_qubits:
trans_swap = layout.param(gate_label, data_qubit)
if trans_swap is None:
raise ValueError(
"The layout does not have the information to run a "
f"transversal SWAP 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_swap["swap_1"] is not None:
swap_1_pairs.add(tuple(sorted([data_qubit, trans_swap["swap_1"]])))
if trans_swap["swap_2"] is not None:
swap_2_pairs.add(tuple(sorted([data_qubit, trans_swap["swap_2"]])))
yield model.incoming_noise(data_qubits)
yield model.tick()
# long-range SWAP gates
int_qubits = list(chain.from_iterable(swap_1_pairs))
idle_qubits = qubits - set(int_qubits)
yield model.swap(int_qubits) + model.idle(idle_qubits)
yield model.tick()
# long-range SWAP gates
int_qubits = list(chain.from_iterable(swap_2_pairs))
idle_qubits = qubits - set(int_qubits)
yield model.swap(int_qubits) + model.idle(idle_qubits)
yield model.tick()
[docs]
def log_fold_trans_swap_s(
model: Model, layout: Layout, detectors: Detectors
) -> Circuit:
"""
Returns the stim circuit corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_s` gate.
See the corresponding setting function for more information.
"""
# update the stabilizer generators
gate_label = "log_fold_trans_swap_s"
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_swap_s_iterator(model=model, layout=layout), start=Circuit()
)
@sq_gate
def log_fold_trans_swap_s_iterator(model: Model, layout: Layout) -> Generator[Circuit]:
"""
Yields the stim circuits corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_s` gate.
See the corresponding setting function for more information.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not a small stellated dodecahedron code, "
f"but a {layout.code}"
)
data_qubits = layout.data_qubits
qubits = set(layout.qubits)
gate_label = "log_fold_trans_swap_s"
swap_1_pairs = set()
swap_2_pairs = set()
for data_qubit in data_qubits:
trans_swap = layout.param(gate_label, data_qubit)
if trans_swap is None:
raise ValueError(
"The layout does not have the information to run a "
f"transversal SWAP 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_swap["swap_1"] is not None:
swap_1_pairs.add(tuple(sorted([data_qubit, trans_swap["swap_1"]])))
if trans_swap["swap_2"] is not None:
swap_2_pairs.add(tuple(sorted([data_qubit, trans_swap["swap_2"]])))
yield model.incoming_noise(data_qubits)
yield model.tick()
# long-range SWAP gates
int_qubits = list(chain.from_iterable(swap_1_pairs))
idle_qubits = qubits - set(int_qubits)
yield model.swap(int_qubits) + model.idle(idle_qubits)
yield model.tick()
# long-range SWAP gates
int_qubits = list(chain.from_iterable(swap_2_pairs))
idle_qubits = qubits - set(int_qubits)
yield model.swap(int_qubits) + model.idle(idle_qubits)
yield model.tick()
[docs]
def log_fold_trans_swap_a(
model: Model, layout: Layout, detectors: Detectors
) -> Circuit:
"""
Returns the stim circuit corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_a` gate.
See the corresponding setting function for more information.
"""
# update the stabilizer generators
gate_label = "log_fold_trans_swap_a"
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_swap_a_iterator(model=model, layout=layout), start=Circuit()
)
@sq_gate
def log_fold_trans_swap_a_iterator(model: Model, layout: Layout) -> Generator[Circuit]:
"""
Yields the stim circuits corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_a` gate.
See the corresponding setting function for more information.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not a small stellated dodecahedron code, "
f"but a {layout.code}"
)
data_qubits = layout.data_qubits
qubits = set(layout.qubits)
gate_label = "log_fold_trans_swap_a"
swap_pairs = set()
for data_qubit in data_qubits:
trans_swap = layout.param(gate_label, data_qubit)
if trans_swap is None:
raise ValueError(
"The layout does not have the information to run a "
f"transversal SWAP 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_swap["swap"] is not None:
swap_pairs.add(tuple(sorted([data_qubit, trans_swap["swap"]])))
yield model.incoming_noise(data_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_fold_trans_swap_b(
model: Model, layout: Layout, detectors: Detectors
) -> Circuit:
"""
Returns the stim circuit corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_b` gate.
See the corresponding setting function for more information.
"""
# update the stabilizer generators
gate_label = "log_fold_trans_swap_a"
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_swap_b_iterator(model=model, layout=layout), start=Circuit()
)
@sq_gate
def log_fold_trans_swap_b_iterator(model: Model, layout: Layout) -> Generator[Circuit]:
"""
Yields the stim circuits corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_b` gate.
See the corresponding setting function for more information.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not a small stellated dodecahedron code, "
f"but a {layout.code}"
)
data_qubits = layout.data_qubits
qubits = set(layout.qubits)
gate_label = "log_fold_trans_swap_b"
swap_pairs = set()
for data_qubit in data_qubits:
trans_swap = layout.param(gate_label, data_qubit)
if trans_swap is None:
raise ValueError(
"The layout does not have the information to run a "
f"transversal SWAP 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_swap["swap"] is not None:
swap_pairs.add(tuple(sorted([data_qubit, trans_swap["swap"]])))
yield model.incoming_noise(data_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_fold_trans_swap_c(
model: Model, layout: Layout, detectors: Detectors
) -> Circuit:
"""
Returns the stim circuit corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_c` gate.
See the corresponding setting function for more information.
"""
# update the stabilizer generators
gate_label = "log_fold_trans_swap_c"
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_swap_c_iterator(model=model, layout=layout), start=Circuit()
)
@sq_gate
def log_fold_trans_swap_c_iterator(model: Model, layout: Layout) -> Generator[Circuit]:
"""
Yields the stim circuits corresponding to a transversal logical SWAP-like gate,
in particular the :math:`\\sigma_c` gate.
See the corresponding setting function for more information.
"""
if layout.code != "small_stellated_dodecahedron_code":
raise TypeError(
"The given layout is not a small stellated dodecahedron code, "
f"but a {layout.code}"
)
data_qubits = layout.data_qubits
qubits = set(layout.qubits)
gate_label = "log_fold_trans_swap_c"
swap_pairs = set()
for data_qubit in data_qubits:
trans_swap = layout.param(gate_label, data_qubit)
if trans_swap is None:
raise ValueError(
"The layout does not have the information to run a "
f"transversal SWAP 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_swap["swap"] is not None:
swap_pairs.add(tuple(sorted([data_qubit, trans_swap["swap"]])))
yield model.incoming_noise(data_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 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.
The ancilla qubits are also initialized in the correct basis for the
case ``anc_reset = False``.
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.
The ancilla qubits are also initialized in the correct basis for the
case ``anc_reset = False``.
By default, the logical initialization is in the Z basis.
If rot_basis, the logical initialization is in the X basis.
"""
xancs = layout.get_qubits(role="anc", stab_type="x_type")
zancs = layout.get_qubits(role="anc", stab_type="z_type")
data_qubits = layout.data_qubits
qubits = set(layout.qubits)
reset_data = model.reset_x(data_qubits) if rot_basis else model.reset(data_qubits)
yield reset_data + model.reset(zancs) + model.reset_x(xancs)
yield model.tick()
init_circ = Circuit()
exc_qubits = set([q for q, s in data_init.items() if s and (q in data_qubits)])
if exc_qubits:
if rot_basis:
init_circ += model.z_gate(exc_qubits)
else:
init_circ += model.x_gate(exc_qubits)
idle_qubits = qubits - exc_qubits
yield init_circ + model.idle(idle_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.
The ancilla qubits are also initialized in the correct basis for the
case ``anc_reset = False``.
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.
The ancilla qubits are also initialized in the correct basis for the
case ``anc_reset = False``.
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.
The ancilla qubits are also initialized in the correct basis for the
case ``anc_reset = False``.
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.
The ancilla qubits are also initialized in the correct basis for the
case ``anc_reset = False``.
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
)
gate_to_iterator = {
"TICK": qec_round_iterator,
"I": idle_iterator,
"R": init_qubits_z0_iterator,
"RZ": init_qubits_z0_iterator,
"RX": init_qubits_x0_iterator,
"M": log_meas_z_iterator,
"MZ": log_meas_z_iterator,
"MX": log_meas_x_iterator,
}