Variational Quantum Eigensolver (VQE)#

The tutorial requires qiskit-nature package which can be installed with these instructions. The tutorial assumes that the user is familiar with VQE. User’s can follow the Ground State Solvers tutorial by qiskit-nature for the same.

[1]:
"""Variational Quantum Eigensolver (VQE) tutorial."""

from IPython.display import clear_output
import matplotlib.pyplot as plt
import numpy as np

from qiskit_nature.units import DistanceUnit
from qiskit_nature.second_q.drivers import PySCFDriver
from qiskit_nature.second_q.mappers import JordanWignerMapper
from qiskit_nature.second_q.circuit.library import HartreeFock
from qiskit_nature.second_q.algorithms import GroundStateEigensolver
from qiskit_nature import settings

from qiskit_algorithms.utils import algorithm_globals
from qiskit.circuit.library import EfficientSU2
from qiskit_algorithms.optimizers import L_BFGS_B
from qiskit_algorithms.minimum_eigensolvers import NumPyMinimumEigensolver, VQE

from qiskit_qulacs.qulacs_estimator import QulacsEstimator
from qiskit_qulacs.qulacs_estimator_gradient import QulacsEstimatorGradient

settings.use_pauli_sum_op = False

algorithm_globals.random_seed = 42
[2]:
driver = PySCFDriver(
    atom="H 0 0 0; H 0 0 0.735",
    basis="sto3g",
    charge=0,
    spin=0,
    unit=DistanceUnit.ANGSTROM,
)
[3]:
problem = driver.run()
fermionic_op = problem.hamiltonian.second_q_op()

mapper = JordanWignerMapper()
qubit_op = mapper.map(fermionic_op)
print(qubit_op)
SparsePauliOp(['IIII', 'IIIZ', 'IIZI', 'IIZZ', 'IZII', 'IZIZ', 'ZIII', 'ZIIZ', 'YYYY', 'XXYY', 'YYXX', 'XXXX', 'IZZI', 'ZIZI', 'ZZII'],
              coeffs=[-0.81054798+0.j,  0.17218393+0.j, -0.22575349+0.j,  0.12091263+0.j,
  0.17218393+0.j,  0.16892754+0.j, -0.22575349+0.j,  0.16614543+0.j,
  0.0452328 +0.j,  0.0452328 +0.j,  0.0452328 +0.j,  0.0452328 +0.j,
  0.16614543+0.j,  0.17464343+0.j,  0.12091263+0.j])
[4]:
print("Number of qubits:", qubit_op.num_qubits)
print("Number of hamiltonian terms:", len(qubit_op))
Number of qubits: 4
Number of hamiltonian terms: 15
[5]:
# Get reference solution
numpy_solver = NumPyMinimumEigensolver()
calc = GroundStateEigensolver(mapper, numpy_solver)
res_actual = calc.solve(problem)
print(res_actual)
=== GROUND STATE ENERGY ===

* Electronic ground state energy (Hartree): -1.857275030202
  - computed part:      -1.857275030202
~ Nuclear repulsion energy (Hartree): 0.719968994449
> Total ground state energy (Hartree): -1.137306035753

=== MEASURED OBSERVABLES ===

  0:  # Particles: 2.000 S: 0.000 S^2: 0.000 M: 0.000

=== DIPOLE MOMENTS ===

~ Nuclear dipole moment (a.u.): [0.0  0.0  1.3889487]

  0:
  * Electronic dipole moment (a.u.): [0.0  0.0  1.388948701555]
    - computed part:      [0.0  0.0  1.388948701555]
  > Dipole moment (a.u.): [0.0  0.0  -0.000000001555]  Total: 0.000000001555
                 (debye): [0.0  0.0  -0.000000003953]  Total: 0.000000003953

[6]:
exact_energy = res_actual.eigenvalues[0]
print(f"Expected ground state energy: {exact_energy:.12f} Ha")
Expected ground state energy: -1.857275030202 Ha
[7]:
# Hartree focks state
num_particles = problem.num_particles
num_spatial_orbitals = problem.num_spatial_orbitals

init_state = HartreeFock(
    num_spatial_orbitals=num_spatial_orbitals,
    num_particles=num_particles,
    qubit_mapper=mapper,
)

# ansatz
ansatz = EfficientSU2(qubit_op.num_qubits, su2_gates=["ry"]).decompose()

# Add the initial state
init_state.barrier()
ansatz = init_state.compose(ansatz)
ansatz.draw("mpl")
[7]:
../_images/tutorials_03_vqe_7_0.png
[8]:
intermediate_info = []


def callback(eval_count, parameters, value, std):  # pylint: disable=unused-argument
    """
    A callback that can be registered with the optimizer to store the intermediate
    value and parameters.
    """
    intermediate_info.append(value)
    clear_output(wait=True)
    plt.plot(
        intermediate_info,
        color="purple",
        lw=2,
        label=f"Simulated VQE {np.round(value,4)}",
    )
    plt.ylabel("Energy")
    plt.xlabel("Iterations")

    # Exact ground state energy value
    plt.axhline(
        y=exact_energy,
        color="tab:red",
        ls="--",
        lw=2,
        label="Target: " + str(np.round(exact_energy, 4)),
    )
    plt.legend()
    plt.grid()
    plt.show()
[9]:
optimizer = L_BFGS_B(maxiter=20)

We use primitives from qiskit-qulacs of computing the expectation value and gradient.

[10]:
qulacs_estimator = QulacsEstimator()
qulacs_gradient = QulacsEstimatorGradient()

vqe = VQE(
    qulacs_estimator, ansatz, optimizer, callback=callback, gradient=qulacs_gradient
)
result = vqe.compute_minimum_eigenvalue(operator=qubit_op)
../_images/tutorials_03_vqe_11_0.png
[11]:
print(result)
{   'aux_operators_evaluated': None,
    'cost_function_evals': 22,
    'eigenvalue': -1.8567922267701444,
    'optimal_circuit': <qiskit_nature.second_q.circuit.library.initial_states.hartree_fock.HartreeFock object at 0x7dcf14be65c0>,
    'optimal_parameters': {   ParameterVectorElement(θ[0]): 3.596756540143855,
                              ParameterVectorElement(θ[1]): -1.3110309933022815,
                              ParameterVectorElement(θ[2]): 5.32593579823301,
                              ParameterVectorElement(θ[3]): 1.209594897445057,
                              ParameterVectorElement(θ[4]): -5.154494260584071,
                              ParameterVectorElement(θ[5]): 5.666947151363986,
                              ParameterVectorElement(θ[6]): 2.574344487887185,
                              ParameterVectorElement(θ[7]): 2.442111235436972,
                              ParameterVectorElement(θ[8]): -5.410130960542183,
                              ParameterVectorElement(θ[9]): -1.261167613799997,
                              ParameterVectorElement(θ[10]): -0.9072274382239279,
                              ParameterVectorElement(θ[11]): 4.979937294664101,
                              ParameterVectorElement(θ[12]): 2.755549835643336,
                              ParameterVectorElement(θ[13]): 3.4937762155898726,
                              ParameterVectorElement(θ[14]): 0.138866002148125,
                              ParameterVectorElement(θ[15]): -3.654455036700532},
    'optimal_point': array([ 3.59675654, -1.31103099,  5.3259358 ,  1.2095949 , -5.15449426,
        5.66694715,  2.57434449,  2.44211124, -5.41013096, -1.26116761,
       -0.90722744,  4.97993729,  2.75554984,  3.49377622,  0.138866  ,
       -3.65445504]),
    'optimal_value': -1.8567922267701444,
    'optimizer_evals': None,
    'optimizer_result': <qiskit_algorithms.optimizers.optimizer.OptimizerResult object at 0x7dcf14c184f0>,
    'optimizer_time': 3.0805983543395996}
[12]:
def rel_err(target, measured):
    """Compute the relative error."""
    return abs((target - measured) / target)
[13]:
# Compute the relative error between the expected ground state energy and the VQE's output
rel_error = rel_err(exact_energy, result.eigenvalue)

print(f"Expected ground state energy: {exact_energy:.12f}")
print(f"Computed ground state energy: {result.eigenvalue:.12f}")
print(f"Relative error: {rel_error:.12f}")
Expected ground state energy: -1.857275030202
Computed ground state energy: -1.856792226770
Relative error: 0.000259952578