Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phase-flip-protected repetition qubit #69

Merged
merged 6 commits into from
Dec 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
477 changes: 54 additions & 423 deletions qtcodes/circuits/repetition.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion qtcodes/circuits/rotated_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,9 @@ def parse_readout(
x_syndromes = [
(x & int(mask_x, base=2)) >> num_syn[self.SYNZ] for x in xor_syndromes
]
z_syndromes = [x & int(mask_z, base=2) for x in xor_syndromes]
z_syndromes = []
if mask_z != "":
z_syndromes = [x & int(mask_z, base=2) for x in xor_syndromes]

dw = self.params["d"][self.W]
X = []
Expand Down
7 changes: 6 additions & 1 deletion qtcodes/fitters/lattice_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ def draw(
dpi: Optional[int] = None,
node_size: Optional[int] = None,
font_size: Optional[float] = None,
show: Optional[bool] = True,
) -> Tuple[figure.Figure, axes.Axes]:
"""
Plots 2D graphs in IPython/Jupyter.
Expand All @@ -434,6 +435,7 @@ def draw(
dpi (int): dpi used for Figure. Defaults to dynamically sized value based on node count.
node_size (int): size of node used for `mpl_draw`. Defaults to dynamically sized value based on node count.
font_size (float): font size used for `mpl_draw`. Defaults to dynamically sized value based on node count.
show (bool): whether to display the plot automatically. Defaults to True.

Returns:
(figure, axes): A matplotlib Figure and Axes object
Expand Down Expand Up @@ -467,6 +469,8 @@ def draw(
alpha=0.8,
)
fig.tight_layout()
if not show:
plt.close(fig)
return (fig, ax)

def draw3D(self, graph: rx.PyGraph, angle: Optional[List[float]] = None) -> None:
Expand Down Expand Up @@ -494,7 +498,8 @@ def node_to_pos3D(node):
# 3D network plot
with plt.style.context(("ggplot")):
fig = plt.figure(figsize=(20, 14))
ax = Axes3D(fig)
ax = Axes3D(fig, auto_add_to_figure=False)
fig.add_axes(ax)

# Loop on the nodes and look up in pos dictionary to extract the x,y,z coordinates of each node
for node in graph.nodes():
Expand Down
100 changes: 11 additions & 89 deletions qtcodes/fitters/repetition.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
"""
Graph decoder for rep code
"""
from typing import Tuple, List, Dict
from numbers import Number
from typing import Tuple

from qtcodes.fitters.lattice_decoder import (
LatticeDecoder,
TQubit,
)
from qtcodes.circuits.repetition import RepetitionQubit
from qtcodes.fitters.rotated_surface import RotatedDecoder
from qtcodes.circuits.base import LatticeError
from qtcodes.common import constants


class RepetitionDecoder(LatticeDecoder):
class RepetitionDecoder(RotatedDecoder):
"""
Class to construct the graph corresponding to the possible syndromes
of a quantum error correction Repetition code, and then run suitable decoders.
Expand All @@ -20,88 +20,10 @@ class RepetitionDecoder(LatticeDecoder):
encoder_type = RepetitionQubit
syndrome_graph_keys = ["Z"]

def _make_syndrome_graph(self) -> None:
"""
Populates self.S["Z"] syndrome rx.PyGraph's with nodes specified by time and position.
Args:
Returns:
"""
for vnode in self.virtual["Z"]:
self.node_map["Z"][vnode] = self.S["Z"].add_node(vnode)
def _params_validation(self):
self.params = RepetitionQubit._validate_params(self.params)

edge_weight = 1
for t in range(0, self.params["T"]):
# real nodes
for j in range(0, self.params["d"] - 1):
node = (t, j + 0.5, 0)
self.node_map["Z"][node] = self.S["Z"].add_node(node)
if self.params["phase-flip-protected"]:
self.syndrome_graph_keys = ["X"]

# edges (real-real)
for j in range(1, self.params["d"] - 1):
left = (t, j - 0.5, 0)
right = (t, j + 0.5, 0)
self.S["Z"].add_edge(
self.node_map["Z"][left], self.node_map["Z"][right], edge_weight
)

# edges (real-virtual)
self.S["Z"].add_edge(
self.node_map["Z"][self.virtual["Z"][0]],
self.node_map["Z"][(t, 0.5, 0)],
edge_weight,
) # left
self.S["Z"].add_edge(
self.node_map["Z"][self.virtual["Z"][1]],
self.node_map["Z"][(t, self.params["d"] - 1.5, 0)],
edge_weight,
) # right

# connect physical qubits in same location across subgraphs of adjacent times
syndrome_nodes_t0 = [(t, x, y) for t, x, y in self.S["Z"].nodes() if t == 0]
for node in syndrome_nodes_t0:
space_label = (node[1], node[2])
for t in range(0, self.params["T"] - 1):
self.S["Z"].add_edge(
self.node_map["Z"][(t,) + space_label],
self.node_map["Z"][(t + 1,) + space_label],
edge_weight,
)

def _specify_virtual(self) -> Dict[str, List[TQubit]]:
"""
Define coordinates of P virtual syndrome nodes
(i.e. parity measurements to which we don't have access),

Args:
Returns:
virtual (dictionary): where virtual["Z"] holds a list of tuples
specifying virtual P syndrome nodes
"""
virtual: Dict[str, List[TQubit]] = {}
virtual["Z"] = []
virtual["Z"].append((-1, -0.5, 0))
virtual["Z"].append((-1, self.params["d"] - 0.5, 0))
return virtual

def _is_crossing_readout_path(
self, match: Tuple[TQubit, TQubit], logical_readout_type: str
) -> bool:
"""
Helper method that detects whether the match is crossing the readout path.

Args:
match (Tuple[TQubit, TQubit]): match in MWPM between two nodes
logical_readout_type (str): e.g. "X"

Returns:
(bool): whether or not the match is crosing the readout path
"""
source, target = match
if logical_readout_type == "Z":
return (source[0] == -1 and source[1] == -0.5) or (
target[0] == -1 and target[1] == -0.5
) # top
else:
raise NotImplementedError(
"Only Z readout is supported in the Repetition code."
)
super()._params_validation()
2 changes: 1 addition & 1 deletion qtcodes/fitters/rotated_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _make_syndrome_graph(self) -> None:
Returns:
"""
start_nodes = {"Z": (0.5, 0.5), "X": (0.5, 1.5)}
for syndrome_graph_key in ["X", "Z"]:
for syndrome_graph_key in self.syndrome_graph_keys:
# subgraphs for each time step
for t in range(0, self.params["T"]):
start_node = start_nodes[syndrome_graph_key]
Expand Down
146 changes: 138 additions & 8 deletions tests/rep.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import sys
import unittest
from qiskit import execute, Aer
from qtcodes.circuits.base import LatticeError
from qtcodes.common import constants

from qtcodes.fitters.rotated_surface import RotatedDecoder

sys.path.insert(0, "../")
from qtcodes import RepetitionQubit, RepetitionDecoder
Expand Down Expand Up @@ -50,14 +54,15 @@ def get_logical_error_rate(
def test_single_errors_rep(self):
"""
Setting up a |+z> state, stabilizing twice, and reading out logical Z.
Inserting X gates on each data qubit between tbe two stabilizer measurement rounds.
Inserting X gates on each data qubit between the two stabilizer measurement rounds.

Then, testing:
1. The MWPM decoder is able to correct these single qubit errors
and predict the correct logical readout value.
"""
# set up circuit
for i in range(self.params["d"]):
d = self.params["d"]
for i in range(d[constants.DH] * d[constants.DW]):
for error in ["x"]:
# Set up circuit
qubit = RepetitionQubit(self.params)
Expand All @@ -71,15 +76,88 @@ def test_single_errors_rep(self):

# Simulate
results = (
execute(
qubit.circ,
Aer.get_backend("aer_simulator"),
shots=1000,
)
execute(qubit.circ, Aer.get_backend("aer_simulator"), shots=1000,)
.result()
.get_counts()
)

# Get Logical Error Rate
logical_error_rate = self.get_logical_error_rate(results, 0)
print(
f"Decoding result for {error} Error on the {i}th data qubit, logical_error_rate: {logical_error_rate}."
)
self.assertEqual(
logical_error_rate,
0,
f"Decoding did not work for an {error} Error on the {i}th data qubit.",
)


class TestPhaseFlipProtectedRep(unittest.TestCase):
"""
Test the Repetition Rotated Surface Code
"""

def setUp(self):
self.params = {"d": 5, "phase-flip-protected": True}
self.params["T"] = 1
self.decoder = RepetitionDecoder(self.params)

def get_logical_error_rate(
self, readout_strings, correct_logical_value, err_prob=None
):
"""
Args:
readout_strings: a dictionary of readout strings along with counts
e.g. {"1 00000000 00000000":48, "1 00100000 00100000":12, ...} in the case of d=3, T=2

correct_logical_value: integer (0/1) depicting original encoded logical value

Returns:
error_rate:
float = (# of unsuccessful logical value predictions) / (total # of pred )
"""
total_count = 0
total_errors = 0
for readout, count in readout_strings.items():
total_count += count
predicted_logical_value = self.decoder.correct_readout(
readout, "X", err_prob=err_prob
)
if predicted_logical_value != correct_logical_value:
total_errors += count

return total_errors / total_count

def test_single_errors_rep(self):
"""
Setting up a |+x> state, stabilizing twice, and reading out logical X.
Inserting Z gates on each data qubit between the two stabilizer measurement rounds.

Then, testing:
1. The MWPM decoder is able to correct these single qubit errors
and predict the correct logical readout value.
"""
# set up circuit
d = self.params["d"]
for i in range(d[constants.DH] * d[constants.DW]):
for error in ["z"]:
# Set up circuit
qubit = RepetitionQubit(self.params)
qubit.reset_x()
qubit.stabilize()
qubit.circ.__getattribute__(error)(
qubit.lattice.qregisters["data"][i]
) # error
qubit.stabilize()
qubit.readout_x()

# Simulate
results = (
execute(qubit.circ, Aer.get_backend("aer_simulator"), shots=1000,)
.result()
.get_counts()
)
print(results)

# Get Logical Error Rate
logical_error_rate = self.get_logical_error_rate(results, 0)
Expand All @@ -93,6 +171,58 @@ def test_single_errors_rep(self):
)


class TestRepExceptions(unittest.TestCase):
amirebrahimi marked this conversation as resolved.
Show resolved Hide resolved
"""
Test the Repetition Rotated Surface Code exceptions if wrong arguments are passed
"""

def test_wrong_argument_type(self):
self.params = {}
self.params["d"] = "3,1"
self.params["T"] = 1

with self.assertRaises(LatticeError):
self.qubit = RepetitionQubit(self.params)

with self.assertRaises(LatticeError):
self.decoder = RepetitionDecoder(self.params)

def test_wrong_width(self):
self.params = {}
self.params["d"] = (3, 2)
self.params["T"] = 1

with self.assertRaises(LatticeError):
self.qubit = RepetitionQubit(self.params)

with self.assertRaises(LatticeError):
self.decoder = RepetitionDecoder(self.params)

def test_wrong_argument_type_phase_flip_protected(self):
self.params = {}
self.params["d"] = "1,3"
self.params["phase-flip-protected"] = True
self.params["T"] = 1

with self.assertRaises(LatticeError):
self.qubit = RepetitionQubit(self.params)

with self.assertRaises(LatticeError):
self.decoder = RepetitionDecoder(self.params)

def test_wrong_height(self):
self.params = {}
self.params["d"] = (2, 3)
self.params["phase-flip-protected"] = True
self.params["T"] = 1

with self.assertRaises(LatticeError):
self.qubit = RepetitionQubit(self.params)

with self.assertRaises(LatticeError):
self.decoder = RepetitionDecoder(self.params)


# %%

if __name__ == "__main__":
Expand Down
20 changes: 14 additions & 6 deletions tutorials/rep/1-circuits.ipynb

Large diffs are not rendered by default.

62 changes: 37 additions & 25 deletions tutorials/rep/2-fitters.ipynb

Large diffs are not rendered by default.