Hi @juliae,
Thanks for sharing your code. We have taken a look and the issue is the use of QubitStateVector which is presently non-differentiable. If you replace with MottonenStatePreparation, as shown in the code below, this will fix your problem.
However, that unfortunately uncovers a genuine bug in PL and so the code will not yet work. We have a fix coming through for this bug. Once we have merged the fix then you should be good to go, provided that you install the development branch of PennyLane, following these instructions. Thanks for helping us uncover a bug!
import pennylane as qml
from pennylane import numpy as np
from pennylane.optimize import GradientDescentOptimizer
# Normalization function
def normalize(values):
mean = np.sqrt(np.sum(values ** 2))
return np.array(values / mean)
# load data
num_qubits = 6
np.random.seed(0)
## Set up Discriminator
dev = qml.device("default.qubit", wires=num_qubits)
def layer(num_wires, W):
for i in range(num_wires):
qml.Rot(W[i, 0], W[i, 1], W[i, 2], wires=i)
for i in range(num_wires):
qml.CNOT(wires=[i, (i + 1) % num_wires])
@qml.qnode(dev)
def real_disc_circuit(real_values, weights):
# initialize the input state real data
qml.templates.MottonenStatePreparation(real_values, wires=range(num_qubits))
# assign weights to layers
for W in weights:
layer(num_qubits, W)
# measure
return qml.expval(qml.PauliZ(0))
def prob_real_true(real_values, disc_weights):
# get measurement
r = real_disc_circuit(real_values, disc_weights)
assert r >= -1 and r <= 1
# convert "r" to a probability
p = (r + 1) / 2
assert p >= 0 and r <= 1
return p
def prob_fake_true(fake_values, disc_weights):
# get measurement
r = real_disc_circuit(fake_values, disc_weights)
assert r >= -1 and r <= 1
# convert "r" to a probability
p = (r + 1) / 2
assert p >= 0 and r <= 1
return p
def disc_cost(real_values, disc_weights):
cost = -prob_real_true(real_values, disc_weights)
return cost
## Set up Generator
dev_gaussian = qml.device("default.gaussian", wires=2 ** num_qubits)
@qml.qnode(dev_gaussian)
def mean_photon_gaussian(params):
for i in range(2 ** num_qubits):
qml.Displacement(params[i][0], params[i][1], wires=i)
qml.Rotation(params[i][2], wires=i)
return [qml.expval(qml.NumberOperator(i)) for i in range(2 ** num_qubits)]
def gen_cost(params, disc_weights):
# calculate expected number of photons
fake_values = mean_photon_gaussian(params)
# normalize fake values
norm_fake_values = normalize(fake_values)
# determine the probability of recognizing them as true values
cost = -prob_real_true(norm_fake_values, disc_weights)
return cost
# discriminator weights
disc_weights = 0.01 * np.random.randn(2, num_qubits, 3)
# generator weights
gen_weights = 0.1 * np.ones([2 ** num_qubits, 3], dtype=float)
# create the optimizer
opt = qml.GradientDescentOptimizer(stepsize=0.1)
# set the number of steps
for i in range(20):
# update the circuit parameters
gen_weights = opt.step(lambda v: gen_cost(v, disc_weights), gen_weights)
cost = gen_cost(gen_weights, disc_weights)
print(f"Cost: {cost}")