Skip to content

Commit 6fdb7fd

Browse files
Rotated Surface Codes (Union-Find tested) (#55)
* Update requirements * Fix for benchmarker * requirements fix for compatibility * rotated codes implementation * fixed rotated logical operators * removing mwpm plotting for rotated codes --------- Co-authored-by: Mark Shui Hu <watermarkhu@gmail.com>
1 parent 48ca66b commit 6fdb7fd

File tree

11 files changed

+179
-27
lines changed

11 files changed

+179
-27
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pip install qsurface
4444

4545
* Python 3.7+
4646
* [Tkinter](https://docs.python.org/3/library/tkinter.html) or [PyQt5](https://riverbankcomputing.com/software/pyqt/intro) for interactive plotting.
47-
* Matplotlib 3.4+ for plotting on a 3D lattice (Refers to a future release of matplotlib, see [pull request](https://github.com/matplotlib/matplotlib/pull/18816))
47+
* Matplotlib 3.4+ for plotting on a 3D lattice
4848

4949
### MWPM decoder
5050

examples.ipynb

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"cells": [
33
{
4+
"cell_type": "markdown",
5+
"metadata": {},
46
"source": [
57
"# Qsurface\n",
68
"\n",
@@ -15,9 +17,7 @@
1517
"The included examples in this section uses `qsurface.main.initialize` to setup the surface code and decoder, and `qsurface.main.run` to perform simulations. We'll expand these examples with more in-depth descriptions and how to perform a threshold simulation with `qsurface.main.threshold`. \n",
1618
"\n",
1719
"To simulate the toric code and simulate with bit-flip error for 10 iterations and decode with the MWPM decoder:"
18-
],
19-
"cell_type": "markdown",
20-
"metadata": {}
20+
]
2121
},
2222
{
2323
"cell_type": "code",
@@ -33,11 +33,11 @@
3333
]
3434
},
3535
{
36+
"cell_type": "markdown",
37+
"metadata": {},
3638
"source": [
3739
"Benchmarking of decoders can be enabled by attaching a *benchmarker* object to the decoder. See the docs for the syntax and information to setup benchmarking."
38-
],
39-
"cell_type": "markdown",
40-
"metadata": {}
40+
]
4141
},
4242
{
4343
"cell_type": "code",
@@ -51,14 +51,14 @@
5151
]
5252
},
5353
{
54+
"cell_type": "markdown",
55+
"metadata": {},
5456
"source": [
5557
"\n",
5658
"The figures in Qsurface allows for step-by-step visualization of the surface code simulation (and if supported the decoding process). Each figure logs its history such that the user can move backwards in time to view past states of the surface (and decoder). Press `h` when the figure is open for more information.\n",
5759
"\n",
5860
"The GUI of the figure is made possible by the PyQt5 or Tkinter backend. However, for Jupyter notebooks such as this one, the GUI is not available. Qsurface automatically detects this and plots each iteration inline instead. If you're running the notebook locally, or have proper X11 forwarding setup, you can still force the PyQt5 or Tkinter with the magic `%matplotlib qt` or `%matplotlib tk` prior to importing qsurface. "
59-
],
60-
"cell_type": "markdown",
61-
"metadata": {}
61+
]
6262
},
6363
{
6464
"cell_type": "code",
@@ -94,24 +94,17 @@
9494
"code, decoder = initialize((3,3), \"toric\", \"mwpm\", enabled_errors=[\"pauli\"], faulty_measurements=True, plotting=True, initial_states=(0,0))\n",
9595
"run(code, decoder, error_rates = {\"p_bitflip\": 0.05, \"pm_bitflip\": 0.05}, decode_initial=False)"
9696
]
97-
},
98-
{
99-
"cell_type": "code",
100-
"execution_count": null,
101-
"metadata": {},
102-
"outputs": [],
103-
"source": []
10497
}
10598
],
10699
"metadata": {
107100
"kernelspec": {
108-
"name": "python3",
109101
"display_name": "Python 3.9.0 64-bit ('matplotlib34')",
110102
"metadata": {
111103
"interpreter": {
112104
"hash": "e34dec7d75d7c83c55b9c6f024af2f7a8fb83b2b2afb6029879e4fbdbc47e352"
113105
}
114-
}
106+
},
107+
"name": "python3"
115108
},
116109
"language_info": {
117110
"codemirror_mode": {
@@ -128,4 +121,4 @@
128121
},
129122
"nbformat": 4,
130123
"nbformat_minor": 2
131-
}
124+
}

qsurface/codes/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
CODES = [
88
"toric",
99
"planar",
10+
"rotated"
1011
]

qsurface/codes/rotated/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import sim
2+
from . import plot

qsurface/codes/rotated/plot.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from .sim import PerfectMeasurements as SimPM, FaultyMeasurements as SimFM
2+
from .._template.plot import PerfectMeasurements as TemplatePM, FaultyMeasurements as TemplateFM
3+
4+
5+
class PerfectMeasurements(SimPM, TemplatePM):
6+
# Inherited docstring
7+
8+
class Figure(TemplatePM.Figure):
9+
# Inherited docstring
10+
11+
def __init__(self, code, *args, **kwargs) -> None:
12+
self.main_boundary = [-1, -1, code.size[0] + 1, code.size[1] + 1]
13+
super().__init__(code, *args, **kwargs)
14+
15+
16+
class FaultyMeasurements(SimFM, TemplateFM):
17+
"""Plotting code class for faulty measurements.
18+
19+
Inherits from `.codes.planar.sim.FaultyMeasurements` and `.codes.planar.plot.PerfectMeasurements`. See documentation for these classes for more.
20+
21+
Dependent on the ``figure3d`` argument, either a 3D figure object is created that inherits from `~.plot.Template3D` and `.codes.planar.plot.PerfectMeasurements.Figure`, or the 2D `.codes.planar.plot.PerfectMeasurements.Figure` is used.
22+
23+
Parameters
24+
----------
25+
args
26+
Positional arguments are passed on to `.codes.planar.sim.FaultyMeasurements`.
27+
figure3d
28+
Enables plotting on a 3D lattice. Disable to plot layer-by-layer on a 2D lattice, which increases responsiveness.
29+
kwargs
30+
Keyword arguments are passed on to `.codes.planar.sim.FaultyMeasurements` and the figure object.
31+
"""
32+
33+
class Figure2D(PerfectMeasurements.Figure, TemplateFM.Figure2D):
34+
# Inherited docstring
35+
pass
36+
37+
class Figure3D(PerfectMeasurements.Figure, TemplateFM.Figure3D):
38+
# Inherited docstring
39+
pass

qsurface/codes/rotated/sim.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from qsurface.codes.elements import AncillaQubit
2+
from ..toric.sim import PerfectMeasurements as ToricPM, FaultyMeasurements as ToricFM
3+
4+
5+
class PerfectMeasurements(ToricPM):
6+
# Inherited docstring
7+
8+
name = "rotated"
9+
10+
_syndrome_dict = { 0: "x", 1: "z" }
11+
12+
def init_surface(self, z: float = 0, **kwargs):
13+
"""Initializes the rotated surface code on layer ``z``.
14+
15+
Parameters
16+
----------
17+
z : int or float, optional
18+
Layer of qubits, ``z=0`` for perfect measurements.
19+
"""
20+
self.ancilla_qubits[z], self.data_qubits[z], self.pseudo_qubits[z] = {}, {}, {}
21+
parity = self.init_parity_check
22+
23+
# Add data qubits to surface
24+
for y in range(self.size[1]):
25+
for x in range(self.size[0]):
26+
self.add_data_qubit((x, y), z=z, **kwargs)
27+
28+
# Add ancilla qubits to surface
29+
if self.size[1] % 2 == 1:
30+
for y in range(self.size[1] - 1):
31+
for x in range(self.size[0]):
32+
parity(self.add_ancilla_qubit((0.5 - 1 * (y%2) + x, y + 0.5), z=z, state_type=self._syndrome_dict[x%2], **kwargs))
33+
else:
34+
commute = -1
35+
for y in range(self.size[1] - 1):
36+
for x in range(self.size[0] + commute):
37+
commute *= -1
38+
parity(self.add_ancilla_qubit((0.5 - 1 * (y%2) + x, y + 0.5), z=z, state_type=self._syndrome_dict[x%2], **kwargs))
39+
40+
outer_qubits = self.size[0] // 2
41+
42+
for x in range(outer_qubits): # for first and last row
43+
parity(self.add_ancilla_qubit((0.5 + 2*x, -0.5), z=z, state_type=self._syndrome_dict[1], **kwargs))
44+
parity(self.add_ancilla_qubit((self.size[0] - 1.5 - 2*x, self.size[1] - 0.5), z=z, state_type=self._syndrome_dict[1], **kwargs))
45+
46+
# Add pseudo qubits to surface (boundaries)
47+
if self.size[1] % 2 == 1:
48+
for y in range(self.size[1] - 1):
49+
if y % 2 == 0:
50+
parity(self.add_pseudo_qubit((-0.5, y + 0.5), z=z, state_type=self._syndrome_dict[1], **kwargs))
51+
else:
52+
parity(self.add_pseudo_qubit((self.size[0] - 0.5, y + 0.5), z=z, state_type=self._syndrome_dict[1], **kwargs))
53+
else:
54+
for y in range(self.size[1] - 1):
55+
if y % 2 == 0:
56+
parity(self.add_pseudo_qubit((-0.5, y + 0.5), z=z, state_type=self._syndrome_dict[1], **kwargs))
57+
parity(self.add_pseudo_qubit((self.size[0] - 0.5, y + 0.5), z=z, state_type=self._syndrome_dict[1], **kwargs))
58+
59+
for x in range(self.size[0]+1):
60+
if x % 2 == 0 or x >= (outer_qubits)*2:
61+
parity(self.add_pseudo_qubit((x - 0.5, - 0.5), z=z, state_type=self._syndrome_dict[x % 2], **kwargs))
62+
parity(self.add_pseudo_qubit((self.size[0] - 0.5 - x, self.size[1] - 0.5), z=z, state_type=self._syndrome_dict[x % 2], **kwargs))
63+
64+
def init_parity_check(self, ancilla_qubit: AncillaQubit, **kwargs):
65+
"""Initiates a parity check measurement.
66+
67+
For every ancilla qubit on ``(x,y)``, four neighboring data qubits are entangled for parity check measurements.
68+
69+
Parameters
70+
----------
71+
ancilla_qubit : `~.codes.elements.AncillaQubit`
72+
Ancilla qubit to initialize.
73+
"""
74+
(x, y), z = ancilla_qubit.loc, ancilla_qubit.z
75+
checks = {
76+
(0.5, 0.5): ((x + 0.5), (y+0.5)),
77+
(-0.5, 0.5): ((x - 0.5), (y+0.5)),
78+
(0.5, -0.5): ((x + 0.5), (y - 0.5)),
79+
(-0.5, -0.5): ((x - 0.5), (y - 0.5)),
80+
}
81+
for key, loc in checks.items():
82+
if loc in self.data_qubits[z]:
83+
self.entangle_pair(self.data_qubits[z][loc], ancilla_qubit, key)
84+
85+
def init_logical_operator(self, **kwargs):
86+
"""Initiates the logical operators [x,z] of the rotated code."""
87+
operators = {
88+
"x": [self.data_qubits[self.decode_layer][(i, 0)].edges["x"] for i in range(self.size[0])],
89+
"z": [self.data_qubits[self.decode_layer][(0, i)].edges["z"] for i in range(self.size[1])],
90+
}
91+
self.logical_operators = operators
92+
93+
94+
class FaultyMeasurements(ToricFM, PerfectMeasurements):
95+
# Inherited docstring
96+
97+
pass

qsurface/decoders/mwpm/plot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ class Planar(Toric, SimPlanar):
2323
Positional and keyword arguments are passed on to `~.decoders.mwpm.plot.Toric` and `.decoders.mwpm.sim.Planar`.
2424
"""
2525

26-
pass
26+
pass

qsurface/decoders/unionfind/plot.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ...codes.elements import AncillaQubit, DataQubit, Edge
22
from ...plot import Template2D, Template3D
33
from .._template import Plot
4-
from .sim import Toric as SimToric, Planar as SimPlanar
4+
from .sim import Toric as SimToric, Planar as SimPlanar, Rotated as SimRotated
55
from matplotlib.patches import Rectangle
66
from matplotlib.lines import Line2D
77

@@ -362,3 +362,9 @@ class Figure2D(Toric.Figure2D):
362362
def _plot_ancilla(self, ancilla, **kwargs):
363363
if type(ancilla) == AncillaQubit:
364364
super()._plot_ancilla(ancilla, **kwargs)
365+
366+
class Rotated(Planar, SimRotated):
367+
def init_plot(self, **kwargs):
368+
# Inherited docstring
369+
size = [xy - 0.5 for xy in self.code.size]
370+
self._init_axis([-0.5, -0.5] + size, title=self.decoder, aspect="equal")

qsurface/decoders/unionfind/sim.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,3 +661,6 @@ def peel_clusters(self, **kwargs):
661661
key, (new_ancilla, edge) = leaf
662662
self._edge_peel(edge, variant="peel")
663663
self.peel_leaf(cluster, new_ancilla)
664+
665+
class Rotated(Planar):
666+
pass

qsurface/main.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ def run(
185185
if benchmark:
186186
output["benchmark"] = {
187187
**benchmark.data,
188+
**benchmark.values,
188189
**benchmark.lists_mean_var(),
189190
}
190191

@@ -431,17 +432,26 @@ def wrapper(*args, **kwargs):
431432

432433
# Decorate decoder methods
433434
decorator_names = ["value_to_list"] + self.list_decorators + self.value_decorators
434-
for method_name, decorators in self.methods_to_benchmark.items():
435+
for handle_name, decorators in self.methods_to_benchmark.items():
435436
if isinstance(decorators, str):
436437
decorators = [decorators]
437438

438-
class_method = getattr(decoder, method_name)
439+
attribute_names = handle_name.split('.')
440+
handle = decoder
441+
for i, attribute_name in enumerate(attribute_names):
442+
owner = handle
443+
if hasattr(owner, attribute_name):
444+
handle = getattr(owner, attribute_name)
445+
else:
446+
raise NameError(f"{attribute_name} is not a method of {decoder}.{attribute_names[:i]}")
447+
439448
for decorator in decorators:
440449
if decorator not in decorator_names:
441450
raise NameError(f"Decorator {decorator} not defined.")
442451
wrapper = getattr(self, decorator)
443-
class_method = wrapper(class_method)
444-
setattr(decoder, method_name, class_method)
452+
handle = wrapper(handle)
453+
454+
setattr(owner, attribute_name, handle)
445455

446456
def lists_mean_var(self, reset: bool = True):
447457
"""Get mean and stand deviation of values in ``self.lists``.

0 commit comments

Comments
 (0)