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

Add dimension checking to QubitUnitary. #1439

Merged
merged 15 commits into from
Jul 7, 2021
Merged

Conversation

glassnotes
Copy link
Contributor

@glassnotes glassnotes commented Jun 29, 2021

Context: Development of #1427 uncovered a bug where QubitUnitary was not checking whether the number of wires matched with the size of the unitary matrix.

Description of the Change: Adds an __init__ method to QubitUnitary to verify dimensionality and unitarity. Changes up the _matrix method accordingly. Also, converts QubitUnitary functions to use qml.math, and separates QubitUnitary tests into separate integration tests for all interfaces.

Benefits: Fixes the bug and clears up ambiguous behaviour such as

qml.QubitUnitary(qml.Hadamard(wires=0).matrix, wires=[0, 1])

which would previously run without warnings.

Possible Drawbacks: None

Related GitHub Issues: None

@github-actions
Copy link
Contributor

Hello. You may have forgotten to update the changelog!
Please edit .github/CHANGELOG.md with:

  • A one-to-two sentence description of the change. You may include a small working example for new features.
  • A link back to this PR.
  • Your name (or GitHub username) in the contributors section.

@glassnotes glassnotes added the bug 🐛 Something isn't working label Jun 29, 2021
@@ -125,6 +125,9 @@ def test_heisenberg(self, test_class, tol):
def test_operation_init(self, test_class, monkeypatch):
"Operation subclass initialization."

if test_class == qml.QubitUnitary:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to write a separate test for the new __init__ method (see below). I'm actually not sure why it worked before; the issue was that in line 135, the number of wires is computed and then used to construct a list of wires, but QubitUnitary works on any number of wires so it was yielding a list of length 0 and causing the test to fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

op = qml.QubitUnitary(U, wires=wires)

assert op.name == qml.QubitUnitary.__name__
assert np.allclose([U], op.data)
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 line and the next line are maybe redundant?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆

@glassnotes glassnotes requested a review from josh146 June 29, 2021 15:36
@codecov
Copy link

codecov bot commented Jun 29, 2021

Codecov Report

Merging #1439 (259482d) into master (63f493d) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1439   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files         163      163           
  Lines       12076    12082    +6     
=======================================
+ Hits        11863    11869    +6     
  Misses        213      213           
Impacted Files Coverage Δ
pennylane/ops/qubit.py 98.65% <100.00%> (+<0.01%) ⬆️

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 63f493d...259482d. 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! I left some minor comments in the review itself, but have a bigger request here:

Rather than having a unit test for __init__ (which seems a bit redundant), perhaps it makes more sense to have an integration test, which does the following:

  • Tests the validation catches incorrect input and allows correct input using NumPy arrays and tensors
  • Tests that ControlledQubitUnitary continues to avoid the validation?

# For pure QubitUnitary operations (not controlled), check that the number
# of wires fits the dimensions of the matrix
if not isinstance(self, ControlledQubitUnitary):
U = np.asarray(params[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize that the previous version of the code does this, but, I only just noticed it 😆

We can't assume that params[0] will be a NumPy array - it could very easily be a TensorFlow or a PyTorch tensor. As a result, best to use qml.math here. E.g.,

U = params[0]
shape = qml.math.shape(U)

(and then down below, qml.math.dot and qml.math.conj)


@classmethod
def _matrix(cls, *params):
return np.asarray(params[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here, this should probably just return params[0]?

@@ -125,6 +125,9 @@ def test_heisenberg(self, test_class, tol):
def test_operation_init(self, test_class, monkeypatch):
"Operation subclass initialization."

if test_class == qml.QubitUnitary:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

op = qml.QubitUnitary(U, wires=wires)

assert op.name == qml.QubitUnitary.__name__
assert np.allclose([U], op.data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆

@glassnotes
Copy link
Contributor Author

Thanks @glassnotes! I left some minor comments in the review itself, but have a bigger request here:

Rather than having a unit test for __init__ (which seems a bit redundant), perhaps it makes more sense to have an integration test, which does the following:

* Tests the validation catches incorrect input and allows correct input using NumPy arrays and tensors

* Tests that `ControlledQubitUnitary` continues to _avoid_ the validation?

@josh146 probably, then, it would be best to split out the QubitUnitary tests into their own test class like the excitation operations, since otherwise that will be quite a big chunk of test living in the TestOperation class. Furthermore, the extensive decomposition tests from #1427 could be included in the new class as well.

@pytest.mark.parametrize(
"U", [np.array([0]), np.array([1, 0, 0, 1]), np.array([[[1, 0], [0, 1]]])]
)
def test_qubit_unitary_not_matrix_exception(self, U):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this test because the case is already covered in the unitary_exceptions test, where U[1:] is passed as input.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

@glassnotes glassnotes requested a review from josh146 July 7, 2021 14:51
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.

@josh146 probably, then, it would be best to split out the QubitUnitary tests into their own test class like the excitation operations, since otherwise that will be quite a big chunk of test living in the TestOperation class. Furthermore, the extensive decomposition tests from #1427 could be included in the new class as well.

I think this makes a lot of sense, it has annoyed me for a while that that test file is so large and hard to add to!

Also new tests look 💯, thanks so much @glassnotes for adding them. It will make the QubitUnitary operation a lot more bug resistant going forward.

@pytest.mark.parametrize(
"U", [np.array([0]), np.array([1, 0, 0, 1]), np.array([[[1, 0], [0, 1]]])]
)
def test_qubit_unitary_not_matrix_exception(self, U):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants