{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Ground state solvers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction\n", "\n", "\n", "\n", "In this tutorial we are going to discuss the ground state calculation interface of Qiskit Nature. The goal is to compute the ground state of a molecular Hamiltonian. This Hamiltonian can for example be electronic or vibrational. To know more about the preparation of the Hamiltonian, check out the [Electronic structure](01_electronic_structure.ipynb) and [Vibrational structure tutorials](02_vibrational_structure.ipynb). \n", "\n", "> It should be said, that in the electronic case, we are actually computing purely the **electronic** part. When using the Qiskit Nature stack as presented in this tutorial, the _nuclear repulsion energy_ will be added automatically, to obtain the **total** ground state energy.\n", "\n", "The first step is to define the molecular system. In the following we ask for the electronic part of a hydrogen molecule." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from qiskit_nature.units import DistanceUnit\n", "from qiskit_nature.second_q.drivers import PySCFDriver\n", "\n", "driver = PySCFDriver(\n", " atom=\"H 0 0 0; H 0 0 0.735\",\n", " basis=\"sto3g\",\n", " charge=0,\n", " spin=0,\n", " unit=DistanceUnit.ANGSTROM,\n", ")\n", "\n", "es_problem = driver.run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will also be sticking to the Jordan-Wigner mapping. To learn more about the various mappers available in Qiskit Nature, check out the [Qubit Mappers tutorial](06_qubit_mappers.ipynb)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from qiskit_nature.second_q.mappers import JordanWignerMapper\n", "\n", "mapper = JordanWignerMapper()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Solver\n", "\n", "After these steps, we need to define a solver. The solver is the algorithm through which the ground state is computed. \n", "\n", "Let's first start with a purely classical example: the `NumPyMinimumEigensolver`. This algorithm exactly diagonalizes the Hamiltonian. Although it scales badly, it can be used on small systems to check the results of the quantum algorithms. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "from qiskit_algorithms import NumPyMinimumEigensolver\n", "\n", "numpy_solver = NumPyMinimumEigensolver()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To find the ground state we could also use the Variational Quantum Eigensolver (VQE) algorithm. The VQE algorithm works by exchanging information between a classical and a quantum computer as depicted in the following figure.\n", "\n", "\n", "\n", "Let's initialize a VQE solver." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "from qiskit_algorithms import VQE\n", "from qiskit_algorithms.optimizers import SLSQP\n", "from qiskit.primitives import Estimator\n", "from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD\n", "\n", "ansatz = UCCSD(\n", " es_problem.num_spatial_orbitals,\n", " es_problem.num_particles,\n", " mapper,\n", " initial_state=HartreeFock(\n", " es_problem.num_spatial_orbitals,\n", " es_problem.num_particles,\n", " mapper,\n", " ),\n", ")\n", "\n", "vqe_solver = VQE(Estimator(), ansatz, SLSQP())\n", "vqe_solver.initial_point = [0.0] * ansatz.num_parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To define the VQE solver one needs three essential elements:\n", "\n", "1. An Estimator primitive: these were released as part of Qiskit Terra 0.22. To learn more about primitives, check out [this resource](https://docs.quantum.ibm.com/api/qiskit/primitives).\n", "2. A variational form: here we use the Unitary Coupled Cluster (UCC) ansatz (see for instance [Physical Review A 98.2 (2018): 022322]). Since it is a chemistry standard, a factory is already available allowing a fast initialization of a VQE with UCC. The default is to use all single and double excitations. However, the excitation type (S, D, SD) as well as other parameters can be selected. We also prepend the `UCCSD` variational form with a `HartreeFock` initial state, which initializes the occupation of our qubits according to the problem which we are trying solve.\n", "3. An optimizer: this is the classical piece of code in charge of optimizing the parameters in our variational form. See [the corresponding documentation](https://qiskit-community.github.io/qiskit-algorithms/apidocs/qiskit_algorithms.optimizers.html) for more information.\n", "\n", "One could also use any available ansatz / initial state or even define one's own. For instance," ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from qiskit_algorithms import VQE\n", "from qiskit.circuit.library import TwoLocal\n", "\n", "tl_circuit = TwoLocal(\n", " rotation_blocks=[\"h\", \"rx\"],\n", " entanglement_blocks=\"cz\",\n", " entanglement=\"full\",\n", " reps=2,\n", " parameter_prefix=\"y\",\n", ")\n", "\n", "another_solver = VQE(Estimator(), tl_circuit, SLSQP())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The calculation and results\n", "\n", "We are now ready to put everything together to compute the ground-state of our problem.\n", "Doing so requires us to wrap our `mapper` and quantum algorithm into a single `GroundStateEigensolver` like so:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from qiskit_nature.second_q.algorithms import GroundStateEigensolver\n", "\n", "calc = GroundStateEigensolver(mapper, vqe_solver)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This will now take of the entire workflow:\n", "1. generating the second-quantized operators stored in our problem (here referred to as `es_problem`)\n", "2. mapping (and potentially reducing) the operators in the qubit space\n", "3. running the quantum algorithm on the Hamiltonian qubit operator\n", "4. once converged, evaluating the additional observables at the determined ground state" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "=== GROUND STATE ENERGY ===\n", " \n", "* Electronic ground state energy (Hartree): -1.857275030145\n", " - computed part: -1.857275030145\n", "~ Nuclear repulsion energy (Hartree): 0.719968994449\n", "> Total ground state energy (Hartree): -1.137306035696\n", " \n", "=== MEASURED OBSERVABLES ===\n", " \n", " 0: # Particles: 2.000 S: 0.000 S^2: 0.000 M: 0.000\n", " \n", "=== DIPOLE MOMENTS ===\n", " \n", "~ Nuclear dipole moment (a.u.): [0.0 0.0 1.3889487]\n", " \n", " 0: \n", " * Electronic dipole moment (a.u.): [0.0 0.0 1.38894893]\n", " - computed part: [0.0 0.0 1.38894893]\n", " > Dipole moment (a.u.): [0.0 0.0 -0.00000023] Total: 0.00000023\n", " (debye): [0.0 0.0 -0.00000058] Total: 0.00000058\n", " \n" ] } ], "source": [ "res = calc.solve(es_problem)\n", "print(res)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can compare the VQE results to the NumPy exact solver and see that they match. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "=== GROUND STATE ENERGY ===\n", " \n", "* Electronic ground state energy (Hartree): -1.857275030202\n", " - computed part: -1.857275030202\n", "~ Nuclear repulsion energy (Hartree): 0.719968994449\n", "> Total ground state energy (Hartree): -1.137306035753\n", " \n", "=== MEASURED OBSERVABLES ===\n", " \n", " 0: # Particles: 2.000 S: 0.000 S^2: 0.000 M: 0.000\n", " \n", "=== DIPOLE MOMENTS ===\n", " \n", "~ Nuclear dipole moment (a.u.): [0.0 0.0 1.3889487]\n", " \n", " 0: \n", " * Electronic dipole moment (a.u.): [0.0 0.0 1.3889487]\n", " - computed part: [0.0 0.0 1.3889487]\n", " > Dipole moment (a.u.): [0.0 0.0 0.0] Total: 0.0\n", " (debye): [0.0 0.0 0.0] Total: 0.0\n", " \n" ] } ], "source": [ "calc = GroundStateEigensolver(mapper, numpy_solver)\n", "res = calc.solve(es_problem)\n", "print(res)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using a filter function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes the true ground state of the Hamiltonian is not of interest because it lies in a different symmetry sector of the Hilbert space. In this case the `NumPyEigensolver` can take a filter function to return only the eigenstates with for example the correct number of particles. This is of particular importance in the case of vibrational structure calculations where the true ground state of the Hamiltonian is the vacuum state. A default filter function to check the number of particles is implemented in the different problems and can be used as follows:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "=== GROUND STATE ===\n", " \n", "* Vibrational ground state energy (cm^-1): -8e-12\n", "The number of occupied modals for each mode is: \n", "- Mode 0: 0.0\n", "- Mode 1: 0.0\n", "- Mode 2: 0.0\n", "- Mode 3: 0.0\n", "\n", "\n", "\n", "=== GROUND STATE ===\n", " \n", "* Vibrational ground state energy (cm^-1): 2432.106954036546\n", "The number of occupied modals for each mode is: \n", "- Mode 0: 1.0\n", "- Mode 1: 1.0\n", "- Mode 2: 1.0\n", "- Mode 3: 1.0\n" ] } ], "source": [ "from qiskit_algorithms import NumPyMinimumEigensolver\n", "from qiskit_nature.second_q.drivers import GaussianForcesDriver\n", "from qiskit_nature.second_q.mappers import DirectMapper\n", "from qiskit_nature.second_q.problems import HarmonicBasis\n", "\n", "driver = GaussianForcesDriver(logfile=\"aux_files/CO2_freq_B3LYP_631g.log\")\n", "basis = HarmonicBasis([2, 2, 2, 2])\n", "vib_problem = driver.run(basis=basis)\n", "vib_problem.hamiltonian.truncation_order = 2\n", "\n", "mapper = DirectMapper()\n", "\n", "solver_without_filter = NumPyMinimumEigensolver()\n", "solver_with_filter = NumPyMinimumEigensolver(\n", " filter_criterion=vib_problem.get_default_filter_criterion()\n", ")\n", "\n", "gsc_wo = GroundStateEigensolver(mapper, solver_without_filter)\n", "result_wo = gsc_wo.solve(vib_problem)\n", "\n", "gsc_w = GroundStateEigensolver(mapper, solver_with_filter)\n", "result_w = gsc_w.solve(vib_problem)\n", "\n", "print(result_wo)\n", "print(\"\\n\\n\")\n", "print(result_w)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "

Version Information

Qiskit SoftwareVersion
qiskit-terra0.24.0.dev0+2b3686f
qiskit-aer0.11.2
qiskit-ibmq-provider0.19.2
qiskit-nature0.6.0
System information
Python version3.9.16
Python compilerGCC 12.2.1 20221121 (Red Hat 12.2.1-4)
Python buildmain, Dec 7 2022 00:00:00
OSLinux
CPUs8
Memory (Gb)62.50002670288086
Thu Apr 06 08:55:38 2023 CEST
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

This code is a part of Qiskit

© Copyright IBM 2017, 2023.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import tutorial_magics\n", "\n", "%qiskit_version_table\n", "%qiskit_copyright" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" } }, "nbformat": 4, "nbformat_minor": 4 }