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: {'t', 'a'}

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.3170456293753139 * (X|[0]) +
0.3170456293753139 * (X|[1]) +
0.3170456293753139 * (X|[2])
('RY', [0.5852045573898859], [0])
('RZ', [2.3408182295595434], [0])
('RY', [0.5852045573898859], [1])
('RZ', [2.3408182295595434], [1])
('RY', [0.5852045573898859], [2])
('RZ', [2.3408182295595434], [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: -1.0535543484035734
best parameters: [0.21386246146459703, 3.5228592844020548]
trace: [0.23023394601833708, 0.525598333230915, -0.1625373208852662, 0.24153779537957393, 1.1190350309461403, -0.013918491946085864, -0.06660669310438928, -0.050291849849898804, -0.18045239953365885, -0.14837787967627572, -0.20513176006445216, -0.21791345451744162, -0.24888764574313532, -0.2530856270643055, -0.28741147103484604, -0.2927615022323163, -0.32709569187752735, -0.3300463490782594, -0.35281443135401513, -0.36503750854421013, -0.37483905293998004, -0.3646173859872324, -0.38488337245530024, -0.39464905565080655, -0.39285212360897814, -0.4016197564079874, -0.4039301834533988, -0.3957589332280686, -0.4109350169845357, -0.41757676689138995, -0.42412494830421693, -0.4306361551955472, -0.43693471356721586, -0.443009952836967, -0.4494308522418564, -0.45578540979316007, -0.4618541542861915, -0.45983897311750477, -0.46723407596941013, -0.4680437394542225, -0.4572330229495508, -0.4761632226958782, -0.4823104386861832, -0.4883727923276844, -0.49272801565302005, -0.49972774933835784, -0.5050966182661131, -0.511850091914444, -0.5183339720078973, -0.5248403572156138, -0.5313687918492184, -0.5378841084336909, -0.5442995097072647, -0.5508435393440521, -0.5573808544315809, -0.5639003341649894, -0.5704255738796897, -0.5769504429688562, -0.5834746865852676, -0.5899954753689766, -0.596519651367486, -0.6030384872100165, -0.6094998479118106, -0.6160215579849863, -0.6224937920601328, -0.6290027852302037, -0.6354888504385794, -0.6417470348408066, -0.6481996561251921, -0.6544564045762152, -0.658752459188292, -0.6658469697433878, -0.6705632914572499, -0.6775748931634669, -0.68345162720198, -0.6901549292782395, -0.6966738595252107, -0.7031343625990352, -0.7096797791210385, -0.7161440152446009, -0.7226795651667086, -0.7291795393282529, -0.7357126617799159, -0.7422202214347153, -0.7487477890554384, -0.7552676957758593, -0.7617927491159681, -0.7683164541074002, -0.7746287130342893, -0.7812311551013976, -0.7876598886653607, -0.7942094050171223, -0.8006946606268895, -0.8072130353823462, -0.813726356366986, -0.8202162056592261, -0.8258004601596134, -0.8324954789235863, -0.8377914199305319, -0.8443937408067015, -0.8496009604778694, -0.8562229286496592, -0.8611651177846835, -0.8677958578319224, -0.872412801045249, -0.8791434666417766, -0.8833601548214083, -0.8903306242014501, -0.8942126308386168, -0.901453525011318, -0.9052994091852706, -0.9126972746979145, -0.9168760531643741, -0.9242472060683788, -0.9289719688234066, -0.9361924627491658, -0.9415213360362706, -0.9485261950635094, -0.954454298420911, -0.9611872489880045, -0.9676484046764948, -0.9741223194255795, -0.9803924846331032, -0.9864481475341722, -0.9926647172054, -0.9982623584633092, -0.9897853267112934, -1.0016476882515177, -1.0075145052840946, -1.0133476061497437, -1.018956551874509, -1.0226805382234356, -1.000977427900113, -1.031262877274588, -1.030237107321668, -1.0278753927525854, -1.03268528811321, -1.0343361138678575, -1.0359599370136885, -1.0375871358104902, -1.0392023380237996, -1.0404794541139208, -1.0421737594576108, -1.0438162186125086, -1.0454391136867855, -1.047068320890722, -1.0486931999794413, -1.050288153713508, -1.051926036910897, -1.0535543484035734]

This plugin also supports a nice feature: it can read optimization parameters directly from the job’s meta data. 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('trace:', result.meta_data['optimization_trace'])
final energy: -9.965780513890424
best parameters: [-3.569648018662705, 0.8404426840256503]
trace: [-0.26206620901978184, 1.3678633035898786, 0.4479530724113151, 0.41574725375156923, -0.23164782251837385, -0.011052544675557469, -0.5211534621536809, -0.16398004529488502, -0.2548266388339272, -0.7082714029057499, -1.3286155025279558, -1.8776263503509871, -2.352819651955327, -2.711749574883413, -3.3469669416467625, -3.8232122791884784, -4.190765794499425, -4.905904255892461, -4.790760906937601, -4.313807637809999, -4.896775442247454, -4.793699685050337, -4.9438988980389915, -4.966346701263004, -4.902177011627475, -5.075419424138152, -5.121878027574326, -5.1918217857292515, -5.256112705290899, -5.294045256649619, -5.37358028719393, -5.418082858581928, -5.492398857935661, -5.557086139187592, -5.625337372745027, -5.693804686491074, -5.762361767518781, -5.83076229748441, -5.898961062948015, -5.967045553875727, -6.0352081556002695, -6.103282874800803, -6.171424757325964, -6.239509575352557, -6.307616881737205, -6.375698489022737, -6.44345472357714, -6.511163841382887, -6.578858068067451, -6.64507573239309, -6.7101631325584705, -6.776038865933999, -6.8366273939966735, -6.755199133438202, -6.876079485030762, -6.940720453122382, -7.006981329691362, -7.076167753545612, -7.150626530226583, -7.223447328161315, -7.288294606093979, -7.357396824419305, -7.423914276807704, -7.492720561374094, -7.559017720271933, -7.62781992562907, -7.694663216753306, -7.7633445860309935, -7.830055265614726, -7.898760045329942, -7.965585445203045, -8.03426216275245, -8.100896518939924, -8.16961510203216, -8.236167747886135, -8.304893775627146, -8.371152138353004, -8.43985214178907, -8.50561601322762, -8.574060052383473, -8.638698090050674, -8.705105776306157, -8.76816462562866, -8.786885879154282, -8.87485945851803, -8.871425154066277, -8.884251392132052, -8.925087052634757, -8.956522910619643, -8.98922591681785, -9.019363800898503, -9.01155117888316, -9.046816346069381, -9.045474334677342, -9.049501697627536, -9.025766415386476, -9.069517356342383, -9.087228324406915, -9.103839892918844, -9.120819112939484, -9.137573837387269, -9.152742533497324, -9.170153314025423, -9.18707407451272, -9.204043329088949, -9.220798638032168, -9.237758894406682, -9.254744487354253, -9.271568755422678, -9.288541682513143, -9.305538351850682, -9.322414588687602, -9.339397417026449, -9.356402077388864, -9.373314335306434, -9.38699049366343, -9.404674139992359, -9.421782136890485, -9.4387083199882, -9.455736512937369, -9.472750479428093, -9.489725037730057, -9.505940225862016, -9.523177063219025, -9.540178287607901, -9.557175587953912, -9.574006884276756, -9.591012398079958, -9.608015628626143, -9.62505712851756, -9.642096297269891, -9.659134239767152, -9.676169489362273, -9.693208736591885, -9.710246104163737, -9.727284020376239, -9.74432066258894, -9.761356553231003, -9.778394251747319, -9.79543180414771, -9.812468956955785, -9.829503162760021, -9.846533975414172, -9.86357026404613, -9.880604761084792, -9.897631192399189, -9.914670007904038, -9.931704406939105, -9.948742498429556, -9.965780513890424]