Variational algorithms with Qaptiva

Variational algorithms are believed to be well suited to Noisy, Intermediate-Scale Quantum (NISQ) processors as they do not necessarily require long circuits to nevertheless prepare powerful ansatz states.

In the code snippet below, we illustrate how this can be used to write such variational algorithms in a few lines of code: we first define the Hamiltonian \(H\) (here the antiferromagnetic Heisenberg Hamiltonian) whose ground-state energy we want to approximate. We then define the ansatz circuit, i.e a parametric circuit with parameters \(\theta_i\) to be optimized. Finally, our quantum stack is composed of a QPU (here a simulator) and a so-called “plugin” that is going to perform the iterative optimization of the parameters given the ansatz circuit and the observable to be minimized.

import numpy as np
from qat.core import Observable as Obs
from qat.lang import RY, CNOT, qfunc

# Here we specify that thetas is an array of length 2
@qfunc(thetas=2)
def energy(thetas):
                    # Quantum job
                    # Define Ansätz
    RY(thetas[0])(0)
    RY(thetas[1])(1)
    CNOT(0, 1)

                    # Measure observable
    return (
        Obs.sigma_z(0) * Obs.sigma_z(1)
        + Obs.sigma_x(0) * Obs.sigma_x(1)
        + Obs.sigma_y(0) * Obs.sigma_y(1)
    )

from scipy.optimize import minimize

res = minimize(energy, x0=np.array([1.8, 2.8]))
print(res)

# Equivalently, one can delegate the minimization to the default qpu which is
# equiped with a variational optimizer
result = energy.run()
print(f"Minimum VQE energy = {result.value}")
  message: Optimization terminated successfully.
  success: True
   status: 0
      fun: -0.3099330343247272
        x: [ 2.810e+00  2.800e+00]
      nit: 1
      jac: [ 0.000e+00  0.000e+00]
 hess_inv: [[1 0]
            [0 1]]
     nfev: 6
     njev: 2
Minimum VQE energy = -2.999999999999992
No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
from qat.core import Observable as Obs, Term
from qat.lang import Program, RY, CNOT
from qat.qpus import get_default_qpu
from qat.plugins import ScipyMinimizePlugin

# we instantiate the Hamiltonian we want to approximate the ground state energy of
hamiltonian = (
    Obs.sigma_z(0) * Obs.sigma_z(1)
    + Obs.sigma_x(0) * Obs.sigma_x(1)
    + Obs.sigma_y(0) * Obs.sigma_y(1)
)

# we construct the variational circuit (ansatz)
prog = Program()
reg = prog.qalloc(2)
thetas = [prog.new_var(float, '\\theta_%s'%i) for i in range(2)]
RY(thetas[0])(reg[0])
RY(thetas[1])(reg[1])
CNOT(reg[0], reg[1])
circ = prog.to_circ()

# construct a (variational) job with the variational circuit and the observable
job = circ.to_job(observable=hamiltonian)

# we now build a stack that can handle variational jobs
qpu = get_default_qpu()
optimizer_scipy = ScipyMinimizePlugin(method="COBYLA",
                                        tol=1e-6,
                                        options={"maxiter": 200},
                                        x0=[0, 0])
stack = optimizer_scipy | qpu

# we submit the job and print the optimized variational energy (the exact GS energy is -3)
result = stack.submit(job)
# the output of the optimizer can be found here
print(result.meta_data['optimizer_data'])
print(f"Minimum VQE energy = {result.value}")
 message: Optimization terminated successfully.
 success: True
  status: 1
     fun: -2.9999999999992464
       x: [-1.571e+00  3.142e+00]
    nfev: 58
   maxcv: 0.0
Minimum VQE energy = -2.9999999999992464

For more information, the writing section explains how to create quantum Circuit, Observable, or quantum Job. In addition, the running variational jobs section explains how to execute variational algorithms using Qaptiva framework