{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Variational Quantum Eigensolver (VQE)\n",
    "In this section, the variational quantum eigensolver (VQE) is run on the simulator using Qulacs to find the ground state of the quantum chemical Hamiltonian obtained using OpenFermion/PySCF.\n",
    "\n",
    "This notebook is translated from https://dojo.qulacs.org/ja/latest/notebooks/6.2_qulacs_VQE.html\n",
    "\n",
    "Necessary packages:\n",
    "\n",
    "* qulacs\n",
    "* openfermion\n",
    "* openfermion-pyscf\n",
    "* pyscf\n",
    "* scipy\n",
    "* numpy\n",
    "\n",
    "## Install and import necessary packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Please execute if various libraries are not installed\n",
    "## When use Google Colaboratory, please ignore 'You must restart the runtime in order to use newly installed versions'.\n",
    "## Crash when restarting runtime.\n",
    "!pip install qulacs pyscf openfermion openfermionpyscf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import qulacs\n",
    "from openfermion.transforms import get_fermion_operator, jordan_wigner\n",
    "from openfermion.linalg import get_sparse_operator\n",
    "from openfermion.chem import MolecularData\n",
    "from openfermionpyscf import run_pyscf\n",
    "from scipy.optimize import minimize\n",
    "from pyscf import fci\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create Hamiltonian\n",
    "Create Hamiltonian by PySCF in the same procedure as described in the previous section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "basis = \"sto-3g\"\n",
    "multiplicity = 1\n",
    "charge = 0\n",
    "distance  = 0.977\n",
    "geometry = [[\"H\", [0,0,0]],[\"H\", [0,0,distance]]]\n",
    "description  = \"tmp\"\n",
    "molecule = MolecularData(geometry, basis, multiplicity, charge, description)\n",
    "molecule = run_pyscf(molecule,run_scf=1,run_fci=1)\n",
    "n_qubit = molecule.n_qubits\n",
    "n_electron = molecule.n_electrons\n",
    "fermionic_hamiltonian = get_fermion_operator(molecule.get_molecular_hamiltonian())\n",
    "jw_hamiltonian = jordan_wigner(fermionic_hamiltonian)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Convert Hamiltonian to qulacs Hamiltonian\n",
    "In Qulacs, observables like Hamiltonians are handled by the `Observable` class. There is a function `create_observable_from_openfermion_text` that converts OpenFermion Hamiltonian to Qulacs `Observable`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qulacs import Observable\n",
    "from qulacs.observable import create_observable_from_openfermion_text\n",
    "qulacs_hamiltonian = create_observable_from_openfermion_text(str(jw_hamiltonian))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Construct ansatz\n",
    "Construct a quantum circuit on Qulacs. Here, the quantum circuit is modeled after the experiments with superconducting qubits (A. Kandala et. al. , “Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets“, Nature **549**, 242–246)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qulacs import QuantumState, QuantumCircuit\n",
    "from qulacs.gate import CZ, RY, RZ, merge\n",
    "\n",
    "depth = n_qubit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def he_ansatz_circuit(n_qubit, depth, theta_list):\n",
    "    \"\"\"he_ansatz_circuit\n",
    "    Returns hardware efficient ansatz circuit.\n",
    "\n",
    "    Args:\n",
    "        n_qubit (:class:`int`):\n",
    "            the number of qubit used (equivalent to the number of fermionic modes)\n",
    "        depth (:class:`int`):\n",
    "            depth of the circuit.\n",
    "        theta_list (:class:`numpy.ndarray`):\n",
    "            rotation angles.\n",
    "    Returns:\n",
    "        :class:`qulacs.QuantumCircuit`\n",
    "    \"\"\"\n",
    "    circuit = QuantumCircuit(n_qubit)\n",
    "    for d in range(depth):\n",
    "        for i in range(n_qubit):\n",
    "            circuit.add_gate(merge(RY(i, theta_list[2*i+2*n_qubit*d]), RZ(i, theta_list[2*i+1+2*n_qubit*d])))\n",
    "        for i in range(n_qubit//2):\n",
    "            circuit.add_gate(CZ(2*i, 2*i+1))\n",
    "        for i in range(n_qubit//2-1):\n",
    "            circuit.add_gate(CZ(2*i+1, 2*i+2))\n",
    "    for i in range(n_qubit):\n",
    "        circuit.add_gate(merge(RY(i, theta_list[2*i+2*n_qubit*depth]), RZ(i, theta_list[2*i+1+2*n_qubit*depth])))\n",
    "\n",
    "    return circuit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define VQE cost function\n",
    "As explained in Section 5-1, VQE obtains an approximate ground state by minimizing the expected value \n",
    "\n",
    "\\begin{equation}\n",
    "\\left<H(\\theta)\\right>=\\left<\\psi(\\theta)|H|\\psi(\\theta)\\right>\n",
    "\\end{equation}\n",
    "\n",
    "of Hamiltonian for state $\\left|\\psi(\\theta)\\right>=U(\\theta)\\left|0\\right>$ output from the quantum circuit $U(\\theta)$ with parameters. The following defines a function that returns the expectation value of this Hamiltonian.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cost(theta_list):\n",
    "    state = QuantumState(n_qubit) #Prepare |00000>\n",
    "    circuit = he_ansatz_circuit(n_qubit, depth, theta_list) #Construct quantum circuit\n",
    "    circuit.update_quantum_state(state) #Operate quantum circuit on state\n",
    "    return qulacs_hamiltonian.get_expectation_value(state) #Calculate expectation value of Hamiltonian"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Run VQE\n",
    "Now everthing is prepared, run VQE. For optimization, the BFGS method implemented in scipy is applied, and initial parameters are randomly selected. This process should end in tens of seconds."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "cost_history = []\n",
    "init_theta_list = np.random.random(2*n_qubit*(depth+1))*1e-1\n",
    "cost_history.append(cost(init_theta_list))\n",
    "method = \"BFGS\"\n",
    "options = {\"disp\": True, \"maxiter\": 50, \"gtol\": 1e-6}\n",
    "opt = minimize(cost, init_theta_list,\n",
    "               method=method,\n",
    "               callback=lambda x: cost_history.append(cost(x)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results can be plotted to see that they converge to the correct solution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 21348 characters]",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.rcParams[\"font.size\"] = 18\n",
    "plt.plot(cost_history, color=\"red\", label=\"VQE\")\n",
    "plt.plot(range(len(cost_history)), [molecule.fci_energy]*len(cost_history), linestyle=\"dashed\", color=\"black\", label=\"Exact Solution\")\n",
    "plt.xlabel(\"Iteration\")\n",
    "plt.ylabel(\"Energy expectation value\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you are interested, you can calculate the ground state by varying the `distance` between hydrogen atoms to find the interatomic distance at which the hydrogen molecule is most stable. (It should be about 0.74 angstroms, depending on the performance of the ansatz.)"
   ]
  }
 ],
 "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.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
