Qml.expval() returns a tensor object

Hello!

I am building a quantum classifier based on this paper which is an extension of this Pennylane demo. I find that in my implementation of the referenced paper, the qcuruit function i.e. the return qml.expval(...) statement returns a vector of size 3. For example, it returns [0.98952308 0.99227369 0.82933498]. In my understanding, qml.expval() should always return a scalar and the documentation does not describe any circumstances in which it returns a vector.

I have reused most of the code from the demo itself while making a few changes to the qcircuit function and input parameter vector params. To avoid clutter, I am only specifying the changes I have made below.

dev = qml.device("lightning.gpu", wires=2)

@qml.qnode(dev, interface="autograd")
def qcircuit(params, x, y):
    """A variational quantum circuit representing the Universal classifier with Compact encoding.

    Args:
        params (array[float]): array of parameters
        x (array[float]): single input vector
        y (array[float]): single output state density matrix

    Returns:
        float: fidelity between output state and input
    """
    for l in range(params.shape[0]): # Iterate num_layers times
        w_0 = params[l, 0, :-1]
        b_0 = params[l, 0,  -1]
        w_1 = params[l, 1, :-1]
        b_1 = params[l, 1,  -1]
        
        encoding_0 = w_0 * x + b_0
        
        encoding_1 = w_1 * x + b_1
        
        qml.Rot(encoding_0, encoding_0, encoding_0, wires=0)
        qml.Rot(encoding_1, encoding_1, encoding_1, wires=1)

        
        qml.CZ([0, 1]) 
    
    return qml.expval(qml.Hermitian(y, wires=[0]))

Cost is the same as that of the demo. Because of its importance in this issue, I am describing it here again anyway.

def cost(params, x, y, state_labels=None):
    """Cost function to be minimized.

    Args:
        params (array[float]): array of parameters
        x (array[float]): 2-d array of input vectors
        y (array[float]): 1-d array of targets
        state_labels (array[float]): array of state representations for labels

    Returns:
        float: loss value to be minimized
    """
    # Compute prediction for each input in data batch
    loss = 0.0
    dm_labels = [density_matrix(s) for s in state_labels]
    for i in range(len(x)):
        f = qcircuit(params, x[i], dm_labels[y[i]])
        loss = loss + (1 - f) ** 2
    return loss / len(x)

In the training script, the params vector is defined as

params = np.random.uniform(size=(num_layers, 4, 3+1), requires_grad=True)

qml.about() shows:

Name: PennyLane
Version: 0.33.1
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /home/vishwa/QC/604/quantum-image-classifier/.venv/lib/python3.10/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Lightning, PennyLane-Lightning-GPU

Platform info:           Linux-5.15.133.1-microsoft-standard-WSL2-x86_64-with-glibc2.35
Python version:          3.10.12
Numpy version:           1.26.1
Scipy version:           1.11.3
Installed devices:
- lightning.gpu (PennyLane-Lightning-GPU-0.33.1)
- default.gaussian (PennyLane-0.33.1)
- default.mixed (PennyLane-0.33.1)
- default.qubit (PennyLane-0.33.1)
- default.qubit.autograd (PennyLane-0.33.1)
- default.qubit.jax (PennyLane-0.33.1)
- default.qubit.legacy (PennyLane-0.33.1)
- default.qubit.tf (PennyLane-0.33.1)
- default.qubit.torch (PennyLane-0.33.1)
- default.qutrit (PennyLane-0.33.1)
- null.qubit (PennyLane-0.33.1)
- lightning.qubit (PennyLane-Lightning-0.33.1)

Hello @vishwa !

Welcome to the forum! About the outputs of the QNode you defined, I would like to mention that PennyLane supports execution at multiple parameters simultaneously for certain types of measurements. This is known as Parameter Broadcasting.

On this line:

params = np.random.uniform(size=(num_layers, 4, 3+1), requires_grad=True)

You are creating a tensor object here, which is passed as an argument for the QNode, right? For me it is not clear how you defined num_layers. Anyway, it seems that you created a parameter object which is a tensor object as well, and checking your code, it looks like the operations you used indeed support broadcasting.

Besides, it is possible actually to combine measurements! I don’t think this is the case with your code but feel free to check it as well.

You can find more details on the following pages:

I hope it helps! :slight_smile:

Hi @ludmilaaasb ,

Thank you so much for your response. After reading it, I quickly realized that indeed the Qnode was returning Combined Measurements. I altered my Qnode definition to fix the issue.

Thanks again for your input!!!

2 Likes