Combined measurements: qml.expval list comprehension returns list instead of tensor

Hello,

I have been using pennylane version 0.29.1 for a while, and I started having a problem when updating to version 0.35.1

I have a simple quantum circuit that returns

return [qml.expval(qml.PauliZ(i)) for i in range(self.n_qubits)]

While using the older version, this was very straightforward and it returned a torch.tensor with size equals to n_qubits.

tensor([0.2375, 0.5132], dtype=torch.float64, grad_fn=<ExecuteTapesBackward>)

however, in the new version, running the same code, I get a list of tensors

[tensor(0.6101, dtype=torch.float64, grad_fn=<ExecuteTapesBackward>), tensor(0.4156, dtype=torch.float64, grad_fn=<ExecuteTapesBackward>)]

which breaks the computational graph when running backpropagation. I was able to work around this by running the circuit once for every qubit and allocating the (scalar) expval in a new tensor, but this makes training extremely low.

Is there an easy way to obtain the same behavior as in version 0.29?

Thanks in advance for the help!

How about use

return qml.math.stack([qml.expval(...) for i in range(n_qubits)])

Hi @vini, welcome to the Forum!

It’s great to see that you’re upgrading your PennyLane version. Does @LdBeth’s solution work for you?

Our latest PennyLane version is version 0.36 and next week we’ll be releasing version 0.37 so please let us know if you have any other issues while upgrading.

Thank you for the responses. Unfortunately I couldn’t make it work with the new version.

With the old version of pennylane (0.29) it works, exactly like doing

return [qml.expval(qml.PauliZ(i)) for i in range(self.n_qubits)]

but with the new one (0.36) I get this error:

Traceback (most recent call last):
  File "/Users/vini/Documents/phd/test_pennylane/test.py", line 72, in <module>
    test_circuit()
  File "/Users/vini/Documents/phd/test_pennylane/test.py", line 64, in test_circuit
    sample = generator(noise)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1501, in _call_impl
    return forward_call(*args, **kwargs)
  File "/Users/vini/Documents/phd/test_pennylane/test.py", line 48, in forward
    expsZ = self.quantum_circuit(batch, self.q_params)
  File "/Users/vini/Documents/phd/test_pennylane/test.py", line 42, in quantum_circuit
    return qnode(noise, weights)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/pennylane/workflow/qnode.py", line 1098, in __call__
    res = self._execution_component(args, kwargs, override_shots=override_shots)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/pennylane/workflow/qnode.py", line 1073, in _execution_component
    return _to_qfunc_output_type(
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/pennylane/workflow/qnode.py", line 101, in _to_qfunc_output_type
    return type(qfunc_output)(results)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/pennylane/numpy/tensor.py", line 111, in __new__
    obj = asarray(input_array, *args, **kwargs)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/autograd/tracer.py", line 48, in f_wrapped
    return f_raw(*args, **kwargs)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/pennylane/numpy/tensor.py", line 36, in asarray
    return _np.array(vals, *args, **kwargs)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/autograd/numpy/numpy_wrapper.py", line 58, in array
    return array_from_args(args, kwargs, *map(array, A))
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/autograd/numpy/numpy_wrapper.py", line 60, in array
    return _array_from_scalar_or_array(args, kwargs, A)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/autograd/tracer.py", line 48, in f_wrapped
    return f_raw(*args, **kwargs)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/autograd/numpy/numpy_wrapper.py", line 73, in _array_from_scalar_or_array
    return _np.array(scalar, *array_args, **array_kwargs)
  File "/Users/vini/miniforge3/envs/qml/lib/python3.9/site-packages/torch/_tensor.py", line 970, in __array__
    return self.numpy()
RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.

In both cases I have

numpy==1.22
torch==2.0
autograd==1.5

I made a minimal version of my code that reproduces the same error:

import pennylane as qml
import torch 
import torch.nn as nn 
import torch.optim as optim
import numpy as np 
import sys


class Generator(nn.Module):
    def __init__(self, qubits, layers):

        super().__init__()

        self.n_qubits = qubits
        self.number_param_layers = layers 
        self.q_params = nn.Parameter(torch.rand(2 * self.number_param_layers * self.n_qubits), requires_grad = True)
    

    def circ(self, noise, weights):

        weights = weights.reshape(self.number_param_layers, self.n_qubits, 2)
        # encoding 
        for j in range(self.n_qubits):
            qml.RX(noise[j], wires = j)
        # repeated layer
        for i in range(self.number_param_layers):
            # parameterised
            for j in range(self.n_qubits):
                qml.RY(weights[i][j][0], wires = j)
                qml.RZ(weights[i][j][1], wires = j)
            # control-z gates
            for j in range(self.n_qubits - 1):
                qml.CZ(wires = [j, j + 1])
        
        #return [qml.expval(qml.PauliZ(i)) for i in range(self.n_qubits)]
        return qml.math.stack([qml.expval(qml.PauliZ(i)) for i in range(self.n_qubits)])


    def quantum_circuit(self, noise, weights):
        dev = qml.device('lightning.qubit', wires = self.n_qubits, shots = None)
        qnode =  qml.QNode(self.circ, dev, interface = 'torch', diff_method = 'parameter-shift')
        return qnode(noise, weights)


    def forward(self, x):     
        sample = torch.zeros((x.shape[0], self.n_qubits)).to(torch.float32)
        for b, batch in enumerate(x):
            expsZ = self.quantum_circuit(batch, self.q_params)
            sample[b,:] = expsZ  
        return sample
    
def test_circuit():
    n_qubits = int(sys.argv[1])
    layers = 1
    generator = Generator(n_qubits, layers)
    opt = optim.SGD(generator.parameters(), 0.03)
    batch_size = 16
    trainset = torch.rand((1_000, n_qubits))
    dataloader = torch.utils.data.DataLoader(trainset, batch_size = batch_size, drop_last = True)

    for iteration, data in enumerate(dataloader):
        print(f'Generator parameters at step {iteration}: {generator.q_params}')
        noise = torch.rand((batch_size, n_qubits))
        sample = generator(noise)
        loss = (sample - data).sum()
        generator.zero_grad()
        loss.backward()
        opt.step()


if __name__ == '__main__':
    test_circuit()

Hi @vini ,

Your current issue is different.

First, you need to extract the numpy value from your weights before the encoding.

weights = weights.detach().numpy() # added

Then you need to change your output into a Torch tensor

expsZ = torch.tensor(self.quantum_circuit(batch, self.q_params)) # Converted into Torch tensor

You’ll see that the forward pass now should run with no errors. However your loss function is ill-defined so you’ll get another error. I think the Optimization section of the 3-qubit Ising model in PyTorch demo can help you figure out how to fix this. I’d say that you will likely need to create an actual cost function which takes the parameters that you want to train, and then use that cost function to calculate your loss.

I hope this helps!