Because evolution is unitary.
Implementing a Variational Quantum Algorithms module in QuTiP
I’m an undergraduate student at the University of Sydney, studying Physics and Computer Science. I’ve worked on a few quantum computing projects in the past and was really excited to be awarded a microgrant by the Unitary Fund to work on this project. My personal website is available at benbraham.com.
Many groundbreaking quantum algorithms (e.g. Shor’s, Grover’s) require quantum computers with a large number of qubits and high fault-tolerance to produce useful output for non-trivial problem instances. While this is likely some time away, there exist algorithms that aim to leverage the current power of noisy intermediate–scale quantum (NISQ) systems. Variational Quantum Algorithms (VQAs) are a hybrid classical-quantum type of algorithm in which parameterized quantum circuits (PQCs) are optimized, with an objective function being evaluated on quantum hardware and the parameter update occurring on a classical machine.
One current area of interest in VQAs is that of the “initialization” for the algorithm. Problems are described by a quantum circuit, with its structure based on a circuit ansatz - usually inspired by the problem one wishes to solve. The performance of the algorithm is heavily influenced by the circuit structure, as well as the initial parameters of the circuit. It is therefore worthwhile to have the tools to test VQA formulations on a classical simulation of a quantum system, before expending time and resources to apply it on real quantum hardware.
QuTiP is a open-source
python project for simulating quantum systems. In this project, I designed and wrote an implementation of a
VQA module for
qutip-qip package, which allows users to define and evaluate the effectiveness of VQAs on their local machines.
Most quantum algorithms deal with quantum circuits, which are made up of quantum gates. In
qutip-qip package, there exist classes —
Gate — which simulate the dynamics of quantum circuits.
For VQAs, we now want these gates to be parameterized. In addition to the gate parameters, we want initialization options, which are fixed throughout the optimization process, to be easily defined in one object. For these purposes, we define two new classes,
VQA, which are abstractions on
1. The VQA_Block class
VQABlock class encapsulates one component of our circuit and can store the action of one or more quantum gates. It can hold parameters, or keep note of how many free parameters it needs as input to be translated into a gate on the quantum circuit.
In quantum computing, all quantum gates can be described by unitary operators. Our
VQABlock class has a method that will generate a unitary operator for the quantum circuit; but crucially, the value of this operator can change based on the parameters the
VQABlock holds information about the operator it can generate, the parameters it needs, and methods to compute derivates of its operator. Using this information, the algorithm can compute gradients of its cost function. The optimization process is thus a loop in which
VQABlocks are evaluated to inform the minimization of the cost of a circuit.
2. The VQA class
VQA class holds information about the problem being optimized. It has methods to run optimization procedures with various options, such as the number of qubits required, the number of layers — or repetitions — of the circuit, and the optimization method to use. Here, we allow access to all of the function minimization methods available within the
scipy library, as well as allowing user-defined optimization methods.
One part of the optimization process is specifying a “cost function” which the optimizer can use to assess the performance of a particular parameter set. In the case of a real quantum computer, the only output we have comes from taking measurements of the system. In quantum computing, these are usually represented as “bitstrings”, which encode the measurement outcome. For example, the measurement outcome of a two qubit system with one qubit measured in the |0> state, and another in the |1> state can be represented by the bitstring |01>.
If the cost method is set to
BITSTRING, then the user needs to provide a function that takes in this string of 1’s and 0’s, and returns the associated cost.
Because we’re performing a quantum simulation, we don’t just have to deal with measurements — we can “peek under the hood” of the simulation to get more information, including the actual state of the system before measurement. With this, we allow two more methods for specifying a cost:
If the cost method is set to
STATE, then the user can provide a function that takes in a quantum state and returns a cost.
If the cost method is set to
OBSERVABLE, then the user can provide an observable and the cost becomes the expectation value of that observable in the final state of the circuit.
VQA class stores a list of
VQABlock instances, and can perform an optimization of their free parameters with a number of different options.
VQABlock instances to take more complicated structures and custom functions that generate their unitaries, we enable a range of quantum control problems to be approached within the framework of a PQC. One such abstraction is the
Parameterized_Hamiltonian class, which can sit in a
VQABlock within the circuit. On the side of the user, it’s easy to define different types of
VQABlocks with different methods for generating unitaries, and let the module decide how to compute gradients for you.
Below I have included a general example of how the module can be used, in which a combinatorial optimization problem is approached using a VQA.
Here we will look at how one might implement a VQA in this module. To begin, we’ll introduce the
partition problem asks if there is a way to partition a set of integers, S = [s0,s1,…sn], into two subsets S1 and S2 such that the sum of elements in each of these sets is equal. For example, the set S = [1,4,3] can be partitioned into the sets S1 = [1,3], S2 = . The optimization version of this algorithm asks for the partitioning that minimizes the difference in these two sums.
This problem is known to be NP-Hard, so there is no known efficient classical algorithm for solving it. One way of finding approximate solutions to this (although it is not yet clear if VQAs give a speed-up here), and other combinatorial problems is through a VQA known as the Quantum Approximation Optimization Algorithm (QAOA).
The QAOA gives us a circuit ansatz that we can use to approximate solutions to combinatorial problems. In general, if we can encode the solution to our problem in the ground state of a Hamiltonian, HP, and we can pick another Hamiltonian, HB, that doesn’t commute with HP, then our circuit takes the form:
U(β,γ) = ∏pj = 0 e−iβjHBe−iγjHP
where each γj and βj is a free parameter, and we repeatedly apply unitaries generated by HP and HB, p times.
We now need to work out how to encode our problem in terms of the ground state of a Hamiltonian. The cost of our solution is given by the difference of the sums of the two sets, i.e.
sum(S2). As S1 ∪ S2 = S and S1 ∩ S2 = ∅, this is equivalent to taking a weighted sum over S, where we assign a weight of − 1 to elements partitioned into S2. We can describe this with the Hamiltonian
HP = (∑si ∈ S siσz(i))2
where σz is the Pauli z operator, and the superscript indicates it acts on the ith qubit. Clearly the lowest energy state for this Hamiltonian corresponds to the partitioning of the elements of S that minimizes the difference between sum(S1) and sum(S2). Now that we have encoded the cost minimization into the energy levels of a Hamiltonian, we’re ready to translate our idea into code.
After defining our Hamiltonians as quantum objects (matrices with more bells and whistles) with
Qobj class, we can define our VQA as follows:
This code has constructed a quantum circuit that looks as follows:
After calling the
optimize_parameters method, we can plot the results of the optimization process in terms of the measurement outcome probabilities.
Here, we have passed in the
label_sets parameter to the plot function, which labels measurement outcomes by their corresponding set partitioning of the problem instance.
There are many parameters to tweak here, such as the number of layers used, initialization conditions, the optimization algorithm parameters and constraints, as well as gradient computations. It is my hope that this tool allows anyone to dive straight into VQAs.
I opened an issue to discuss with the QuTiP admins how to best include these features into the main
qutip-qip project codebase.
There are always possible optimizations and additions for a tool with this kind of scope. There have already been some great suggestions on the associated GitHub Issue for integrating this with other
QuTiP tools, and when the pull request goes up, there will likely be more. Please feel free to contribute or join the discussion!
This project has laid the foundations for a very general set of tools to define VQA problems. The code written should be robust enough that expansions to allow more specific formulations are possible.
Throughout my work on this project, I’ve been privileged to have the supervision of A/Prof. Daniel Burgarth and Dr. Mattias Johnsson at Macquarie University.
Thanks to the Unitary Fund for supporting my project, and for all the advice and chats along the way!