Trying to build a Quantum Convolution but 'DefaultQubit' object has no attribute 'reset'

Hi! I want to make a quantum convolution layer for a hybrid CNN model trainable.

I was trying make a single Keras layer out of it so I could just add it to my CNN model. However, since the quantum convolution requires applying one circuit many times with different parameters depending on the image patch I am looking at, I need to run multiple circuits inside the QNode. I tried to build a loop where I do it for every image patch at once. However I get the error:

ValueError: ops operation Rot must occur prior to measurements. Please place earlier in the queue.

after the first iteration. I tried to reset the full circuit with dev.reset() but apparently the DefaultQubit device does not have a reset method. Why is this?

Is there a way of making this work or do you suggest doing things differently?
Should I make a Keras layer out of the circuit itself and then call it many times with Keras Functional API or something similar?

Code idea:

dev = qml.device("default.qubit", wires=n_qubits)
out = np.empty(shape, dtype=qml.measurements.ExpectationMP)

@qml.qnode(dev)
def quantum_convolution(inputs, params):
    for ...:
        ...
        # Assign measurement expectation values to different channels of the output pixel (j/stride, k/stride)
        for c in range(n_qubits):
            out[j // stride, k // stride, c] = qml.expval(qml.PauliZ(c))

        dev.reset() # Try to fix ValueError: ops operation Rot must occur prior to measurements. Please place earlier in the queue.
    return out

qlayer = qml.qnn.KerasLayer(quantum_convolution, params_shape, output_dim=((image_height-2) // stride + 1, (image_width-2) // stride + 1, n_qubits))

Full error message:

AttributeError                            Traceback (most recent call last)
Cell In[20], line 50
     47 params_shape = {"params": n_qubits*3*2}
     48 qlayer = qml.qnn.KerasLayer(quantum_convolution, params_shape, output_dim=((image_height-2) // stride + 1, (image_width-2) // stride + 1, n_qubits))
---> 50 quantum_convolution(train_images[0], rand_params)

File c:\Users\bravo\AppData\Local\anaconda3\envs\qml\lib\site-packages\pennylane\qnode.py:970, in QNode.__call__(self, *args, **kwargs)
    967         kwargs["shots"] = _get_device_shots(self._original_device)
    969 # construct the tape
--> 970 self.construct(args, kwargs)
    972 cache = self.execute_kwargs.get("cache", False)
    973 using_custom_cache = (
    974     hasattr(cache, "__getitem__")
    975     and hasattr(cache, "__setitem__")
    976     and hasattr(cache, "__delitem__")
    977 )

File c:\Users\bravo\AppData\Local\anaconda3\envs\qml\lib\site-packages\pennylane\qnode.py:856, in QNode.construct(self, args, kwargs)
    853     self.interface = qml.math.get_interface(*args, *list(kwargs.values()))
    855 with qml.queuing.AnnotatedQueue() as q:
--> 856     self._qfunc_output = self.func(*args, **kwargs)
    858 self._tape = QuantumScript.from_queue(q, shots)
    860 params = self.tape.get_parameters(trainable_only=False)

Cell In[20], line 43
     40     for c in range(n_qubits):
     41         out[j // stride, k // stride, c] = qml.expval(qml.PauliZ(c))
---> 43     dev.reset() # Try to fix ValueError: ops operation Rot must occur prior to measurements. Please place earlier in the queue.
     44 return out

AttributeError: 'DefaultQubit' object has no attribute 'reset'

Output of qml.about():

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: c:\users\bravo\appdata\local\anaconda3\envs\qml\lib\site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Cirq, PennyLane-Lightning, PennyLane-qiskit

Platform info:           Windows-10-10.0.19045-SP0
Python version:          3.10.11
Numpy version:           1.24.3
Scipy version:           1.11.2
Installed devices:
- 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)
- cirq.mixedsimulator (PennyLane-Cirq-0.33.0)
- cirq.pasqal (PennyLane-Cirq-0.33.0)
- cirq.qsim (PennyLane-Cirq-0.33.0)
- cirq.qsimh (PennyLane-Cirq-0.33.0)
- cirq.simulator (PennyLane-Cirq-0.33.0)
- lightning.qubit (PennyLane-Lightning-0.33.1)
- qiskit.aer (PennyLane-qiskit-0.33.0)
- qiskit.basicaer (PennyLane-qiskit-0.33.0)
- qiskit.ibmq (PennyLane-qiskit-0.33.0)
- qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.33.0)
- qiskit.ibmq.sampler (PennyLane-qiskit-0.33.0)
- qiskit.remote (PennyLane-qiskit-0.33.0)

Hey @joaofbravo, welcome back!

A few releases ago, we totally revamped our device API. So, some features were added and others were removed. You can access the old device API by using default.qubit.legacy :slight_smile:. Let me know if that solves things!

Hi. With the legacy device, it accepts the reset method but still gives the initial error

ValueError: ops operation Rot must occur prior to measurements. Please place earlier in the queue.

Hey @joaofbravo, can you attach your full code just so that I can try to replicate the error? In the mean time, let me ask one of the PennyLane developers to see if they know something I don’t :smiley:

I cannot share my data but here is a version without explicit data. I generated random data for you to see the error. This comes from a Jupyter notebook and you can run it cell by cell in VS Code.

qccnn_trainable_dummy.py (12.4 KB)

The cell starting with

test full quantum convolution in one layer

is the trial with the dev.reset() method. The errors I experience are written as comments at the end of the cell.

I also tried to use Keras Functional API in the cell starting with

test Keras Functional API

Here, I create the Keras layer named QuantumConv from one quantum circuit and then apply the layer many times to each image patch. However, I get another error when using the QuantumConv layer: Input to reshape is a tensor with 4 values, but the requested shape has 64 [Op:Reshape].

This makes it seem like the implementation of qml.qnn.KerasLayer has some issue or maybe it is just my ignorance. I set the custom layer to receive 64 samples of 4 values but it is only receiving 1 sample of 4 values and assumes those values as the first dimension.

I wrote about it on StackOverflow here.

Hi @joaofbravo! I just want to take a step back for a second and understand what you would like to do. Do you want to:

  • Have an input circuit
  • Map the input circuit to a batch of new circuits, e.g., with slightly different parameters
  • Execute the batch of circuits
  • Do some calculation on the execution results

If so, I’d recommend creating your own transform. When applying a transform to your target QNode, PennyLane will be able to take care of the batch creation and execution.

If this sounds along the right lines, we’d be happy to help you with creating the transform.

1 Like

I want to create a Keras model with one trainable Quantum Convolution layer and one classical Dense layer.

I tried two things:

  • building the Quantum Convolution layer all with Pennylane and then using qml.qnn.KerasLayer to get a usable Keras layer.

  • Using qml.qnn.KerasLayer to turn the Quantum convolution step of applying the quantum circuit to one image patch into one layer and then using this layer many times for all image patches.

Both are not working.

Your description sounds along the lines of what I want this Quantum Convolution to behave like. For now, the circuit structure is fixed but I would like to change it on the fly at some point. If “Map the input circuit to a batch of new circuits” includes the typical ML training process of inputting values to some parameters and then training the trainable parameters according to some loss function, then yes.
If you could help me do it I would appreciate it a lot.

@joaofbravo ok I think that transforms might work here, though I still need to fully understand the objective. Are you able to share a small-scale example of the circuits you want to run? E.g., as a small script or even just the qml.draw() drawing of the circuits.

1 Like

Thanks Tom. Yeah, the py file I shared has that code already.
I am trying to do the same yet again, now with Keras model subclassing. Let’s see if I can make it work but I doubt it. The circuit is this:

# Create a quantum device
self.dev = qml.device(device, wires=n_qubits)

# Define the trainable quantum circuit
# @qml.qnode(self.dev, interface='tensorfloww')
@qml.qnode(self.dev)
def circuit(inputs, params):
    # Apply trainable encoding operations on each qubit
    for i in range(self.n_qubits):
        qml.Rot(params[i] * inputs[i] + params[i + self.n_qubits],
                params[i + 2 * self.n_qubits] * inputs[i] + params[i + 3 * self.n_qubits],
                params[i + 4 * self.n_qubits] * inputs[i] + params[i + 5 * self.n_qubits],
                wires=i)

    # Apply circular entanglement
    for i in range(self.n_qubits):
        qml.CNOT(wires=[i, (i + 1) % self.n_qubits])

    # Measure all qubits
    # return [qml.expval(qml.PauliZ(i)) for i in range(self.n_qubits)]
    return qml.expval(qml.PauliZ(0))
  
# Convert quantum circuit to a Keras layer
# params_shape = {"params": n_qubits*3*2}
# self.circuit = qml.qnn.KerasLayer(circuit, params_shape, output_dim=n_qubits, trainable=True, name='QuantumConv')
self.circuit = circuit

Here is the cute version:

I would also like to try global measurements but I commented the line to try to make the simpler case work first

Hi @joaofbravo,
Thank you for your question!
We have forwarded this question to members of our technical team who will be getting back to you within a week. Feel free to post any updates to your question here in the thread in the meantime!

1 Like

Hi @joaofbravo! Sorry for the late response.

I did mention transforms earlier, but if your objective is to support batching over the inputs to the circuit, this should work with the following:

import pennylane as qml
import tensorflow as tf
import numpy as np

n_qubits = 5
batch_size = 10

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

@qml.qnode(dev)
def circuit(inputs, params):
    for i in range(n_qubits):
        qml.Rot(params[i] * inputs[:, i] + params[i + n_qubits],
                params[i + 2 * n_qubits] * inputs[:, i] + params[i + 3 * n_qubits],
                params[i + 4 * n_qubits] * inputs[:, i] + params[i + 5 * n_qubits],
                wires=i)

    for i in range(n_qubits):
        qml.CNOT(wires=[i, (i + 1) % n_qubits])

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

np.random.seed(1967)
inputs = tf.Variable(np.random.random((batch_size, n_qubits)), dtype=tf.float32)
weight_shapes = {"params": 6 * n_qubits}

qlayer = qml.qnn.KerasLayer(circuit, weight_shapes, output_dim=n_qubits)

Now, you can run qlayer with a batched inputs:

>>> with qml.Tracker(dev) as tracker:
...    qlayer(inputs)
<tf.Tensor: shape=(10, 5), dtype=float64, numpy=
array([[0.93605248, 0.99408909, 0.99408903, 0.99371811, 0.93396978],
       [0.96466191, 0.98711064, 0.98701866, 0.98617346, 0.95601068],
       [0.92515829, 0.99426603, 0.99416447, 0.98064854, 0.92399067],
       [0.94792998, 0.99527761, 0.98947615, 0.98812718, 0.9478812 ],
       [0.9350724 , 0.98666198, 0.98418557, 0.97639893, 0.9241491 ],
       [0.952517  , 0.98494446, 0.98465518, 0.97125787, 0.94331156],
       [0.9555457 , 0.99163104, 0.98932147, 0.98863627, 0.95170552],
       [0.9693765 , 0.98815707, 0.98808828, 0.98784985, 0.96172342],
       [0.93760943, 0.98209362, 0.9752507 , 0.97349233, 0.9279201 ],
       [0.9497824 , 0.99702711, 0.99106769, 0.97656875, 0.94877646]])>
>>> tracker.totals
{'batches': 1, 'simulations': 1, 'executions': 10}

In the above, PennyLane has automatically handled the input batch dimension so that there is a single batch run, which would require batch_size executions on real hardware.

Does this help with what you’re working on?