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

[BUG] output shape and type of gradient_transform applied to QNodes depend on classical processing #4940

Closed
1 task done
dwierichs opened this issue Dec 12, 2023 · 0 comments · Fixed by #4945
Closed
1 task done
Labels
bug 🐛 Something isn't working

Comments

@dwierichs
Copy link
Contributor

dwierichs commented Dec 12, 2023

Expected behavior

When applying a gradient_transform to a QNode, the output shape and type should not differ whether or not there is classical processing.

Actual behavior

If there is no classical processing (and other conditions apply, such as "there is only one QNode argument"), the output shape and type differs from cases where there is classical processing:

import pennylane as qml
from jax import numpy as np
from pennylane import numpy as np

dev = qml.device("default.qubit")

@qml.qnode(dev)
def circuit(w, factor=1.):
    qml.Rot(*(factor * w), 0)
    return qml.expval(qml.PauliZ(0))

w = np.array([0.612, 0.42, 1.2])

print(qml.gradients.hadamard_grad(circuit)(w, factor=1.))
# (tensor(-0., requires_grad=True), tensor(-0.40776045, requires_grad=True), tensor(-0., requires_grad=True))
print(qml.gradients.hadamard_grad(circuit)(w, factor=2.))
# [ 0.         -1.48928624  0.        ]

Another example, this time causing a transpose, is

@qml.qnode(dev)
def circuit(w, factor=1.):
    qml.Rot(*(factor * w), 0)
    return qml.probs([0, 1])

w = np.array([0.612, 0.42, 1.2])

print(qml.gradients.hadamard_grad(circuit)(w, factor=1.))
# (tensor([0., 0., 0., 0.], requires_grad=True), tensor([-0.20388023,  0.        ,  0.20388023,  0.        ], requires_grad=True), tensor([0., 0., 0., 0.], requires_grad=True))
# -> Could be interpreted as shape (3, 4)
print(qml.gradients.hadamard_grad(circuit)(w, factor=2.))
# [[ 0.         -0.74464312  0.        ]
#  [ 0.          0.          0.        ]
#  [ 0.          0.74464312  0.        ]
#  [ 0.          0.          0.        ]]
# Has shape (4, 3)

Additional information

This is due to a special branch in _contract_qjac_with_cjac in gradients/gradient_transform.py, which is activated whenever the classical Jacobian is the identity.
While it is correct that no numerics have to be performed in this case, the contraction that would be performed without this branch (and yields the correct behaviour) also leads to transposing the output. As the contraction is skipped, the transpose is as well, leading to the observed behaviour.

Source code

No response

Tracebacks

No response

System information

pl dev; master

Existing GitHub issues

  • I have searched existing GitHub issues to make sure the issue does not already exist.
@dwierichs dwierichs added the bug 🐛 Something isn't working label Dec 12, 2023
dwierichs added a commit that referenced this issue Apr 25, 2024
… `gradient_transform` (#4945)

**Context:**
When applying a `gradient_transform` to a QNode, the QNode wrapper makes
use of the function `_contract_qjac_with_cjac` to contract the quantum
and classical Jacobians with each other.
Here, the classical Jacobian is the Jacobian of classical processing
that happens within the QNode between the QNode input arguments and the
tape-level operation arguments.

For the special case where the classical Jacobian is the identity, there
is a separate logic in `_contract_qjac_with_cjac` that skips the
contraction.
This is a problem because the contraction, even if it does not affect
the numerical values of the output, also implies reformatting of the
quantum Jacobian, by virtue of transposition, casting to tuples,
stacking.
All this is skipped when the classical Jacobian is the identity, which
happens for example for 1D QNode arguments that are not processed any
further but just passed to operations within the QNode. Unfortunately,
that's a very common scenario, making this PR a breaking change with
high visibility.

In addition, we never seem to test certain return type setups _with_
classical processing, so that the contraction is skipped and relevant
cases are not covered.

**Description of the Change:**
This PR removes the special logic for the case where the classical
Jacobian is the identity.
There is an alternative version that still skips the contraction but
performs all the reformatting steps. Given the amount of code
duplication, I do not think we want to go for that version.

In addition, this PR adds the contraction logic for previously uncovered
cases, which are now hit because we do not skip the contraction any
longer.

**Benefits:**
The output of `gradient_transform`s applied to QNodes is
self-consistent.
The output of `gradient_transform`s applied to QNodes without classical
processing is consistent with results from using interface entrypoints.

**Possible Drawbacks:**
Breaking change

**Related GitHub Issues:**
Fixes #4940 

[sc-51888]
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
1 participant