## Implementing a Variational Quantum Algorithms module in QuTiP

2022-02-09
Ben Braham

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.

## Background

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`’s `qutip-qip` package, which allows users to define and evaluate the effectiveness of VQAs on their local machines.

## Module Overview

Most quantum algorithms deal with quantum circuits, which are made up of quantum gates. In `QuTiP`’s `qutip-qip` package, there exist classes — `QubitCircuit` and `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, `VQABlock` and `VQA`, which are abstractions on `Gate` and `QubitCircuit`, respectively.

### 1. The VQA_Block class

The `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` receives.

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 `VQABlock`s are evaluated to inform the minimization of the cost of a circuit.

### 2. The VQA class

The `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.

The `VQA` class stores a list of `VQABlock` instances, and can perform an optimization of their free parameters with a number of different options.

By allowing `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 `VQABlock`s 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.

## Example

Here we will look at how one might implement a VQA in this module. To begin, we’ll introduce the `partition` problem.

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  eiβjHBeiγ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(S1)` `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 `QuTiP`’s `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.

Other examples, including the `max-cut` problem examined in the original QAOA paper, can currently be found on my GitHub.

I opened an issue to discuss with the QuTiP admins how to best include these features into the main `qutip-qip` project codebase.

## Future work

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.

## Acknowledgements

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!