-
Notifications
You must be signed in to change notification settings - Fork 575
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
Incorporate Pauli group functions into existing grouping module. #1181
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1181 +/- ##
==========================================
+ Coverage 98.12% 98.14% +0.02%
==========================================
Files 146 147 +1
Lines 11071 11197 +126
==========================================
+ Hits 10863 10989 +126
Misses 208 208
Continue to review full report at Codecov.
|
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.
Thanks @glassnotes! Did a first pass, and left some documentation suggestions and implementation questions
This function is called by ``pauli_group`` in order to actually generate the | ||
group elements. They are split so that the outer function can handle input | ||
validation, while this generator is responsible for performing the actual | ||
operations. |
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.
Nice!
:class:`~pennylane.PauliZ`) or identity (:class:`~pennylane.Identity`) acting on | ||
the :math:`i^{th}` qubit. The full :math:`n`-qubit Pauli group has size | ||
:math:`4^n` (neglecting the four possible global phases that may arise from | ||
multiplication of its elements). |
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 is a great introduction!
def pauli_word_to_matrix(pauli_word, wire_map=None): | ||
"""Convert a Pauli word from a tensor to its matrix representation. |
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 is great to have. Perhaps we can have Tensor.__matmul__
call this function at some point?
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.
The only problem with using __matmul__
though is that it doesn't permit you to include wire maps, so there is ambiguity as to the ordering of the wires.
op_idx = pauli_word.wires.labels.index(wire_label) | ||
pauli_mats[wire_idx] = getattr(qml, pauli_names[op_idx]).matrix | ||
|
||
return reduce(np.kron, pauli_mats) |
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.
Out of curiosity, how does this logic scale for larger and larger Pauli words?
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.
... exponentially 😞 Since it will compute the full 2^n x 2^n unitary matrix. The key use-case I had in mind was for computing matrix representations of qml.Hamiltonian
.
Co-authored-by: Josh Izaac <josh146@gmail.com>
…I/pennylane into pauli_group_functions
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 is looking great @glassnotes! 💯 Mostly minor suggestions. For the tests, worth adding some cases with more exotic wire maps and reiterating on the docstrings.
.github/CHANGELOG.md
Outdated
>>> from pennylane.grouping import pauli_group | ||
>>> pauli_group_3_qubits = list(pauli_group(3)) |
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.
Just a thought: could it be worth having the 1 qubit version to also show the output?
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.
Yes, that's a better idea 😄
pennylane/grouping/pauli.py
Outdated
PauliZ(wires=[0]) | ||
PauliZ(wires=[0]) @ PauliZ(wires=[2]) | ||
PauliZ(wires=[0]) @ PauliZ(wires=[1]) | ||
... |
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.
Using the three dots might be confused with the multi-line statement block, perhaps there could be a note about having only part of the output prior to the example?
This is recurring for other examples too.
... |
], | ||
) | ||
def test_pauli_word_to_matrix(self, pauli_word, wire_map, expected_matrix): | ||
"""Test that Pauli words are correctly converted into strings.""" |
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.
Docstring needs an update. (seems to apply in other cases to in going forward)
tests/grouping/test_pauli_group.py
Outdated
"""Testing for Pauli group construction and manipulation functions.""" | ||
|
||
def test_pauli_group_size(self): | ||
"""Test that the Pauli group is constructed correctly given the wire map.""" |
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.
Docstring seems to be unrelated to the test case.
tests/grouping/test_pauli_group.py
Outdated
# With no wire map; ordering is based on construction from binary representation | ||
expected_pg_1 = [Identity(0), PauliZ(0), PauliX(0), PauliY(0)] | ||
pg_1 = list(pauli_group(1)) | ||
assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)]) | ||
|
||
# With an arbitrary wire map |
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.
Worth separating these into two separate tests.
Co-authored-by: antalszava <antalszava@gmail.com>
|
||
phase = 1 | ||
|
||
for qubit_1_char, qubit_2_char in zip(pauli_1_string, pauli_2_string): |
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 function has been streamlined from the first iteration by converting the Pauli words into strings, and using the strings as a means to compare. This automatically handles any issues that might otherwise occur with qubits in the tensor product that are ordered differently than they are in the wire map; the pauli_word_to_string
function takes care of that internally by virtue of ordering them and then inserting "I"
on the unused wires.
np.array([[0, -1j, 0, 0], [1j, 0, 0, 0], [0, 0, 0, 1j], [0, 0, -1j, 0]]), | ||
), | ||
( | ||
PauliY(1) @ PauliZ(0), |
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.
Two of these three tests involving PauliY/PauliZ on two qubits are new, and are intended to test that the matrix is computed correctly even when the mapping and wires are scrambled.
pg_1 = list(pauli_group(1)) | ||
assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)]) | ||
|
||
def test_one_qubit_pauli_group_valid_float_input(self): |
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.
New test.
), | ||
(PauliZ(0) @ PauliY(1), PauliX(1) @ PauliY(0), {0: 0, 1: 1}, -1), | ||
(PauliZ(0) @ PauliY(1), PauliX(1) @ PauliY(0), {1: 0, 0: 1}, -1), | ||
(PauliZ(0) @ PauliY(3) @ PauliZ(1), PauliX(1) @ PauliX(2) @ PauliY(0), None, 1), |
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.
New cases with mixed up order.
tests/grouping/test_pauli_group.py
Outdated
PauliZ(0) @ PauliY(3) @ PauliZ(1), | ||
PauliX(1) @ PauliX(2) @ PauliY(0), | ||
None, | ||
PauliX(0) @ PauliY(3) @ PauliY(1) @ PauliX(2) |
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.
New case that ensures that mixed up qubit orderings when no wire map is specified get constructed using combined set of wires in the order presented.
@glassnotes nice! 🥇 overall looks good, it seems that codecov found a line that is not covered |
I'm not sure, but I've updated the tests to get it incorporated now. Thanks for the heads up! |
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.
Looks good to me! 💯
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.
Thanks Olivia, this is a great addition!
Context: Iterating over the Pauli group, and manipulation of Pauli operations at the level of words are common tasks. Pauli operators as observables are available in PennyLane, however more advanced functionality is not fully implemented.
Description of the Change: Adds the function pauli_group to construct the Pauli group, and pauli_mult/pauli_mult_with_phase to compute products of Pauli words. Adds other miscellaneous utility functions.
Benefits: Users can now quickly iterate over the full Pauli group, convert back and forth between Pauli observables and their string representation, and multiply Paulis together at the level of their words without full matrix multiplication.
Possible Drawbacks: None
Related GitHub Issues: None