Value error: need at least one array to stack while using Qiskit ansatz with QML's QNGOptimizer

Hi, I hope you are doing well. I am trying to run a natural gradient descent optimization on my circuit. My original work was in qiskit but I switched to pennylane for its qng optimizer. I noticed that using similar routine for making ansatz in pennylane turns out to be different than qiskit’s ansatz so I am now making my ansatz in qiskit and converting it to qml’s ansatz and running the optimization. The ansatz conversion runs smoothely but I am having errors when I put it in qngoptimizer’s step_and_cost function. I was originally having an error where the parameters I was binding to my qiskit ansatz were tensor or autograd.arraybox so I tried converting them into a float to bind them to my qiskit ansatz. Now, I am getting an error of “Value error: need at least one array to stack.” Can you kindly help me with this error or guide me how I can use my qiskit ansatz with pennylane’s qng optimizer?

I have the following code:

from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit import Parameter
import numpy as np
from pennylane import numpy as nnn
import pickle
import pennylane as qml
import pennylane_qiskit
from observable import returnStringObservable

dev = qml.device("default.qubit", wires=8)

connectivity = [(1, 2), (0, 6), (2, 3), (0, 7), (3, 4), (0, 1), (4, 5), (0, 2), (5, 6), (0, 3), (6, 7), (0, 4), (7, 1), (0, 5)]
boundary = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 1)]


connectivity = list(map(tuple, connectivity))
boundary = list(map(tuple, boundary))

class TwoQubitUnitary(QuantumCircuit):
    def __init__(self,):
        super().__init__()
        work = QuantumRegister(2, name='q')
        self.add_register(work)
        self.u(Parameter('p0'), Parameter('p1'), Parameter('p2'), work[0])
        self.u(Parameter('p3'), Parameter('p4'), Parameter('p5'), work[1])
        self.cx(work[1], work[0])
        self.rz(Parameter('p6'), work[0])
        self.ry(Parameter('p7'), work[1])
        self.cx(work[0], work[1])
        self.ry(Parameter('p8'), work[1])
        self.cx(work[1], work[0])
        self.u(Parameter('p9'), Parameter('p10'), Parameter('p11'), work[0])
        self.u(Parameter('p12'), Parameter('p13'), Parameter('p14'), work[1])
        
num_qubits = 8
boundary_start = 1
conec_list = [list((i, j)) for (i, j) in connectivity]

paramsList = list()
energyList = list()

def qiskitToQml(parameters):
    num_gates = len(parameters)/15
    gates = list()

    for i in range(int(num_gates)):
        print(i)
        relevant_params = parameters[i*15:(i*15)+15]
        gate = TwoQubitUnitary()
        if (type(relevant_params) == nnn.tensor):
            gate = gate.bind_parameters({gate.parameters.data[index]: val.unwrap() for (index, val) in enumerate(relevant_params)})
        else:
            gate = gate.bind_parameters({gate.parameters.data[index]: (val._value).unwrap() for (index, val) in enumerate(relevant_params)})
        # gate = gate.bind_parameters({gate.parameters.data[index]: val for (index, val) in enumerate(relevant_params)})
        gates.append(gate)
        
    ansatz = QuantumCircuit(num_qubits)

    print("===start===")
    for (index, (i, j)) in enumerate(connectivity):
        if index % 2 == 0:
            ansatz.append(gates[1], [i, j])
        else:
            ansatz.append(gates[0], [i, j])
    for (index, (i, j)) in enumerate(connectivity[::-1]):
        if index % 2 == 0:
            ansatz.append(gates[3], [i, j])
        else:
            ansatz.append(gates[2], [i, j])
    print("===End====")
    return qml.from_qiskit(ansatz.decompose())

op_list = [(qml.pauli.utils.string_to_pauli_word(singleEntry)) for singleEntry in returnStringObservable(boundary_start, num_qubits)]
H = qml.Hamiltonian([-1]*len(op_list), op_list)

@qml.qnode(dev)
def cost_fn(params):
    circ = qiskitToQml(params)
    circ()
    return qml.expval(H)


max_iterations = 1000
step_size = .01
conv_tol = 5e-07
opt = qml.QNGOptimizer(stepsize=step_size, approx='diag')

params = np.random.normal(-0.3, .1, 15*4)


qngd_param_history = [params]
qngd_cost_history = []

params, prev_energy = opt.step_and_cost(cost_fn, nnn.array(params, requires_grad=True))
print(prev_energy)

for n in range(max_iterations):

    params, next_energy = opt.step_and_cost(cost_fn, nnn.array(params, requires_grad=True))
    qngd_param_history.append(params)
    qngd_cost_history.append(next_energy)

    conv = np.abs(next_energy - prev_energy)

    print(
            "Iteration = {:},  Energy = {:.8f} Ha,  Convergence parameter = {"
            ":.8f} Ha".format(n, next_energy, conv)
        )

    if conv <= conv_tol:
        
        break

    prev_energy = next_energy
pickle.dump([qngd_param_history, qngd_cost_history], open("qml_depth_1_8_15_params.pkl", "wb"))
print()
print("Final value of the energy = {:.8f} Ha".format(next_energy))
print("Number of iterations = ", n)

Hey @omersajid9! Thanks for integrating PennyLane into your workflow :smiley:!

I’m looking into a solution and will get back to you. Thank you for providing your code!

1 Like

Hey @omersajid9 !

I got a response from one of the PennyLane developers when I posed the following question:

If I want to convert a qiskit circuit to be usable in PennyLane, I can use qml.from_qiskit. But, what do I do if my qiskit circuit has parameters in it that I want to differentiate over after I convert to a PennyLane circuit?

E.g., I have something like

def convert_circuit(x):
    qc = qiskit.QuantumCircuit(1)
    qc.ry(x, [0])
    return qml.from_qiskit(qc)

and would like to inject this into PennyLane, then differentiate w.r.t. x.

Their response:

I could imagine that supporting this in all generality is very tough, because it basically requires that all used parts of Qiskit are compatible with the autodifferentiation interface.

At this time, it might be best to migrate your qiskit code into PennyLane. Is that feasible for you?

1 Like

To add to @isaacdevlugt’s response:

You can convert a Qiskit circuit to PennyLane and differentiate the circuit parameters if you use qiskit.circuit.Parameter to mark the variational parameters in your input Qiskit circuit.

For example, suppose you have:

from qiskit.circuit import QuantumCircuit, Parameter

phi = Parameter('phi')

qc = QuantumCircuit(1)
qc.ry(phi, [0])

You can convert this to PennyLane:

import pennylane as qml

my_circuit = qml.from_qiskit(qc)
dev = qml.device("default.qubit", wires=1)

@qml.qnode(dev)
def circuit(psi):
    my_circuit({phi: psi})
    return qml.expval(qml.PauliZ(0))

Here, we have mapped from the Qiskit circuit parameter phi to a new PennyLane parameter psi. This circuit can be differentiated:

>>> psi = qml.numpy.array(0.5, requires_grad=True)
>>> qml.grad(circuit)(psi)
tensor(-0.47942554, requires_grad=True)
1 Like