Adapt-VQE
In 2019, ADAPT-VQE was introduced in this paper with the purpose of creating a more accurate, compact, and problem-customized ansatz for VQE. This version will hereby be denoted fermionic-ADAPT-VQE to distinguish it against a more recent version that will be covered shortly. The idea behind the proposal is to let the molecule in study ‘choose’ its own state preparation circuit, by creating the ansatz in a strongly system-adapted manner.
The ADAPT-VQE algorithm constructs the molecular system’s wavefunction dynamically and can in principle avoid redundant terms. It is grown iteratively in the form of a disentangled UCC ansatz as given in the below equation:
$$\prod_{k=1}^{\infty} \prod_{pq} \left( e^{\theta_{pq} (k)\hat{A}_{p,q}}\prod_{rs} e^{\theta_{pqrs} (k)\hat{A}_{pq,rs}} \right) |\psi_{\mathrm{HF}} \rangle$$which is the in the form of a long product of one-body $\hat{A}_{p,q}$ and two-body $\hat{A}_{pq,rs}$ operators generated from the pool of excitations, where each of the variational parameters $\left\{ \theta_{pq} ,\theta_{pqrs} \right\}$
At each step, an operator or a few operators are chosen from a pool: the operator(s) contributing to the largest energy deviations is (are) chosen and added gradually to the ansatz until the exact FCI wavefunction has been reached is associated to an operator.
Algorithm Steps
Initialize Circuit: Start with the identity circuit $U^{(0)}(\theta) = I$, where the state is initialized to the Hartree-Fock state $|\Psi_{HF}\rangle$.
Gradient Measurement: For the current ansatz state $|\Psi^{(k-1)}\rangle$, compute the energy gradient for each operator $A_m$ in the operator pool using the formula:
$$ \frac{\partial E^{(k-1)}}{\partial \theta_m} = \langle \Psi(\theta_{k-1}) | [H, A_m] | \Psi(\theta_{k-1}) \rangle. $$Check Gradient Norm: Evaluate the norm of the gradient vector $||g^{(k-1)}||$. If it is below the threshold $\epsilon$, the algorithm stops. If not, proceed to the next step.
Select Operator with Maximum Gradient: The operator with the largest gradient is chosen, and its corresponding variational parameter $\theta_k$ is added to the ansatz.
Update Ansatz: Perform a Variational Quantum Eigensolver (VQE) experiment to re-optimize all the parameters $\{\theta_k, \theta_{k-1}, \dots, \theta_1\}$ in the ansatz.
Repeat: Return to step 2 and repeat the process until convergence.
In the following numerical simulation, we used UCCGSD excitation for $H_2$ molecule in 6-31g basis set thus give us 8 qubits; we can run the following script to see the result (The result will not be shown all in here due to its long scrolled lines,you can see it yourself when running the script )
1from openvqe.common_files.molecule_factory_with_sparse import MoleculeFactory
2from openvqe.adapt.fermionic_adapt_vqe import fermionic_adapt_vqe
3molecule_factory = MoleculeFactory()
4
5## non active case
6molecule_symbol = 'H2'
7type_of_generator = 'spin_complement_gsd'
8transform = 'JW'
9active = False
10r, geometry, charge, spin, basis = molecule_factory.get_parameters(molecule_symbol)
11print(" --------------------------------------------------------------------------")
12print("Running in the non active case: ")
13print(" molecule symbol: %s " %(molecule_symbol))
14print(" molecule basis: %s " %(basis))
15print(" type of generator: %s " %(type_of_generator))
16print(" transform: %s " %(transform))
17print(" --------------------------------------------------------------------------")
18
19print(" --------------------------------------------------------------------------")
20print(" ")
21print(" Generate Hamiltonians and Properties from :")
22print(" ")
23print(" --------------------------------------------------------------------------")
24print(" ")
25hamiltonian, hamiltonian_sparse, hamiltonian_sp, hamiltonian_sp_sparse, n_elec, noons_full, orb_energies_full, info = molecule_factory.generate_hamiltonian(molecule_symbol,active=active, transform=transform)
26nbqbits = len(orb_energies_full)
27print(n_elec)
28hf_init = molecule_factory.find_hf_init(hamiltonian, n_elec, noons_full, orb_energies_full)
29reference_ket, hf_init_sp = molecule_factory.get_reference_ket(hf_init, nbqbits, transform)
30print(" --------------------------------------------------------------------------")
31print(" ")
32print(" Generate Cluster OPS from :")
33print(" ")
34print(" --------------------------------------------------------------------------")
35print(" ")
36pool_size,cluster_ops, cluster_ops_sp, cluster_ops_sparse = molecule_factory.generate_cluster_ops(molecule_symbol, type_of_generator=type_of_generator, transform=transform, active=active)
37# for case of UCCSD from library
38# pool_size,cluster_ops, cluster_ops_sp, cluster_ops_sparse,theta_MP2, hf_init = molecule_factory.generate_cluster_ops(molecule_symbol, type_of_generator=type_of_generator,transform=transform, active=active)
39
40print('Pool size: ', pool_size)
41print('length of the cluster OP: ', len(cluster_ops))
42print('length of the cluster OPS: ', len(cluster_ops_sp))
43print(hf_init_sp)
44print(reference_ket)
45print(" --------------------------------------------------------------------------")
46print(" ")
47print(" Start adapt-VQE algorithm:")
48print(" ")
49print(" --------------------------------------------------------------------------")
50print(" ")
51
52
53n_max_grads = 1
54optimizer = 'COBYLA'
55tolerance = 10**(-6)
56type_conver = 'norm'
57threshold_needed = 1e-2
58max_external_iterations = 35
59fci = info['FCI']
60fermionic_adapt_vqe(hamiltonian_sparse, cluster_ops_sparse, reference_ket, hamiltonian_sp,
61 cluster_ops_sp, hf_init_sp, n_max_grads, fci,
62 optimizer,
63 tolerance,
64 type_conver = type_conver,
65 threshold_needed = threshold_needed,
66 max_external_iterations = max_external_iterations)
We make an analysed plot for the converged energy and the fidelity. It took 5 fermionic adapt iteration and the converged energy is -1.1516 Ha with the fidelity : 0.999 and 368 number of CNOT gates
Second version of OpenVQE (Updated code)
Parameters
- Molecule Symbol:
H2
- Type of Generator:
spin_complement_gsd
- Transformation:
JW
- Active:
False
Workflow
- Initialization: Initialize the VQE algorithm with the specified parameters.
- Execution: Execute the VQE algorithm to find the ground state energy.
- Results: Plot the energy results and error results obtained from the VQE execution.
1from openvqe.vqe import VQE
2import matplotlib.pyplot as plt
3import numpy as np
4
5
6molecule_symbol = 'H2'
7type_of_generator = 'spin_complement_gsd'
8transform = 'JW'
9algorithm = 'fermionic_adapt'
10
11opts = {
12 'n_max_grads': 1,
13 'optimizer': 'COBYLA',
14 'tolerance': 10**(-6),
15 'type_conver': 'norm',
16 'threshold_needed': 1e-2,
17 'max_external_iterations': 35
18 }
19
20
21vqe_non_active = VQE.algorithm(algorithm, molecule_symbol, type_of_generator, transform, False, opts)
22vqe_non_active.execute()
Make the plot
1
2energies_1, energies_2 = vqe_non_active.iterations['energies'], vqe_active.iterations['energies']
3# Plot results with custom styles
4plt.figure(figsize=(14, 8)) # Larger plot size
5plt.plot(
6 energies_1,
7 "-o", # Line style with circle markers
8 color="orange", # Use custom color
9 label=f"Non active space"
10)
11plt.plot(
12 energies_2,
13 "-o", # Line style with circle markers
14 color="red", # Use custom color
15 label=f"Active space"
16)
17plt.plot(
18 [vqe_non_active.info['FCI']] * max([len(energies_1), len(energies_2)]),
19 "k--",
20 label="True ground state energy(FCI)"
21)
22plt.xlabel("Optimization step", fontsize=20)
23plt.ylabel("Energy (Ha)", fontsize=20)
24
25plt.xticks(fontsize=16) # Set font size for x-axis tick labels
26plt.yticks(fontsize=16)
27
28# Move the legend box outside the plot
29plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0., fontsize=12)
30plt.grid()
31plt.title(f"VQE {algorithm} energy evolution for {molecule_symbol} molecule", fontsize=20)
32plt.tight_layout() # Adjust layout to prevent clipping
33
34plt.show()
You can go to the git repo for more detail at GitHub