Running a VQA with Qiskit Runtime

Hello,

I am trying to run a VQA on IBM’s quantum computers through the Qiskit plugin, but I’m not sure how to do so. I have an algorithm that, on every iteration step, runs a bunch of different (related) circuits and accumulates the values for a single calculation of the cost function. I saw that Pennylane supports a few primitives like circuit_runner that would let you run the entire optimization with a single job to the quantum computer, but I’m not sure how to prevent my code from spawning over 30 jobs per iteration.

Here is my code run on a toy problem:

import pennylane as qml
from pennylane import numpy as np

import matplotlib.pyplot as plt
# Number of system qubits; this determines the size of the matrix A and vector b
n_qubits = 3

print(type(n_qubits))

# Number of quantum measurements performed to get a probability distribution of results
n_shots = 10**6

# Total number of qubits; here we add an ancillary qubit
tot_qubits = n_qubits + 1

# Index of ancillary qubit (Python lists are 0-indexed)
ancilla_idx = n_qubits

# Number of optimization steps
steps = 30

# Learning rate of optimization algorithm
eta = 0.8

# Initial spread of random quantum weights
q_delta = 0.001

# Seed for RNG
rng_seed = 0
# Coefficients of the linear combination A = c_0 A_0 + c_1 A_1 ...
c = np.array([1.0, 0.2, 0.2])
print(type(c))
print(len(c))



def U_b():
    """Unitary matrix rotating the ground state to the problem vector |b> = U_b |0>."""
    
    # This loop applies the Hadamard operator on each wire
    for idx in range(n_qubits):
        qml.Hadamard(wires=idx)


# This function gives the components of A
def CA(idx):
    """Controlled versions of the unitary components A_l of the problem matrix A."""
    if idx == 0:
        # Identity operation
        None

    elif idx == 1:
        # CNOT gate is the same as controlled Pauli-X gate
        qml.CNOT(wires=[ancilla_idx, 0])
        qml.CZ(wires=[ancilla_idx, 1])

    elif idx == 2:
        qml.CNOT(wires=[ancilla_idx, 0])

def variational_block(weights):
    """Variational circuit mapping the ground state |0> to the ansatz state |x>."""
    # We first prepare an equal superposition of all the states of the computational basis.
    for idx in range(n_qubits):
        qml.Hadamard(wires=idx)

    # A very minimal variational circuit.
    for idx, element in enumerate(weights):
        qml.RY(element, wires=idx)
# TODO: this is what we vary to access different devices (like IBM, Rigetti etc.)
# Loads a particular quantum device
# 'default.qubit' is a simple state simulator
# 'wires' is a aparamter specifying the number of wires (subsytems) to initialise the device with
dev_mu = qml.device("default.qubit", wires=tot_qubits)


# Quantum node contains a quantum function (in this case, local_hadamard_test) and the computational 
# device it's executed on (in this case, dev_mu)
@qml.qnode(dev_mu, interface="autograd")
# For this function, l, lp, j are the indices for the mu coefficients
def local_hadamard_test(weights, l=None, lp=None, j=None, part=None):

    # First Hadamard gate applied to the ancillary qubit.
    qml.Hadamard(wires=ancilla_idx)

    # For estimating the imaginary part of the coefficient "mu", we must add a "-i"
    # phase gate.
    if part == "Im" or part == "im":
        qml.PhaseShift(-np.pi / 2, wires=ancilla_idx)

    # Variational circuit generating a guess for the solution vector |x>
    variational_block(weights)

    # Controlled application of the unitary component A_l of the problem matrix A.
    CA(l)

    # Adjoint of the unitary U_b associated to the problem vector |b>.
    # In this specific example Adjoint(U_b) = U_b.
    U_b()

    # Controlled Z operator at position j. If j = -1, apply the identity.
    if j != -1:
        qml.CZ(wires=[ancilla_idx, j])

    # Unitary U_b associated to the problem vector |b>.
    U_b()

    # Controlled application of Adjoint(A_lp).
    # In this specific example Adjoint(A_lp) = A_lp.
    CA(lp)

    # Second Hadamard gate applied to the ancillary qubit.
    qml.Hadamard(wires=ancilla_idx)

    # Expectation value of Z for the ancillary qubit.
    return qml.expval(qml.PauliZ(wires=ancilla_idx))
# Computes the mu coefficients
def mu(weights, l=None, lp=None, j=None):
    """Generates the coefficients to compute the "local" cost function C_L."""

    mu_real = local_hadamard_test(weights, l=l, lp=lp, j=j, part="Re")
    mu_imag = local_hadamard_test(weights, l=l, lp=lp, j=j, part="Im")

    return mu_real + 1.0j * mu_imag
def psi_norm(weights):
    """Returns the normalization constant <psi|psi>, where |psi> = A |x>."""
    norm = 0.0

    for l in range(0, len(c)):
        for lp in range(0, len(c)):
            norm = norm + c[l] * np.conj(c[lp]) * mu(weights, l, lp, -1)

    return abs(norm)
def cost_loc(weights):
    """Local version of the cost function. Tends to zero when A|x> is proportional to |b>."""
    mu_sum = 0.0

    for l in range(0, len(c)):
        for lp in range(0, len(c)):
            for j in range(0, n_qubits):
                mu_sum = mu_sum + c[l] * np.conj(c[lp]) * mu(weights, l, lp, j)

    mu_sum = abs(mu_sum)

    # Cost function C_L
    return 0.5 - 0.5 * mu_sum / (n_qubits * psi_norm(weights))
np.random.seed(rng_seed)
w = q_delta * np.random.randn(n_qubits, requires_grad=True)

opt = qml.GradientDescentOptimizer(eta)

cost_history = []
for it in range(steps):
    w, cost = opt.step_and_cost(cost_loc, w)
    print("Step {:3d}       Cost_L = {:9.7f}".format(it, cost))
    cost_history.append(cost)

If you want help with diagnosing an error, please put the full error message below:

qml.about():

Name: PennyLane
Version: 0.30.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: /Users/bigsad/Downloads/Algorithm-Research/Student-Hub/Indy-Ng/.venv/lib/python3.11/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning

Platform info:           macOS-12.6-x86_64-i386-64bit
Python version:          3.11.6
Numpy version:           1.23.5
Scipy version:           1.10.1
Installed devices:
- default.gaussian (PennyLane-0.30.0)
- default.mixed (PennyLane-0.30.0)
- default.qubit (PennyLane-0.30.0)
- default.qubit.autograd (PennyLane-0.30.0)
- default.qubit.jax (PennyLane-0.30.0)
- default.qubit.tf (PennyLane-0.30.0)
- default.qubit.torch (PennyLane-0.30.0)
- default.qutrit (PennyLane-0.30.0)
- null.qubit (PennyLane-0.30.0)
- lightning.qubit (PennyLane-Lightning-0.30.0)

Hi @jkwan314, welcome back to the Forum!

I see that you’re using an older version of PennyLane.

Both PennyLane and Qiskit have evolved a lot in the past few months so everything you see in the PennyLane documentation works with version 0.33.

My recommendation would be to upgrade PennyLane and the PennyLane-Qiskit plugin using python -m pip install --upgrade pennylane pennylane-qiskit.

You may need to update Qiskit and its subpackages in a similar way.

This upgrade will require that you probably change some things in your code.

Regarding the job issue you can try to do broadcasting to send everything as a single job. Below is an example.

Remember to always load your account at the beginning.

# Import your favourite library
import pennylane as qml

# Import Numpy from PennyLane
from pennylane import numpy as np

# Import the library that you need in order to use your IBM account
import qiskit_ibm_provider

IBM_token = #'Your Token Goes Here'

try:
    qiskit_ibm_provider.IBMProvider()
except:
    qiskit_ibm_provider.IBMProvider.save_account(token=IBM_token, overwrite=True)

Here’s an example without broadcasting. Note that theta is a single number.

# Choose the device you want to use.
# dev = qml.device("lightning.qubit", wires=2, shots=1000)
dev = qml.device('qiskit.aer', wires=2, shots=1000)
# dev = qml.device('qiskit.ibmq', wires=2, shots=1000, backend='ibmq_qasm_simulator')
# dev = qml.device('qiskit.ibmq', wires=2, shots=1000, backend='ibm_lagos')


# Create a QNode with 2 entangled qubits
@qml.qnode(dev)
def circuit(theta):
    qml.RX(theta,wires=0)
    qml.CNOT(wires=[0,1])
    return qml.probs()

theta = np.array(1.0,requires_grad=True)
# Draw your circuit
qml.draw_mpl(circuit,style='pennylane')(theta)

# Run your circuit
print('Probabilities of measuring 00, 01, 10 , and 11:', circuit(theta))

Now here’s an example with broadcasting. Note that everywhere except in the QNode I’ve changed theta into thetas, and it’s now an array of values.

# Choose the device you want to use.
# dev = qml.device("lightning.qubit", wires=2, shots=1000)
dev = qml.device('qiskit.aer', wires=2, shots=1000)
# dev = qml.device('qiskit.ibmq', wires=2, shots=1000, backend='ibmq_qasm_simulator')
# dev = qml.device('qiskit.ibmq', wires=2, shots=1000, backend='ibm_lagos')


# Create a QNode with 2 entangled qubits
@qml.qnode(dev)
def circuit(theta):
    qml.RX(theta,wires=0)
    qml.CNOT(wires=[0,1])
    return qml.probs()

thetas = np.array([1.0,2.0],requires_grad=True)
# Draw your circuit
qml.draw_mpl(circuit,style='pennylane')(thetas)

# Run your circuit
print('Probabilities of measuring 00, 01, 10 , and 11:', circuit(thetas))

This should help you adapt your code to run it on IBM devices and reduce the number of jobs needed.

Please let me know if you have any further questions!

By the way,

We have a very small survey for PennyLane v0.33 , and it would be awesome if you’d give us some feedback and tell us about your needs. Thank you! :grin:

Hi @CatalinaAlbornoz ,

Thanks for the prompt reply. Just to make sure I’m understanding your solution correctly, you are suggesting that I run a single job where I broadcast the parameters (ie. running the same circuit on each parameter)? The reason I’m clarifying is that if you look at my local_hadamard_test function, the circuit I’m trying to run is actually different for each “parameter” I’m trying to accumulate.

Hi @jkwan314,

My solution wouldn’t work in every single case so I’m not sure whether it works in your exact situation.

Did you try it out?