{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "043d7c67-77f3-4195-918d-5b01b680451f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit import *\n",
    "from qiskit.quantum_info import Operator\n",
    "from qiskit_aer import AerSimulator\n",
    "from qiskit.visualization import plot_histogram\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "from IPython.display import Image"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "626d16af-6d0e-47b7-9271-c88899f30c73",
   "metadata": {},
   "source": [
    "# Grover's Algorithm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "637c8466-3502-428b-9d50-a7a6da4de774",
   "metadata": {},
   "source": [
    "All images were stolen from the Qiskit textbook (link in your course's README page). The chapter on Grover's algorithm might even help with this exercise! \n",
    "\n",
    "In this notebook, we will use Qiskit to implement Grover's algorithm. As you learned in class, Grover's algorithm yields a quadratic speedup in unstructured search problems. Concretely, suppose we were given a list of $N$ items, represented as boxes below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0292d049-6d30-48bd-8ec7-db9e207420d0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"grover_list.png\" width=\"800\" height=\"400\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url=\"grover_list.png\", width=800, height=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef7d284c-d717-4a2b-bee4-0b06dccc1b26",
   "metadata": {},
   "source": [
    "One item---the ``winner'' $\\omega$ coloured purple above---is the marked item we're searching for. Classically, finding $\\omega$ would require that we check $O(N)$ boxes. On a quantum computer, the same task can be accomplished in $O(\\sqrt{N})$ steps using Grover's amplitude amplification trick. At a high level, the algorithm involves three steps: state preparation, application of the oracle, and application of the diffuser:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8aefa899-0cbc-46d2-a53a-62c949910f71",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"grover_circuit_high_level.png\" width=\"1000\" height=\"400\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url=\"grover_circuit_high_level.png\", width=1000, height=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6861b28c-c1f1-45f7-aa69-7c10774f1e52",
   "metadata": {},
   "source": [
    "The first step, state preparation, is a step you've seen before: We create a uniform superposition state $\\vert s\\rangle=H^{\\otimes n}\\ket{0}$ by applying Hadamards to all $n$ qubits initialized in $\\vert 0\\rangle$. Geometrically, we can visualize this state as a vector in a two-dimensional plane spanned by $\\vert\\omega\\rangle$, representing the marked element $\\omega$, and the state $\\ket{s'}$ obtained by subtracting $\\vert \\omega\\rangle$ from $\\vert s\\rangle$ and renormalizing. Concretely, we have \n",
    "\n",
    "$$\\vert s\\rangle=\\sin{\\theta}\\vert \\omega\\rangle + \\cos{\\theta}\\vert s'\\rangle,$$\n",
    "\n",
    "where the angle $\\theta$ is given by\n",
    "\n",
    "$$\\theta=\\mathrm{arcsin}\\langle s\\vert \\omega\\rangle=\\mathrm{arcsin}(1/\\sqrt{N}).$$\n",
    "\n",
    "Here, $N=2^n$ is the dimension of the $n$-qubit Hilbert space."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7e6a56ab-4d21-48b1-bdff-b662c0f1d3d0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"grover_step1.jpg\" width=\"1000\" height=\"400\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url=\"grover_step1.jpg\", width=1000, height=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74e6312d-ade1-4e80-8cb2-d5a4dbabdf6e",
   "metadata": {},
   "source": [
    "The oracle $U_f$ imparts a $\\pi$ phase shift (minus sign) to the marked element $\\vert \\omega\\rangle$, leaving all other states unchanged. This corresponds to a reflection of $\\vert s\\rangle$ about $\\vert s'\\rangle$. The negative amplitude of $\\vert \\omega\\rangle$ also implies that the average amplitude (indicated by a dashed line below) has been reduced from its original value of $1/\\sqrt{N}$. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "3616ec93-7cc3-4abf-8e2b-b17df8a75a26",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"grover_step2.jpg\" width=\"1000\" height=\"400\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url=\"grover_step2.jpg\", width=1000, height=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b329f170-5c8b-4829-8d16-42270ab5fc33",
   "metadata": {},
   "source": [
    "Finally, we apply the diffuser $U_s=2\\vert s\\rangle\\langle s \\vert-1$, which implements an additional reflection about the state $\\vert s\\rangle$. The net effect of the oracle and diffuser is to rotate the initial state $\\vert s\\rangle$ towards the marked element $\\vert \\omega\\rangle$, effectively amplifying the amplitude of $\\vert \\omega\\rangle$ relative to all other unmarked states."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "1c333685-bb80-4e1b-b3a2-13dc6673f64b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"grover_step3.jpg\" width=\"1000\" height=\"400\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url=\"grover_step3.jpg\", width=1000, height=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbab24ca-c425-4234-8e6f-137cb08a89b1",
   "metadata": {},
   "source": [
    "For $k$ marked elements, the optimal number of repetitions is given by $$r=\\left\\lfloor\\frac{\\pi}{4}\\sqrt{\\frac{N}{k}}\\right\\rfloor,$$\n",
    "\n",
    "where $\\lfloor \\rfloor$ is the floor function. Note the $\\sqrt{N}$ scaling."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfe9c9a9-a46a-4f1c-9dfb-2854e1af7e9b",
   "metadata": {},
   "source": [
    "Now, let's implement both the oracle and diffuser in Qiskit. The function ```oracle``` below takes two arguments, ```n``` and ```marked_items```. The first is the number of qubits in the circuit, while the second is a list containing the indices of marked elements. To define the oracle, create an identity matrix of dimension $2^n$, and flip the signs of all diagonal elements contained in the list ```marked_items```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "22cf5f65-cee8-40d1-add1-76b9fae0f45c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def oracle(n, marked_items):\n",
    "    \n",
    "    # Create an n-qubit quantum circuit\n",
    "    qc = QuantumCircuit(n, name='Oracle')\n",
    "\n",
    "    ### YOUR CODE GOES HERE - START\n",
    "    \n",
    "        \n",
    "    ### YOUR CODE GOES HERE - END\n",
    "\n",
    "    # This converts your matrix into an operator and appends it to the circuit. Do not alter this line.\n",
    "    qc.unitary(Operator(oracle), range(n))\n",
    "    \n",
    "    return qc"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7809fbf9-597e-4c52-9320-dccb31eb5607",
   "metadata": {},
   "source": [
    "The purpose of the diffuser is to reflect all amplitudes about the average amplitude. One way to do this (see Qiskit textbook) is to apply Hadamards to all qubits, followed by the oracle with only the state $\\vert0\\rangle^{\\otimes n}$ as the marked element, i.e. ```marked_items=[0]```. Following the application of the oracle, Hadamards are once again applied to all qubits. A helpful function for applying a pre-defined operator is ```qc.append(operator, indices)```, where ```operator``` is the operator and ```indices``` is a list of qubits to which the operator should be applied. For instance, let's say we've defined a three-qubit operator called ```myunitary``` that we want to apply to qubits 0, 3, and 4. This can be done with the line ```qc.append(myunitary, [0,3,4])```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "728f1c28-8e89-4943-addb-168baf20a714",
   "metadata": {},
   "outputs": [],
   "source": [
    "def diffuser(n, name='Diffuser'):\n",
    "    \n",
    "    # Create an n-qubit quantum circuit\n",
    "    qc = QuantumCircuit(n, name='Diffuser')\n",
    "    \n",
    "    ### YOUR CODE GOES HERE - START\n",
    "\n",
    "     \n",
    "    ### YOUR CODE GOES HERE - END\n",
    "    \n",
    "    return qc"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5029bf7f-74fc-485e-aab4-f1ef74c13935",
   "metadata": {},
   "source": [
    "Next, let's define a function that calculates $r$, the optimal number of repetitions. Your job is to code the formula for $r$ given above. The function ```np.floor``` may be helpful."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "97c1eb1a-f562-48ef-8074-31aa1f8550f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def optimal_r(n, marked_items):\n",
    "    k = len(marked_items)\n",
    "\n",
    "    ### YOUR CODE GOES HERE - START\n",
    "\n",
    "    ### YOUR CODE GOES HERE - END\n",
    "    \n",
    "    return r"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0681841d-5672-41b5-b8b6-4989e22f63da",
   "metadata": {},
   "source": [
    "We can finally put all of the pieces together. Let's use the functions you wrote above to run Grover's algorithm for six qubits ($n=6$) and the marked items 10 and 32. Note that these marked items correspond to the bitstrings 001010 and 100000, respectively. You don't need to modify the code below -- just run it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "7f81c298-521a-4fe1-8625-0ecf771d3633",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The optimal number of repetitions is r = 4\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"word-wrap: normal;white-space: pre;background: #fff0;line-height: 1.1;font-family: &quot;Courier New&quot;,Courier,monospace\">     ┌───┐┌─────────┐┌───────────┐┌─────────┐┌───────────┐┌─────────┐»\n",
       "q_0: ┤ H ├┤0        ├┤0          ├┤0        ├┤0          ├┤0        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_1: ┤ H ├┤1        ├┤1          ├┤1        ├┤1          ├┤1        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_2: ┤ H ├┤2        ├┤2          ├┤2        ├┤2          ├┤2        ├»\n",
       "     ├───┤│  Oracle ││  Diffuser ││  Oracle ││  Diffuser ││  Oracle │»\n",
       "q_3: ┤ H ├┤3        ├┤3          ├┤3        ├┤3          ├┤3        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_4: ┤ H ├┤4        ├┤4          ├┤4        ├┤4          ├┤4        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_5: ┤ H ├┤5        ├┤5          ├┤5        ├┤5          ├┤5        ├»\n",
       "     └───┘└─────────┘└───────────┘└─────────┘└───────────┘└─────────┘»\n",
       "c: 6/════════════════════════════════════════════════════════════════»\n",
       "                                                                     »\n",
       "«     ┌───────────┐┌─────────┐┌───────────┐┌─┐               \n",
       "«q_0: ┤0          ├┤0        ├┤0          ├┤M├───────────────\n",
       "«     │           ││         ││           │└╥┘┌─┐            \n",
       "«q_1: ┤1          ├┤1        ├┤1          ├─╫─┤M├────────────\n",
       "«     │           ││         ││           │ ║ └╥┘┌─┐         \n",
       "«q_2: ┤2          ├┤2        ├┤2          ├─╫──╫─┤M├─────────\n",
       "«     │  Diffuser ││  Oracle ││  Diffuser │ ║  ║ └╥┘┌─┐      \n",
       "«q_3: ┤3          ├┤3        ├┤3          ├─╫──╫──╫─┤M├──────\n",
       "«     │           ││         ││           │ ║  ║  ║ └╥┘┌─┐   \n",
       "«q_4: ┤4          ├┤4        ├┤4          ├─╫──╫──╫──╫─┤M├───\n",
       "«     │           ││         ││           │ ║  ║  ║  ║ └╥┘┌─┐\n",
       "«q_5: ┤5          ├┤5        ├┤5          ├─╫──╫──╫──╫──╫─┤M├\n",
       "«     └───────────┘└─────────┘└───────────┘ ║  ║  ║  ║  ║ └╥┘\n",
       "«c: 6/══════════════════════════════════════╩══╩══╩══╩══╩══╩═\n",
       "«                                           0  1  2  3  4  5 </pre>"
      ],
      "text/plain": [
       "     ┌───┐┌─────────┐┌───────────┐┌─────────┐┌───────────┐┌─────────┐»\n",
       "q_0: ┤ H ├┤0        ├┤0          ├┤0        ├┤0          ├┤0        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_1: ┤ H ├┤1        ├┤1          ├┤1        ├┤1          ├┤1        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_2: ┤ H ├┤2        ├┤2          ├┤2        ├┤2          ├┤2        ├»\n",
       "     ├───┤│  Oracle ││  Diffuser ││  Oracle ││  Diffuser ││  Oracle │»\n",
       "q_3: ┤ H ├┤3        ├┤3          ├┤3        ├┤3          ├┤3        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_4: ┤ H ├┤4        ├┤4          ├┤4        ├┤4          ├┤4        ├»\n",
       "     ├───┤│         ││           ││         ││           ││         │»\n",
       "q_5: ┤ H ├┤5        ├┤5          ├┤5        ├┤5          ├┤5        ├»\n",
       "     └───┘└─────────┘└───────────┘└─────────┘└───────────┘└─────────┘»\n",
       "c: 6/════════════════════════════════════════════════════════════════»\n",
       "                                                                     »\n",
       "«     ┌───────────┐┌─────────┐┌───────────┐┌─┐               \n",
       "«q_0: ┤0          ├┤0        ├┤0          ├┤M├───────────────\n",
       "«     │           ││         ││           │└╥┘┌─┐            \n",
       "«q_1: ┤1          ├┤1        ├┤1          ├─╫─┤M├────────────\n",
       "«     │           ││         ││           │ ║ └╥┘┌─┐         \n",
       "«q_2: ┤2          ├┤2        ├┤2          ├─╫──╫─┤M├─────────\n",
       "«     │  Diffuser ││  Oracle ││  Diffuser │ ║  ║ └╥┘┌─┐      \n",
       "«q_3: ┤3          ├┤3        ├┤3          ├─╫──╫──╫─┤M├──────\n",
       "«     │           ││         ││           │ ║  ║  ║ └╥┘┌─┐   \n",
       "«q_4: ┤4          ├┤4        ├┤4          ├─╫──╫──╫──╫─┤M├───\n",
       "«     │           ││         ││           │ ║  ║  ║  ║ └╥┘┌─┐\n",
       "«q_5: ┤5          ├┤5        ├┤5          ├─╫──╫──╫──╫──╫─┤M├\n",
       "«     └───────────┘└─────────┘└───────────┘ ║  ║  ║  ║  ║ └╥┘\n",
       "«c: 6/══════════════════════════════════════╩══╩══╩══╩══╩══╩═\n",
       "«                                           0  1  2  3  4  5 "
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def full_algorithm(n, marked_items):\n",
    "    \n",
    "    qc = QuantumCircuit(n, n)\n",
    "    r = optimal_r(n, marked_items)\n",
    "    print('The optimal number of repetitions is r =', r)\n",
    "    \n",
    "    # Step 1: State preparation\n",
    "    qc.h(range(n))\n",
    "    \n",
    "    # Steps 2 and 3, repeated r times: \n",
    "    for _ in range(r):\n",
    "        qc.append(oracle(n, marked_items), range(n))\n",
    "        qc.append(diffuser(n), range(n))\n",
    "        \n",
    "    # Measure all qubits\n",
    "    qc.measure(range(n), range(n))\n",
    "  \n",
    "    return qc\n",
    "\n",
    "grover = full_algorithm(6, [10, 32])\n",
    "grover.draw()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8549077-0b75-41b7-a73d-a74cf2ffc182",
   "metadata": {},
   "source": [
    "Let's simulate the outcomes using the code below. If you've done everything correctly, most of the counts should be clustered about the two marked elements, ```001010``` and ```100000```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "d8083ade-cc89-465f-844b-a8ff5228ec4b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 22912 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "backend = AerSimulator()\n",
    "\n",
    "circuit = transpile(grover, backend)\n",
    "job = backend.run(circuit, shots=2000)\n",
    "counts = job.result().get_counts()\n",
    "plot_histogram(counts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d17721b4-c7e7-424f-b9c2-1653c33c320d",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
