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

Incorporate Pauli group functions into existing grouping module. #1181

Merged
merged 35 commits into from
May 5, 2021

Conversation

glassnotes
Copy link
Contributor

@glassnotes glassnotes commented Mar 31, 2021

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

@codecov
Copy link

codecov bot commented Mar 31, 2021

Codecov Report

Merging #1181 (e72c76e) into master (8fe31ad) will increase coverage by 0.02%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            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              
Impacted Files Coverage Δ
pennylane/grouping/__init__.py 100.00% <100.00%> (ø)
pennylane/grouping/pauli.py 100.00% <100.00%> (ø)
pennylane/grouping/utils.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8fe31ad...e72c76e. Read the comment docs.

Copy link
Member

@josh146 josh146 left a 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

Comment on lines +28 to +31
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.
Copy link
Member

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).
Copy link
Member

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!

doc/code/qml_grouping.rst Outdated Show resolved Hide resolved
doc/code/qml_grouping.rst Outdated Show resolved Hide resolved
doc/code/qml_grouping.rst Outdated Show resolved Hide resolved
pennylane/grouping/utils.py Outdated Show resolved Hide resolved
Comment on lines +451 to +452
def pauli_word_to_matrix(pauli_word, wire_map=None):
"""Convert a Pauli word from a tensor to its matrix representation.
Copy link
Member

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?

Copy link
Member

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)
Copy link
Member

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?

Copy link
Contributor Author

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.

pennylane/grouping/utils.py Show resolved Hide resolved
pennylane/grouping/utils.py Show resolved Hide resolved
@glassnotes glassnotes requested a review from josh146 April 20, 2021 21:01
Copy link
Contributor

@antalszava antalszava left a 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.

Comment on lines 12 to 13
>>> from pennylane.grouping import pauli_group
>>> pauli_group_3_qubits = list(pauli_group(3))
Copy link
Contributor

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?

Copy link
Contributor Author

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 Show resolved Hide resolved
pennylane/grouping/pauli.py Show resolved Hide resolved
PauliZ(wires=[0])
PauliZ(wires=[0]) @ PauliZ(wires=[2])
PauliZ(wires=[0]) @ PauliZ(wires=[1])
...
Copy link
Contributor

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.

Suggested change
...

pennylane/grouping/pauli.py Outdated Show resolved Hide resolved
tests/grouping/test_grouping_utils.py Show resolved Hide resolved
tests/grouping/test_grouping_utils.py Outdated Show resolved Hide resolved
],
)
def test_pauli_word_to_matrix(self, pauli_word, wire_map, expected_matrix):
"""Test that Pauli words are correctly converted into strings."""
Copy link
Contributor

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)

"""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."""
Copy link
Contributor

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.

Comment on lines 43 to 48
# 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
Copy link
Contributor

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.


phase = 1

for qubit_1_char, qubit_2_char in zip(pauli_1_string, pauli_2_string):
Copy link
Contributor Author

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.

@josh146 josh146 added this to the v0.16.0 milestone Apr 29, 2021
np.array([[0, -1j, 0, 0], [1j, 0, 0, 0], [0, 0, 0, 1j], [0, 0, -1j, 0]]),
),
(
PauliY(1) @ PauliZ(0),
Copy link
Contributor Author

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):
Copy link
Contributor Author

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),
Copy link
Contributor Author

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.

PauliZ(0) @ PauliY(3) @ PauliZ(1),
PauliX(1) @ PauliX(2) @ PauliY(0),
None,
PauliX(0) @ PauliY(3) @ PauliY(1) @ PauliX(2)
Copy link
Contributor Author

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.

@antalszava
Copy link
Contributor

@glassnotes nice! 🥇 overall looks good, it seems that codecov found a line that is not covered pennylane/grouping/utils.py#L450. Was that something newly added or was just missed before and rearranged now?

@glassnotes
Copy link
Contributor Author

@glassnotes nice! 1st_place_medal overall looks good, it seems that codecov found a line that is not covered pennylane/grouping/utils.py#L450. Was that something newly added or was just missed before and rearranged now?

I'm not sure, but I've updated the tests to get it incorporated now. Thanks for the heads up!

Copy link
Contributor

@antalszava antalszava left a 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! 💯

Copy link
Member

@josh146 josh146 left a 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!

@glassnotes glassnotes merged commit d400c15 into master May 5, 2021
@glassnotes glassnotes deleted the pauli_group_functions branch May 5, 2021 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants