Running variational algorithms

QLM comes with a collection of tools to efficiently describe and run variational quantum algorithm. This page introduces the basic mechanics allowing you to write and run adaptive variational schemes.

For a more advanced usage Combinatorial optimization describes a high-level interface to described combinatorial optimization problems and automatically generate parametrized Ansätze.

Variational jobs

When running a variational quantum algorithm, we are, most of the time, interested in minimizing the energy of some observable $$H$$ over some parametrized quantum state $$|\psi(\theta)\rangle$$, i.e minimizing $$\langle \psi(\theta)| H |\psi(\theta) \rangle$$.

In the QLM, it is possible to build a variational quantum circuit by introducing open parameters in a pyAQASM Program:

from qat.lang.AQASM import Program, RY, RZ

prog = Program()
qbits = prog.qalloc(1)

variable = prog.new_var(float, "a")
RY(variable)(qbits)
RZ(4 * variable)(qbits)

circuit = prog.to_circ()

print("Variables:", circuit.get_variables())

job = circuit.to_job()
print("Job variables:", job.get_variables())

Variables: ['a']
Job variables: ['a']


Additionally, the sampled observable itself can have parametrized coefficients:

from qat.core import Observable, Term, Variable

t = Variable("t")

obs = Observable(3)
# Obs = \sum_i \sigma_x^i
for i in range(3):
obs += (1 - t) * Observable.sigma_x(i, 3)

print(obs)
print("Observable variables:", obs.get_variables())

from qat.lang.AQASM import Program, RY, RZ

prog = Program()
qbits = prog.qalloc(3)

variable = prog.new_var(float, "a")
for qbit in qbits:
RY(variable)(qbit)
RZ(4 * variable)(qbit)

circuit = prog.to_circ()

print("Circuit variables:", circuit.get_variables())

job = circuit.to_job(observable=obs)
print("Job variables:", job.get_variables())

(1 - t) * (X|[0]) +
(1 - t) * (X|[1]) +
(1 - t) * (X|[2])
Observable variables: ['t']
Circuit variables: ['a']
Job variables: ['a', 't']


This allows to have layered parametrized optimization, or even compilation tradeoffs where some variational parameters end up in the sampled observable.

Binding variables

Once we’ve built a parametrized job, its variables can be instantiated using the overloaded __call__ operator:

import numpy as np
from qat.lang.AQASM import Program, RY, RZ
from qat.core import Observable, Term, Variable

t = Variable("t")

obs = Observable(3)
for i in range(3):
obs += (1 - t) * Observable.sigma_x(i, 3)

prog = Program()
qbits = prog.qalloc(3)

variable = prog.new_var(float, "a")
for qbit in qbits:
RY(variable)(qbit)
RZ(4 * variable)(qbit)
job = prog.to_circ().to_job(observable=obs)

job_2 = job(t=0.5)

print(job_2.observable)

job_3 = job(** {v: np.random.random() for v in job.get_variables()})

print(job_3.observable)
for op in job_3.circuit.iterate_simple():
print(op)

0.5 * (X|[0]) +
0.5 * (X|[1]) +
0.5 * (X|[2])
0.19376009394501548 * (X|[0]) +
0.19376009394501548 * (X|[1]) +
0.19376009394501548 * (X|[2])
('RY', [0.05218544329296437], [0])
('RZ', [0.2087417731718575], [0])
('RY', [0.05218544329296437], [1])
('RZ', [0.2087417731718575], [1])
('RY', [0.05218544329296437], [2])
('RZ', [0.2087417731718575], [2])


Warning

When binding variables used inside a custom parametrized gate, a gate set containing the custom gate should be provided via the gate_set kwargs.

from qat.lang.AQASM import Program, AbstractGate

XX = AbstractGate("XX", [float], arity=2)

prog = Program()
qbits = prog.qalloc(2)
XX(prog.new_var(float, "theta"))(qbits)
circuit = prog.to_circ()

try:
circuit_2 = circuit(theta=0.3)
except Exception as e:
print("Caught: {}".format(e))

circuit_2 = circuit(theta=0.3, gate_set=prog.gate_set)
for op in circuit_2.iterate_simple():
print(op)

('XX', [0.3], [0, 1])


Running variational algorithms

The simplest way to run a variational algorithm is to use a dedicated Plugin that will take care of the energy minimization.

The default variational Plugin wraps the scipy.optimize.minimize function: qat.plugins.ScipyMinimizePlugin.

import numpy as np
from qat.lang.AQASM import Program, RY, RZ
from qat.core import Observable, Term, Variable

t = Variable("t")

obs = Observable(3)
for i in range(3):
obs += (1 - t) * Observable.sigma_x(i, 3)

prog = Program()
qbits = prog.qalloc(3)

variable = prog.new_var(float, "a")
for qbit in qbits:
RY(variable)(qbit)
RZ(4 * variable)(qbit)
job = prog.to_circ().to_job(observable=obs)

from qat.plugins import ScipyMinimizePlugin

optimize = ScipyMinimizePlugin(method="COBYLA", tol=1e-3, options={"maxiter": 150})

from qat.qpus import get_default_qpu

stack = optimize | get_default_qpu()

result = stack.submit(job)

print('final energy:', result.value)
print('best parameters:', result.meta_data['parameters'])
print('trace:', result.meta_data['optimization_trace'])

final energy: -9.322011516646588
best parameters: [0.8404604370593229, -3.2744581093021234]
trace: [-1.6542044178243307, 1.522528606876513, 0.4600418823233312, -0.19763252326101638, 0.12161851355556108, -1.3367288858795783, -1.1568120512241027, -1.5240708875401292, -1.7831758640430047, -1.783067431339242, -1.898582108787429, -1.9961616751259958, -2.035175466107516, -1.7695199366353465, -2.2226276735202988, -2.3289637912053656, -2.4628249732793783, -2.5844756108726177, -2.622647945482948, -2.8045624008752794, -2.879432831302896, -3.0303402310996335, -3.167845748857512, -3.302692160012131, -3.43833989763662, -3.5748241598427573, -3.7093242169637235, -3.8146953550543374, -3.9623776975642038, -4.09177847203119, -4.227709306745507, -4.364089490015164, -4.501374054153844, -4.639606407421018, -4.77512683192955, -4.911702619833916, -5.048112237525279, -5.1843125329549204, -5.316038697612379, -5.449307420598486, -5.57793902944917, -5.6361915583827695, -5.786592915166521, -5.720844440897336, -5.781041703199319, -5.817806730076392, -5.83049397834022, -5.6904389935974855, -5.876840761902623, -5.915496543522651, -5.94922016387747, -5.982528133025671, -6.0167055675630206, -6.050791549517733, -6.0848173932422, -6.118898928096417, -6.152978405686907, -6.187055272698263, -6.221131375418384, -6.255204130988062, -6.289275717158464, -6.323351569541634, -6.357424586396318, -6.391500847843571, -6.425575960899747, -6.459624010946081, -6.493703524347264, -6.527782046661525, -6.5618582839687445, -6.595934105490131, -6.630010118426331, -6.6640861190609275, -6.698162112094824, -6.732237990281151, -6.766314020142756, -6.800389994416243, -6.834465990421256, -6.868541984510246, -6.90261797316362, -6.936693910940624, -6.970769824910599, -7.004845782272799, -7.038921668017336, -7.072996457082628, -7.1070727512992375, -7.141148288885076, -7.175224347388305, -7.20930037686013, -7.243376385643818, -7.277452376826689, -7.311528170818232, -7.345604162470208, -7.379680110506615, -7.413756121411927, -7.44783211838713, -7.481908104936262, -7.515984104546942, -7.550060098281098, -7.584136092763954, -7.6182120890665015, -7.652288085348309, -7.686364059645324, -7.720440063494587, -7.7545160633591514, -7.788591531979922, -7.8226676738230605, -7.856743628038478, -7.890819588268419, -7.924895589229755, -7.958971564260205, -7.993047564639911, -8.027123557178639, -8.061199557695536, -8.095275555928685, -8.129351554389057, -8.163427553391942, -8.197503552068534, -8.231579551057816, -8.265655549941322, -8.299731548853709, -8.33380754777702, -8.367883546692681, -8.401959545623853, -8.436035544546751, -8.470111543474445, -8.504187542402331, -8.538263541329417, -8.572339540256761, -8.606415539183535, -8.640491538110993, -8.674567537038394, -8.708643535965752, -8.742719534892679, -8.776795533820081, -8.810871532747338, -8.844947531674707, -8.87902353060206, -8.913099529527612, -8.947175528455496, -8.981251527382554, -9.015327526309827, -9.049403525236993, -9.083479524163451, -9.117555523080766, -9.15163152200929, -9.185707520937996, -9.219783519865612, -9.253859518792881, -9.287935517720179, -9.322011516646588]


This plugin also supports a nice feature: it can read optimization parameters directly from the job’s meta data or directly from the result. This allows you to build a stack with no particular choice of optimization parameters and attach these parameters directly to the job when submitting it. In this setting, the previous example becomes:

import numpy as np
import json
from qat.lang.AQASM import Program, RY, RZ
from qat.core import Observable, Term, Variable
t = Variable("t")

obs = Observable(3)
for i in range(3):
obs += (1 - t) * Observable.sigma_x(i, 3)

prog = Program()
qbits = prog.qalloc(3)

variable = prog.new_var(float, "a")
for qbit in qbits:
RY(variable)(qbit)
RZ(4 * variable)(qbit)
job = prog.to_circ().to_job(observable=obs)

from qat.plugins import ScipyMinimizePlugin

optimize = ScipyMinimizePlugin()

from qat.qpus import get_default_qpu

stack = optimize | get_default_qpu()

optimizer_args = {
"method": "COBYLA",
"tol": 1e-3,
"options": {"maxiter": 150}
}
result = stack.submit(job, meta_data={"ScipyMinimizePlugin": json.dumps(optimizer_args)})

print('final energy:', result.value)
print('best parameters:', result.meta_data['parameters'])
print('best parameters (dictionary):', result.parameter_map)
print('trace:', result.meta_data['optimization_trace'])

final energy: -10.37897827416127
best parameters: [0.8404659162867164, -3.759113178926179]
best parameters (dictionary): {'a': 0.8404659162867164, 't': -3.759113178926179}
trace: [-1.2143076105966697, 0.7588791337710157, 0.9665542416203782, 0.512210219453474, -0.5865243763525982, -0.7774354038825964, -0.6597150401936086, -1.2993337498308635, -0.869034473462408, -1.620796132306022, -1.8452061735267566, -2.1265190876302325, -2.4087078252751435, -2.673159899680575, -2.9495190627158694, -3.2134865954867573, -3.4889450747203306, -3.7393109721655406, -3.9765244074891912, -4.2322774027464405, -4.483599946687795, -4.674731035263205, -3.3036052016168123, -4.715893656140858, -4.865811124997464, -5.039250682023593, -5.226567556684156, -5.407037977139794, -5.559873965352185, -5.690421901977887, -5.794681095837122, -5.942463902816103, -6.081475118047292, -6.2097368583381565, -6.33505886311209, -6.460352799150998, -6.545081827972163, -6.655087025563991, -6.614815676901753, -6.516733890799152, -6.693267184092688, -6.725131612643694, -6.757358875148346, -6.788928935669324, -6.8107557788558335, -6.847462453560443, -6.878157710140355, -6.9124383435005825, -6.946615828768124, -6.980720767587634, -7.014738312603643, -7.0485174608492835, -7.078486654030733, -7.113947708342362, -7.146508217551165, -7.180671016516593, -7.214779782928378, -7.248845502964948, -7.282840429247688, -7.316504481362573, -7.3506924736481105, -7.384355805196323, -7.418241878768785, -7.452234038230894, -7.486056504099665, -7.517240305615317, -7.552092550848734, -7.5850158316577065, -7.619251112076947, -7.653411374036782, -7.6875105325987, -7.721589675861759, -7.75563640539851, -7.7897236661816205, -7.823786395615709, -7.857863451990067, -7.89194026390933, -7.926016720056387, -7.960092698161248, -7.994166946697607, -8.027940513995542, -8.062085638682005, -8.096164859831756, -8.130220893009133, -8.164302244746017, -8.198373485438085, -8.232448623095129, -8.266522534944881, -8.30059022020823, -8.334664708101265, -8.368732276110787, -8.402736786486223, -8.43681236760626, -8.470846329597371, -8.504925919021792, -8.538986176978218, -8.573066441918233, -8.607139977725225, -8.641215441065869, -8.675290189875579, -8.70936085579368, -8.743435786373107, -8.777506135465755, -8.811533570760357, -8.845608203483067, -8.879652557012, -8.913728096373022, -8.947788951367093, -8.98186845368109, -9.015938283754974, -9.05001568090243, -9.08409144230122, -9.118166952028595, -9.15224312289666, -9.186318843363354, -9.220394904748028, -9.254470905423796, -9.288546902243498, -9.3226228620446, -9.356698860909946, -9.390774859289728, -9.424850793219255, -9.458926803952226, -9.493002797206262, -9.527078791550823, -9.561154780617695, -9.595230780113681, -9.629306767838207, -9.66338276573509, -9.697458764598908, -9.731534736324333, -9.765610735345266, -9.799686731506121, -9.833762715584285, -9.867838717015513, -9.901914715647134, -9.93599025370216, -9.970066399547012, -10.004142246782209, -10.038218258159901, -10.072294278318518, -10.106370288198688, -10.140446287261305, -10.174522285655959, -10.208598284389575, -10.242674283166398, -10.276750281482002, -10.31082627982783, -10.34490227809733, -10.37897827416127]


An alternative is the qat.plugins.SeqOptim Plugin, which only works for a certain class of circuits but stands out for its shot-noise resilience.

Differentiating jobs

Many variational algorithms require computing the gradient of the cost function $$E(\vec{\theta}) = \langle \psi(\vec{\theta}) | H | \psi(\vec{\theta})\rangle$$. The gradient can be used in gradient-based optimization methods. QLM jobs come with methods to compute the derivative of $$E(\vec{\theta})$$ automically: differentiate() and gradient().

Examples of use of this feature are given in this notebook.

Testing for several initializations of the parameters

Variational optimization tends to be sensitive to the initial parameters it started from. You can insert the qat.plugins.MultipleLaunchesAnalyzer Plugin before a VQE optimizer to automatically perform several VQE runs and keep the lowest-energy result. This is illustrated in the notebook NOTEBOOK LINK ERROR.