-
Notifications
You must be signed in to change notification settings - Fork 17
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
Observables #213
Conversation
In my opinion there would be very little benefit and significant additional
complexity allowing even code distances, so I would support not allowing
this at all and consequently always having well defined mid lines.
Best,
Austin.
…On Mon, Apr 22, 2024, 8:19 AM Philipp Seitz ***@***.***> wrote:
closes: #141 <#141>
I started to develop this, and I have some open questions:
- I've implemented a test for the RawRectangleTemplate. @nelimee
<https://github.com/nelimee>, can you clarify if this is the intended
behavior?
- What should the scope of this Issue/PR be? Only build this for the
available templates, including moves and so on.
- How do we define midlines for uneven-sized templates, e.g., where
the number of qubit rows is even?
Todos:
- Add all templates
- Write tests, including scaling
- Figure out edge cases: Moving, uneven ...
- Documentation
------------------------------
You can view, comment on, or merge this pull request online at:
#213
Commit Summary
- 4f5cd82
<4f5cd82>
Add pytest as explicit dependency
- a0ad6d9
<a0ad6d9>
Add new abstract method to base template
- 17db053
<17db053>
Add initial rectangle implementation
File Changes
(4 files <https://github.com/QCHackers/tqec/pull/213/files>)
- *M* requirements-dev.txt
<https://github.com/QCHackers/tqec/pull/213/files#diff-2b4945591edfeaa4cf4d3f155e66d4b43d1bda7a55d881d5cf3107f1b05abbbc>
(3)
- *M* src/tqec/templates/atomic/rectangle.py
<https://github.com/QCHackers/tqec/pull/213/files#diff-8b5421da61ed89127cacca5e2b190c31922a1c751c8197b0037126c1bde61b26>
(43)
- *M* src/tqec/templates/atomic/rectangle_test.py
<https://github.com/QCHackers/tqec/pull/213/files#diff-9596ec56c970721b10b588d25b854f559278da999c8e57993fb6d4ee752374a9>
(26)
- *M* src/tqec/templates/base.py
<https://github.com/QCHackers/tqec/pull/213/files#diff-3f1946f8f161e910c0c51cfbcae118a9e500fc25c8a93b908ac5ee5715d7f5eb>
(17)
Patch Links:
- https://github.com/QCHackers/tqec/pull/213.patch
- https://github.com/QCHackers/tqec/pull/213.diff
—
Reply to this email directly, view it on GitHub
<#213>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKAXTAR2AFW3WUIWSIA7CLY6US7VAVCNFSM6AAAAABGS75KBKVHI2DSMVQWIX3LMV43ASLTON2WKOZSGI2TMOBSGQYDSMA>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
The output you fixed is dependent on the actual In any case, I think this method needs the
For this PR, only build this for the available
As Austin said, I would rule out that case. To do that, I would change the Also, I think the implementations of |
The rationale is to get the plaquette indices from the template and then compute the positions according to the plaquettes.
This construction is similar to the circuit generation
@nelimee, I've updated this; let me know what you think. If the midline is supposed to depend on the plaquettes, we need this information, so I built |
I do not get how you can generate correct results without any idea of the plaquette used. For the moment, all the plaquettes we use have the support
with:
but (as far as I remember) we never explicitly said that was the only possible default. This means that a plaquette with the same support but with
is possible. I am making a few comments on the code before commenting again on the overall approach. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not check thoroughly the code validity for the moment, but your tests help on that.
For the Template.get_midline_plaquettes
method, I completely agree with the fact that we need a convention such as the one you used (plaquettes left of the vertical midline or above the horizontal midline). The two branches (depending on the value of the horizontal
boolean) are very similar, maybe they can be "merged" in one more generic version.
|
||
def observable_qubits_from_template( | ||
template: Template, | ||
plaquettes: list[Plaquette] | dict[int, Plaquette], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be less of an issue with #203 merged, but maybe
plaquettes: list[Plaquette] | dict[int, Plaquette], | |
plaquettes: Sequence[Plaquette] | Mapping[int, Plaquette], |
to have the covariance of the typing
annotations?
def observable_qubits_from_template( | ||
template: Template, | ||
plaquettes: list[Plaquette] | dict[int, Plaquette], | ||
horizontal: bool = True, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though I am not always doing it, in general, I prefer to use enumerations with explicit names for boolean parameters. I find that easier to read when calling the function/method. Doing so would require to introduce something along the lines of
class Orientation(Enum):
HORIZONTAL = auto()
VERTICAL = auto()
and then the function can be used as
observable_qubits_from_template(template, plaquettes, Orientation.HORIZONTAL)
this is really minor though.
template (Template): The template to get the default observable qubits from. | ||
plaquettes (list[Plaquette] | dict[int, Plaquette]): The plaquettes to use to get the | ||
acurate positions of the observable qubits. | ||
horizontal (bool, optional): Whether to get the observable qubits from | ||
the horizontal or vertical midline. Defaults to True -> Horizontal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure the type annotations in the docstring are needed. To check in #211.
If not needed because Sphinx is able to read directly from the annotations in code, then we should remove them in the docstring as we will likely eventually forget to change the docstring and that will lead to outdated comments.
try: | ||
midline_indices = template.get_midline_plaquettes(horizontal) | ||
except TQECException: | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot remember what is the norm in Python, but I try to avoid exceptions for control-flow handling. Seeing it implemented, I think I do not agree with myself anymore. I wrote that
As Austin said, I would rule out that case. To do that, I would change the default_observable_qubits return type to ty.Sequence[tuple[cirq.GridQubit, int]] | None, returning None being a signal that the midline could not be computed.
but I now think that the TQECException
should not be caught here but should rather go up in the stack. We can then simply document that the function/method raise TQECException
when the Template
dimensions are not compatible. Sorry for misleading you in the first place, this is my mistake.
for index in midline_indices: | ||
row_index, column_index = index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well be written in one line:
for index in midline_indices: | |
row_index, column_index = index | |
for (row_index, column_index) in midline_indices: |
max_index = max( | ||
plaquette.qubits.data_qubits, key=lambda q: q.position.y | ||
).position.y |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max_index = max( | |
plaquette.qubits.data_qubits, key=lambda q: q.position.y | |
).position.y | |
max_index = max(q.position.y for q in plaquette.qubits.data_qubits) |
should be equivalent here. I did not tested the above code though.
TQECException: If the plaquettes are provided as a dictionary and 0 is in the keys. | ||
|
||
Returns: | ||
Sequence[tuple[cirq.GridQubit, int]] | None: The observable qubits and their offsets. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure the type annotations in the docstring are needed. To check in #211.
src/tqec/templates/base.py
Outdated
and to the left of the midline for the vertical case. | ||
|
||
Args: | ||
horizontal (bool, optional): Horizontal or vertical qubits. Defaults to True. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure the type annotations in the docstring are needed. To check in #211.
src/tqec/templates/base.py
Outdated
horizontal (bool, optional): Horizontal or vertical qubits. Defaults to True. | ||
|
||
Returns: | ||
list[tuple[int,int]]: The sequence of qubits and offsets. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure the type annotations in the docstring are needed. To check in #211.
indices = self._indices[midline] | ||
return [(midline, column ) for column, _ in enumerate(indices)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indices = self._indices[midline] | |
return [(midline, column ) for column, _ in enumerate(indices)] | |
return [(midline, column) for column in range(len(self._indices[midline]))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even
indices = self._indices[midline] | |
return [(midline, column ) for column, _ in enumerate(indices)] | |
return [(midline, column) for column in range(self.shape.x)] |
Some reasonable restrictions. We already restrict ourselves to plaquettes
with a rectangular footprint. The midline calculation is associated with a
square 2kx2k template of such plaquettes. It would be reasonable to
restrict ourselves to plaquettes with exactly four qubits in the four
corners of the plaquette, then the midline calculation should depend on the
shape of the plaquette, rather than the precise labeling of the qubits in
the plaquette.
Best,
Austin.
…On Fri, Apr 26, 2024, 8:34 AM Adrien Suau ***@***.***> wrote:
If the midline is supposed to depend on the plaquettes
I do not get how you can generate correct results without any idea of the
plaquette used. For the moment, all the plaquettes we use have the support
1 2
5
3 4
with:
1. cirq.GridQubit(-1, -1)
2. cirq.GridQubit( 1, -1)
3. cirq.GridQubit(-1, 1)
4. cirq.GridQubit( 1, 1)
5. cirq.GridQubit( 0, 0)
but (as far as I remember) we never explicitly said that was the only
possible default. This means that a plaquette with the same support but with
1. cirq.GridQubit(0, 0)
2. cirq.GridQubit(2, 0)
3. cirq.GridQubit(0, 2)
4. cirq.GridQubit(2, 2)
5. cirq.GridQubit(1, 1)
is possible.
If that is the case, then we need to have access to that information, else
the midline qubits will be completely wrong. I am missing something here?
I am making a few comments on the code before commenting again on the
overall approach.
—
Reply to this email directly, view it on GitHub
<#213 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKAXTCWTHPW2B4VAOCLI2TY7JXYDAVCNFSM6AAAAABGS75KBKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANZZGYZDMNZYG4>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Using their coordinates, I take the qubits furthest to the right (bottom). This works only for rectangular shapes but has the advantage that this works independently of the orientation. |
I agree, the issue here is that, in the current state of the code, the positions of the plaquette qubits can change all the positions. Basically, the positioning algorithm starts by positioning the origin of the top-left-most plaquette on the qubit This limitation should be quite easy to remove though, using the top-left corner of the plaquette instead of the origin in the first step would solve it on the current use-case.
Both approaches seems fine for me. The second one is likely more generic, but we are only interested in rectangular plaquette for the moment so this is not really a problem to implement a simpler, less generic, version. |
@nelimee, I'm confused about why the test keeps failing. Do you have any idea why this is the case? I can run it locally without this problem, and I'm not sure how to fix this as it seems it works completely fine with |
This might be due to #198 being merged in main. I am not sure what https://github.com/actions/checkout is checking out, but it might be worth updating this branch with the latest changes from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One suggestion to fix at least one test.
The other errors are expected as not all Template
subclasses implement the get_midline_plaquettes
method, which makes them abstract and non-instantiable
@@ -0,0 +1,96 @@ | |||
from typing import Any, Mapping, Sequence |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from typing import Any, Mapping, Sequence | |
from __future__ import annotations | |
from typing import Any, Mapping, Sequence |
fixes some test issues.
I've now implemented the midline definition for the existing template instances. |
It seems at least hard, maybe impossible (there are likely instantiations of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future it might be better to define a method in Plaquette
to get the "extremum" qubits in a provided direction. This would be even more general than the _get_edge_qubits
and should be quite simple to implement using geometry. Also it would pave the way for non-square plaquettes (for which the edge qubits might not only be horizontal or vertical, say for example for an octagonal plaquette).
You also should document somewhere the convention you used w.r.t which plaquette is returned by the get_midline_plaquettes
method (if horizontal, the plaquette on the left or on the right? same question for vertical) and so on which edge the qubits are taken from.
You should document somewhere that the qubits returned by the get_midline_plaquettes
method are in the (row, column)
format. Even better, maybe create or re-use a structure with named fields for that?
|
||
def observable_qubits_from_template( | ||
template: Template, | ||
plaquettes: Union[Sequence[Plaquette], Mapping[int, Plaquette]], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be
plaquettes: Union[Sequence[Plaquette], Mapping[int, Plaquette]], | |
plaquettes: Sequence[Plaquette] | Mapping[int, Plaquette], |
if we also include
from __future__ import annotations
This version (with |
) is used across the code base, it might be better to stay consistent.
template (Template): The template to get the default observable qubits from. | ||
plaquettes (list[Plaquette] | dict[int, Plaquette]): The plaquettes to use to get the | ||
acurate positions of the observable qubits. | ||
horizontal (bool, optional): Whether to get the observable qubits from | ||
the horizontal or vertical midline. Defaults to True -> Horizontal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for the previous comments, I am pretty sure types do not have to appear in the docstrings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't updated docs and tests yet, I'll do this once functionality is fixed
(qubit.to_grid_qubit() + offset, 0) | ||
for qubit in _get_edge_qubits(plaquette, horizontal) | ||
] | ||
return list(set(observable_qubits)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe sorting the qubits before returning them would help human readability in the final generated Stim
file?
return list(set(observable_qubits)) | |
return sorted(set(observable_qubits)) |
else qubit.position.x | ||
) | ||
|
||
max_index = max_index = max( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max_index = max_index = max( | |
max_index = max( |
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure to understand that part. If I understand it correctly, you are sorting obs
with a comparator that I do not understand.
Maybe
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) | |
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 |
is what you wanted?
src/tqec/templates/atomic/square.py
Outdated
|
||
def get_midline_plaquettes( | ||
self, horizontal: TemplateOrientation = TemplateOrientation.HORIZONTAL | ||
) -> list[tuple[int, int]]: | ||
midline_shape, iteration_shape = self.shape.x, self.shape.y | ||
if midline_shape % 2 == 1: | ||
raise TQECException( | ||
"Midline is not defined for odd " | ||
+ f"{'height' if horizontal == TemplateOrientation.HORIZONTAL else 'width'}." | ||
) | ||
midline = midline_shape // 2 - 1 | ||
if horizontal == TemplateOrientation.VERTICAL: | ||
return [(midline, column) for column in range(iteration_shape)] | ||
return [(row, midline) for row in range(iteration_shape)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this one already implemented by the AlternatingRectangleTemplate.get_midline_plaquettes
? Why aren't you swapping midline_shape
and iteration_shape
depending on the orientation here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this is a square midline_shape == iteration_shape
, and this class inherits from Template
so we can't inherit the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I missed that, thanks for the answer :)
src/tqec/templates/stack.py
Outdated
if ( | ||
horizontal == TemplateOrientation.VERTICAL | ||
and template.shape.x == midline_shape | ||
): | ||
template.get_midline_plaquettes(horizontal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ( | |
horizontal == TemplateOrientation.VERTICAL | |
and template.shape.x == midline_shape | |
): | |
template.get_midline_plaquettes(horizontal) | |
elif ( | |
horizontal == TemplateOrientation.VERTICAL | |
and template.shape.x == midline_shape | |
): | |
return template.get_midline_plaquettes(horizontal) |
I wrote tests for all the templates now. |
@nelimee I'll merge this once you approve |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some very minor comments, mostly about renaming horizontal
to orientation
.
There is also some duplication of code, but this can be addressed later.
A few other comments for the future of that feature:
- I can see the
_get_edge_qubits
function being implemented in thePlaquetteQubits
class. With a little bit of geometry, we could even implement something likePlaquetteQubits.extremum_qubits_in_direction(self, x: float, y: float)
. This can be thought about in a follow-up issue. - The more I think about the short-term and end goal of the community, the more I think I made a mistake by introducing
StackedTemplate
andShiftedTemplate
. These are 1. not really useful and 2. making a few PR more difficult than what they should be (this PR being a good example, another exploration work I have locally being another).
In the end, except the minor comments, I think this is a good first step to include automatic observable computation.
def observable_qubits_from_template( | ||
template: Template, | ||
plaquettes: Sequence[Plaquette] | Mapping[int, Plaquette], | ||
horizontal: TemplateOrientation = TemplateOrientation.HORIZONTAL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
horizontal: TemplateOrientation = TemplateOrientation.HORIZONTAL, | |
orientation: TemplateOrientation = TemplateOrientation.HORIZONTAL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: should also be changed in the function body if changed here.
horizontal: Whether to get the observable qubits from | ||
the horizontal or vertical midline. Defaults to True -> Horizontal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
horizontal: Whether to get the observable qubits from | |
the horizontal or vertical midline. Defaults to True -> Horizontal. | |
orientation: Whether to get the observable qubits from | |
the horizontal or vertical midline. Defaults to horizontal. |
|
||
def _get_edge_qubits( | ||
plaquette: Plaquette, | ||
horizontal: TemplateOrientation = TemplateOrientation.HORIZONTAL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
horizontal: TemplateOrientation = TemplateOrientation.HORIZONTAL, | |
orientation: TemplateOrientation = TemplateOrientation.HORIZONTAL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: should also be changed in the function body if changed here.
@@ -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, horizontal: TemplateOrientation = TemplateOrientation.HORIZONTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self, horizontal: TemplateOrientation = TemplateOrientation.HORIZONTAL | |
self, orientation: TemplateOrientation = TemplateOrientation.HORIZONTAL |
WARNING: should also be changed in the function body if changed here.
closes: #141
I started to develop this, and I have some open questions:
RawRectangleTemplate
. @nelimee, can you clarify if this is the intended behavior?Todos: