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

Observables #213

Merged
merged 37 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4f5cd82
Add pytest as explicit dependency
Gistbatch Apr 19, 2024
a0ad6d9
Add new abstract method to base template
Gistbatch Apr 22, 2024
17db053
Add initial rectangle implementation
Gistbatch Apr 22, 2024
7e7b508
Switch abstract method to indices
Gistbatch Apr 23, 2024
f05bddb
Implement sample midline function for rectangle
Gistbatch Apr 23, 2024
b342c3c
Implement function to ge observable qubits from template
Gistbatch Apr 23, 2024
e66b5a5
Move test accordin to new structure
Gistbatch Apr 23, 2024
2bae306
Add first batch of fixes
Gistbatch May 7, 2024
096aa90
Move function to avoid circular import
Gistbatch May 7, 2024
37ec4e8
Fix plaquette import
Gistbatch May 7, 2024
0b220a7
Move test to avoid import error
Gistbatch May 7, 2024
d357d54
Use fixture instead
Gistbatch May 7, 2024
de7b88e
Use relative import
Gistbatch May 7, 2024
e3e2483
Merge branch 'main' into observables
Gistbatch May 7, 2024
8073864
Update test to functional style
Gistbatch May 7, 2024
622ee21
Fix ptype hint
Gistbatch May 7, 2024
b6af8c1
Add midline plaquettes
Gistbatch May 7, 2024
3317416
Add midlines for square
Gistbatch May 7, 2024
1b42bb2
Add midline plaquettes for stack template
Gistbatch May 7, 2024
f04a522
Fix rectangle with unequal sidelength
Gistbatch May 7, 2024
964277d
Fix square orientation
Gistbatch May 7, 2024
9c922ec
Implement midline for shifted template
Gistbatch May 7, 2024
8f8742e
Add not implemented error to make tests pass
Gistbatch May 7, 2024
fdc01e4
Apply suggestions
Gistbatch May 7, 2024
bb27dba
Remove type hints in docstrings
Gistbatch May 7, 2024
f1bac10
Merge branch 'main' into observables
Gistbatch May 8, 2024
ebacea7
Add qubit template midline
Gistbatch May 23, 2024
df85a1a
Fix logic mistace in rectengular templates
Gistbatch May 23, 2024
829093a
Improve test with scaling
Gistbatch May 23, 2024
15913fc
Add exception testing to test
Gistbatch May 23, 2024
237a4aa
Add tests for rectangle
Gistbatch May 23, 2024
37380ca
Add test for corner square
Gistbatch May 23, 2024
a046540
Implement stack test
Gistbatch May 23, 2024
4e88c3f
Add test for shifted template
Gistbatch May 23, 2024
007d684
Merge branch 'main' into observables
Gistbatch May 24, 2024
779c5b4
Rename argument "horizontal" to "orientation"
Gistbatch May 28, 2024
ca3bf33
Move _get_edge_qubits to PlaquetteQubits
Gistbatch May 28, 2024
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
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ myst-parser
sphinx-rtd-theme
pytest
mypy
pytest-cov
pytest-cov
1 change: 1 addition & 0 deletions src/tqec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .enums import (
CornerPositionEnum,
PlaquetteOrientation,
TemplateOrientation,
TemplateRelativePositionEnum,
)
from .exceptions import TQECException
Expand Down
2 changes: 1 addition & 1 deletion src/tqec/circuit/circuit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def one_by_one_template() -> Template:

def test_generate_initialisation_circuit_list(
initialisation_plaquette: Plaquette, one_by_one_template
):
) -> None:
circuit = generate_circuit(one_by_one_template, [initialisation_plaquette])
assert circuit == cirq.Circuit(
cirq.R(q.to_grid_qubit()) for q in initialisation_plaquette.qubits
Expand Down
75 changes: 75 additions & 0 deletions src/tqec/circuit/observable_qubits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from __future__ import annotations
from typing import Mapping, Sequence

import cirq

from tqec.enums import TemplateOrientation
from tqec.exceptions import TQECException
from tqec.plaquette.plaquette import Plaquette
from tqec.plaquette.qubit import PlaquetteQubit
from tqec.templates.base import Template


def observable_qubits_from_template(
template: Template,
plaquettes: Sequence[Plaquette] | Mapping[int, Plaquette],
orientation: TemplateOrientation = TemplateOrientation.HORIZONTAL,
) -> Sequence[tuple[cirq.GridQubit, int]]:
"""Return the default observable qubits for the given template and its plaquettes.

Args:
template: The template to get the default observable qubits from.
plaquettes: The plaquettes to use to get the acurate positions of the observable qubits.
orientation: Whether to get the observable qubits from
the horizontal or vertical midline. Defaults to horizontal.

Raises:
TQECException: If the number of plaquettes does not match the expected number.
TQECException: If the plaquettes are provided as a dictionary and 0 is in the keys.
TQECException: If the template does not have a definable midline.

Returns:
The sequence of qubits and offsets for the observable qubits.
"""
if len(plaquettes) != template.expected_plaquettes_number:
raise TQECException(
f"{len(plaquettes)} plaquettes have been provided, but "
f"{template.expected_plaquettes_number} were expected."
)
if isinstance(plaquettes, Mapping) and 0 in plaquettes:
raise TQECException(
"If using a dictionary, the input plaquettes parameter should not "
f"contain the entry 0. Found a value ({plaquettes[0]}) at entry 0."
)

if isinstance(plaquettes, list):
plaquettes = {i + 1: plaquette for i, plaquette in enumerate(plaquettes)}

_indices = list(range(1, len(plaquettes) + 1))
template_plaquettes = template.instantiate(_indices)
increments = template.get_increments()

try:
midline_indices = template.get_midline_plaquettes(orientation)
except TQECException as e:
raise TQECException(
"The template does not have a midline. "
"The observable qubits cannot be defined."
) from e

observable_qubits = []
for row_index, column_index in midline_indices:
plaquette_index = template_plaquettes[row_index][column_index]
if plaquette_index == 0:
continue
plaquette = plaquettes[plaquette_index]
# GridQubits are indexed as (row, col), so (y, x)
offset = (
row_index * increments.y + plaquette.origin.y,
column_index * increments.x + plaquette.origin.x,
)
observable_qubits += [
(qubit.to_grid_qubit() + offset, 0)
for qubit in plaquette.qubits.get_edge_qubits(orientation)
]
return sorted(set(observable_qubits))
36 changes: 36 additions & 0 deletions src/tqec/circuit/observable_qubits_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import cirq
import pytest

from tqec.circuit.observable_qubits import observable_qubits_from_template
from tqec.plaquette.library import xxxx_memory_plaquette, zzzz_memory_plaquette
from tqec.plaquette.plaquette import Plaquette
from tqec.templates import RawRectangleTemplate


@pytest.fixture
def plaquettes() -> list[Plaquette]:
return [
xxxx_memory_plaquette([1, 2, 3, 4, 5, 6, 7, 8]),
zzzz_memory_plaquette([1, 3, 4, 5, 6, 8]),
]


def test_raw_rectangle_default_obserevable_qubits(plaquettes: list[Plaquette]):
template = RawRectangleTemplate(
[
[0, 1, 0, 1],
[1, 0, 1, 0],
[0, 1, 0, 1],
[1, 0, 1, 0],
]
)

obs = observable_qubits_from_template(template, plaquettes)
result = [
(cirq.GridQubit(3, -1), 0),
(cirq.GridQubit(3, 1), 0),
(cirq.GridQubit(3, 3), 0),
(cirq.GridQubit(3, 5), 0),
(cirq.GridQubit(3, 7), 0),
]
assert sorted(obs, key=lambda t: t[0].col) == result
17 changes: 11 additions & 6 deletions src/tqec/circuit/operations/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from tqec.exceptions import TQECException


STIM_TAG = "STIM_OPERATION"


Expand Down Expand Up @@ -304,9 +305,11 @@ def make_detector(
A :class:`Detector` operation with the `cirq.VirtualTag` tag.
"""
relative_measurements_data = [
RelativeMeasurementData(*rm)
if not isinstance(rm, RelativeMeasurementData)
else rm
(
RelativeMeasurementData(*rm)
if not isinstance(rm, RelativeMeasurementData)
else rm
)
for rm in relative_measurements
]
return Detector(
Expand Down Expand Up @@ -343,9 +346,11 @@ def make_observable(
A :class:`Observable` operation with the `cirq.VirtualTag` tag.
"""
relative_measurements_data = [
RelativeMeasurementData(*rm)
if not isinstance(rm, RelativeMeasurementData)
else rm
(
RelativeMeasurementData(*rm)
if not isinstance(rm, RelativeMeasurementData)
else rm
)
for rm in relative_measurements
]
return Observable(
Expand Down
5 changes: 5 additions & 0 deletions src/tqec/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ class PlaquetteSide(Enum):
LEFT = auto()
DOWN = auto()
UP = auto()

class TemplateOrientation(Enum):
"""Indicates the orientation of the midline."""
HORIZONTAL = auto()
VERTICAL = auto()
30 changes: 29 additions & 1 deletion src/tqec/plaquette/qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import cirq

from tqec.enums import PlaquetteOrientation, PlaquetteSide
from tqec.enums import PlaquetteOrientation, PlaquetteSide, TemplateOrientation
from tqec.position import Position


Expand Down Expand Up @@ -57,6 +57,34 @@ def permute_data_qubits(self, permutation: ty.Sequence[int]) -> PlaquetteQubits:
[self.data_qubits[i] for i in permutation], self.syndrome_qubits
)

def get_edge_qubits(
self,
orientation: TemplateOrientation = TemplateOrientation.HORIZONTAL,
) -> list[PlaquetteQubit]:
"""Return the data qubits on the edge of the plaquette.
By convention, the edge is the one with the highest index in the relevant axis.

Args:
orientation (TemplateOrientation, optional): Wheter to use horizontal or
vertical orientation. Defaults to horizontal.
Returns:
list[PlaquetteQubit]: The qubits on the edge of the plaquette.
"""

def _get_relevant_value(qubit: PlaquetteQubit) -> int:
return (
qubit.position.y
if orientation == TemplateOrientation.HORIZONTAL
else qubit.position.x
)

max_index = max(_get_relevant_value(q) for q in self.data_qubits)
return [
qubit
for qubit in self.data_qubits
if (_get_relevant_value(qubit) == max_index)
]


class SquarePlaquetteQubits(PlaquetteQubits):
def __init__(self):
Expand Down
41 changes: 38 additions & 3 deletions src/tqec/templates/atomic/rectangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import numpy
from tqec.exceptions import TQECException
from tqec.enums import TemplateOrientation
from tqec.position import Shape2D
from tqec.templates.base import Template
from tqec.templates.scale import Dimension
Expand Down Expand Up @@ -90,6 +91,23 @@ def to_dict(self) -> dict[str, ty.Any]:
def expected_plaquettes_number(self) -> int:
return 2

def get_midline_plaquettes(
self, orientation: TemplateOrientation = TemplateOrientation.HORIZONTAL
) -> list[tuple[int, int]]:
midline_shape, iteration_shape = self.shape.x, self.shape.y
if orientation == TemplateOrientation.VERTICAL:
midline_shape, iteration_shape = iteration_shape, midline_shape

if midline_shape % 2 == 1:
raise TQECException(
"Midline is not defined for odd "
+ f"{'height' if orientation == TemplateOrientation.HORIZONTAL else 'width'}."
)
midline = midline_shape // 2 - 1
if orientation == TemplateOrientation.VERTICAL:
return [(row, midline) for row in range(iteration_shape)]
return [(midline, column) for column in range(iteration_shape)]


@ty.final
class RawRectangleTemplate(Template):
Expand Down Expand Up @@ -179,15 +197,15 @@ def instantiate(self, plaquette_indices: ty.Sequence[int]) -> numpy.ndarray:
plaquette_indices_array = numpy.array(plaquette_indices, dtype=int)
indices = numpy.array(self._indices, dtype=int)
return plaquette_indices_array[indices]
except IndexError:
except IndexError as ex:
raise TQECException(
"RawRectangleTemplate instances should be constructed with 2-dimensional arrays "
"that contain indices that will index the plaquette_indices provided to "
"this method. The bigest index you provided at this instance creation is "
"this method. The biggest index you provided at this instance creation is "
f"{max(max(index) for index in self._indices)} "
f"but you provided only {len(plaquette_indices)} plaquette indices "
"when calling this method."
)
) from ex

def scale_to(self, _: int) -> "RawRectangleTemplate":
return self
Expand All @@ -202,3 +220,20 @@ def to_dict(self) -> dict[str, ty.Any]:
@property
def expected_plaquettes_number(self) -> int:
return max(max(line) for line in self._indices) + 1

def get_midline_plaquettes(
self, orientation: TemplateOrientation = TemplateOrientation.HORIZONTAL
) -> list[tuple[int, int]]:
midline_shape, iteration_shape = self.shape.x, self.shape.y
if orientation == TemplateOrientation.VERTICAL:
midline_shape, iteration_shape = iteration_shape, midline_shape

if midline_shape % 2 == 1:
raise TQECException(
"Midline is not defined for odd "
+ f"{'height' if orientation == TemplateOrientation.HORIZONTAL else 'width'}."
)
midline = midline_shape // 2 - 1
if orientation == TemplateOrientation.VERTICAL:
return [(row, midline) for row in range(iteration_shape)]
return [(midline, column) for column in range(iteration_shape)]
57 changes: 57 additions & 0 deletions src/tqec/templates/atomic/rectangle_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy
import pytest
from tqec.enums import TemplateOrientation
from tqec.exceptions import TQECException
from tqec.templates.atomic.rectangle import (
AlternatingRectangleTemplate,
Expand Down Expand Up @@ -28,6 +29,11 @@ def dim2():
return FixedDimension(2)


@pytest.fixture
def dim2x3():
return Dimension(2, LinearFunction(3))


def test_rectangle_template_init(dim2x2, dim3x2):
AlternatingRectangleTemplate(dim2x2, dim3x2)

Expand Down Expand Up @@ -180,3 +186,54 @@ def test_raw_rectangle_with_noncontiguous_indices():
def test_raw_rectangle_with_negative_index():
with pytest.raises(TQECException, match="starting at 0"):
RawRectangleTemplate([[-1]])


def test_raw_rectangle_midline():
template = RawRectangleTemplate(
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
]
)
midline = template.get_midline_plaquettes()
assert midline == [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5)]
midline = template.get_midline_plaquettes(TemplateOrientation.VERTICAL)
assert midline == [(0, 2), (1, 2), (2, 2), (3, 2)]
template.scale_to(4)
midline = template.get_midline_plaquettes()
# shoudl be 4*3 elements
template = RawRectangleTemplate([[0]])
with pytest.raises(TQECException, match="Midline is not defined for odd height."):
template.get_midline_plaquettes()


def test_rectangle_midline(dim2x2, dim2x3):
template = AlternatingRectangleTemplate(dim2x2, dim2x3)
midline = template.get_midline_plaquettes()
assert midline == [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5)]
midline = template.get_midline_plaquettes(TemplateOrientation.VERTICAL)
assert midline == [(0, 2), (1, 2), (2, 2), (3, 2)]
template.scale_to(4)
midline = template.get_midline_plaquettes()
# shoudl be 4*3 elements
assert midline == [
(3, 0),
(3, 1),
(3, 2),
(3, 3),
(3, 4),
(3, 5),
(3, 6),
(3, 7),
(3, 8),
(3, 9),
(3, 10),
(3, 11),
]
template.scale_to(3)
with pytest.raises(TQECException, match="Midline is not defined for odd width."):
template.get_midline_plaquettes(TemplateOrientation.VERTICAL)
Loading
Loading