TypeError while using qml.GradientDescentOptimizer

Hi,

I am facing an issue when trying to use qml.GradientDescentOptimizer on a gaussian default.gaussian device:

TypeError: loop of ufunc does not support argument 0 of type ArrayBox which has no callable sqrt method

Please find the code, as well as the full trace in this notebook:

Thank you for your help,
Julia

Hi Julia — Welcome to the forum! Can you condense your code to a minimal non-working example?

Hi Nicolas,

I have been unsuccessfully trying to condense the code further.

Do you have any tips on how I can go about it?

Hi @juliae — The first suggestion would be to share the code directly instead of a jupyter notebook.

Here is the code:

import pennylane as qml
from pennylane import numpy as np
from pennylane.optimize import GradientDescentOptimizer
import math

#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)
real_values = np.random.normal(size=(2**num_qubits,))
real_values = normalize(real_values)

## 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.QubitStateVector(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))

@qml.qnode(dev)
def gen_disc_circuit(fake_values,disc_weights):
    # initialize with generator data
    qml.QubitStateVector(fake_values, wires=range(num_qubits))
    # discriminator layers
    for W in disc_weights:
        layer(W)
    # measure
    return qml.expval(qml.PauliZ(0))

#discriminator weights
disc_weights = 0.01 * np.random.randn(2, num_qubits, 3)

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

#random fake values
fake_values =  0.01 * np.random.randn(2**num_qubits)
norm_fake_values = normalize(fake_values)

#create the optimizer
opt = GradientDescentOptimizer(0.1)
for i in range(50):
    j = 0
    disc_weights = opt.step(lambda v: disc_cost(real_values,v),disc_weights)
    cost = disc_cost(real_values, disc_weights)
p_R = prob_real_true(real_values, disc_weights)

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

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

Thanks @juliae,

I was able to reproduce the error message with your code. I’ll have to check with the team to determine a fix/workaround (or whether this is a bug) and get back to you.

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}")