{ "cells": [ { "cell_type": "markdown", "id": "c55ed687-d560-4f18-b90b-f0fbc5f83ec6", "metadata": {}, "source": [ "# ADAPT VQE" ] }, { "cell_type": "markdown", "id": "763fb71e-c7fa-490d-917a-5a0f0b66a144", "metadata": {}, "source": [ "## Overview\n", "In this notebook, we will implement [ADAPT-VQE](https://www.nature.com/articles/s41467-019-10988-2), to show how to use TenCirChem to build novel algorithms." ] }, { "cell_type": "markdown", "id": "7b89ed75-e5b8-46b8-969a-69b3ef232f27", "metadata": {}, "source": [ "## Form Operator Pool" ] }, { "cell_type": "code", "execution_count": 1, "id": "62546ebf-c313-466b-9593-3ca49b6e0368", "metadata": {}, "outputs": [], "source": [ "from tencirchem import UCC\n", "from tencirchem.molecule import h4\n", "\n", "ucc = UCC(h4)\n", "\n", "# get single and double excitations\n", "# param_id maps operators to parameters (some operators share the same parameter)\n", "ex1_ops, ex1_param_ids, _ = ucc.get_ex1_ops()\n", "ex2_ops, ex2_param_ids, _ = ucc.get_ex2_ops()" ] }, { "cell_type": "markdown", "id": "90514e33-0ae1-4ad8-b797-9986596db474", "metadata": {}, "source": [ "The meanings of the numbers are best summarized in the following figure. The elements in the tuples are spin-orbital indices, with the first half and the second half corresponds to creation and annihilation operators, respectively.\n", "\n", "![excitation](../statics/excitation.png)" ] }, { "cell_type": "code", "execution_count": 2, "id": "2a78904b-8394-4629-be92-9d25ef326724", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[((6, 4), 0),\n", " ((2, 0), 0),\n", " ((7, 4), 1),\n", " ((3, 0), 1),\n", " ((6, 5), 2),\n", " ((2, 1), 2),\n", " ((7, 5), 3),\n", " ((3, 1), 3)]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(zip(ex1_ops, ex1_param_ids))" ] }, { "cell_type": "code", "execution_count": 3, "id": "dcfa46af-f568-46c1-a6af-252aea534fc5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[((6, 7, 5, 4), 0),\n", " ((2, 3, 1, 0), 0),\n", " ((2, 6, 4, 0), 1),\n", " ((2, 7, 4, 0), 2),\n", " ((6, 3, 0, 4), 2),\n", " ((3, 7, 4, 0), 3),\n", " ((2, 6, 5, 0), 4),\n", " ((6, 2, 1, 4), 4),\n", " ((2, 7, 5, 0), 5),\n", " ((6, 3, 1, 4), 5),\n", " ((3, 6, 5, 0), 6),\n", " ((7, 2, 1, 4), 6),\n", " ((3, 7, 5, 0), 7),\n", " ((7, 3, 1, 4), 7),\n", " ((2, 6, 5, 1), 8),\n", " ((2, 7, 5, 1), 9),\n", " ((6, 3, 1, 5), 9),\n", " ((3, 7, 5, 1), 10)]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(zip(ex2_ops, ex2_param_ids))" ] }, { "cell_type": "code", "execution_count": 4, "id": "a5b187c4-a739-4bc8-a53d-b50f262c78be", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[(6, 4), (2, 0)],\n", " [(7, 4), (3, 0)],\n", " [(6, 5), (2, 1)],\n", " [(7, 5), (3, 1)],\n", " [(6, 7, 5, 4), (2, 3, 1, 0)],\n", " [(2, 6, 4, 0)],\n", " [(2, 7, 4, 0), (6, 3, 0, 4)],\n", " [(3, 7, 4, 0)],\n", " [(2, 6, 5, 0), (6, 2, 1, 4)],\n", " [(2, 7, 5, 0), (6, 3, 1, 4)],\n", " [(3, 6, 5, 0), (7, 2, 1, 4)],\n", " [(3, 7, 5, 0), (7, 3, 1, 4)],\n", " [(2, 6, 5, 1)],\n", " [(2, 7, 5, 1), (6, 3, 1, 5)],\n", " [(3, 7, 5, 1)]]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# group the operators to form an operator pool\n", "# operators with the same parameter are grouped together.\n", "from collections import defaultdict\n", "\n", "op_pool = defaultdict(list)\n", "for ex1_op, ex1_id in zip(ex1_ops, ex1_param_ids):\n", " op_pool[(1, ex1_id)].append(ex1_op)\n", "for ex2_op, ex2_id in zip(ex2_ops, ex2_param_ids):\n", " op_pool[(2, ex2_id)].append(ex2_op)\n", "op_pool = list(op_pool.values())\n", "op_pool" ] }, { "cell_type": "markdown", "id": "6b5a6955-223a-4cb5-97f7-449e502c1599", "metadata": {}, "source": [ "## ADAPT-VQE Iteration\n", "Once the operator pool is formed, ADAPT-VQE constructs the ansatz by picking operators in the pool iteratively. The criteria for picking the operators is to maximize the absolute energy gradient if the operator is picked.\n", "Supposing the original ansatz wavefunction is $| \\psi \\rangle$ and the operator picked is $G_k$, the new ansatz with $G_k$ is\n", "\n", "$$\n", " | \\Psi \\rangle = e^{\\theta_k G_k} | \\psi \\rangle \\ ,\n", "$$\n", "\n", "and the energy gradient is\n", "$$\n", " \\frac{\\partial \\langle E \\rangle }{\\partial \\theta_k} =2 \\langle \\Psi|HG_k|\\Psi \\rangle \\ .\n", "$$\n", "\n", "With $\\theta_k = 0$, the gradient is then\n", "\n", "$$\n", " \\frac{\\partial \\langle E \\rangle }{\\partial \\theta_k}\\bigg{|}_{\\theta_k=0} = 2 \\langle \\psi|HG_k|\\psi \\rangle \\ .\n", "$$\n", "\n", "If multiple operators sharing the same parameter are added at the same time,\n", "then their gradient should be added together.\n", "The iteration stops when the norm of the gradient vector is below a predefined threshold $\\epsilon$.\n", "In the following, $| \\psi \\rangle$ is obtained by `ucc.civector()` as a vector in the configuration interaction space." ] }, { "cell_type": "code", "execution_count": 5, "id": "4507e813-7d7c-4474-b9dd-b476bdb4d3d0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "######## Iter 0 ########\n", "Gradient vector norm: 0.6413625239691856\n", "Nonzero gradient vector elements:\n", "0.0000001787 [(6, 4), (2, 0)]\n", "-0.0000001938 [(7, 5), (3, 1)]\n", "0.2103420256 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.2141378225 [(2, 6, 4, 0)]\n", "0.1874444822 [(3, 7, 4, 0)]\n", "0.3756623781 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "0.1653203524 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "0.2765500090 [(2, 6, 5, 1)]\n", "0.2029254292 [(3, 7, 5, 1)]\n", "Picked operator [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "Optimized energy: -2.130032459500918\n", "######## Iter 1 ########\n", "Gradient vector norm: 0.5218024526621244\n", "Nonzero gradient vector elements:\n", "-0.0125038973 [(6, 4), (2, 0)]\n", "0.0167842115 [(7, 5), (3, 1)]\n", "0.1713002616 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.2226765539 [(2, 6, 4, 0)]\n", "0.2132785933 [(3, 7, 4, 0)]\n", "0.1071344471 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "0.3020073191 [(2, 6, 5, 1)]\n", "0.2115115746 [(3, 7, 5, 1)]\n", "Picked operator [(2, 6, 5, 1)]\n", "Optimized energy: -2.1521053433859345\n", "######## Iter 2 ########\n", "Gradient vector norm: 0.4071773446367265\n", "Nonzero gradient vector elements:\n", "0.0401782688 [(6, 4), (2, 0)]\n", "-0.0344678940 [(7, 5), (3, 1)]\n", "0.1829807723 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.1754330302 [(2, 6, 4, 0)]\n", "0.2209264766 [(3, 7, 4, 0)]\n", "0.0035378050 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "0.1519036061 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "0.1638188641 [(3, 7, 5, 1)]\n", "Picked operator [(3, 7, 4, 0)]\n", "Optimized energy: -2.1560078326527474\n", "######## Iter 3 ########\n", "Gradient vector norm: 0.3397631170243717\n", "Nonzero gradient vector elements:\n", "0.0447472512 [(6, 4), (2, 0)]\n", "-0.0390906463 [(7, 5), (3, 1)]\n", "0.1863194802 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.1647591097 [(2, 6, 4, 0)]\n", "0.0000000382 [(3, 7, 4, 0)]\n", "-0.0000000803 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "0.1629033819 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "-0.0000000722 [(2, 6, 5, 1)]\n", "0.1533315226 [(3, 7, 5, 1)]\n", "Picked operator [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "Optimized energy: -2.158313503086184\n", "######## Iter 4 ########\n", "Gradient vector norm: 0.2904720974252725\n", "Nonzero gradient vector elements:\n", "0.0507437926 [(6, 4), (2, 0)]\n", "-0.0431791207 [(7, 5), (3, 1)]\n", "-0.0000000009 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.1548519273 [(2, 6, 4, 0)]\n", "0.0000000012 [(3, 7, 4, 0)]\n", "-0.0000000017 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "0.1881028425 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "0.0000000006 [(2, 6, 5, 1)]\n", "0.1434324638 [(3, 7, 5, 1)]\n", "Picked operator [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "Optimized energy: -2.160631774640665\n", "######## Iter 5 ########\n", "Gradient vector norm: 0.23155645245221745\n", "Nonzero gradient vector elements:\n", "0.0418683900 [(6, 4), (2, 0)]\n", "-0.0344273185 [(7, 5), (3, 1)]\n", "0.1647882822 [(2, 6, 4, 0)]\n", "-0.0000000002 [(3, 7, 4, 0)]\n", "0.1533786504 [(3, 7, 5, 1)]\n", "Picked operator [(2, 6, 4, 0)]\n", "Optimized energy: -2.164921018130742\n", "######## Iter 6 ########\n", "Gradient vector norm: 0.16766398932285673\n", "Nonzero gradient vector elements:\n", "0.0241699861 [(6, 4), (2, 0)]\n", "-0.0308084251 [(7, 5), (3, 1)]\n", "0.0004344058 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.0012217449 [(3, 7, 4, 0)]\n", "0.0008976241 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "0.0004632254 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "0.0003003856 [(2, 6, 5, 1)]\n", "0.1630186312 [(3, 7, 5, 1)]\n", "Picked operator [(3, 7, 5, 1)]\n", "Optimized energy: -2.1674443274948185\n", "######## Iter 7 ########\n", "Gradient vector norm: 0.026968274543560385\n", "Nonzero gradient vector elements:\n", "0.0206092184 [(6, 4), (2, 0)]\n", "-0.0173939056 [(7, 5), (3, 1)]\n", "-0.0000000813 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.0000000039 [(2, 6, 4, 0)]\n", "0.0000001792 [(3, 7, 4, 0)]\n", "0.0000001301 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "-0.0000000779 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "-0.0000000055 [(2, 6, 5, 1)]\n", "0.0000000310 [(3, 7, 5, 1)]\n", "Picked operator [(6, 4), (2, 0)]\n", "Optimized energy: -2.1674988485893616\n", "######## Iter 8 ########\n", "Gradient vector norm: 0.022742856617093358\n", "Nonzero gradient vector elements:\n", "-0.0000000004 [(6, 4), (2, 0)]\n", "-0.0227422801 [(7, 5), (3, 1)]\n", "0.0001099764 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "0.0000004105 [(2, 6, 4, 0)]\n", "0.0000167703 [(3, 7, 4, 0)]\n", "0.0001173127 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "0.0000073356 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "0.0000055027 [(2, 6, 5, 1)]\n", "Picked operator [(7, 5), (3, 1)]\n", "Optimized energy: -2.1675452943964704\n", "######## Iter 9 ########\n", "Gradient vector norm: 2.482330523964407e-05\n", "Nonzero gradient vector elements:\n", "-0.0000000015 [(6, 4), (2, 0)]\n", "-0.0000000041 [(7, 5), (3, 1)]\n", "-0.0000059866 [(6, 7, 5, 4), (2, 3, 1, 0)]\n", "-0.0000001058 [(2, 6, 4, 0)]\n", "0.0000190189 [(3, 7, 4, 0)]\n", "0.0000015227 [(2, 7, 5, 0), (6, 3, 1, 4)]\n", "0.0000075106 [(3, 6, 5, 0), (7, 2, 1, 4)]\n", "0.0000041626 [(2, 6, 5, 1)]\n", "0.0000119403 [(3, 7, 5, 1)]\n", "Converged. Norm of gradient: 2.482330523964407e-05\n" ] } ], "source": [ "import numpy as np\n", "\n", "ucc.ex_ops = []\n", "ucc.params = []\n", "ucc.param_ids = []\n", "\n", "converged = False\n", "MAX_ITER = 100\n", "EPSILON = 1e-3\n", "for i in range(MAX_ITER):\n", " print(f\"######## Iter {i} ########\")\n", " # calculate gradient of each operator from the pool\n", " op_gradient_list = []\n", " psi = ucc.civector()\n", " bra = ucc.hamiltonian(psi)\n", " for op_list in op_pool:\n", " grad = bra.conj() @ ucc.apply_excitation(psi, op_list[0])\n", " if len(op_list) == 2:\n", " grad += bra.conj() @ ucc.apply_excitation(psi, op_list[1])\n", " op_gradient_list.append(2 * grad)\n", " print(\"Gradient vector norm: \", np.linalg.norm(op_gradient_list))\n", " print(\"Nonzero gradient vector elements:\")\n", " for op_list, g in zip(op_pool, op_gradient_list):\n", " if np.abs(g) < 1e-10:\n", " continue\n", " print(f\"{g:.10f}\", op_list)\n", " if np.linalg.norm(op_gradient_list) < EPSILON:\n", " print(f\"Converged. Norm of gradient: {np.linalg.norm(op_gradient_list)}\")\n", " break\n", " chosen_op_list = op_pool[np.argmax(np.abs(op_gradient_list))]\n", " print(\"Picked operator\", chosen_op_list)\n", " if ucc.ex_ops[-len(chosen_op_list) :] == chosen_op_list:\n", " print(f\"Converged. Same operator picked in succession\")\n", " break\n", " # update ansatz and run calculation\n", " ucc.ex_ops.extend(chosen_op_list)\n", " ucc.params = list(ucc.params) + [0]\n", " ucc.param_ids.extend([len(ucc.params) - 1] * len(chosen_op_list))\n", " ucc.init_guess = ucc.params\n", " ucc.kernel()\n", " print(f\"Optimized energy: {ucc.e_ucc}\")\n", "else:\n", " print(\"Maximum number of iteration reached\")" ] }, { "cell_type": "markdown", "id": "0038fe24-6e4f-46de-b58c-64b69c689a7f", "metadata": {}, "source": [ "The final ansatz, optimized parameters and reference energies are available by `print_summary`" ] }, { "cell_type": "code", "execution_count": 6, "id": "1a2456f0-d336-4c82-868c-8526dd73e811", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "################################ Ansatz ###############################\n", " #qubits #params #excitations initial condition\n", " 8 9 14 RHF\n", "############################### Energy ################################\n", " energy (Hartree) error (mH) correlation energy (%)\n", "HF -2.121387 46.173788 -0.000\n", "MP2 -2.151794 15.766505 65.854\n", "CCSD -2.167556 0.004979 99.989\n", "UCC -2.167545 0.015250 99.967\n", "FCI -2.167561 0.000000 100.000\n", "############################# Excitations #############################\n", " excitation configuration parameter initial guess\n", "0 (2, 7, 5, 0) 10010110 -0.051907 -0.052049\n", "1 (6, 3, 1, 4) 01101001 -0.051907 -0.052049\n", "2 (2, 6, 5, 1) 01010101 -0.140231 -0.139297\n", "3 (3, 7, 4, 0) 10101010 -0.033405 -0.033263\n", "4 (6, 7, 5, 4) 11000011 -0.024488 -0.024327\n", "5 (2, 3, 1, 0) 00111100 -0.024488 -0.024327\n", "6 (3, 6, 5, 0) 01011010 -0.028461 -0.028758\n", "7 (7, 2, 1, 4) 10100101 -0.028461 -0.028758\n", "8 (2, 6, 4, 0) 01100110 -0.052825 -0.053072\n", "9 (3, 7, 5, 1) 10011001 -0.030435 -0.030805\n", "10 (6, 4) 01100011 -0.006359 -0.005294\n", "11 (2, 0) 00110110 -0.006359 -0.005294\n", "12 (7, 5) 10010011 0.004086 0.000000\n", "13 (3, 1) 00111001 0.004086 0.000000\n", "######################### Optimization Result #########################\n", " e: -2.1675452943964704\n", " fun: array(-2.16754529)\n", " hess_inv: <9x9 LbfgsInvHessProduct with dtype=float64>\n", " init_guess: [-0.052049049877468376, -0.13929721882902535, -0.033262956584275416, -0.02432719362945896, -0.02875765729787159, -0.05307218567505374, -0.030805233161950704, -0.005294244026331024, 0]\n", " jac: array([ 5.07062545e-09, -8.83791785e-11, 1.30782196e-08, 1.70760122e-09,\n", " 4.83647155e-09, -3.33705792e-11, 3.54077142e-09, -1.48813139e-09,\n", " -4.13038187e-09])\n", " message: 'CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH'\n", " nfev: 13\n", " nit: 11\n", " njev: 13\n", " opt_time: 0.014690637588500977\n", " staging_time: 4.76837158203125e-07\n", " status: 0\n", " success: True\n", " x: array([-0.0519072 , -0.14023057, -0.03340521, -0.02448758, -0.02846146,\n", " -0.0528252 , -0.03043539, -0.00635861, 0.00408631])\n" ] } ], "source": [ "ucc.print_summary()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.7.13" } }, "nbformat": 4, "nbformat_minor": 5 }