# 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 scipy.stats import lognorm, norm, triang
import matplotlib.pyplot as plt
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)

# 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.

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_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)
397
398     def register_hook(self, hook):

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

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,

523     # Loop through the tapes and dys vector
524     for tape, dy in zip(tapes, dys):
526         reshape_info.append(len(g_tapes))
527         processing_fns.append(fn)

379         pass
380
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:

1902         return [], lambda _: np.zeros((tape.output_dim, 0))
1903
1905     method = "analytic" if fallback_fn is None else "best"

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

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

/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.
Author:
Author-email:
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.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