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

Update default qubit so that _apply_operation is more functional #1025

Merged
merged 5 commits into from
Jan 22, 2021

Conversation

trbromley
Copy link
Contributor

@trbromley trbromley commented Jan 22, 2021

This is an update to the internals of default.qubit so that the _apply_operation method accepts an input state and returns an output state. This allows for external functions to interact with the device and request an evolved state - something we need in #1017.

It appears that we do not need to update the tests, since we have not written fully comprehensive unit tests for each method in default.qubit. The current tests use the apply() method, which is one step above _apply_operation, so at least the new changes are still being tested implicitly.

Comment on lines +174 to +177
if isinstance(operation, QubitStateVector):
self._apply_state_vector(operation.parameters[0], operation.wires)
elif isinstance(operation, BasisState):
self._apply_basis_state(operation.parameters[0], operation.wires)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These didn't really fit into the idea of applying an operation to a state, since these provide the initial state.

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense. One thing to consider would be to change them such that the state is returned rather than internally updated. This would be useful because then there would be no difference between applying a state preparation and applying an operation in terms of syntax.

self._state = self._apply_state_vector(operation.parameters[0], operation.wires)

Copy link
Contributor

Choose a reason for hiding this comment

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

We could name the functions self._create_basis_state(state, wires) and self._create_state_vector and have them both return a state variable.

They aren't really "applying" something since it overrides what existed before, if anything existed before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I agree with both suggestions, but suggest we defer them until after release as part of another look at default.qubit.

@codecov
Copy link

codecov bot commented Jan 22, 2021

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.92%. Comparing base (8792f0f) to head (480dee8).
Report is 2695 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1025      +/-   ##
==========================================
- Coverage   97.92%   97.92%   -0.01%     
==========================================
  Files         151      151              
  Lines       11231    11228       -3     
==========================================
- Hits        10998    10995       -3     
  Misses        233      233              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines -191 to -198
if isinstance(operation, QubitStateVector):
self._apply_state_vector(operation.parameters[0], wires)
return

if isinstance(operation, BasisState):
self._apply_basis_state(operation.parameters[0], wires)
return

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These have been moved to the parent apply() method.

@@ -527,19 +525,23 @@ def _apply_basis_state(self, state, wires):

self._state = self._create_basis_state(num)

def _apply_unitary(self, mat, wires):
def _apply_unitary(self, state, mat, wires):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally we could think about making these as static methods or simply functions, but we do still use self for things like self._tensordot, i.e., interface-dependent calculations.

Copy link
Contributor

Choose a reason for hiding this comment

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

I second making them static functions 👍
Keeping them methods in the class keeps them organized and contained a bit better, but they should probably be made static methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a nice idea, which would make things even cleaner. 👍

Copy link
Member

Choose a reason for hiding this comment

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

Note: eventually self._tensordot and all the other ML static methods can be replaced by qml.math.tensordot etc.

But until we do that, we'll likely need to keep them as instance methods 🙁

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh awesome, am loving the idea to use qml.math!

@trbromley trbromley marked this pull request as ready for review January 22, 2021 13:54
@trbromley trbromley self-assigned this Jan 22, 2021
@trbromley trbromley added the review-ready 👌 PRs which are ready for review by someone from the core team. label Jan 22, 2021
@antalszava antalszava self-requested a review January 22, 2021 14:21
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! 💯

Comment on lines +174 to +177
if isinstance(operation, QubitStateVector):
self._apply_state_vector(operation.parameters[0], operation.wires)
elif isinstance(operation, BasisState):
self._apply_basis_state(operation.parameters[0], operation.wires)
Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense. One thing to consider would be to change them such that the state is returned rather than internally updated. This would be useful because then there would be no difference between applying a state preparation and applying an operation in terms of syntax.

self._state = self._apply_state_vector(operation.parameters[0], operation.wires)

Copy link
Contributor

@chaserileyroberts chaserileyroberts left a comment

Choose a reason for hiding this comment

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

I like it!

@josh146
Copy link
Member

josh146 commented Jan 22, 2021

I'm pleasantly surprised this required no changes to default.qubit.tf or default.qubit.jax!

Copy link
Contributor

@albi3ro albi3ro left a comment

Choose a reason for hiding this comment

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

With this change, users will also have an easier time playing around in the simulator itself.

"Let's just apply this operation and see how it changes things"
"What's the state look like now, halfway through this circuit?"

I think this will show benefits elsewhere, in addition to just the rewind tape situation.


def _apply_operation(self, operation):
"""Applies operations to the internal device state.
def _apply_operation(self, state, operation):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm mostly going on feeling here, but what about:

_apply_operation(operation, state)

My brain reads that more as "Apply operation onto state" instead of "update state with operation". It just seems a bit easier to process and remember.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice that could work! In the interests of time I'd rather stick with the current, but we could consider swapping around post-release.

@@ -527,19 +525,23 @@ def _apply_basis_state(self, state, wires):

self._state = self._create_basis_state(num)

def _apply_unitary(self, mat, wires):
def _apply_unitary(self, state, mat, wires):
Copy link
Contributor

Choose a reason for hiding this comment

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

I second making them static functions 👍
Keeping them methods in the class keeps them organized and contained a bit better, but they should probably be made static methods.

Copy link
Contributor

@thisac thisac 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! I like the functional route we're on! 🌌

@@ -527,19 +525,23 @@ def _apply_basis_state(self, state, wires):

self._state = self._create_basis_state(num)

def _apply_unitary(self, mat, wires):
def _apply_unitary(self, state, mat, wires):
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a nice idea, which would make things even cleaner. 👍

Copy link
Contributor Author

@trbromley trbromley left a comment

Choose a reason for hiding this comment

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

Thanks for the comments!

Comment on lines +174 to +177
if isinstance(operation, QubitStateVector):
self._apply_state_vector(operation.parameters[0], operation.wires)
elif isinstance(operation, BasisState):
self._apply_basis_state(operation.parameters[0], operation.wires)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I agree with both suggestions, but suggest we defer them until after release as part of another look at default.qubit.

@@ -527,19 +525,23 @@ def _apply_basis_state(self, state, wires):

self._state = self._create_basis_state(num)

def _apply_unitary(self, mat, wires):
def _apply_unitary(self, state, mat, wires):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh awesome, am loving the idea to use qml.math!


def _apply_operation(self, operation):
"""Applies operations to the internal device state.
def _apply_operation(self, state, 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.

Nice that could work! In the interests of time I'd rather stick with the current, but we could consider swapping around post-release.

@josh146 josh146 merged commit a7132c8 into master Jan 22, 2021
@josh146 josh146 deleted the update_apply_operation branch January 22, 2021 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
review-ready 👌 PRs which are ready for review by someone from the core team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants