-
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
Fix bug where a QNode with a single probability output was not correctly differentiated #1007
Conversation
…correctly differentiated
Codecov Report
@@ Coverage Diff @@
## master #1007 +/- ##
=======================================
Coverage 97.89% 97.89%
=======================================
Files 151 151
Lines 11059 11059
=======================================
Hits 10826 10826
Misses 233 233
Continue to review full report at Codecov.
|
@@ -169,7 +169,7 @@ def processing_fn(results): | |||
array[float]: 1-dimensional array of length determined by the tape output | |||
measurement statistics | |||
""" | |||
return np.dot(coeffs, results) | |||
return np.dot(coeffs, np.squeeze(results)) |
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.
How does this change affect the hotfix in the tape QNode? Would that still be required?
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.
Yep, that will still be required. Note that the squeeze
here is functionally independent of the one in the QNode.
-
The one in
QNode.__call__
is designed so that the QNode output matches the usersreturn
function. E.g., if the user specifiesreturn qml.expval(qml.PauliZ(0))
, squeezing occurs so that a float is returned. If instead the user specifiesreturn [qml.expval(qml.PauliZ(0))]
orreturn qml.expval(qml.PauliZ(0)), qml.var(qml.PauliX(0))
, then no squeezing occurs and a list and/or tuple is returned. -
The squeezing here is low level, and not connected to the QNode return statement. Here, we are simply ensuring that
np.dot()
behaves as we need it to.
x = np.array(0.543, requires_grad=True) | ||
y = np.array(-0.654, requires_grad=True) | ||
|
||
@qnode(dev, diff_method=diff_method, interface="autograd") |
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.
It seemed that squeezing is slightly different for each interface (autograd, jax and Torch/TF) as per the previously linked hotfix, could it be worth parametrizing based on the interface?
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 reason why the QNode must use the ML framework for squeezing, and here we don't, is simply due to where we are with respect to the interface.
-
In
QNode.__call__
, we are outside the interface. All array/tensor manipulation must be done via the correct ML framework to ensure differentiability is not broken. -
In
tape.parameter_shift
, we are inside the interface. Inside the interface, no ML frameworks are used; everything has been unwrapped into a NumPy array. Hence why we can usenp.dot
, and don't have to worry abouttorch.matmul
,tf.tensordot
, etc 🙂
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.
Tangent: now that we have a maths module, we should add qml.math.squeeze
, so that we can remove the if
statements in QNode.__call__
to support squeezing in autograd/torch/tf.
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! 💯 Thanks for the details.
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 for fixing this quickly @josh146!
Note: @antalszava has pointed out that we could fix this 'closer to the source', by instead squeezing the linked line in However, this would require framework-independent squeezing, which necessitates creating |
Context:
QNodes with a single probability output, of the form
would result in the following error when differentiated in tape-mode using the parameter-shift rule:
This is due to the fact that we are using
np.dot(coeffs, results)
as a shorthand for[c*r for c, r in zip(coeffs, results)]
. However, in the case of returning a single probability array, NumPy attempts to broadcast the dot product instead.Note that this bug does not affect the case
return qml.probs(wires=0), qml.probs(wires=1)
(e.g., when multiple probability arrays are returned), asnp.dot
behaviour coincides again with[c*r for c, r in zip(coeffs, results)]
.Description of the Change:
Changes the line to
np.dot(coeffs, np.squeeze(results))
, to avoid broadcasting issues.Benefits:
The use case specified above now works.
Possible Drawbacks: n/a
Related GitHub Issues: n/a