{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# This cell is added by sphinx-gallery\n# It can be customized to whatever you like\n%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Adaptive circuits for quantum chemistry\n=======================================\n\n*Author: PennyLane dev team. Posted: 2021. Last updated: 13 September\n2021*\n\nThe key component of variational quantum algorithms for quantum\nchemistry is the circuit used to prepare electronic ground states of a\nmolecule. The variational quantum eigensolver (VQE) \\[\\#peruzzo2014\\]\\_,\n\\[\\#yudong2019\\]\\_ is the method of choice for performing such quantum\nchemistry simulations on quantum devices with few qubits. For a given\nmolecule, the appropriate circuit can be generated by using a\npre-selected wavefunction ansatz, for example, the unitary coupled\ncluster with single and double excitations (UCCSD) \\[\\#romero2017\\]\\_.\nIn this approach, we include all possible single and double excitations\nof electrons from the occupied spin-orbitals of a reference state to the\nunoccupied spin-orbitals \\[\\#givenstutorial\\]\\_. This makes the\nconstruction of the ansatz straightforward for any given molecule.\nHowever, using a pre-constructed ansatz has the disadvantage of reducing\nperformance in favour of generality: the approach may work well in many\ncases, but it will not be optimized for a specific problem.\n\nIn practical applications, including all possible excitations usually\nincreases the cost of the simulations without improving the accuracy of\nthe results. This motivates implementing a strategy that allows for\napproximation of the contribution of the excitations and selects only\nthose excitations that are found to be important for the given molecule.\nThis can be done by using adaptive methods to construct a circuit for\neach given problem \\[\\#grimsley2019\\]\\_. Using adaptive circuits helps\nimprove performance at the cost of reducing generality.\n\n![Examples of selecting specific gates to generate adaptive\ncircuits.](/demonstrations/adaptive_circuits/main.png){width=\"75%\"}\n\nIn this tutorial, you will learn how to **adaptively** build customized\nquantum chemistry circuits. This includes a recipe to adaptively select\ngates that have a significant contribution to the desired state, while\nneglecting those that have a small contribution. You will also learn how\nto use the functionality in PennyLane for leveraging the sparsity of a\nmolecular Hamiltonian to make the computation of the expectation values\neven more efficient. Let's get started!\n\nAdaptive circuits\n-----------------\n\nThe main idea behind building adaptive circuits is to compute the\ngradients with respect to all possible excitation gates and then select\ngates based on the magnitude of the computed gradients.\n\nThere are different ways to make use of the gradient information and\nhere we discuss one of these strategies and apply it to compute the\nground state energy of LiH. This method requires constructing the\nHamiltonian and determining all possible excitations, which we can do\nwith functionality built into PennyLane. But we first need to define the\nmolecular parameters, including atomic symbols and coordinates. Note\nthat the atomic coordinates are in\n[Bohr](https://en.wikipedia.org/wiki/Bohr_radius).\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import pennylane as qml\nfrom pennylane import qchem\nfrom pennylane import numpy as np\nimport time\n\nsymbols = [\"Li\", \"H\"]\ngeometry = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 2.969280527])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now compute the molecular Hamiltonian in the\n[STO-3G](https://en.wikipedia.org/wiki/STO-nG_basis_sets) basis and\nobtain the electronic excitations. We restrict ourselves to single and\ndouble excitations, but higher-level ones such as triple and quadruple\nexcitations can be considered as well. Each of these electronic\nexcitations is represented by a gate that excites electrons from the\noccupied orbitals of a reference state to the unoccupied ones. This\nallows us to prepare a state that is a superposition of the reference\nstate and all of the excited states.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"H, qubits = qchem.molecular_hamiltonian(\n symbols,\n geometry,\n active_electrons=2,\n active_orbitals=5\n)\n\nactive_electrons = 2\n\nsingles, doubles = qchem.excitations(active_electrons, qubits)\n\nprint(f\"Total number of excitations = {len(singles) + len(doubles)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we have a total of 24 excitations which can be represented by\nthe same number of excitation gates \\[\\#givenstutorial\\]\\_. We now\nimplement a strategy that constructs the circuit by adding groups of\ngates one at a time. We follow these steps:\n\n1. Compute gradients for all double excitations.\n2. Select the double excitations with gradients larger than a\n pre-defined threshold.\n3. Perform VQE to obtain the optimized parameters for the selected\n double excitations.\n4. Repeat steps 1 and 2 for the single excitations.\n5. Perform the final VQE optimization with all the\n selected excitations.\n\nWe create a circuit that applies a selected group of gates to a\nreference Hartree-Fock state.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"hf_state = qchem.hf_state(active_electrons, qubits)\n\n\ndef circuit_1(params, wires, excitations):\n qml.BasisState(hf_state, wires=wires)\n\n for i, excitation in enumerate(excitations):\n if len(excitation) == 4:\n qml.DoubleExcitation(params[i], wires=excitation)\n else:\n qml.SingleExcitation(params[i], wires=excitation)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now construct our first group of gates by including all the double\nexcitations and compute the gradient for each one. We also need to\ndefine a device and a cost function. We initialize the parameter values\nto zero such that the gradients are computed with respect to the\nHartree-Fock state.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=qubits)\ncost_fn = qml.ExpvalCost(circuit_1, H, dev, optimize=True)\n\ncircuit_gradient = qml.grad(cost_fn, argnum=0)\n\nparams = [0.0] * len(doubles)\ngrads = circuit_gradient(params, excitations=doubles)\n\nfor i in range(len(doubles)):\n print(f\"Excitation : {doubles[i]}, Gradient: {grads[i]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The computed gradients have different values, reflecting the\ncontribution of each gate in the final state prepared by the circuit.\nMany of the gradient values are zero and we select those gates that have\na gradient above a pre-defined threshold, which we set to $10^{-5}$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5]\ndoubles_select"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are only 6 double excitation gates, out of the original 16, that\nhave gradients above the threshold. We add the selected gates to the\ncircuit and perform one optimization step to determine the updated\nparameters for the selected gates. We also need to define an optimizer.\nNote that the optimization is not very costly as we only have six gates\nin our circuit.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"opt = qml.GradientDescentOptimizer(stepsize=0.5)\n\nparams_doubles = [0.0] * len(doubles_select)\n\nfor n in range(20):\n params_doubles = opt.step(cost_fn, params_doubles, excitations=doubles_select)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we keep the selected gates in the circuit and compute the gradients\nwith respect to all of the single excitation gates, selecting those that\nhave a non-negligible gradient. To do that, we need to slightly modify\nour circuit such that parameters of the double excitation gates are kept\nfixed while the gradients are computed for the single excitation gates.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def circuit_2(params, wires, excitations, gates_select, params_select):\n qml.BasisState(hf_state, wires=wires)\n\n for i, gate in enumerate(gates_select):\n if len(gate) == 4:\n qml.DoubleExcitation(params_select[i], wires=gate)\n elif len(gate) == 2:\n qml.SingleExcitation(params_select[i], wires=gate)\n\n for i, gate in enumerate(excitations):\n if len(gate) == 4:\n qml.DoubleExcitation(params[i], wires=gate)\n elif len(gate) == 2:\n qml.SingleExcitation(params[i], wires=gate)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now compute the gradients for the single excitation gates.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"cost_fn = qml.ExpvalCost(circuit_2, H, dev, optimize=True)\ncircuit_gradient = qml.grad(cost_fn, argnum=0)\nparams = [0.0] * len(singles)\n\ngrads = circuit_gradient(\n params,\n excitations=singles,\n gates_select=doubles_select,\n params_select=params_doubles\n)\n\nfor i in range(len(singles)):\n print(f\"Excitation : {singles[i]}, Gradient: {grads[i]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similar to the double excitation gates, we select those single\nexcitations that have a gradient larger than a predefined threshold.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5]\nsingles_select"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now have all of the gates we need to build our circuit. The selected\nsingle and double excitation gates are highlighted in the figure below.\n\n![](/demonstrations/adaptive_circuits/adapted_circuit.png)\n\n> width\n>\n> : 90%\n>\n> align\n>\n> : center\n>\nWe perform one final step of optimization to get the ground-state\nenergy. The resulting energy should match the exact energy of the ground\nelectronic state of LiH which is -7.8825378193 Ha.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"cost_fn = qml.ExpvalCost(circuit_1, H, dev, optimize=True)\n\nparams = [0.0] * len(doubles_select + singles_select)\n\ngates_select = doubles_select + singles_select\n\nfor n in range(20):\n t1 = time.time()\n params, energy = opt.step_and_cost(cost_fn, params, excitations=gates_select)\n t2 = time.time()\n print(\"n = {:}, E = {:.8f} H, t = {:.2f} s\".format(n, energy, t2 - t1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Success! We obtained the ground state energy of LiH, within chemical\naccuracy, by having only 10 gates in our circuit. This is less than half\nof the total number of single and double excitations of LiH (24).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sparse Hamiltonians -------------\n\nMolecular Hamiltonians and quantum states are sparse. For instance,\nlet\u2019s look at the Hamiltonian we built for LiH. We can compute its\nmatrix representation in the computational basis using the PennyLane\nfunction \\~.pennylane.utils.sparse\\_hamiltonian. This function returns\nthe matrix in the SciPy [sparse\ncoordinate](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.coo_matrix.html)\nformat.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"H_sparse = qml.utils.sparse_hamiltonian(H)\nH_sparse"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The matrix has $1024^2=1,048,576$ entries, but only $11264$ of them are\nnon-zero.\n\n![](/demonstrations/adaptive_circuits/h_sparse.png)\n\n> width\n>\n> : 65%\n>\n> align\n>\n> : center\n>\n> Matrix representation of the LiH Hamiltonian in the computational\n> basis.\n\nLeveraging this sparsity can significantly reduce the simulation times.\nWe use the implemented functionality in PennyLane for computing the\nexpectation value of the sparse Hamiltonian observable. This can reduce\nthe cost of simulations by orders of magnitude depending on the size of\nthe molecule. We use the selected gates obtained in the previous steps\nand perform the final optimization step with the sparse method. Note\nthat the sparse method currently only works with the parameter-shift\ndifferentiation method.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"opt = qml.GradientDescentOptimizer(stepsize=0.5)\n\nexcitations = doubles_select + singles_select\nparams = [0.0] * len(excitations)\n\n\n@qml.qnode(dev, diff_method=\"parameter-shift\")\ndef circuit(params):\n qml.BasisState(hf_state, wires=range(qubits))\n\n for i, excitation in enumerate(excitations):\n if len(excitation) == 4:\n qml.DoubleExcitation(params[i], wires=excitation)\n elif len(excitation) == 2:\n qml.SingleExcitation(params[i], wires=excitation)\n\n return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits)))\n\n\ndef cost(params):\n return circuit(params)\n\n\nfor n in range(20):\n t1 = time.time()\n params, energy = opt.step_and_cost(cost, params)\n t2 = time.time()\n print(\"n = {:}, E = {:.8f} H, t = {:.2f} s\".format(n, energy, t2 - t1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Using the sparse method reproduces the ground state energy while the\noptimization time is much shorter. The average iteration time for the\nsparse method is about 18 times smaller than that of the original\nnon-sparse approach. The performance of the sparse optimization will be\neven better for larger molecules.\n\nConclusions\n===========\n\nWe have learned that building quantum chemistry circuits adaptively and\nusing the functionality for sparse objects makes molecular simulations\nsignificantly more efficient. In this tutorial, we followed an adaptive\nstrategy that selects a group of gates based on information about the\ngradients. This method can be extended such that the gates are selected\none at time, or even to other more elaborate strategies [^1].\n\nReferences\n==========\n\n[^1]: Harper R. Grimsley, Sophia E. Economou, Edwin Barnes, Nicholas J.\n Mayhall, \"An adaptive variational algorithm for exact molecular\n simulations on a quantum computer\". [Nat. Commun. 2019, 10,\n 3007.](https://www.nature.com/articles/s41467-019-10988-2)\n"
]
}
],
"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.7.2"
}
},
"nbformat": 4,
"nbformat_minor": 0
}