Variational Quantum Algorithms
Implemented by Ben Braham as part of a Unitary Fund microgrant.
Overview
Variational Quantum Algorithms (VQAs) are a hybrid quantumclassical optimization algorithm in which an objective function (usually encoded by a parameterized quantum circuit) is evaluated by quantum computation, and the parameters of this function are updated using classical optimization methods. Such algorithms have been proposed for use in NISQera quantum computers as they typically scale well with the number of available qubits, and can function without high faulttolerance.
In QuTiP, VQAs are represented by a parameterized quantum circuit, and include methods for defining a cost function for the circuit, and finding parameters that minimize this cost.
Constructing a VQA circuit
The VQA
class allows for the construction of a parameterized circuit from VQABlock
instances, which act as the gates of the circuit. In the most basic instance, a VQA
should have:
Property 
Description 


Positive integer number of qubits for the circuit. 

Positive integer number of repetitions of the layered elements of the circuit. 

String referring to the method used to evaluate the circuit’s cost. Either “OBSERVABLE”, “BITSTRING”, or “STATE”. 
For example:
from qutip_qip.vqa import VQA
VQA_circuit = VQA(
num_qubits=1,
num_layers=1,
cost_method="OBSERVABLE",
)
After constructing this instance, we are ready to begin adding elements to our parameterized circuit. Circuit elements in this module are represented by VQABlock
instances. Fundamentally, the role of this class is to generate an operator for the circuit. To do this, it keeps track of its free parameters, and gives information to the VQA
instance on how to update them. The operator itself can be generated by a userdefined function call, a parameterized Hamiltonian to exponentiate, a precomputed unitary operator, or a string referring to a gate that has already been defined (either by the user already, or a gate native to QuTiP).
In the absence of specification, a VQA block takes a Qobj
as the Hamiltonian \(H\), and will generate a unitary with free parameter, \(\theta\), as \(U(\theta) = e^{i \theta H}\). For example,
from qutip_qip.vqa import VQA, VQABlock
from qutip import tensor, sigmax
VQA_circuit = VQA(num_qubits=1, num_layers=1)
R_x_block = VQABlock(
sigmax() / 2, name="R_x(\\theta)"
)
VQA_circuit.add_block(R_x_block)
We added our block to the VQA_circuit
with the VQA.add_block()
method. Calling the VQA.export_image()
method renders an image of our circuit in its current form:
Optimisation Loop
After specifying a cost method and function to the VQA
instance, there are various options for optimization of the free circuit parameters. Calling VQA.optimize_parameters()
will begin the optimization process and return an OptimizationResult
instance. By default, the method will randomize initial parameters, and use the nongradientbased COBYLA
method for parameter optimization. Users can specify:
Initial parameters. Given as a list, with length corresponding to the number of free parameters in the circuit. The number of free parameters can be computed automatically with the
VQA.get_free_parameters_num()
method. Alternatively, the string ‘zeros’ will initialize all parameters as 0; and ‘random’ will initialize parameters randomly between 0 and 1. Defaults to ‘random’.Optimization method. This can be a string referring to a predefined
SciPy
method listed here, or a callable function.Jacobian computation. A flag will tell the optimization method to compute the Jacobian at each step, which is passed to the optimizer so that it can use gradient information.
Layerbylayer training. Optimize parameters for the circuit with only a single layer, and hold these fixed while adding additional layers, up to
VQA.num_layers
.Bounds and constraints. To be passed to the optimizer.
The OptimizationResult
class provides information about the completed optimization process. For example, the probability amplitudes of different measurement outcomes of the circuit postoptimization can be plotted with OptimizationResult.plot()
.
Below, we run an optimization on a toy circuit, tuning a parameterized \(x\)rotation gate to try to maximise the probability amplitude of the \(1\rangle\) state.
>>> from qutip_qip.vqa import VQA, VQABlock
>>> from qutip import sigmax, sigmaz
>>> circ = VQA(num_qubits=1, cost_method="OBSERVABLE")
Picking the Pauli Z operator as our cost observable, our circuit’s cost function will be: \(\langle\psi(t) \sigma_z  \psi(t)\rangle\)
>>> circ.cost_observable = sigmaz()
Adding a Pauli X operator as a block to the circuit, the operation of the entire circuit becomes: \(e^{i t X /2}\).
>>> circ.add_block(VQABlock(sigmax() / 2))
We can now try to find a minimum in our cost function using the SciPy inbuilt LBFGSB (LBFGS with box constraints) method. We specify the bounds so that our parameter is \(0 \leq t \leq 4\).
>>> result = circ.optimize_parameters(method="LBFGSB", use_jac=True, bounds=[[0, 4]])
Accessing result.res.x
, we have the array of parameters found during optimization. In our case, we only had one free parameter, so we examine the first element of this array.
>>> angle = round(result.res.x[0], 2)
>>> print(f"Angle found: {angle}")
Angle found: 3.14
Finally, we can plot our the measurement outcome probabilities of our circuit after optimization.
>>> result.plot()
In this simple example, our optimization found that (neglecting phase) \(R_x(\pi) 0\rangle = 1\rangle\). Of course, this very basic usage generalizes to circuits on multiple qubits, with more complicated cost functions and optimization procedures.