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 OrbitalRotation operation #1665

Merged
merged 16 commits into from
Sep 22, 2021

Conversation

obliviateandsurrender
Copy link
Contributor

Context:
This is the first part of the PRs aimed at adding the GateFabric template, which is based on the local, expressive, and quantum number preserving ansatz presented in arXiv:2104.05695.

Description of the Change:
Function OrbitalRotation provides the implementation for the spin-adapted spatial orbital rotation gate which constitutes the gate required for developing the aforementioned template.
Relevant UnitTests for OrbitalRotation operation.

Benefits:
This operation will be required in the implementation of the GateFabric template.

Possible Drawbacks:
No specific drawbacks.

Related GitHub Issues:
No related issues.

Copy link
Contributor

@ixfoduap ixfoduap left a comment

Choose a reason for hiding this comment

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

Looks great! Amazing job!

I didn't find any issues, so I'm happy to approve from my side once a small change in the docstrings is made and we have confirmation that the tests are passing.

Comment on lines 485 to 488
This operation performs a Givens rotation between the orbital bases in the Jordan-Wigner basis using a pair
of parallel Givens gates :math:`G(\phi)`, which are equivalent to :class:`~.SingleExcitation()` operation.
More precisely, for two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`,
it performs the following transformation
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
This operation performs a Givens rotation between the orbital bases in the Jordan-Wigner basis using a pair
of parallel Givens gates :math:`G(\phi)`, which are equivalent to :class:`~.SingleExcitation()` operation.
More precisely, for two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`,
it performs the following transformation
For two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`, this operation
performs the following transformation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in the docstring.

More precisely, for two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`,
it performs the following transformation

.. math::
Copy link
Contributor

Choose a reason for hiding this comment

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

Really clever way to summarize what the operation is doing! 🎉 🧠

Copy link
Member

Choose a reason for hiding this comment

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

I agree!! really nice docstring 🙂

One suggestion: it would be nice to briefly describe the image (in this case, I wasn't originally sure what G(theta) referred to, I assume givens rotations?)

Copy link
Member

Choose a reason for hiding this comment

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

Actually, now that I look at it again: it might be clearer to remove the single-excitation image, and in the text below, simply say

:math:`G(\phi)` represents a single-excitation Given's rotation,
implemented in PennyLane as the :class:`~.SingleExcitation` operation.

This way a reader can easily click on the link to read more about the SingleExcitation operation.

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 agree. I have added this to the documentation now.

Copy link
Member

Choose a reason for hiding this comment

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

Love it!


* Number of wires: 4
* Number of parameters: 1
* Gradient recipe: The ``OrbitalRotation`` operator satisfies a four-term parameter-shift rule
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Gradient recipe: The ``OrbitalRotation`` operator satisfies a four-term parameter-shift rule
* Gradient recipe: The ``OrbitalRotation`` operator satisfies the four-term parameter-shift rule

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in the docstring.

# Additionally, there was a typo in the sign of a matrix element "s" at [2, 8], which is fixed here.

phi = params[0]
c = qml.math.cos(phi / 2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Great to see qml.math in action!

Copy link
Member

Choose a reason for hiding this comment

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

Same! it is really nice not needing to modify tf_ops.py etc.

pytest.importorskip("autograd")

dev = qml.device("default.qubit.autograd", wires=4)
state = np.array(
Copy link
Contributor

Choose a reason for hiding this comment

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

How did you compute this state as to avoid using the OrbitalRotation operation? i.e., how do you know it's the correct state?

Copy link
Contributor Author

@obliviateandsurrender obliviateandsurrender Sep 17, 2021

Choose a reason for hiding this comment

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

I'd computed this state manually using the matrix definition of the gate (and cross-checked it with the mentioned transformation).

@codecov
Copy link

codecov bot commented Sep 17, 2021

Codecov Report

Merging #1665 (8005ab6) into master (6d93cc5) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1665   +/-   ##
=======================================
  Coverage   99.19%   99.19%           
=======================================
  Files         199      199           
  Lines       14835    14857   +22     
=======================================
+ Hits        14715    14737   +22     
  Misses        120      120           
Impacted Files Coverage Δ
pennylane/devices/default_mixed.py 100.00% <ø> (ø)
pennylane/devices/default_qubit.py 100.00% <ø> (ø)
pennylane/ops/qubit/__init__.py 100.00% <ø> (ø)
pennylane/tape/jacobian_tape.py 98.19% <ø> (ø)
pennylane/ops/qubit/qchem_ops.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 6d93cc5...8005ab6. Read the comment docs.


>>> @qml.qnode(dev)
... def circuit(phi):
... qml.BasisState([1, 1, 0, 0], wires=[0, 1, 2, 3])
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be:
qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I have fixed it now.

(0.1),
],
)
def test_autograd_grad(self, phi):
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to test different diff_methods here?

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, we should. I have now added the diff_method for this unit test as well.

Copy link
Contributor

@soranjh soranjh left a comment

Choose a reason for hiding this comment

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

The PR looks good to me @obliviateandsurrender, just left two comments. I am approving as I have already reviewed the new addition in a previous PR.

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.

Really nice PR @obliviateandsurrender! Left a small comment regarding adding a pytorch gradient test. Apart from that, don't forget to add an entry to the changelog!

More precisely, for two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`,
it performs the following transformation

.. math::
Copy link
Member

Choose a reason for hiding this comment

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

I agree!! really nice docstring 🙂

One suggestion: it would be nice to briefly describe the image (in this case, I wasn't originally sure what G(theta) referred to, I assume givens rotations?)

More precisely, for two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`,
it performs the following transformation

.. math::
Copy link
Member

Choose a reason for hiding this comment

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

Actually, now that I look at it again: it might be clearer to remove the single-excitation image, and in the text below, simply say

:math:`G(\phi)` represents a single-excitation Given's rotation,
implemented in PennyLane as the :class:`~.SingleExcitation` operation.

This way a reader can easily click on the link to read more about the SingleExcitation operation.

Comment on lines 509 to 517
>>> dev = qml.device('default.qubit', wires=4)

>>> @qml.qnode(dev)
... def circuit(phi):
... qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3])
... qml.OrbitalRotation(phi, wires=[0, 1, 2, 3])
... return qml.state()

>>> circuit(0.1)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
>>> dev = qml.device('default.qubit', wires=4)
>>> @qml.qnode(dev)
... def circuit(phi):
... qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3])
... qml.OrbitalRotation(phi, wires=[0, 1, 2, 3])
... return qml.state()
>>> circuit(0.1)
>>> dev = qml.device('default.qubit', wires=4)
>>> @qml.qnode(dev)
... def circuit(phi):
... qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3])
... qml.OrbitalRotation(phi, wires=[0, 1, 2, 3])
... return qml.state()
>>> circuit(0.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.

Fixed.

# Additionally, there was a typo in the sign of a matrix element "s" at [2, 8], which is fixed here.

phi = params[0]
c = qml.math.cos(phi / 2)
Copy link
Member

Choose a reason for hiding this comment

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

Same! it is really nice not needing to modify tf_ops.py etc.

qml.OrbitalRotation(phi, wires=[0, 1, 2, 3])
return qml.expval(qml.PauliZ(0))

assert np.allclose(jax.grad(circuit)(phi), np.sin(phi))
Copy link
Member

Choose a reason for hiding this comment

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

@obliviateandsurrender could you add a PyTorch test here as well? Aside from that, great tests!

Copy link
Contributor Author

@obliviateandsurrender obliviateandsurrender Sep 20, 2021

Choose a reason for hiding this comment

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

As requested I have added the PyTorch test cases. However, while doing so I faced a problem that the gradient results were returned as None. After spending quite a good amount of time on this and also looking at torch_ops.py, I concluded that defining the matrix for the operations as a single tensor prevents torch to calculate gradients during the backpropagation due to unavailability of the computation graph (as grad_fn was coming out to be None as well). To fix this, I decomposed the matrix definition into the summation of type func(phi)*matrix. Do you think doing this is fine? Let me know.

Copy link
Member

Choose a reason for hiding this comment

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

Hi @obliviateandsurrender, nice catch -- pytorch can be very finicky with how matrices are defined.

No need to modify, but I'm curious if using qml.math.stack to first stack each row of the array, and then stack all rows, would have worked with PyTorch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey @josh146, you're right. Using qml.math.stack indeed works! I think it would be much better to use this instead of the full decomposition.

Copy link
Member

Choose a reason for hiding this comment

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

Awesome! let me know when the master merge conflict is resolved, and I will give it another review :)

Copy link
Contributor Author

@obliviateandsurrender obliviateandsurrender Sep 21, 2021

Choose a reason for hiding this comment

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

Hi! The merge conflict with the master is resolved now.

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.

Good to go from my end @obliviateandsurrender, once @ixfoduap and @Jaybsoni also approve!

-0.04991671+0.j, 0. +0.j, 0. +0.j,
0.99750208+0.j, 0. +0.j, 0. +0.j,
0. +0.j])
```
Copy link
Member

Choose a reason for hiding this comment

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

changelog entry looks 💯

More precisely, for two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`,
it performs the following transformation

.. math::
Copy link
Member

Choose a reason for hiding this comment

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

Love it!

pennylane/ops/qubit/qchem_ops.py Outdated Show resolved Hide resolved
Co-authored-by: Josh Izaac <josh146@gmail.com>
Copy link
Contributor

@ixfoduap ixfoduap left a comment

Choose a reason for hiding this comment

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

:shipit:

doc/releases/changelog-dev.md Outdated Show resolved Hide resolved
@ixfoduap ixfoduap merged commit 16aac9f into PennyLaneAI:master Sep 22, 2021
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

4 participants