Probs(wires=[]) is not in list

Hello, I’m trying to implement a qGAN in pennylane with the pytorch interface, and I am running into a ValueError. I couldn’t isolate the error to figure out what is causing it. Here is the code below:

import pennylane as qml
import torch
from pennylane import numpy as np
from matplotlib import pyplot as plt
from torch.autograd import Variable
from scipy.stats import lognorm, norm, triang
import matplotlib.pyplot as plt
from torch.optim import Adam
from torch import nn
!pip install pylatexenc

num_qubits = 3 #TODO: make this like 6 or sm bs
num_layers = 3
num_discrete_values = 8

params = np.random.normal(0, np.pi, size = (num_layers * 2 + 1) * num_qubits)
params = Variable(torch.tensor(params), requires_grad = True)

# define generator
def rotation_layer(w):
    for i in range(num_qubits):
        qml.RY(w[i], wires=i)
        
def entangling_block(w):
    for i in range(num_qubits):
        qml.CZ(wires = [i, (i+1)%num_qubits])
        
dev = qml.device("default.qubit", wires=num_qubits, shots = 10000)

@qml.qnode(dev, interface='torch')
def generator(w, num_layers = 3):
    rotation_layer(w[:num_qubits])
    for i in range(1, num_layers*2 + 1, 2):
        entangling_block(w[num_qubits * (i) : num_qubits * (i+1)])
        rotation_layer(w[num_qubits * (i+1) : num_qubits * (i+2)])
        
    return qml.probs()

# define classical discriminator
class Discriminator(nn.Module):
    def __init__(self, input_size):
        super(Discriminator, self).__init__()

        self.linear_input = nn.Linear(input_size, 20)
        self.leaky_relu = nn.LeakyReLU(0.2)
        self.linear20 = nn.Linear(20, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, input: torch.Tensor) -> torch.Tensor:
        x = self.linear_input(input)
        x = self.leaky_relu(x)
        x = self.linear20(x)
        x = self.sigmoid(x)
        return x
    
# generate data
def gauss(x,mu,sigma,A):
    return A*exp(-(x-mu)**2/2/sigma**2)

def generate_data(which = "log-normal", num_discrete_values = 8):
    
    x = np.linspace(0,7, num_discrete_values)
    
    if which == "log-normal":
        rv = lognorm.pdf(x, 1)
    elif which == "triangular":
        """
        triangular distribution with:
            lower limit l = 0
            upper limit u = 7
            mode m = 2
        """
        a = 0
        b = 7
        mode = 2
        list_of_candidates = range(a, b+1)
        rv = triang.pdf(x, mode/(b-a), a, b)
        
        
    else: #bimodal
        """
        bimodal distribution with:
            two superimposed Guassian distributions with
            mu_1 = 0.5
            sigma_1 = 1
            
            mu_2 = 3.5
            sigma_2 = 0.5
        """
        gauss1 = norm.pdf(x, 0.5, 1)
        gauss2 = norm.pdf(x, 3.5, 0.5)
        rv = (gauss1 + gauss2) / (np.sum(gauss1) + np.sum(gauss2))
        
        
    # fig, ax = plt.subplots(1, 1)
    # ax.plot(x, rv,'r-', lw=5, alpha=0.6, label='lognorm pdf')
    
    return x, rv


# cost function
def gen_loss(x): # maximize the discriminator misclassifying fake data
    # print(f"gen loss input: {x}")
    loss = torch.log(x) 
    # loss = torch.log(1 - x)
    # print(f"loss: {loss}")
    return -loss

def disc_loss(real, fake): # log(real) = maxed when disc correctly classifies real data; log(1 - fake) = maxed when disc correctly classifies fake data
    # print(f"disc loss input: {real}, {fake}")
    loss = torch.log(real) + torch.log(1 - fake) 
    # print(f"loss: {loss}")
    return -loss # discriminator is updated via gradient ASCENT

discriminator = Discriminator(input_size = num_discrete_values)

# optimizers
lr = 0.01
b1 = 0.7 # first momentum
b2 = 0.999 # second momentum

generator_optimizer = Adam([params], lr=lr, betas=(b1, b2), weight_decay=0.005)
discriminator_optimizer = Adam(discriminator.parameters(), lr=lr, betas=(b1, b2), weight_decay=0.005)

# visualize training process
from IPython.display import clear_output

def plot_training_progress():
    # we don't plot if we don't have enough data
    if len(generator_loss_values) < 2:
        return

    clear_output(wait=True)
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 9))

    # Generator Loss
    ax1.set_title("Loss")
    ax1.plot(generator_loss_values, label="generator loss", color="royalblue")
    ax1.plot(discriminator_loss_values, label="discriminator loss", color="magenta")
    ax1.legend(loc="best")
    ax1.set_xlabel("Iteration")
    ax1.set_ylabel("Loss")
    ax1.grid()

    # Relative Entropy
    ax2.set_title("Relative entropy")
    ax2.plot(entropy_values)
    ax2.set_xlabel("Iteration")
    ax2.set_ylabel("Relative entropy")
    ax2.grid()

    plt.show()

# training loop
# logic inspired by https://towardsdatascience.com/build-a-super-simple-gan-in-pytorch-54ba349920e4
import time
from scipy.stats import multivariate_normal, entropy

n_epochs = 50

num_qnn_outputs = 2**num_qubits

grid_elements, prob_data = generate_data(num_discrete_values)

generator_loss_values = []
discriminator_loss_values = []
entropy_values = []

start = time.time()
for epoch in range(n_epochs):
    valid = torch.ones(num_qnn_outputs, 1, dtype=torch.float)
    fake = torch.zeros(num_qnn_outputs, 1, dtype=torch.float)
    
    # zero gradients
    generator_optimizer.zero_grad()
    
    
    # generate real data
    real_dist = torch.tensor(prob_data, dtype=torch.float)
    # print(real_dist)
    
    # generate fake data
    gen_dist = generator(params)
    gen_dist = gen_dist.type(torch.float)
    
    # print(gen_dist)
    
    # train generator using discriminator output & true labels
    # We invert the labels here and don't train the discriminator because we want the generator
    # to make things the discriminator classifies as true.
    discriminator_optimizer.zero_grad()
    
    disc_out_fake = discriminator(gen_dist) 
    # print(disc_out_fake)
    generator_loss = gen_loss(disc_out_fake) 
    # print(generator_loss)
    generator_loss_values.append(generator_loss.detach())

    generator_loss.backward(retain_graph=True) # NO GRAD???
    generator_optimizer.step()
    
    # train discrminator on both a true and a generated data and average
    disc_out_real = discriminator(real_dist)
    discriminator_loss = disc_loss(disc_out_real, disc_out_fake)      
    # print(discriminator_loss)
    discriminator_loss_values.append(discriminator_loss.detach().item())

    discriminator_loss.backward()
    discriminator_optimizer.step()

    entropy_value = entropy(gen_dist.detach().squeeze().numpy(), prob_data) # RELATIVE ENTROPY
    entropy_values.append(entropy_value)

    plot_training_progress()

elapsed = time.time() - start
print(f"Fit in {elapsed:2f} sec")

# TODO: initialization strategy

And here is the full error message:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_51/102425105.py in <cell line: 172>()
    199     generator_loss_values.append(generator_loss.detach())
    200 
--> 201     generator_loss.backward(retain_graph=True) # NO GRAD???
    202     generator_optimizer.step()
    203 

/opt/conda/envs/pennylane/lib/python3.9/site-packages/torch/_tensor.py in backward(self, gradient, retain_graph, create_graph, inputs)
    394                 create_graph=create_graph,
    395                 inputs=inputs)
--> 396         torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs=inputs)
    397 
    398     def register_hook(self, hook):

/opt/conda/envs/pennylane/lib/python3.9/site-packages/torch/autograd/__init__.py in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)
    171     # some Python versions print out the first line of a multi-line function
    172     # calls in the traceback and some print out the last line
--> 173     Variable._execution_engine.run_backward(  # Calls into the C++ engine to run the backward pass
    174         tensors, grad_tensors_, retain_graph, create_graph, inputs,
    175         allow_unreachable=True, accumulate_grad=True)  # Calls into the C++ engine to run the backward pass

/opt/conda/envs/pennylane/lib/python3.9/site-packages/torch/autograd/function.py in apply(self, *args)
    251                                "of them.")
    252         user_fn = vjp_fn if vjp_fn is not Function.vjp else backward_fn
--> 253         return user_fn(self, *args)
    254 
    255     def apply_jvp(self, *args):

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/interfaces/torch.py in backward(ctx, *dy)
    167 
    168                     with qml.tape.Unwrap(*ctx.tapes):
--> 169                         vjp_tapes, processing_fn = qml.gradients.batch_vjp(
    170                             ctx.tapes,
    171                             dy,

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/vjp.py in batch_vjp(tapes, dys, gradient_fn, shots, reduction, gradient_kwargs)
    523     # Loop through the tapes and dys vector
    524     for tape, dy in zip(tapes, dys):
--> 525         g_tapes, fn = vjp(tape, dy, gradient_fn, shots=shots, gradient_kwargs=gradient_kwargs)
    526         reshape_info.append(len(g_tapes))
    527         processing_fns.append(fn)

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/vjp.py in vjp(tape, dy, gradient_fn, shots, gradient_kwargs)
    379         pass
    380 
--> 381     gradient_tapes, fn = gradient_fn(tape, shots=shots, **gradient_kwargs)
    382 
    383     def processing_fn(results, num=None):

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/transforms/batch_transform.py in __call__(self, *targs, **tkwargs)
    329             # Input is a quantum tape.
    330             # tapes, fn = some_transform(tape, *transform_args)
--> 331             return self._tape_wrapper(*targs, **tkwargs)(qnode)
    332 
    333         if isinstance(qnode, (qml.QNode, qml.ExpvalCost)):

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/transforms/batch_transform.py in <lambda>(tape)
    419 
    420     def _tape_wrapper(self, *targs, **tkwargs):
--> 421         return lambda tape: self.construct(tape, *targs, **tkwargs)
    422 
    423 

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/transforms/batch_transform.py in construct(self, tape, *args, **kwargs)
    401             tape = self.expand_fn(tape, *args, **kwargs)
    402 
--> 403         tapes, processing_fn = self.transform_fn(tape, *args, **kwargs)
    404 
    405         if processing_fn is None:

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/parameter_shift.py in param_shift(tape, argnum, shifts, gradient_recipes, fallback_fn, f0, broadcast, shots)
   1902         return [], lambda _: np.zeros((tape.output_dim, 0))
   1903 
-> 1904     gradient_analysis(tape, grad_fn=param_shift)
   1905     method = "analytic" if fallback_fn is None else "best"
   1906     diff_methods = grad_method_validation(method, tape)

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/gradient_transform.py in gradient_analysis(tape, use_graph, grad_fn)
     67 
     68             elif (tape._graph is not None) or use_graph:
---> 69                 if not any(tape.graph.has_path(op, ob) for ob in tape.observables):
     70                     # there is no influence of this operation on any of the observables
     71                     info["grad_method"] = "0"

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/gradient_transform.py in <genexpr>(.0)
     67 
     68             elif (tape._graph is not None) or use_graph:
---> 69                 if not any(tape.graph.has_path(op, ob) for ob in tape.observables):
     70                     # there is no influence of this operation on any of the observables
     71                     info["grad_method"] = "0"

/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/circuit_graph.py in has_path(self, a, b)
    491                     self._graph,
    492                     self._graph.nodes().index(a),
--> 493                     self._graph.nodes().index(b),
    494                     weight_fn=None,
    495                     default_weight=1.0,

ValueError: probs(wires=[]) is not in list

Here is the version information:

Name: PennyLane
Version: 0.28.0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /opt/conda/envs/pennylane/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, retworkx, scipy, semantic-version, toml
Required-by: PennyLane-Cirq, PennyLane-Lightning, PennyLane-qiskit, pennylane-qulacs, PennyLane-SF

Platform info:           Linux-5.4.209-116.367.amzn2.x86_64-x86_64-with-glibc2.31
Python version:          3.9.15
Numpy version:           1.23.5
Scipy version:           1.10.0
Installed devices:
- default.gaussian (PennyLane-0.28.0)
- default.mixed (PennyLane-0.28.0)
- default.qubit (PennyLane-0.28.0)
- default.qubit.autograd (PennyLane-0.28.0)
- default.qubit.jax (PennyLane-0.28.0)
- default.qubit.tf (PennyLane-0.28.0)
- default.qubit.torch (PennyLane-0.28.0)
- default.qutrit (PennyLane-0.28.0)
- null.qubit (PennyLane-0.28.0)
- cirq.mixedsimulator (PennyLane-Cirq-0.28.0)
- cirq.pasqal (PennyLane-Cirq-0.28.0)
- cirq.qsim (PennyLane-Cirq-0.28.0)
- cirq.qsimh (PennyLane-Cirq-0.28.0)
- cirq.simulator (PennyLane-Cirq-0.28.0)
- lightning.qubit (PennyLane-Lightning-0.28.2)
- strawberryfields.fock (PennyLane-SF-0.20.1)
- strawberryfields.gaussian (PennyLane-SF-0.20.1)
- strawberryfields.gbs (PennyLane-SF-0.20.1)
- strawberryfields.remote (PennyLane-SF-0.20.1)
- strawberryfields.tf (PennyLane-SF-0.20.1)
- qiskit.aer (PennyLane-qiskit-0.28.0)
- qiskit.basicaer (PennyLane-qiskit-0.28.0)
- qiskit.ibmq (PennyLane-qiskit-0.28.0)
- qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.28.0)
- qiskit.ibmq.sampler (PennyLane-qiskit-0.28.0)
- qulacs.simulator (pennylane-qulacs-0.28.0)

Hey @jkwan314! You need to specify the wires you want the probability distribution to be over:

@qml.qnode(dev, interface='torch')
def generator(w, num_layers = 3):
    rotation_layer(w[:num_qubits])
    for i in range(1, num_layers*2 + 1, 2):
        entangling_block(w[num_qubits * (i) : num_qubits * (i+1)])
        rotation_layer(w[num_qubits * (i+1) : num_qubits * (i+2)])
        
    return qml.probs(wires=range(num_qubits)) # <<< here

See here for more details. Hope this helps!

1 Like