Skip to content

Molecule Eigensolver (VQE Method)

View on GitHub Experiment in the IDE

Evaluating the ground state of a molecular Hamiltonian allows you to understand the chemical properties of the molecule. This tutorial demonstrates the use of Variational Quantum Eigensolver (VQE) to find the ground states and energies of 𝐻2, 𝐻2𝑂, and 𝐿𝑖𝐻 molecules.

VQE is a leading method for finding approximate values of ground state wave functions and energies for complicated quantum systems and can give solutions for complex molecular structures. The overview of the VQE method is as follows: a problem (i.e., a molecule) is defined by a Hamiltonian whose ground state is sought. Then, a choice of a parameterized ansatz is made. A hybrid quantum-classical algorithm finds a solution for the defined parameters that minimizes the expectation value for the energy. A clever ansatz leads to an estimated ground state solution.

Within the scope of Classiq's VQE algorithm, define a molecule that is translated to a concise Hamiltonian. Then, choose among types of well studied ansatzes, which are carefully selected to fit your molecule type. In the last stage, the Hamiltonian and ansatz are sent to a classical optimizer. This tutorial demonstrates the steps and options in Classiq's VQE algorithm. It presents the optimization strength of Classiq's VQE algorithm and its state-of-the-art results in terms of efficient quantum circuit, with the ultimate combination of low depth and high accuracy while minimizing the number of CX gates.

Prerequisites

The model uses Classiq libraries:

import numpy as np

from classiq import *
from classiq.applications.chemistry import (
    ChemistryExecutionParameters,
    HEAParameters,
    Molecule,
    MoleculeProblem,
    UCCParameters,
)
from classiq.execution import (
    ClassiqBackendPreferences,
    ClassiqSimulatorBackendNames,
    ExecutionPreferences,
    OptimizerType,
)

Generating a Qubit Hamiltonian

Define the molecule to simulate, declaring the molecule class and inserting a list of atoms and their spacial positions. The algorithm automatically notes relevant attributes such as the atom's mass, charge, and spin.

As mentioned above, this tutorial demonstrates how to define and find the ground state and energies for these molecules:

molecule_H2 = Molecule(atoms=[("H", (0.0, 0.0, 0)), ("H", (0.0, 0.0, 0.735))])
molecule_O2 = Molecule(atoms=[("O", (0.0, 0.0, 0)), ("O", (0.0, 0.0, 1.16))])
molecule_LiH = Molecule(atoms=[("H", (0.0, 0.0, 0.0)), ("Li", (0.0, 0.0, 1.596))])
molecule_H2O = Molecule(
    atoms=[("O", (0.0, 0.0, 0.0)), ("H", (0, 0.586, 0.757)), ("H", (0, 0.586, -0.757))]
)
molecule_BeH2 = Molecule(
    atoms=[("Be", (0.0, 0.0, 0.0)), ("H", (0, 0, 1.334)), ("H", (0, 0, -1.334))]
)

You can construct any valid assambly of atoms in a similar manner. The distances are received in Γ… (\(10^{-10} m\)). While this demonstration continues with a specific molecule, you can change the molecule below to study other cases.

molecule = molecule_H2

Define the parameters of the Hamiltonian generation program:

  • mapping (str): the mapping between the fermionic Hamiltonian and and qubits Hamiltonian. Supported types:

  • - "jordan_wigner"
    
    - "parity"
    
    - "bravyi_kitaev"
    
    - "fast_bravyi_kitaev"
    
  • freeze_core (bool): removes the "core" orbitals of the atoms defining the molecule.

  • z2_symmetries (bool): whether to perform z2 symmetries reduction. If symmetries in the molecules exist, this option decreases the number of qubits used, making the Hamiltonian and thus the calculations more efficient.

Finally, the Hamiltonian is generated from MoleculeProblem.

chemistry_problem = MoleculeProblem(
    molecule=molecule,
    mapping="jordan_wigner",  #'bravyi_kitaev'
    z2_symmetries=True,
    freeze_core=True,
)

operator = chemistry_problem.generate_hamiltonian()
gs_problem = chemistry_problem.update_problem(operator.num_qubits)
print("Your Hamiltonian is", operator.show(), sep="\n")
Your Hamiltonian is
-1.041 * I
-0.796 * Z
+0.181 * X

The output of the above code lines is the Hamiltonian presented as a superposition of Pauli matrices multiplication. You can confirm that using z2*symmetries=True, the number of qubits is reduced (compared to z2_symmetries=False):

  • for \(H_2\) - from 4 to 1

  • for \(LiH\) from 12 to 8

  • for \(H*{2}O\) from 14 to 10

Constructing and Synthesizing a Ground State Solver

A ground state solver model consists of a parameterized eigenfunction ("the ansatz"), on which to run a VQE. In addition, a postprocess of the result returns the total energy (combining the ground state energy of the Hamiltonian, the nuclear repulsion, and the static nuclear energy).

Specify a Hamiltonian and an ansatz, then send them to the VQE algorithm to find the Hamiltonian's ground state. In the process, the algorithm sends requests to a classical server, whose task is to minimize the energy expectation value and return the optimized parameters. The simulator and optimizing parameters are defined as part of the VQE part of the model. You can control the max_iteration value so the solution reaches a stable convergence. In addition, the num_shots value sets the number of measurements performed after each iteration, thus influencing the accuracy of the solutions.

Below are two proposals for the wavefunction solution ansatz:

  • Hardware (HW) efficient

  • Unitary Coupled Cluster (UCC)

For groundstate solvers, it is typical to initialize the ansatz with the Hartree-Fock state.

HW Efficient Ansatz

The suggested HW efficient ansatz solution is generated to fit a specific hardware [1]. The ansatz creates a state with a given number of parameters according to your choice of the number of qubits that fits the Hamiltonian, and creates entanglement between the qubits using the inputed connectivity map. This example uses a four qubit map, which is specifically made of \(H_2\) with z2_symmetries=False.

After constructing the model, synthesize it and view the output circuit, creating the state with an interactive interface.

chemistry_problem = MoleculeProblem(
    molecule=molecule,
    mapping="jordan_wigner",  #'bravyi_kitaev'
    z2_symmetries=False,
    freeze_core=True,
)

hwea_params = HEAParameters(
    num_qubits=4,
    connectivity_map=[(0, 1), (1, 2), (2, 3)],
    reps=3,
    one_qubit_gates=["x", "ry"],
    two_qubit_gates=["cx"],
)

qmod_hwea = construct_chemistry_model(
    chemistry_problem=chemistry_problem,
    use_hartree_fock=True,
    ansatz_parameters=hwea_params,
    execution_parameters=ChemistryExecutionParameters(
        optimizer=OptimizerType.COBYLA,
        max_iteration=30,
        initial_point=None,
    ),
)

backend_preferences = ClassiqBackendPreferences(
    backend_name=ClassiqSimulatorBackendNames.SIMULATOR
)

qmod_hwea = set_execution_preferences(
    qmod_hwea,
    execution_preferences=ExecutionPreferences(
        num_shots=1000, backend_preferences=backend_preferences
    ),
    out_file="molecule_eigensolver",
)
qprog_hwea = synthesize(qmod_hwea)
show(qprog_hwea)
Opening: https://platform.classiq.io/circuit/dd765d79-bcdb-49e0-85be-74af8c1b3f86?version=0.41.0.dev39%2B79c8fd0855

UCC Ansatz

Create the commonly used chemistry-inspired UCC ansatz, which is a unitary version of the classical coupled cluster (CC) method [2].

The parameter that defines the UCC ansatz: excitations (List[int] or List[str]): list of desired excitations. Allowed excitations:

  • 1 for singles

  • 2 for doubles

  • 3 for triples

  • 4 for quadruples

Once again, after running the code lines below, you can view the output circuit that creates the state with an interactive interface and print the depth of the circuit.

chemistry_problem = MoleculeProblem(
    molecule=molecule,
    mapping="jordan_wigner",  #'bravyi_kitaev'
    z2_symmetries=True,
    freeze_core=True,
)

serialized_chemistry_model = construct_chemistry_model(
    chemistry_problem=chemistry_problem,
    use_hartree_fock=True,
    ansatz_parameters=UCCParameters(excitations=[1, 2]),
    execution_parameters=ChemistryExecutionParameters(
        optimizer=OptimizerType.COBYLA,
        max_iteration=30,
        initial_point=None,
    ),
)

backend_preferences = ClassiqBackendPreferences(
    backend_name=ClassiqSimulatorBackendNames.SIMULATOR
)

serialized_chemistry_model = set_execution_preferences(
    serialized_chemistry_model,
    execution_preferences=ExecutionPreferences(
        num_shots=1000, backend_preferences=backend_preferences
    ),
)

qprog_ucc = synthesize(serialized_chemistry_model)
show(qprog_ucc)

print(f"circuit depth: {qprog_ucc.transpiled_circuit.depth}")
Opening: https://platform.classiq.io/circuit/39cf374d-1965-4081-85bf-75c71cd73296?version=0.41.0.dev39%2B79c8fd0855
circuit depth: 3

The Classiq UCC algorithm provides a highly efficient solution in terms of circuit depth and number of CX gates. These ultimately reduce the gate's time and amount of resources needed for operation.

Executing to Find the Ground State

After synthesizing the model you can execute it:

result = execute(qprog_ucc).result()
chemistry_result_dict = result[1].value

Execution of the quantum program returns several useful outputs:

  • energy : the output of the VQE algorithm - the electronic energy simulated.

  • nuclear_repulsion : the electrostatic energy generated by the atom's nuclei.

  • hartree_fock_energy : the Hartree-Fock energy.

  • total_energy : the ground state energy of the Hamiltonian (combining the energy, the nuclear repulsion, and the static nuclear energy).

It also contains the full VQE result from which you can get, for example:

  • optimal_parameters : the results for the anzatz parameters minimizing that expectation value.

  • eigenstate : the ground state wave function.

Note that energy is presented in units of Hartree.

chemistry_result_dict["total_energy"]
-1.1382090268147285
chemistry_result_dict["vqe_result"]["optimal_parameters"]
{'param_0': 3.2624618497328566}

Finally, compare the VQE solution to the classical solution by employing exact diagonalization:

mat = operator.to_matrix()
w, v = np.linalg.eig(mat)
print("exact result:", np.real(min(w)))
print("vqe result:", chemistry_result_dict["energy"])
exact result: -1.8572750302023786
vqe result: -1.8581780212637082

[1][Abhinav Kandala, Antonio Mezzacapo, Kristan Temme, Maika Takita, Markus Brink, Jerry M. Chow, Jay M. Gambetta Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets. Nature 549, 242 (2017).](https://arxiv.org/abs/1704.05018)

[2][Panagiotis Kl. Barkoutsos, Jerome F. Gonthier, Igor Sokolov, Nikolaj Moll, Gian Salis, Andreas Fuhrer, Marc Ganzhorn, Daniel J. Egger, Matthias Troyer, Antonio Mezzacapo, Stefan Filipp, and Ivano Tavernelli Quantum algorithms for electronic structure calculations: Particle-hole Hamiltonian and optimized wave-function expansions. Phys. Rev. A 98, 022322 (2018).](https://arxiv.org/abs/1805.04340)