Skip to content

Variational Quantum Eigensolver

The Variational Quantum Eigensolver (VQE) is a well-known algorithm for its applications in the chemistry field.

Background

Calculating the energies of a molecular Hamiltonian, especially the ground state, allows insights into the chemical properties of the molecule. Since the molecular Hamiltonian is inherently quantum, quantum computing calculates such properties efficiently by leveraging the same quantum effects in appropriate quantum algorithms.

Steps:

  1. Define the molecule and calculate its Hamiltonian operator.
  2. Generate an appropriate variational quantum circuit (the ansatz) to calculate the Hamiltonian ground state.
  3. Optimize the parameters in the ansatz using the hybrid quantum-classical algorithm to find the Hamiltonian (or molecule) ground state.

The process is described visually in the diagram:

flowchart LR
  classDef default fill:#FFFFFF,stroke:#000000,stroke-width:1px;

  A(Define Molecule and\nGenerate Hamiltonian) --> B(Define an\nAnsatz);
  B --> C(Optimize its\nparameters);

Defining a Molecule and Calculating Its Hamiltonian

Given a molecule, what is the full Hamiltonian describing the interactions between all electrons and nuclei?

Following is an example of calculating the Hamiltonian of the hydrogen molecule.

Check you are in the chemistry application suite in the synthesis page chemistry Suite.

Enter the following model in the ground state problem editor:

{
  "molecule": {
      "atoms": [["H", [0, 0, 0]], ["H", [0, 0, 0.735]]]
  },
  "basis": "sto3g",
  "mapping": "jordan_wigner",
  "num_qubits": 4
}
Click the Generate Hamiltonian button. You should now see the Hamiltonian in compact format, as shown.

Generated_Hamiltonian

{
  "molecule": {
      "atoms": [["H", [0, 0, 0]], ["H", [0, 0, 0.735]]]
  },
  "basis": "sto3g",
  "mapping": "jordan_wigner"
  "num_qubits": 4
}
from classiq.applications.chemistry import Molecule, MoleculeProblem

molecule = Molecule(
    atoms=[
        ("H", (0.0, 0.0, 0.0)),
        ("H", (0.0, 0.0, 0.735)),
    ],
)
gs_problem = MoleculeProblem(
    molecule=molecule,
    basis="sto3g",
    mapping="jordan_wigner",
)

hamiltonian = gs_problem.generate_hamiltonian()
gs_problem = gs_problem.update_problem(hamiltonian.num_qubits)

The example above defines a hydrogen molecule by specifying its two hydrogen atoms and their position in a 3D space. The molecule is then used to create a MoleculeProblem, together with the following optional parameters:

  • basis: str – The basis set to be used. For supported types, see PySCF.
  • mapping: str – The mapping between the fermionic Hamiltonian and a Pauli string Hamiltonian that can be applied on the qubits. Supported types:
    • "jordan_wigner"
    • "parity"
    • "bravyi_kitaev"
    • "fast_bravyi_kitaev"
  • freeze_core: bool – Remove the "core" orbitals of the atoms defining the molecule.
  • two_qubit_reduction: bool – Perform two qubit reduction if possible.
  • z2_symmetries: bool – Perform Z2 symmetries reduction [1] .

Finally, the Hamiltonian is generated from the MoleculeProblem.

Generating a Variational Quantum Circuit

The ground state of a Hamiltonian is found using an appropriate variational quantum circuit (usually called an ansatz), which is combined with a classical optimizer to find the minimal expectation value for the Hamiltonian. See the list of built-in ansatzes.

Note that the chemical ansatz functions UCC and HVA need to know the num_qubits attribute of the chemical problem in advance. Therefore, you can provide the num_qubits directly to the MoleculeProblem or HamiltonianProblem class using the generate_hamiltonian or update_problem methods.

Unitary Coupled Cluster Ansatz Example

This example shows the generation of the commonly used, chemistry-inspired UCC ansatz, which is a unitary version of the classical coupled cluster (CC) method [2] . First, initialize the circuit to the Hartree-Fock state, then apply the UCC function. See details of the UCC ansatz in UCC.

After generating a Hamiltonian as described above, add the circuit in the Model Editor.

{
  "logic_flow": [
      {
        "function": "HartreeFock",
        "function_params": {
          "gs_problem": "ground_state_problem"
        },
        "outputs": "hf_out"
      },
      {
        "function": "UCC",
        "function_params": {
          "gs_problem": "ground_state_problem",
          "max_depth": 100
        },
        "inputs": "hf_out"
      }
    ]
}

Now, click the Synthesis or the Solve button:

  • Synthesis takes you to the Visualization tab.
  • Solve takes you directly to the Execution tab to solve the ground state problem.
{
  "logic_flow": [
      {
        "function": "HartreeFock",
        "function_params": {
          "gs_problem": "ground_state_problem"
        },
        "outputs": "hf_out"
      },
      {
        "function": "UCC",
        "function_params": {
          "gs_problem": "ground_state_problem",
          "max_depth": 100
        },
        "inputs": "hf_out"
      }
    ]
}
from classiq import Model
from classiq.builtin_functions import HartreeFock, UCC
from classiq.applications.chemistry import Molecule, MoleculeProblem

molecule = Molecule(
    atoms=[("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.735))],
)
gs_problem = MoleculeProblem(
    molecule=molecule,
    mapping="jordan_wigner",
)
gs_problem = gs_problem.update_problem()

model = Model()

hf_params = HartreeFock(gs_problem=gs_problem)
output_dict = model.HartreeFock(hf_params)
hf_output = output_dict["OUT"]

ucc_params = UCC(gs_problem=gs_problem, max_depth=100)

model.UCC(ucc_params, in_wires={"IN": hf_output})
generation_result = model.synthesize()

generation_result.show()

This is the output circuit.

Ucc Circuit.

Optimizing to Find the Hamiltonian Ground State

After you specify a molecular Hamiltonian and an ansatz to use in the algorithm, combine everything and execute the VQE algorithm to find the Hamiltonian ground state.

Ensure you are in the circuit's Execution tab. (If you have clicked Solve after Modeling the Circuit, you should already be in the Execution tab. Otherwise, go to the Execution tab in the IDE.)

Check the Execute for Hamiltonian box. (If the checkbox is missing, see the Troubleshooting section below.)

Checkbox

Specify the Optimizer Preferences.

Prefences

(These are the preferences for the Classical Optimizer in VQE.)

Click the Run button in the same way that you execute other circuits throughout the platform. In this case, the Classiq engine executes the hybrid algorithm, optimizing the parameters in the ansatz to find the minimum of the objective function \(\left\langle \psi(\theta) \middle| H \middle| \psi(\theta) \right\rangle\). This process attempts to find the molecule's ground state.

from classiq import Model
from classiq.builtin_functions import UCC, HartreeFock

from classiq.applications.chemistry import (
    Molecule,
    MoleculeProblem,
    GroundStateSolver,
    GroundStateOptimizer,
)
from classiq.execution import IBMBackendPreferences

molecule = Molecule(
    atoms=[
        ("H", (0.0, 0.0, 0.0)),
        ("H", (0.0, 0.0, 0.735)),
    ],
)
gs_problem = MoleculeProblem(
    molecule=molecule,
    mapping="jordan_wigner",
)

hamiltonian = gs_problem.generate_hamiltonian()
gs_problem = gs_problem.update_problem(hamiltonian.num_qubits)

model = Model()

hf_params = HartreeFock(gs_problem=gs_problem)
output_dict = model.HartreeFock(hf_params)
hf_output = output_dict["OUT"]

ucc_params = UCC(gs_problem=gs_problem, max_depth=100)

model.UCC(ucc_params, in_wires={"IN": hf_output})
circuit = model.synthesize()

optimizer_preferences = GroundStateOptimizer(
    max_iteration=30,
    num_shots=1000,
)
backend_preferences = IBMBackendPreferences(
    backend_service_provider="IBM Quantum", backend_name="aer_simulator"
)

gs_solver = GroundStateSolver(
    ground_state_problem=gs_problem,
    ansatz=circuit,
    optimizer_preferences=optimizer_preferences,
    backend_preferences=backend_preferences,
)

result = gs_solver.solve()
result.show_convergence_graph()
exact_result = gs_solver.solve_exact()
The GroundStateSolver function wraps everything together. gs_solver.solve() executes the hybrid algorithm, optimizing the parameters in the ansatz to find the minimum of the objective function \(\left\langle \psi(\theta) \middle| H \middle| \psi(\theta) \right\rangle\). This process attempts to find the molecule's ground state.

This is the resulting convergence graph.

alt text

Compare the VQE result to the exact result for small molecules obtained by the solve_exact method.

The solve method of the GroundStateSolver class returns a GroundStateResult object. For details of the resulting object, see GroundStateResult.

Additional Examples

See further ansatz examples: hardware efficient ansatz, Hamiltonian variational ansatz, and user-defined ansatz.

Troubleshooting the IDE

  • If the "Execute for Hamiltonian" checkbox is missing:
    • The checkbox only appears when there is a Hamiltonian available for the circuit. It might be missing if:
      • You clicked the Synthesize button before the Generate Hamiltonian button.
      • You used the SDK to generate the circuit, but the IDE to execute it. Classiq does not support Hamiltonians generated in the SDK.

References

[1] Bravyi, Sergey, Jay M. Gambetta, Antonio Mezzacapo, and Kristan Temme, Tapering off qubits to simulate fermionic Hamiltonians (2017).

[2] Panagiotis K. 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).