Path: blob/main/chemistry-simulations/adapt_vqe.ipynb
579 views
Implementing and Parallelizing ADAPT-VQE
Adaptive Derivative-Assembled Pseudo-Trotter VQE (ADAPT-VQE) is a variational hybrid quantum-classical algorithm with many benefits over traditional VQE. This lab is an exploration of ADAPT-VQE and how it can be implemented using CUDA-Q.
What you will do:
Learn the concepts behind the ADAPT approach
Explore the importance of selecting good operator pools
Write the quantum kernels and main workflow for ADAPT-VQE
Parallelize multiple parts of the code and simulate running it on multiple GPUs
Learn how CUDA-Q Solvers make using ADAPT-VQE pleasantly simple
CUDA-Q Syntax you will use:
methods for CUDA-Q
kernel
construction including state passingobserve
andget_state
to evaluate kernelsThe
mqpu
backend, the associatedobserve_async
andget_state_async
functions, andget
.exp_pauli
to dynamically construct quantum circuits from CUDA-Q Spin OperatorsCUDA-Solver's
solvers.adapt_vqe
andcreate_molecule
functions.
Prerequisites: This lab assumes you have a basic understanding of quantum algorithms and understand the standard VQE workflow. If you need a refresher, check out the "Quick Start to Quantum Computing with CUDA-Q" series to learn how to code a variational algortim with CUDA-Q from scratch and check out this tutorial for more details about VQE. This lab focuses on a chemistry application, so some knowledge of chemistry terminology is helpful but not required. All exercises emphasize the quantum computing concepts rather than the chemistry.
💻 Just a heads-up: This notebook is designed to be run on an environment with a GPU. If you don't have access to a GPU, feel free to read through the cells and explore the content without executing them. Enjoy learning! ⭐
Before we get started, execute the cells below to load the required packages.
ADAPT-VQE algorithm
The variational quantum eigensolver (VQE) is a well known quantum chemistry technique, which estimates the ground state of a molecule by optimizing a parameterized ansatz through iterative loops involving expectation values computed on a QPU and classical parameter updates performed by a supercomputer. VQE suffers from a a number of issues including costly sampling of the Hamiltonian terms, and the so called barren plateau problem where even modest problems sizes do not converge due to the flat optimization landscape.
For VQE to have any viability for near term applications, clever implementations are necessary to avoid these challenges. One promising approch is the Adaptive Derivative-Assembled Pseudo-Trotter VQE (ADAPT-VQE) approach. The core idea is rather than have a fixed ansatz, to "adaptively" build the ansatz over the course of the algorithm by performing the following steps:
On classical hardware, compute the one and two-electron integrals and transform the Hamiltonian to a qubit representation.
Build an operator pool, usually corresponding to physical excitations from techniques like unitary coupled cluster with singles and doubles (UCCSD).
Initialize qubits in a reference Hatree Fock state.
Measure the commutator of the Hamiltonian with each operator in the operator pool to compute the gradient.
If the norm of the gradient is below a threshold, then exit. Otherwise, add the operator with the largest gradient contribution along with a new variational parameter, .
Run VQE on this circuit to obtain optimized variational parameters.
Repeat steps 4 through 6 until convergence.
All of the steps are represented below in the following figure.
If you are a careful reader, this may seem like a we are now running an entire VQE experiment for each iteration. This is correct, but, the additive constructions of the ansatz means that we begin with very simple and easy to optimize circuits relative to a starting a VQE procedure with a fixed ansatz with many circuit parameters. Using the gradient allows strategic operator selection so we are optimizing parameters for operators more likely to converge to the ground state. Adding new parameters one at a time to an already converged solution allows each iteration to begin with a warm start. In practice, ADAPT-VQE generally converges faster than VQE and is more resilient to barren plateaus given this warm start.
To get a better sense of the process for ADAPT-VQE and how it compares with VQE, try the following interactive widget linked below. The data is artificial, but is qualitatively representative of the two approaches.
The original paper for ADAPT-VQE entitled "An adaptive variational algorithm for exact molecular simulations on a quantum computer" presents data (figure below) that demonstrates the performance benefits of the ADAPT approach. The chart below shows the convergence of different ansatz construction techniques compared to ADAPT as a function of variational parameters for BeH. ADAPT converges much faster and with far fewer parameters.
The standard UCCSD ansatz is noted in the plot as having nearly twice the number of parameters as ADAPT as well as much worse convergece.
The rest of this lab will walk you though coding up an ADAPT-VQE implementation. You will explore the impact of different operator pools as well as learn how ADAPT is well suited for parallelization across multiple QPUs.
Building the Operator Pool and Computing the Gradient
Before building the entire workflow, it is helpful to construct some of the subroutines. You will begin by creating subroutines to define the operator pool and to compute the gradient. First, consider the operator pool. Any Pauli word could be placed in the operator pool. An operator can then be selected and applied to a quantum circuit as to build a parameterized quantum circuit ansatz. For traditional VQE, the same procedure is used, except all of the operators are applied a priori to construct a fixed parametrized ansatz.
Naively one might think that a bigger and more diverse operator pool is inherently better. However, because quantum chemistry has certain symmetries, many operators would be poor choices and even violate physical constraints of the system.
A common choice of operators ubiquitous in chemistry comes from unitary coupled cluster with single and double excitations (UCCSD). Where single excitations are defined as:
And double excitations as:
Exercise 1:
This exercise will allow you to explore some of the reasons why poor selection of an operator pool can lead to convergence issues with ADAPT-VQE.
We've demonstrated how to create an molecule using solvers
. Your task is to prepare a Hartree Fock kernel. The second part of this exercise is to write one more kernel that applies to the Hartree Fock state. To help guide you, we've created a kernel that applies as an example using exp_pauli
where the coefficient is a variational parameter.
Run VQE and comment on the results.
The reason the operator did not work, is that it commutes with the Hamiltonian. This means that it has no impact on the expectation value and is therefore a useless addition to an operator pool, potentially adding to the circuit depth with no gain in accuracy. Confirm this below by computing its commutator with the Hamiltonian:
Another potential issue is adding an operator that breaks a symmetry like the number of electrons. Even if such an operator lowered the energy, you no longer have a solution for the molecule in question.
Use the number operator to compute and prove that produces states that have more than 2 electrons.
Auxiliary scripts in CUDA-Q make it easy to build a standard operator pool using a function like get_uccsd_pool
to provide a list of spin operators corresponding to all UCCSD excitations. The code also splits each operator into a list of Pauli words (word_pool
) and a list of the associated coefficients (sign_pool
).
This section of code also includes a function to compute the commutator operator for each element in the full operator pool. Note, that the factor of i
was removed from each operator to make data transfer into CUDA-Q kernels easier, but it is included in the commutator function below for accuracy.
Preparing Kernels
So far, you have completed a number of the prerequisite functions required to construct the ADAPT-VQE workflow. The figure below shows green checks for these already completed items. You will now focus on the yellow boxes where a state is prepared from which the gradient is computed.
Working backwards, the simple kernel below takes advantage of CUDA-Q's state passing capabilites to build a kernel constructed from an arbitrary state vector. Such an abstraction is helpful as it allows easy separation from an initial state kernel (the Hartree Fock state) and the resulting kernel from each ADAPT-VQE iteration. Regardless of the origin, cudaq.get_state()
can be used to extract the state vector which will be input to the kernel psi
to initialize the next iteration.
Exercise 2:
You will now construct the two sorts of kernels which will be used to construct input states for psi
.
A kernel which prepares the Hartree Fock state
A kernel which applies all operators selected from the previous ADAPT-VQE iterations on the Hartree Fock state.
The second kernel is the tricky one here as the kernel must receive a number of inputs in order to apply the selected operators and their optimized 's. CUDA-Q kernels require inputs to be simple typed lists consisting of strings, integers, or floats. This may seem inconvenient, but is an important aspect of ensuring kernels are lightweight and result in high performance computations. Thus, it is easiest to break the input operators into separate lists corresponding to the coefficients, and Pauli words of the singles and doubles operators, respectively.
The kernel definition is provided for you below. Fill in the appropriate code to apply all of the operators selected from the ADAPT-VQE process. Hints:
Consider two separate loops, one for applying the singles operators and one for applying the doubles, each starting from a flattened list of all operators.
Assume the theta list contains the parameters corresponding to the singles operators followed by the doubles operators sorted by the order in which they were added.
Also, do not forget to initialize the Hartree Fock state.
Putting it all Together
Exercise 3:
You are now ready to put all of your work together and construct the complete ADAPT-VQE workflow. For this exercise, you will fill in the incomplete parts of the code (the yellow boxes in the figure below) and then run your code to compute molecular energies.
First, scan the entire cell below and see if the rough workflow makes sense.
There are four "TODO" sections. Complete the following tasks where annotated below:
Compute the commutator for each term in the operator pool, store as the gradient vector, and check if the gradient norm has converged.
Identify and print the operator selected at step , adding it to the list of selected operators.
Define the cost function for the intermediate VQE optimization that includes the kernel with the new operator added.
Save the updated energy and compute a new trial state with the optimized parameters
By completing these four central tasks, you should be able to run ADAPT-VQE and compute the energy of the ground state. How does it compare to the classically computed FCI energy?
Exercise 4:
Now that you have a working version of ADAPT-VQE, explore how it matches up to standard VQE. Go back to the previous cell and change the molecule to LiH. Run ADAPT-VQE for a max of 5 iterations. Then, run standard VQE using the cell below and plot the convergence of the two data series. How do the convergence behaviors compare? How many parameters does standard VQE need to optimize? What is the maximum number of parameters that require optimization for your ADAPT-VQE run with LiH?
Parallelizing ADAPT-VQE
In the future, data centers will host multiple QPUs, allowing quantum algorithms to parallelize and run faster. For example, obtaining one million circuit samples can be accomplished by pooling 100,000 samples from 10 QPUs.
ADAPT-VQE is well suited to benefit from parallelization in a few parts of the code. In the next exercise, you will explore how this can speed up the runtime of ADAPT-VQE.
Computing the commutators for the gradient in each ADAPT-VQE step is a trivially parallelizable task. Each operator is independent of the others, so each one can be evaluated on a separate QPU. CUDA-Q's MQPU backend allows you to simulate this by designating each circuit to run on a different virtual QPU simulated by a GPU.
Examine the code below to see how this is done. Simply change the backend with cudaq.set_target('nvidia', option='mqpu,fp64')
and cudaq.observe
with cudaq.observe_async
and add a variable qpu_id
which specifies an integer corresponding to your index of available GPUs. These results are run asynchronously and stored in a list of futures which are then accessed later with get()
. One slight complication is when the initial set and subsequent states are obtained with get_state
, they must also be updated to get_state_async
. You can no longer save the new state at the end of each new iteration as the get_state_async
needs to run on the same qpu_id
as the command computing the expectation value, otherwise the state will be prepared on a different GPU.
Exercise 5:
Your task is to further parallelize the code and modify the parameter_shift
function, which computes the gradient of the VQE step, to run asynchronously across two simulated QPUs. Use the parallel code for the commutator evaluation as a guide for this.
Next, add flags to capture the execution of the serial and parallel implementations. Then, run them on LiH for a maximum of 5 steps (iterations). Do you notice a difference? You may not, and this is because the problem is too small to benefit from the overhead required to distribute the information across multiple simulated QPUs. Increase the maximum number of steps to 15 and try again. Now do you notice the difference? The longer ADAPT-VQE runs, the harder each circuit evaluation and VQE procedure becomes, meaning there is greater benefit from using parallelization. Similar to the fact that a GPU must be used for a problem large enough to benefit from parallelization, asynchronous execution across multiple QPUs will be most valuable when the individual tasks are harder.
Conclusion
At this point, you have coded an ADAPT-VQE implementation of your own and explored specific parts of the workflow in detail like operator pool selection. There is great benefit in understanding the details, but often it is helpful to have an out-of-the-box implementation that is optimized and easy to plug into your larger workflow or ready to generate data for your research. CUDA-Q Solvers provides easy to use ADAPT-VQE functions so you can produce the same results without the need to code at the kernel level.
The code below, with just a few lines, runs the entire procedure coded above. You can easily swap out operator pools, molecules, and still run in parallel by following the instructions outlined in the docs.
ADAPT methods can also be generalized beyond chemistry and extend to problems like QAOA. This tutorial in the CUDA-Q docs demonstrates how one can setup a QAOA problem and iteratively add layers where the mixer Hamiltonian is selected via the ADAPT process, leading to faster convergence. If you have completed the series Divide-and-Conquer QAOA for MaxCut, a good exercise might be to revisit your code and try improving it with it with ADAPT-QAOA.
Though ADAPT methods have clear benefits over conventional variational algorithms, they still suffer from the same fundamental measurements problems and barren plateaus for large problem sizes. Nevertheless, they are fantastic drop-in substitutions for any situation where a variation method is used for a subroutine like state preparation.
Generalizing beyond ADAPT methods, with this notebook you have learned how to use fundamental CUDA-Q functions like exp_pauli
, get_state_async
and observe_async
as well as how to use a CUDA-Q Solvers method out-of-the box. To learn more, visit the CUDA-Q docs page and the CUDA-Q Solvers docs.