Error raised when optimizing ansatz on the `qiskit.aer` device

Dear Pennylane team,

Thank you for the great efforts on developing Pennylane and keeping fast iteration. When I update to the new released version of pennnylane, it reports an error when I run the following code. However, when running it on the pennylane of version 0.14.1, everything works well. Sorry for that I have not tracked the latest change of pennylane recently.

import pennylane as qml
from pennylane import numpy as np
import qiskit.providers.aer.noise as noise

p_phase = 0.05
phase_flip = noise.pauli_error([('Z', p_phase), ('I', 1 - p_phase)])
noise_model = noise.NoiseModel()
noise_model.add_all_qubit_quantum_error(phase_flip, ['u1', 'u2', 'u3'])

dev = qml.device('qiskit.aer', wires=4, noise_model=noise_model)

def encode_layer(feature, n_qubits):
    for i in range(n_qubits):
        qml.RY(feature[i], wires=i)

def layer(params, n_qubits):
    for i in range(n_qubits):
        qml.RX(params[i], wires=i)
    qml.CZ(wires=[0, 1])
    qml.CZ(wires=[1, 2])
    qml.CZ(wires=[2, 3])

def circuit(params, feature=None, A=None):
    encode_layer(feature, 4)
    for j in range(2):
        layer(params[j], 4)
    return qml.expval(qml.Hermitian(A, wires=[0, 1, 2, 3]))

def cost_fn_retrain(params, model, inputs, labels, A):
    loss = 0
    size = inputs.shape[0]
    for data, label in zip(inputs, labels):
        out = model(params, data, A)
        loss += (label - out)**2
    loss /= size
    return loss

model = qml.QNode(circuit, dev)
opt = qml.AdamOptimizer(0.2)
A = np.kron(np.eye(8), np.array([[1, 0], [0, 0]]))

data = np.random.uniform(0, np.pi*2, (2, 4))
label = np.random.uniform(0, np.pi*2, (2))

params = np.random.uniform(0, np.pi * 2, (2, 4))
opt.step_and_cost(lambda para: cost_fn_retrain(para, model, data, label, A), params)

The raised error is quite confusing. Here is the full picture of the error information.

D:\xxx\software\Python\Python38\lib\site-packages\pennylane\tape\jacobian_tape.py:562: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different 
lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
  params = np.array(params)
TypeError: only size-1 arrays can be converted to Python scalars

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "D:\xxx\software\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "D:\xxx\software\Python\Python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "c:\Users\xxx\.vscode\extensions\ms-python.python-2021.10.1336267007\pythonFiles\lib\python\debugpy\__main__.py", line 45, in <module>
    cli.main()
  File "c:\Users\xxx\.vscode\extensions\ms-python.python-2021.10.1336267007\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 444, in main
    run()
  File "c:\Users\xxx\.vscode\extensions\ms-python.python-2021.10.1336267007\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 285, in run_file
    runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
  File "D:\xxx\software\Python\Python38\lib\runpy.py", line 265, in run_path
    return _run_module_code(code, init_globals, run_name,
  File "D:\xxx\software\Python\Python38\lib\runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "D:\xxx\software\Python\Python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "d:\xxx\document\quantum academy\QAS-original\machine_learning\tmp.py", line 46, in <module>
    opt.step_and_cost(lambda para: cost_fn_retrain(para, model, data, label, A), params)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\optimize\gradient_descent.py", line 100, in step_and_cost
    g, forward = self.compute_grad(objective_fn, args, kwargs, grad_fn=grad_fn)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\optimize\gradient_descent.py", line 158, in compute_grad
    grad = g(*args, **kwargs)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\_grad.py", line 101, in __call__
    grad_value, ans = self._get_grad_fn(args)(*args, **kwargs)
  File "D:\xxx\software\Python\Python38\lib\site-packages\autograd\wrap_util.py", line 20, in nary_f
    return unary_operator(unary_f, x, *nary_op_args, **nary_op_kwargs)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\_grad.py", line 126, in _grad_with_forward
    grad_value = vjp(vspace(ans).ones())
  File "D:\xxx\software\Python\Python38\lib\site-packages\autograd\core.py", line 14, in vjp
    def vjp(g): return backward_pass(g, end_node)
  File "D:\xxx\software\Python\Python38\lib\site-packages\autograd\core.py", line 21, in backward_pass
    ingrads = node.vjp(outgrad[0])
  File "D:\xxx\software\Python\Python38\lib\site-packages\autograd\core.py", line 67, in <lambda>
    return lambda g: (vjp(g),)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\interfaces\autograd.py", line 274, in gradient_product
    vjp = dy @ jacobian(params)
  File "D:\xxx\software\Python\Python38\lib\site-packages\autograd\tracer.py", line 48, in f_wrapped
    return f_raw(*args, **kwargs)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\interfaces\autograd.py", line 252, in jacobian
    return _evaluate_grad_matrix(p, "jacobian")
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\interfaces\autograd.py", line 235, in _evaluate_grad_matrix
    grad_matrix = getattr(self, grad_matrix_fn)(device, params=p, **self.jacobian_options)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\tape\qubit_param_shift.py", line 131, in jacobian
    return super().jacobian(device, params, **options)
  File "D:\xxx\software\Python\Python38\lib\site-packages\pennylane\tape\jacobian_tape.py", line 584, in jacobian
    params_f64 = np.array(params, dtype=np.float64)
ValueError: setting an array element with a sequence.

Any suggestions are greatly appreciated.

Hi @Yang, thanks for the kind words!

Regarding the error you’re receiving, could you post the output of running the following in the same Python environment where you are seeing the error?

import pennylane as qml 
qml.about()

Hi @Yang and @nathan, I can recreate the error and fix it by changing
label = np.random.uniform(0, np.pi*2, (2))
to
label = np.random.uniform(0, np.pi*2, (2,4))

However I see that a grad error arises. I’m not sure where it’s coming from but I’ll let you know in case I can figure it out.

In any case I hope this helps!

Hi @nathan, thanks for your reply. I run the code and it outputs the following:

WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
Name: PennyLane
Version: 0.18.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: d:\qianyang9\software\python\python38\lib\site-packages
Requires: numpy, toml, semantic-version, appdirs, autoray, pennylane-lightning, cachetools, networkx, autograd, scipy
Required-by: PennyLane-qiskit, PennyLane-Lightning
Platform info:           Windows-10-10.0.19041-SP0
Python version:          3.8.7
Numpy version:           1.19.5
Scipy version:           1.6.0
Installed devices:
- default.gaussian (PennyLane-0.18.0)
- default.mixed (PennyLane-0.18.0)
- default.qubit (PennyLane-0.18.0)
- default.qubit.autograd (PennyLane-0.18.0)
- default.qubit.jax (PennyLane-0.18.0)
- default.qubit.tf (PennyLane-0.18.0)
- default.qubit.torch (PennyLane-0.18.0)
- default.tensor (PennyLane-0.18.0)
- default.tensor.tf (PennyLane-0.18.0)
- qiskit.aer (PennyLane-qiskit-0.15.0)
- qiskit.basicaer (PennyLane-qiskit-0.15.0)
- qiskit.ibmq (PennyLane-qiskit-0.15.0)
- lightning.qubit (PennyLane-Lightning-0.18.0)

Hi, @CatalinaAlbornoz, thanks for your help. When changing

label = np.random.uniform(0, np.pi*2, (2))

to

label = np.random.uniform(0, np.pi*2, (2,4)),

the final loss becomes a vector rather than a scalar, which is the cause of grad error.

Hi @Yang,

From the output you posted, I noticed that you have an older version of the pennylane-qiskit plugin. The latest version is 0.18.0 (matching the latest version of PennyLane).

Does the error still remain if you upgrade the version of the PennyLane-Qiskit plugin?

pip install pennylane-qiskit==0.18.0

(also note that this will update your qiskit install if it is a lower version than 0.25)

1 Like

Hi @Yang and @nathan, I managed to make it work!

I added a loss function and modified the cost_fn_retrain function.
Having the loss function separate made it easier to debug. It seems that the for you had in the cost_fn_retrain function was causing the problem.

import pennylane as qml
from pennylane import numpy as np
import qiskit.providers.aer.noise as noise

p_phase = 0.05
phase_flip = noise.pauli_error([('Z', p_phase), ('I', 1 - p_phase)])
noise_model = noise.NoiseModel()
noise_model.add_all_qubit_quantum_error(phase_flip, ['u1', 'u2', 'u3'])

dev = qml.device('qiskit.aer', wires=4, noise_model=noise_model)

def encode_layer(feature, n_qubits):
    for i in range(n_qubits):
        qml.RY(feature[i], wires=i)

def layer(params, n_qubits):
    for i in range(n_qubits):
        qml.RX(params[i], wires=i)
    qml.CZ(wires=[0, 1])
    qml.CZ(wires=[1, 2])
    qml.CZ(wires=[2, 3])

def circuit(params, feature=None, A=None):
    encode_layer(feature, 4)
    for j in range(2):
        layer(params[j], 4)
    return qml.expval(qml.Hermitian(A, wires=[0, 1, 2, 3]))

def loss_func(predictions):
    # This is a postprocessing step. Here we use a least squares metric 
    # based on the predictions of the quantum circuit and the labels 
    # of the training data points.
    total_losses = 0
    for i in range(len(label)):
        lab = label[i]
        prediction = predictions[i]
        loss = (prediction - lab)**2
        total_losses += loss
    return total_losses

def cost_fn_retrain(params, model, inputs, labels, A):
    
    #calculate the predictions for each data input
    predictions = [model(params, data, A)  for data in inputs] 
    
    #calculate the loss
    cost = loss_func(predictions)
    
    # Extract the value from "cost" since it's an arraybox
    print('cost: ', cost._value)
    return cost._value

model = qml.QNode(circuit, dev)
opt = qml.AdamOptimizer(0.2)
A = np.kron(np.eye(8), np.array([[1, 0], [0, 0]]))

data = np.random.uniform(0, np.pi*2, (2, 4))
label = np.random.uniform(0, np.pi*2, (2))

params = np.random.uniform(0, np.pi * 2, (2, 4))
opt.step_and_cost(lambda para: cost_fn_retrain(para, model, data, label, A), params)

Let me know if this helps!

1 Like

Hi, @nathan. I have updated the pennylane-qiskit plugin to version 0.18.0, following your instructions. But the error still exists.

Hi @CatalinaAlbornoz, great thanks for your help. It solved my problem. No error is reported anymore. I am still curious what caused the error in the previous code and why the error can be removed by seperating the loss function. A more detailed explanation is greatly appreciated @nathan.

Thanks again for your helpful suggestions. I was trapped in this error for a few days.

Hi @CatalinaAlbornoz, I find that the key modification of your code is to replace the loss with loss._value, which is the direct cause of successful run. However, after extracting the value of variable loss in the cost function, the gradient of loss with respect to the parameters remains constant 0, failing to update the parameters. So we have to seek another solution.

Hi @Yang — from having a look at your existing code, the reason could be that PennyLane is attempting to treat your Hermitian matrix A as trainable. To avoid this, you can specify requires_grad=False:

A = np.kron(np.eye(8), np.array([[1, 0], [0, 0]]), requires_grad=False)

Note: automatically treating all arrays as trainable is currently the default behaviour in PennyLane, however soon we will be looking to change this, and all parameters will have to be explicitly marked as trainable.


On a slight tangent, I have noticed that your Hermitian matrix corresponds to a projector. You could have a go replacing it with

p = np.array([0], requires_grad=False)
return qml.expval(qml.Projector(P, wires=3))

which should be a lot more efficient :slightly_smiling_face:

@Yang making both of the above changes,

  • Explicitly specifying which parameters are trainable and which are not, and
  • Using qml.Projector(),

your code now runs, and is somewhat faster :slight_smile:

import pennylane as qml
from pennylane import numpy as np
import qiskit.providers.aer.noise as noise

p_phase = 0.05
phase_flip = noise.pauli_error([('Z', p_phase), ('I', 1 - p_phase)])
noise_model = noise.NoiseModel()
noise_model.add_all_qubit_quantum_error(phase_flip, ['u1', 'u2', 'u3'])

dev = qml.device('qiskit.aer', wires=4, noise_model=noise_model)

def encode_layer(feature, n_qubits):
    for i in range(n_qubits):
        qml.RY(feature[i], wires=i)

def layer(params, n_qubits):
    for i in range(n_qubits):
        qml.RX(params[i], wires=i)
    qml.CZ(wires=[0, 1])
    qml.CZ(wires=[1, 2])
    qml.CZ(wires=[2, 3])

def circuit(params, feature=None, A=None):
    encode_layer(feature, 4)
    for j in range(2):
        layer(params[j], 4)
    return qml.expval(qml.Projector(A, wires=[3]))

def cost_fn_retrain(params, model, inputs, labels, A):
    loss = 0
    size = inputs.shape[0]
    for data, label in zip(inputs, labels):
        out = model(params, data, A)
        loss += (label - out)**2
    loss /= size
    return loss

model = qml.QNode(circuit, dev)
opt = qml.AdamOptimizer(0.2)

A = np.array([0], requires_grad=False)

data = np.random.uniform(0, np.pi*2, (2, 4), requires_grad=False)
label = np.random.uniform(0, np.pi*2, (2), requires_grad=False)

params = np.random.uniform(0, np.pi * 2, (2, 4), requires_grad=True)
new_params = opt.step_and_cost(lambda para: cost_fn_retrain(para, model, data, label, A), params)

print(new_params)
1 Like

Thanks @josh. That is quite helpful, perfectly solving the problem. One more thing that confuses me is that my previous code works well when employing backend default.qubit. Does it mean that parameter-shift rule (qiskit.aer) and gradient back propagation (default.qubit) process variable in different manner?

@Yang yes! Behind the scenes, the logic that occurs for backpropagation vs. parameter-shift is very different :slight_smile:

@josh Thanks for your reply. Have a nice day!

1 Like

Hi @josh, I found optimizing the circuit with the original measurement qml.expval(qml.Hermitian(A, wires=[0, 1, 2, 3])) is quite slow on noisy devices qiskit.aer when the Hermitian matrix is not a projector. Is there some tips for accelerating the code in this case?

@Yang I would recommend converting your Hermitian matrix A into a Hamiltonian object. This can be done via qml.utils.decompose_hamiltonian():

>>> A = np.array([[-2, -2+1j, -2, -2], [-2-1j,  0,  0, -1], [-2,  0, -2, -1], [-2, -1, -1,  0]])
>>> coeffs, obs_list = decompose_hamiltonian(A)
>>> H = qml.Hamiltonian(coeffs, obs_list)
>>> print(H)
(-1.0) [I0 I1]
+ (-1.5) [X1]
+ (-0.5) [Y1]
+ (-1.0) [Z1]
+ (-1.5) [X0]
+ (-1.0) [X0 X1]
+ (-0.5) [X0 Z1]
+ (1.0) [Y0 Y1]
+ (-0.5) [Z0 X1]
+ (-0.5) [Z0 Y1]

You can then use qml.expval(H) inside your Hamiltonian.

1 Like

@josh thanks! That is quite helpful!