Use this file to discover all available pages before exploring further.
View on GitHub
Open this notebook in GitHub to run it yourself
Quantum Phase Estimation (QPE) is a key algorithm in quantum computing for estimating the phase (or eigenvalue) of an eigenvector of a unitary operation.For a given Hamiltonian H and an eigenvalue ∣ψ⟩, the output of the algorithm is ϵ whereU∣ψ⟩=e2πiϵ∣ψ⟩,U=e2πiH.Therefore, by measuring the accumulated phase, the QPE algorithm calculates the energies relating to the chosen initial state.When using QPE for chemistry problems, it is common to search for the lowest energy of a given molecule. As the molecule can be written in the form of a Hamiltonian (a Hermitian matrix representing the energetic forces of the structure), to obtain the minimal energy value using QPE, you only need to insert the ground eigenvector. However, obtaining the ground state is not a trivial problem. To overcome this, it is sufficient to use a state with big overlap with the ground state.Define a state ∣v⟩ as the algorithm’s initial state.Define {ψi} as the set of (unknown) eigenvalues of H. Generally, any vector can be rewritten as a superposition of any basis set, thus∣v⟩=∑iai∣ψi⟩andU∣v⟩=∑iaie2πiϵi∣ψi⟩where ϵi are the eigenvalues of H, i.e., the span of energies relating to the molecule.Using execution with enough shots, you obtain this set of ϵi, i.e., a subset of the Hamiltonian’s eigenvalues. As you are specifically interested in ϵ0, the ground state of H, it is important to have a large overlap between ψ0 and ∣v⟩ so the probability to measure ϵ0 is high, i.e.,P(ϵ0)=∣⟨v∣ψ0⟩∣2>ζ.How large is ζ? After execution, you obtain a set of Ei. If you have 1000 execution shots and P(ϵ0)>1%, you should sample ϵ0 roughly 10 times.A common choice for ∣v⟩ (the initial state) is the Hartree-Fock (HF) state, which typically has a large overlap with the ground state. However, other guesses for the initial state are possibly good or an even better fit, and choosing the right initial state is an art and an active field of research.To read more about QPE, refer to [1].What are the benefits of using the QPE algorithm to find a molecule’s ground state?The two most prominent methods to solve ground energy for molecules are quantum variational algorithm (VQE) and QPE.They promise better scalability compared to their classical counterparts as the molecules become more complex, with a larger number of electrons, referring to a physical problem with more degrees of freedom.The number of parameters in VQE is closely related to the number of electrons.This may create an inherent difficulty achieving high-precision calculations through sampling statistical estimators, and may not even converge for very large systems. On the other hand, the number of parameters in QPE is a flexible value that is directly related to the resolution of the problem, but is not bounded with the number of electrons.Furthermore, it is known that advanced quantum algorithms based on QPE can perform electronic structure calculations in sub-exponential time with accuracy that rivals exact diagonalization methods.This guarantee of simultaneously achieving high accuracy, efficiency, and generality is a feat that is believed to be impossible for classical algorithms.For these reasons, VQE is applicable in the near term (NISQ) era, while QPE is suited for fault-tolerant design.This tutorial follows the QPE algorithm steps as follows:
Define a molecule and convert it into a Hamiltonian.
Prepare the Hamiltonian for QPE, including normalization and trimming of negligible terms.
Construct a quantum model, initializing the state for the HF state and leveraging the qpe_flexible function.
Execute the circuit to find the related phases and analyze the results to find the ground state.
!pip install -qq "classiq[chemistry]"
## Importsimport matplotlib.pyplot as pltimport numpy as npfrom classiq import *from classiq.applications.chemistry.op_utils import qubit_op_to_qmod# for chemistryfrom classiq.applications.chemistry.problems import FermionHamiltonianProblemfrom classiq.applications.chemistry.z2_symmetries import Z2SymTaperMapper
# define your molecule problem and mapperproblem = FermionHamiltonianProblem.from_molecule( molecule=molecule, first_active_index=1)mapper = Z2SymTaperMapper.from_problem(problem)num_qubits = mapper.get_num_qubits(problem)constant_energy = problem.fermion_hamiltonian.constantmol_hamiltonian = mapper.map(problem.fermion_hamiltonian - constant_energy)print( f"The Hamiltonian is defined on {num_qubits} qubits, and contains {len(mol_hamiltonian.terms)} Pauli strings")
Output:
The Hamiltonian is defined on 6 qubits, and contains 231 Pauli strings
Finally, we calculate the ground state energy as a reference solution to the quantum solver
Since you are working with QPE, the ground state energy is inferred as a phase. Therefore, normalize the Hamiltonian so that its eigenvalues are in [−21,21).This is done by finding a bound on the maximal absolute value of eigenvalues λ~max and normalizing the Hamiltonian by 2λ~max. A simple bound is given by the sum of Pauli coefficients of the Hamiltonian:
def normalize_hamiltonian(hamiltonian): approx_lambda_max = sum(np.abs(value) for value in hamiltonian.terms.values()) normalization = 2 * approx_lambda_max normalized_mol_hamiltonian = hamiltonian * (1 / normalization) return normalization, normalized_mol_hamiltoniannormalization, normalized_mol_hamiltonian = normalize_hamiltonian(mol_hamiltonian)print(f"The normalization value of the Hamiltonian is {normalization}")
Output:
The normalization value of the Hamiltonian is 17.61546220154498
Create a quantum model of the QPE algorithm using the Classiq platform, in particular, using the open library qpe_flexible function (and see this notebook as well).To approximate the Hamiltonian simulation e2πiH, use the Classiq built-in implementation for Suzuki-Trotter formulas.For a given Suzuki-Trotter order o, you can specify a repetition parameter r that controls the level of approximation.The literature provides lower bounds for r as a function of the operator error ϵ (defined by the diamond norm [3]).For example, Eq. (14) in Ref. [2] states that the Suzuki-Trotter formula of order 2 approximates ei∑αmPmt up to an error ϵ, given r repetitions that satisfiesr≤(3ϵ25γ2)1/2t3/2,(1)where γ2≡∑l,m,n∣αmαnαl∣∣[Pl,[Pm,Pn]]∣∞. In particular, note that the number of repetitions grows as t3/2.In QPE, apply a powered Hamiltonian simulation:(e2πiH)p=e2pπiH,(2)and approximate each power with Suzuki-Trotter for appropriate order and repetition parameters, keeping the same error per QPE iteration.You can thus use the bound above to define a powered Suzuki-Trotter qfunc for the specific molecule.First, define a classical auxiliary function that help evaluate the right-hand-side side of Eq. (1):
import itertoolsfrom openfermion import QubitOperatorfrom openfermion.utils import commutatordef calculate_gamma_2(hamiltonian): """ Compute the $\gamma_2$ value appearing in the bound for Suzuki Trotter of order 2 """ gamma_2 = 0 for triplet in itertools.combinations(range(len(hamiltonian.terms)), 3): terms = [list(hamiltonian.terms.keys())[index] for index in triplet] values = [list(hamiltonian.terms.values())[index] for index in triplet] factor = np.abs(values[0] * values[1] * values[2]) inner_commutator = commutator( QubitOperator(terms[1], 1), QubitOperator(terms[0], 1) ).induced_norm() if inner_commutator != 0: outer_commutator = ( 2 * commutator( QubitOperator(terms[2], 1), QubitOperator(terms[0], 1) * QubitOperator(terms[1], 1), ).induced_norm() ) gamma_2 += factor * outer_commutator return gamma_2
In QPE, the power of the Hamiltonian simulation grows exponentially with the phase variable size.Examine the number of repetitions needed per QPE iteration, according to the bound above for QPE of size 7:
QPE_SIZE = 7qpe_powers = 2 ** np.arange(QPE_SIZE)print( f"""The power of the Hamiltonian simulation along a QPE routine of size {QPE_SIZE}: {qpe_powers}""")
Output:
The power of the Hamiltonian simulation along a QPE routine of size 7: [ 1 2 4 8 16 32 64]
These powers enter as an evolution coefficient for the Hamiltonian simulation (see Eq. (2) above).Using the theoretical bound, we find this:
EPS = 0.1gamma_2_LiH = calculate_gamma_2(normalized_mol_hamiltonian)theoretical_r0 = np.sqrt(2**5 * gamma_2_LiH / (3 * EPS)) * (2 * np.pi) ** (3 / 2)print( f"""The theoretical bounds for the repetitions for QPE size {QPE_SIZE}, keeping an error {EPS} per QPE iteration are:{np.ceil(theoretical_r0*qpe_powers**(3/2))}""")
Output:
The theoretical bounds for the repetitions for QPE size 7, keeping an error 0.1 per QPE iteration are: [ 5. 12. 33. 93. 261. 737. 2084.]
Note that applying a naive QPE, i.e., assuming a single unitary approximated with Suzuki-Trotter, eiHt≈ST(H,o,r,t), and simply taking its powers, gives (eiHt)p≈(ST(H,o,r,t))p=ST(H,o,pr,pt).
print( f"""The repetitions for QPE size {QPE_SIZE}, taking a naive QPE, per QPE iteration:{np.ceil(theoretical_r0*qpe_powers)}""")
Output:
The repetitions for QPE size 7, taking a naive QPE, per QPE iteration: [ 5. 9. 17. 33. 66. 131. 261.]
While this naive QPE results in a shallower circuit, compared to taking repetitions according to the theoretical bounds (due to smaller values of repetitions), it is unclear whether it keeps the same operator error per phase bit.In practice, the bounds given in the literature are quite loose.This tutorial therefore takes a more experimental approach, assuming that the scaling of the bound with the evolution time t is similar to Eq. (1), but taking a smaller prefactor.
experimental_r0 = 0.05print( f"""The experimental repetitions for QPE size {QPE_SIZE}, per QPE iteration are:{np.ceil(experimental_r0*qpe_powers**(3/2))}""")
Output:
The experimental repetitions for QPE size 7, per QPE iteration are: [ 1. 1. 1. 2. 4. 10. 26.]
Use this approach to define the powered Suzuki-Trotter function for the specific Hamiltonian at hand:
from classiq.qmod.symbolic import ceiling as ceiling_qmod, pi@qfuncdef powered_st2_for_LiH(p: CInt, state: QArray[QBit]): suzuki_trotter( pauli_operator=qubit_op_to_qmod(normalized_mol_hamiltonian), evolution_coefficient=-2 * np.pi * p, order=2, repetitions=ceiling_qmod(experimental_r0 * p ** (3 / 2)), qbv=state, )
Energy with maximal probability: -7.904148198365434 Ha Precision: 0.13762079844957015 Ha Classical solution:, -7.882386993638953 Ha
You are looking for a signal from the smallest eigenvalue under the assumption that the initial state has some overlap with the ground state. Now, estimate the energy as the first peak of the histogram, such that the corresponding probability is larger than ASSUMED_OVERLAP*0.4 (0.4 is the case for ASSUMED_OVERLAP=1).*Note that this is a very rough and simplistic analysis of the QPE algorithm result.You can utilize more complex spectral analysis tools such as Gaussian mixtures.Additional assumptions, such as the difference between adjacent eigenvalues or the number of overlapping eigenstates, can facilitate the analysis further.*
from scipy.signal import find_peaksASSUMED_OVERLAP = 0.05def estimate_energy(data_dict, assumed_overlap): max_prob = assumed_overlap * 0.4 data = tuple(data_dict.items()) data_sorted = sorted( data, key=lambda x: x[0] ) # sort the data according to the energy value probs_sorted = [data[1] for data in data_sorted] maxima = find_peaks(probs_sorted, height=max_prob)[0] print(f"Number of maxima: {maxima.size}") if maxima.size == 0 and np.all(np.array(probs_sorted) <= max_prob): print( """No probabilities above threshold were found, try to increase the assumed_overlap.Returning energy with max probability""" ) return max(data_dict, key=data_dict.get) elif maxima.size == 0: # strictly increasing or decreasing function return max(data_dict, key=data_dict.get) else: print( f"maxima over the threshold at {[data_sorted[maxima[k]][0] for k in range(maxima.size)]} Ha" ) return data_sorted[maxima[0]][0]measured_energy = estimate_energy(energy_results, ASSUMED_OVERLAP)print(f"\nLowest eigenvalue: {measured_energy} Ha")print(f"Precision: {(2**(-QPE_SIZE))* normalization} Ha")print(f"Classical solution:, {classical_sol} Ha")
Output:
Number of maxima: 1 maxima over the threshold at [-7.904148198365434] Ha Lowest eigenvalue: -7.904148198365434 Ha Precision: 0.13762079844957015 Ha Classical solution:, -7.882386993638953 Ha