{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "91ad3d04-8f28-49c8-91bc-39fec385235d",
   "metadata": {},
   "source": [
    "# Practice Exercise 1 (\"due\" October 14)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "4695ad83-81f7-4aab-b442-c190e2c26648",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit import QuantumCircuit, transpile\n",
    "from qiskit_aer import AerSimulator\n",
    "from qiskit.visualization import plot_histogram\n",
    "import numpy as np\n",
    "from scipy.linalg import expm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1aa322a6-03e6-4a2d-8e0f-c01867c73dca",
   "metadata": {},
   "source": [
    "## Question 1: Generating entanglement \n",
    "\n",
    "Bell states are foundational to quantum information science and represent the simplest examples of entangled states. The four Bell states are typically denoted\n",
    "\n",
    "$$\\vert \\Phi^+\\rangle=\\frac{1}{\\sqrt{2}}\\left(\\vert 00\\rangle+\\vert 11\\rangle\\right),$$\n",
    "$$\\vert \\Phi^-\\rangle=\\frac{1}{\\sqrt{2}}\\left(\\vert 00\\rangle-\\vert 11\\rangle\\right),$$\n",
    "$$\\vert \\Psi^+\\rangle=\\frac{1}{\\sqrt{2}}\\left(\\vert 01\\rangle+\\vert 10\\rangle\\right),$$\n",
    "$$\\vert \\Psi^-\\rangle=\\frac{1}{\\sqrt{2}}\\left(\\vert 01\\rangle-\\vert 10\\rangle\\right).$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40dde07a-fbc8-48ef-b65a-4418f8ee9959",
   "metadata": {},
   "source": [
    "The following Qiskit code generates a quantum circuit that prepares the Bell state $\\vert \\Phi^+\\rangle$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "id": "86507470-9c79-4640-a1e9-c931f4ad3a1d",
   "metadata": {},
   "outputs": [
    {
     "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 ├──■──┤M├───\n",
       "     └───┘┌─┴─┐└╥┘┌─┐\n",
       "q_1: ─────┤ X ├─╫─┤M├\n",
       "          └───┘ ║ └╥┘\n",
       "c: 2/═══════════╩══╩═\n",
       "                0  1 </pre>"
      ],
      "text/plain": [
       "     ┌───┐     ┌─┐   \n",
       "q_0: ┤ H ├──■──┤M├───\n",
       "     └───┘┌─┴─┐└╥┘┌─┐\n",
       "q_1: ─────┤ X ├─╫─┤M├\n",
       "          └───┘ ║ └╥┘\n",
       "c: 2/═══════════╩══╩═\n",
       "                0  1 "
      ]
     },
     "execution_count": 83,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "qc = QuantumCircuit(2,2)\n",
    "qc.h(0)\n",
    "qc.cx(0,1)\n",
    "qc.measure([0,1],[0,1])\n",
    "qc.draw()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a701a1ee-998d-40d0-8705-218d1c02fd0a",
   "metadata": {},
   "source": [
    "We can sample the measurement outcomes using the ```AerSimulator```:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "cc082fc1-bcaa-4035-a2ab-3a8ab979f81d",
   "metadata": {},
   "outputs": [],
   "source": [
    "backend = AerSimulator()\n",
    "job = backend.run(qc, shots=2048)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "b17f5172-ede9-4dff-843d-9b715258cff5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 16296 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "execution_count": 85,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output = job.result()\n",
    "counts = output.get_counts()\n",
    "plot_histogram(counts)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ace7b3b0-82e6-410e-9669-7c0b6cf32cd5",
   "metadata": {},
   "source": [
    "The measurement above measured both qubits, prepared in $\\vert \\Phi^+\\rangle$, in the $Z$ basis. Ask yourself if the measurement outcomes make sense.\n",
    "\n",
    "**TO DO**: Write a circuit that measures both qubits in the $X$ basis instead. We will then use ```AerSimulator``` to sample the measurement outcomes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "fcc04d9c-a191-4cd5-90cf-13219cf7351e",
   "metadata": {},
   "outputs": [
    {
     "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 ├──■──┤ H ├┤M├───\n",
       "     └───┘┌─┴─┐├───┤└╥┘┌─┐\n",
       "q_1: ─────┤ X ├┤ H ├─╫─┤M├\n",
       "          └───┘└───┘ ║ └╥┘\n",
       "c: 2/════════════════╩══╩═\n",
       "                     0  1 </pre>"
      ],
      "text/plain": [
       "     ┌───┐     ┌───┐┌─┐   \n",
       "q_0: ┤ H ├──■──┤ H ├┤M├───\n",
       "     └───┘┌─┴─┐├───┤└╥┘┌─┐\n",
       "q_1: ─────┤ X ├┤ H ├─╫─┤M├\n",
       "          └───┘└───┘ ║ └╥┘\n",
       "c: 2/════════════════╩══╩═\n",
       "                     0  1 "
      ]
     },
     "execution_count": 86,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#Use this as a skeleton\n",
    "qc2 = QuantumCircuit(2,2)\n",
    "\n",
    "#Prepare |Psi^+> (do not alter)\n",
    "qc2.h(0)\n",
    "qc2.cx(0,1)\n",
    "\n",
    "## YOUR CODE GOES HERE ###\n",
    "qc2.h(0)\n",
    "qc2.h(1)\n",
    "qc2.measure([0,1],[0,1])\n",
    "qc2.draw()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cc9b15e-1518-454d-a6b0-994f583c8c05",
   "metadata": {},
   "source": [
    "Run this cell to sample the output of your circuit and plot a histogram of the results. Vary the number of shots if you like."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "8e6ec719-b213-434f-9843-3c9634a7075b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 14776 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "job = backend.run(qc2, shots=1024)\n",
    "counts = job.result().get_counts()\n",
    "plot_histogram(counts)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52b6096a-a214-4166-8ce4-036f929b9b3b",
   "metadata": {},
   "source": [
    "***TO DO*** If you've done this correctly, only certain measurement outcomes will be represented. Explain why (write your answer in this code cell below). It may help to think about the eigenstates of the Pauli-X operator, which you investigated in the last set of exercises. If you'd like to include equations in your explanation, you can have them render nicely by putting them inside dollar signs like this:$$1+1=2$$ \n",
    "\n",
    "\n",
    "***Your answer goes here***: The Bell state $\\vert \\Phi^+\\rangle$ can be written as $\\vert \\Phi^+\\rangle=(\\vert ++\\rangle+\\vert --\\rangle)/\\sqrt{2}$, where $\\vert\\pm\\rangle=(\\vert 0\\rangle\\pm \\vert 1\\rangle)/\\sqrt{2}$. An $X$-basis measurement measures a qubit in the basis $\\vert\\pm\\rangle$. The measurement outcomes are therefore correlated, same as in the case of $Z$-basis measurements. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "453a2885-156c-48a9-9832-7d466c98dab2",
   "metadata": {},
   "source": [
    "# Question 2: Fun with Hadamards\n",
    "\n",
    "Recall that the Hadamard matrix is given by $$H=\\frac{1}{\\sqrt{2}}\\left[\\begin{matrix}1 & 1\\\\\n",
    "1 & -1\\end{matrix}\\right].$$\n",
    "\n",
    "Single-qubit gates can be interpreted as rotations on the Bloch sphere. A rotation of the qubit by angle $\\theta$ about the axis pointing along the unit vector $\\hat{n}=(n_x,n_y,n_z)$ can be expressed as the unitary\n",
    "\n",
    "$$U=e^{i\\theta \\hat{n}\\cdot \\vec{\\sigma}},$$\n",
    "\n",
    "where $\\vec{\\sigma}=(X,Y,Z)$ is a vector of Pauli matrices. For the Hadamard gate, we have\n",
    "\n",
    "$$H = e^{i\\varphi}U$$\n",
    "\n",
    "for some choice of $\\theta$, $\\varphi$, and $\\hat{n}$. What are the values of these parameters? Recall that unit vectors must satisfy the normalization condition $n_x^2+n_y^2+n_z^2=1$ by definition. A useful identity is that\n",
    "\n",
    "$$e^{i\\theta \\hat{n}\\cdot \\vec{\\sigma} }=\\cos{\\theta} I + i \\sin{\\theta}(\\hat{n}\\cdot \\vec{\\sigma}),$$\n",
    "\n",
    "where $I$ is the $2\\times 2$ identity matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7283ede9-7ddf-4db8-ab17-682756074572",
   "metadata": {},
   "outputs": [],
   "source": [
    "X = np.array([[0,1],[1,0]])\n",
    "Y = np.array([[0,-1.j],[1.j,0]])\n",
    "Z = np.array([[1,0],[0,-1]])\n",
    "\n",
    "H = 1/np.sqrt(2)*np.array([[1,1],[1,-1]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1cc9159d-9c4b-4c48-88ff-2138a467bcac",
   "metadata": {},
   "outputs": [],
   "source": [
    "def rotation(n, theta, varphi):\n",
    "\n",
    "    \"ARGS: unit vector n=[nx,ny,nz], theta, varphi\"\n",
    "    n_dot_sigma=(n[0]*X + n[1]*Y + n[2]*Z)\n",
    "    U = expm(1.j*theta*n_dot_sigma)\n",
    "    rot = np.exp(1.j*varphi)*U\n",
    "    return rot\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46e84071-9382-409a-a797-8fef0ed333ce",
   "metadata": {},
   "source": [
    "***TO DO*** Fill in your answer here. If your answer is correct the cell will return ```True```: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2e6e6a1d-b9ca-4d0c-8ab4-281d7e4ab0ad",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "your_Hadamard = rotation([1/np.sqrt(2), 0, 1/np.sqrt(2)], np.pi/2, -np.pi/2)\n",
    "np.allclose(H, your_Hadamard)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bee18c16-4b8a-49bb-a917-b3743531966a",
   "metadata": {},
   "source": [
    "# Question 3: Trotterization"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "adf782ef-83be-41ef-9e2f-1a23075a7703",
   "metadata": {},
   "source": [
    "The Hadamard can be constructed from `rx` and `rz` operations as\n",
    "\n",
    "$$ R_x(\\theta) = e^{i\\frac{\\theta}{2} X}, ~~~ R_z(\\theta) = e^{i\\frac{\\theta}{2} Z},\\\\ H \\equiv \\lim_{n\\rightarrow\\infty} \\left( ~R_x\\left(\\frac{\\theta}{n}\\right) ~~R_z \\left(\\frac{\\theta}{n}\\right) ~\\right)^n$$\n",
    "\n",
    "for some suitably chosen $\\theta$. When implemented for finite $n$, the resulting gate will be an approximation to the Hadamard whose error decreases with $n$.\n",
    "\n",
    "The following shows an example of this implemented with Qiskit with an incorrectly chosen value of $\\theta$ (and with the global phase ignored).\n",
    "\n",
    "* Determine the correct value of $\\theta$.\n",
    "\n",
    "* Show that the error (when using the correct value of $\\theta$) decreases quadratically with $n$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "614b4c15-7789-4199-8bb8-7971464fb027",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 29176 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "execution_count": 74,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "error = {}\n",
    "for n in range(1,11):\n",
    "\n",
    "    qc = QuantumCircuit(1,1)\n",
    "    \n",
    "    # Implement the Trotterized Hadamard\n",
    "\n",
    "    ### YOUR ANSWER GOES HERE\n",
    "    theta = np.pi/np.sqrt(2)\n",
    "    ###\n",
    "\n",
    "    for j in range(n):\n",
    "        qc.rx(theta/n,0)\n",
    "        qc.rz(theta/n,0)\n",
    "      \n",
    "    # We need to measure how good the above approximation is. Here's a simple way to do this.\n",
    "    # Step 1: Use a real Hadamard to cancel the above approximation.\n",
    "    # For a perfect approximation, the qubit will return to its initial state (0) since H squares to the identity. \n",
    "    qc.h(0)\n",
    "    \n",
    "    # Step 2: Run the circuit, and see how many times we get the outcome 1. The fraction of 1s is a measure of the error.\n",
    "    qc.measure(0,0)\n",
    "    shots = 100000\n",
    "    job = backend.run(qc, shots=shots) \n",
    "    try:\n",
    "        error[n] = (job.result().get_counts()['1']/shots)\n",
    "    except:\n",
    "        pass\n",
    "        \n",
    "plot_histogram(error)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f35fd6ba-7182-482e-904b-12562c487c6f",
   "metadata": {},
   "source": [
    "In terms of showing that the scaling is (...or isn't) quadratic, you can do this however you like, but one possibility is to look into fitting functions like numpy's ```polyfit```. If it's helpful, you can convert the error data from a dictionary to a list like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "c3ee4673-1a3f-4492-9b64-f4bf549d0af1",
   "metadata": {},
   "outputs": [],
   "source": [
    "n_vals = []\n",
    "error_vals = []\n",
    "for key, val in error.items():\n",
    "    n_vals.append(key)\n",
    "    error_vals.append(val)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc561d6e-4c32-49ff-82f7-305571f80f64",
   "metadata": {},
   "source": [
    "***Sample solution***: Errors due to Trotterization scale quadratically because for noncommuting matrices, we have $$e^{\\Delta t(A+B)}=e^{\\Delta t A}e^{\\Delta t B}e^{-\\frac{\\Delta t^2}{2}[A,B] + h.o.t.'s}.$$ This is the well-known Baker-Campbell-Hausdorff formula. Since the time slice $\\Delta t$ scales like $1/n$, errors due to the commutator (neglected when we Trotterize) scale like $1/n^2$. \n",
    "\n",
    "If the error for $n$ Trotter steps scales like $$\\mathrm{error}(n)=k/n^2 + c,\\quad k,c=constants,$$\n",
    "then we can extract $k$ and $c$ from a linear fit $y=kx + c$ with $x=1/n^2$. The following code finds and plots the resulting fit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "4a74b248-962d-4b0d-84b2-59ff936c22dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def inverse_quad(n):\n",
    "    return 1/n**2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "de8e9609-a966-45f7-9afd-4ed230edd794",
   "metadata": {},
   "outputs": [],
   "source": [
    "degs = np.polyfit(inverse_quad(np.array(n_vals)), error_vals, 1)\n",
    "polynomial_fit = np.poly1d(degs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "5ac02f4c-6543-49fe-8bb5-345cb8cac555",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "0f4d3213-65ca-4c5e-b551-27a4cd376410",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 25024 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(n_vals, error_vals)\n",
    "plt.plot(np.linspace(1,10,100), polynomial_fit(inverse_quad(np.linspace(1,10,100))))\n",
    "plt.xlabel('n')\n",
    "plt.ylabel('Error')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d4919269-c721-4018-a30b-cc54e05ee530",
   "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
}
