'QNode' object has no attribute 'draw' in Trainable Quantum Convolution

I try to run Demo Trainable Quantum Convolution.ipynb offered from here in my google colab evironment.

There are two bugs in this workflow

①I get the following error when executing the third block


AttributeError Traceback (most recent call last)
in
2
3 qonv = QonvLayer(circuit_layers=1, n_rotations=8, out_channels=4, stride=2)
----> 4 qonv.draw()
5 x = torch.rand(size=(1,28,28,1))
6

in draw(self)
35 # build circuit by sending dummy data through it
36 _ = self.circuit(inputs=torch.from_numpy(np.zeros(4)))
—> 37 print(self.circuit.qnode.draw())
38 self.circuit.zero_grad()
39

AttributeError: ‘QNode’ object has no attribute ‘draw’


②I get the following error when executing the 8th block


AttributeError Traceback (most recent call last)
in
16
17 # start training
—> 18 model, losses, accs = train(model, train_loader, epochs=1)
19
20

1 frames
in train(model, train_loader, epochs)
45
46 if i % 5 == 0:
—> 47 model[0].draw()
48
49 print("---------------------------------------\n")

in draw(self)
35 # build circuit by sending dummy data through it
36 _ = self.circuit(inputs=torch.from_numpy(np.zeros(4)))
—> 37 print(self.circuit.qnode.draw())
38 self.circuit.zero_grad()
39

AttributeError: ‘QNode’ object has no attribute ‘draw’

Both errors show ‘QNode’ object has no attribute 'draw
.

I would like to know how to solve this problem.

See this sample code for details.
https://github.com/PlanQK/TrainableQuantumConvolution/blob/main/Demo%20Trainable%20Quantum%20Convolution.ipynb

Hi @TM_MEME!

It looks like this code is very old. I would recommend that you try using qml.draw_mpl(). In our documentation you can see examples of how to use it.

The simplest way to use it would be to write:
qml.draw_mpl(circuit)(parameters)

Where ‘circuit’ is the name of your circuit and ‘parameters’ are the parameters it takes. In your case it seems that ‘circuit’ is the name of the circuit and the parameters are ‘inputs’ and ‘weights’.

Please let me know if this is helpful!

1 Like

Hi Catalina!
Thank you so much for your thoughtful response.
As you pointed out, there was a problem with the draw() function.
The code ran without error!

Thanks a lot for the draw_mpl introduction!

I’m glad this helped @TM_MEME :smiley:. Enjoy using PennyLane!

Hi @CatalinaAlbornoz. I am trying to get the draw_mpl to work in the same notebook but I am not able to. What is wrong in my code?

def draw(self):
    # build circuit by sending dummy data through it
    # _ = self.circuit(inputs=torch.from_numpy(np.zeros(4))) # old code
    # print(self.circuit.qnode.draw())                       # old code
    qml.draw_mpl(self.circuit)(torch.from_numpy(np.zeros(4)))
    self.circuit.zero_grad()

I get a blank image:

Proabably missing %matplotlib inline

I am not missing that. Otherwise, not even the blank plot would have appeared. I am using VS Code
Simple plots also work fine.

Hey @joaofbravo!

It would help me if you could attach a minimal example that reproduces what you’re seeing :slight_smile:. However, it sounds like QonvLayer is a PennyLane-PyTorch hybrid model. Make sure you’re using v0.31 of PennyLane (came out last week), where we just added drawing capabilities for TorchLayer!

1 Like

Try outside VS code, from the command line, does that work?

Does not work when I put the code in a script and run it in the terminal either:
image

Here is the script

import pennylane as qml
from pennylane import numpy as np
from pennylane.templates import RandomLayers

import torch
from torch import nn
import torchvision

from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(context='notebook', style='darkgrid', palette='deep')

class QonvLayer(nn.Module):
    def __init__(self, stride=2, device="default.qubit", wires=4, circuit_layers=1, n_rotations=4, out_channels=4, seed=None):
        super(QonvLayer, self).__init__()
        
        # init device
        self.wires = wires
        self.dev = qml.device(device, wires=self.wires)
        
        self.stride = stride
        self.out_channels = min(out_channels, wires)
        
        if seed is None:
            seed = np.random.randint(low=0, high=10e6)
            
        print("Initializing Circuit with random seed", seed)
        
        # random circuits
        @qml.qnode(device=self.dev, interface="torch")
        def circuit(inputs, weights):
            n_inputs = 4
            # Encoding of 4 classical input values
            for j in range(n_inputs):
                qml.RY(inputs[j], wires=j)
            # Random quantum circuit
            RandomLayers(weights, wires=list(range(self.wires)), seed=seed)
            
            # Measurement producing 4 classical output values
            return [qml.expval(qml.PauliZ(j)) for j in range(self.out_channels)]
        
        weight_shapes = {"weights": [circuit_layers, n_rotations]}
        self.circuit = qml.qnn.TorchLayer(circuit, weight_shapes=weight_shapes)
    
    
    def draw(self):
        # build circuit by sending dummy data through it
        _ = self.circuit(inputs=torch.from_numpy(np.zeros(4)))
        # print(self.circuit.qnode.draw()) # old code
        qml.draw_mpl(self.circuit)(inputs=torch.from_numpy(np.zeros(4)))
        self.circuit.zero_grad()
        
    
    def forward(self, img):
        bs, h, w, ch = img.size()
        if ch > 1:
            img = img.mean(axis=-1).reshape(bs, h, w, 1)
                        
        kernel_size = 2        
        h_out = (h-kernel_size) // self.stride + 1
        w_out = (w-kernel_size) // self.stride + 1
        
        
        out = torch.zeros((bs, h_out, w_out, self.out_channels))
        
        # Loop over the coordinates of the top-left pixel of 2X2 squares
        for b in range(bs):
            for j in range(0, h_out, self.stride):
                for k in range(0, w_out, self.stride):
                    # Process a squared 2x2 region of the image with a quantum circuit
                    q_results = self.circuit(
                        inputs=torch.Tensor([
                            img[b, j, k, 0],
                            img[b, j, k + 1, 0],
                            img[b, j + 1, k, 0],
                            img[b, j + 1, k + 1, 0]
                        ])
                    )
                    # Assign expectation values to different channels of the output pixel (j/2, k/2)
                    for c in range(self.out_channels):
                        out[b, j // kernel_size, k // kernel_size, c] = q_results[c]
                        
                 
        return out

# Test QonvLayer
qonv = QonvLayer(circuit_layers=1, n_rotations=8, out_channels=4, stride=2)
qonv.draw()
plt.show()

Updating to v0.31, I get the error:

Traceback (most recent call last):
File “c:\Users\bravo\OneDrive\Documents\Code\qccnn\test.py”, line 89, in
qonv.draw()
File “c:\Users\bravo\OneDrive\Documents\Code\qccnn\test.py”, line 51, in draw
qml.draw_mpl(self.circuit)(inputs=torch.from_numpy(np.zeros(4)))
File “C:\Users\bravo\AppData\Local\anaconda3\envs\qml\lib\site-packages\pennylane\drawer\draw.py”, line 541, in wrapper
qnode.construct(args, kwargs_qnode)
File “C:\Users\bravo\AppData\Local\anaconda3\envs\qml\lib\site-packages\pennylane\qnn\torch.py”, line 450, in construct
x = args[0]
IndexError: tuple index out of range

Hey @joaofbravo,

Updating to v0.31, I get the error:

circuit is expecting to see an argument, not a single keyword argument. That’s the source of the error :slight_smile:. So, you just need to change your draw and forward function slightly:

    def draw(self):
        print(qml.draw(self.circuit)(torch.from_numpy(np.zeros(4))))
        self.circuit.zero_grad()

    def forward(self, img):
        bs, h, w, ch = img.size()
        if ch > 1:
            img = img.mean(axis=-1).reshape(bs, h, w, 1)

        kernel_size = 2
        h_out = (h - kernel_size) // self.stride + 1
        w_out = (w - kernel_size) // self.stride + 1

        out = torch.zeros((bs, h_out, w_out, self.out_channels))

        # Loop over the coordinates of the top-left pixel of 2X2 squares
        for b in range(bs):
            for j in range(0, h_out, self.stride):
                for k in range(0, w_out, self.stride):
                    # Process a squared 2x2 region of the image with a quantum circuit
                    q_results = self.circuit(
                        torch.Tensor(
                            [
                                img[b, j, k, 0],
                                img[b, j, k + 1, 0],
                                img[b, j + 1, k, 0],
                                img[b, j + 1, k + 1, 0],
                            ]
                        )
                    )
                    # Assign expectation values to different channels of the output pixel (j/2, k/2)
                    for c in range(self.out_channels):
                        out[b, j // kernel_size, k // kernel_size, c] = q_results[c]

        return out

I also put the qml.draw function call inside of print, since that’s what needs to happen for it to be shown. This should work now!

qonv = QonvLayer(circuit_layers=1, n_rotations=8, out_channels=4, stride=2)
qonv.draw()
Initializing Circuit with random seed 6926762
0: ──RY(0.00)─╭RandomLayers(M0)─┤  <Z>
1: ──RY(0.00)─├RandomLayers(M0)─┤  <Z>
2: ──RY(0.00)─├RandomLayers(M0)─┤  <Z>
3: ──RY(0.00)─╰RandomLayers(M0)─┤  <Z>
M0 = 
tensor([[4.7877, 2.3488, 4.5178, 5.3039, 1.6268, 1.3644, 3.8853, 2.3275]],
       dtype=torch.float64)
1 Like

Thank you @isaacdevlugt! Is there a way to use the draw_mpl method to get a prettier print?

Yep! Just substitute qml.draw with qml.draw_mpl :slight_smile:

1 Like