Question about how gradients are calculated when using interfaces

Mushahid asked me to make this post since he couldn’t interact with the discussion forum on his computer, his question is as follows:

Hello, I am trying to understand how gradients are calculated for different gates when an interface is specified. When i use pytorch as an interface and when i don’t use an interface i get different results despite having a compute_matrix with qml.math, grad_method = “A”, parameter_frequencies = [(1,)] and an adjoint method. I read somewhere on pennylane documentation that i would need a grad_method and parameter_frequencies. Any reason why i might get different results when i specify pytorch as an interface from when i don’t specify anything as an interface?
Here is my code :

class GPI2(Operation):

    num_wires = 1
    num_params = 1
    ndim_params = (0,)

    grad_method = "A"
    parameter_frequencies = [(1,)]

    def __init__(self, phi, wires, do_queue=True, id=None):
        super().__init__(phi, wires=wires, do_queue=do_queue, id=id)

    @staticmethod
    def compute_matrix(phi):  
        
        exponent = (0 -1j) * phi
        a = (1 + 0j) / qml.math.sqrt(2)
        b = ((0 - 1j) * qml.math.exp(exponent)) / qml.math.sqrt(2)
        c = ((0 - 1j) * qml.math.exp(qml.math.conj(exponent))) / qml.math.sqrt(2)

        return qml.math.stack([
            qml.math.stack([stack_last([a, b]), stack_last([c, a])])
        ])

    def adjoint(self):
        return GPI2(self.data[0] + np.pi, self.wires)
dev = qml.device("default.qubit", wires=1)

@qml.qnode(dev)
def circuit(x): 
   GPI2(x[0], wires=0)
   return qml.expval(qml.PauliZ(0))
 
phi = torch.tensor([0.5], requires_grad=True)
res = circuit(phi)
print(res.backward())

dev1 = qml.device("default.qubit", wires=1)
@qml.qnode(dev1)
def circuit1(x): 
   GPI2(x[0], wires=0)
   return qml.expval(qml.PauliZ(0))

s=pnp.array([0.5], requires_grad=True)
print(qml.grad(circuit1, argnum=0)(s))
1 Like

Hey @Gabe! Welcome to the forum :rocket:

I think I know the answer to your problem but just want to make sure I can replicate thew behaviour you’re seeing to be sure there aren’t any bugs. Can you include everything I need to run the above example? I think (?) I’m just missing some of your imports (not sure where stack_last is coming from, for example).

Hi the imports are the following:
import functools

import numpy as np

import pennylane as qml

from pennylane.operation import Operation

stack_last = functools.partial(qml.math.stack, axis=-1)

Thanks! Was able to run your code :slight_smile:.

I’m not sure if this was intentional or not, but comparing/equating the results of .backward() and .grad() isn’t apples to apples. backward is populating the grad field of the variable you want to differentiate. You can verify this by outputting phi.grad after you call backward :slight_smile: :

phi = torch.tensor([0.5], requires_grad=True)
res = circuit(phi)
print(res.backward())
print(phi.grad)

'''
None
tensor([2.9802e-08])
'''

Example here: PyTorch interface — PennyLane 0.30.0 documentation

With that sorted, now we can compare what torch.Tensor.grad and qml.grad output. PyTorch is using backprop, whereas qml.grad will call upon the “best” differentiation method, where the hierarchy of “best” methods is:

  • backprop
  • parameter-shift
  • finite-diff

In your case, “best” is “parameter-shift”:

print(circuit1.diff_method)
print(circuit1.best_method_str(dev, circuit.interface))

'''
best
parameter-shift
'''

My best guess at any discrepancy in the numeric values of both gradients that you’re calculating is simply numerical precision :slight_smile:. Let me know if this helps!

1 Like

Hi, for making my class auto differentiable, do i just need the compute_matrix method? or do i need everything i have so far above in the class?

Also, when i calculate the gradients i get tensor([2.9802e-08]) and
[0.] for Torch and and without torch. Based on results, it seems like its simply numerical precision.

According to the qml.operation.Operation docs, this is what you need to define:

1 Like

Is there any specific way we have to import or use classes from another file when writing unit tests. When I import GPI2 when writing and running a unit test, I get the following error: TypeError: Gradient only defined for scalar-output functions. Output had shape: (1,). This does not occur when i have the class inside the unit test function. What could be causing this?

Hey @Mushahid_Khan!

The issue ended up being with your compute_matrix method — it was outputting a (1, 2, 2) shaped array and it needs to be (2, 2).

This works for me!

import pennylane as qml
import jax

import functools

from jax import numpy as np

stack_last = functools.partial(qml.math.stack, axis=-1)

class GPI2(qml.operation.Operation):

    num_wires = 1
    num_params = 1
    ndim_params = (0,)
    grad_method = "A"
    parameter_frequencies = [(1,)]

    def __init__(self, phi, wires, do_queue=True, id=None):
        super().__init__(phi, wires=wires, do_queue=do_queue, id=id)

    @staticmethod
    def compute_matrix(phi):  # pylint: disable=arguments-differ

        exponent = (0 -1j) * phi
        a = (1 + 0j) / qml.math.sqrt(2)
        b = ((0 - 1j) * qml.math.exp(exponent)) / qml.math.sqrt(2)
        c = ((0 - 1j) * qml.math.exp(qml.math.conj(exponent))) / qml.math.sqrt(2)

        return qml.math.stack(
            qml.math.stack([stack_last([a, b]), stack_last([c, a])])
        )

    def adjoint(self):
        return GPI2(self.data[0] + np.pi, self.wires)

dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev, interface="jax")
def circuit(x):
    GPI2(x, wires=0)
    #qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0))
 
phi = np.array(phi)
print(qnode(phi))
grad_fcn = jax.grad(qnode)
phi_grad = grad_fcn(phi)
print(phi_grad)

'''
-8.940697e-08
Array(2.1940759e-08, dtype=float32, weak_type=True)
'''

At first glance, it honestly shouldn’t :thinking:. It should make no difference. Can you make a unittest to make sure that compute_matrix is indeed 2 \times 2?

Yes i had tried that and it had failed. It was returning (1,2,2)

It seems like the test file isnt picking up changes in the class at all.

Hmm… I’m afraid I don’t understand what’s going on then :thinking: