{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": [
     "remove_cell"
    ]
   },
   "source": [
    "<h1 style=\"font-size:35px;\n",
    "        color:black;\n",
    "        \">Exercise 2: Quantum Logic Gates</h1>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Prerequisite\n",
    "- [Qiskit basics](https://qiskit.org/documentation/tutorials/circuits/1_getting_started_with_qiskit.html)\n",
    "- [Ch.1.2 The Atoms of Computation](https://qiskit.org/textbook/ch-states/atoms-computation.html)\n",
    "\n",
    "Other relevant materials\n",
    "- [Access IBM Quantum Systems](https://qiskit.org/documentation/install.html#access-ibm-quantum-systems)\n",
    "- [IBM Quantum Systems Configuration](https://quantum-computing.ibm.com/docs/manage/backends/configuration)\n",
    "- [Transpile](https://qiskit.org/documentation/apidoc/transpiler.html)\n",
    "- [IBM Quantum account](https://quantum-computing.ibm.com/docs/manage/account/ibmq)\n",
    "- [Quantum Circuits](https://qiskit.org/documentation/apidoc/circuit.html)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit import *\n",
    "from qiskit.visualization import plot_histogram\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Exercise 1**\n",
    "\n",
    "See 'Part 1' below, and find the circuits required for the:\n",
    "* (a) `XOR` gate;\n",
    "* (b) `AND` gate;\n",
    "* (c) `NAND` gate;\n",
    "* (d) `OR` gate.\n",
    "\n",
    "## **Exercise 2**\n",
    "\n",
    "See 'Part 2' below, and find a `layout` for which the AND gate compiles to 6 non-local gates for `ibmqx2`. Note that there is some randomness in the compiling process. So you might need to try a few times."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2 style=\"font-size:24px;\">Part 1: Classical logic gates with quantum circuits</h2>\n",
    "\n",
    "<br>\n",
    "\n",
    "An implementation of the `NOT` gate is provided as an example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def NOT(inp):\n",
    "    \"\"\"An NOT gate.\n",
    "    \n",
    "    Parameters:\n",
    "        inp (str): Input, encoded in qubit 0.\n",
    "        \n",
    "    Returns:\n",
    "        QuantumCircuit: Output NOT circuit.\n",
    "        str: Output value measured from qubit 0.\n",
    "    \"\"\"\n",
    "\n",
    "    qc = QuantumCircuit(1, 1) # A quantum circuit with a single qubit and a single classical bit\n",
    "    qc.reset(0)\n",
    "    \n",
    "    # We encode '0' as the qubit state |0⟩, and '1' as |1⟩\n",
    "    # Since the qubit is initially |0⟩, we don't need to do anything for an input of '0'\n",
    "    # For an input of '1', we do an x to rotate the |0⟩ to |1⟩\n",
    "    if inp=='1':\n",
    "        qc.x(0)\n",
    "        \n",
    "    # barrier between input state and gate operation \n",
    "    qc.barrier()\n",
    "    \n",
    "    # Now we've encoded the input, we can do a NOT on it using x\n",
    "    qc.x(0)\n",
    "    \n",
    "    #barrier between gate operation and measurement\n",
    "    qc.barrier()\n",
    "    \n",
    "    # Finally, we extract the |0⟩/|1⟩ output of the qubit and encode it in the bit c[0]\n",
    "    qc.measure(0,0)\n",
    "    qc.draw('mpl')\n",
    "    \n",
    "    # We'll run the program on a simulator\n",
    "    backend = Aer.get_backend('qasm_simulator')\n",
    "    # Since the output will be deterministic, we can use just a single shot to get it\n",
    "    job = execute(qc, backend, shots=1, memory=True)\n",
    "    output = job.result().get_memory()[0]\n",
    "    \n",
    "    return qc, output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Test the function\n",
    "for inp in ['0', '1']:\n",
    "    qc, out = NOT(inp)\n",
    "    print('NOT with input',inp,'gives output',out)\n",
    "    display(qc.draw())\n",
    "    print('\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h3 style=\"font-size: 20px\">&#128211; XOR gate</h3>\n",
    "\n",
    "Takes two binary strings as input and gives one as output.\n",
    "\n",
    "The output is '0' when the inputs are equal and  '1' otherwise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def XOR(inp1,inp2):\n",
    "    \"\"\"An XOR gate.\n",
    "    \n",
    "    Parameters:\n",
    "        inpt1 (str): Input 1, encoded in qubit 0.\n",
    "        inpt2 (str): Input 2, encoded in qubit 1.\n",
    "        \n",
    "    Returns:\n",
    "        QuantumCircuit: Output XOR circuit.\n",
    "        str: Output value measured from qubit 1.\n",
    "    \"\"\"\n",
    "  \n",
    "    qc = QuantumCircuit(2, 1) \n",
    "    qc.reset(range(2))\n",
    "    \n",
    "    if inp1=='1':\n",
    "        qc.x(0)\n",
    "    if inp2=='1':\n",
    "        qc.x(1)\n",
    "    \n",
    "    # barrier between input state and gate operation \n",
    "    qc.barrier()\n",
    "    \n",
    "    # this is where your program for quantum XOR gate goes\n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    # barrier between input state and gate operation \n",
    "    qc.barrier()\n",
    "    \n",
    "    qc.measure(1,0) # output from qubit 1 is measured\n",
    "  \n",
    "    #We'll run the program on a simulator\n",
    "    backend = Aer.get_backend('qasm_simulator')\n",
    "    #Since the output will be deterministic, we can use just a single shot to get it\n",
    "    job = execute(qc, backend, shots=1, memory=True)\n",
    "    output = job.result().get_memory()[0]\n",
    "  \n",
    "    return qc, output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Test the function\n",
    "for inp1 in ['0', '1']:\n",
    "    for inp2 in ['0', '1']:\n",
    "        qc, output = XOR(inp1, inp2)\n",
    "        print('XOR with inputs',inp1,inp2,'gives output',output)\n",
    "        display(qc.draw())\n",
    "        print('\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h3 style=\"font-size: 20px\">&#128211; AND gate</h3>\n",
    "\n",
    "Takes two binary strings as input and gives one as output.\n",
    "\n",
    "The output is `'1'` only when both the inputs are `'1'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def AND(inp1,inp2):\n",
    "    \"\"\"An AND gate.\n",
    "    \n",
    "    Parameters:\n",
    "        inpt1 (str): Input 1, encoded in qubit 0.\n",
    "        inpt2 (str): Input 2, encoded in qubit 1.\n",
    "        \n",
    "    Returns:\n",
    "        QuantumCircuit: Output XOR circuit.\n",
    "        str: Output value measured from qubit 2.\n",
    "    \"\"\"\n",
    "    qc = QuantumCircuit(3, 1) \n",
    "    qc.reset(range(2))\n",
    "  \n",
    "    if inp1=='1':\n",
    "        qc.x(0)\n",
    "    if inp2=='1':\n",
    "        qc.x(1)\n",
    "        \n",
    "    qc.barrier()\n",
    "\n",
    "    # this is where your program for quantum AND gate goes\n",
    "\n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "\n",
    "    qc.barrier()\n",
    "    qc.measure(2, 0) # output from qubit 2 is measured\n",
    "  \n",
    "    # We'll run the program on a simulator\n",
    "    backend = Aer.get_backend('qasm_simulator')\n",
    "    # Since the output will be deterministic, we can use just a single shot to get it\n",
    "    job = execute(qc, backend, shots=1, memory=True)\n",
    "    output = job.result().get_memory()[0]\n",
    "  \n",
    "    return qc, output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Test the function\n",
    "for inp1 in ['0', '1']:\n",
    "    for inp2 in ['0', '1']:\n",
    "        qc, output = AND(inp1, inp2)\n",
    "        print('AND with inputs',inp1,inp2,'gives output',output)\n",
    "        display(qc.draw())\n",
    "        print('\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h3 style=\"font-size: 20px\">&#128211; NAND gate</h3>\n",
    "\n",
    "Takes two binary strings as input and gives one as output.\n",
    "\n",
    "The output is `'0'` only when both the inputs are `'1'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def NAND(inp1,inp2):\n",
    "    \"\"\"An NAND gate.\n",
    "    \n",
    "    Parameters:\n",
    "        inpt1 (str): Input 1, encoded in qubit 0.\n",
    "        inpt2 (str): Input 2, encoded in qubit 1.\n",
    "        \n",
    "    Returns:\n",
    "        QuantumCircuit: Output NAND circuit.\n",
    "        str: Output value measured from qubit 2.\n",
    "    \"\"\"\n",
    "    qc = QuantumCircuit(3, 1) \n",
    "    qc.reset(range(3))\n",
    "    \n",
    "    if inp1=='1':\n",
    "        qc.x(0)\n",
    "    if inp2=='1':\n",
    "        qc.x(1)\n",
    "    \n",
    "    qc.barrier()\n",
    "    \n",
    "    # this is where your program for quantum NAND gate goes\n",
    "\n",
    "\n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    qc.barrier()\n",
    "    qc.measure(2, 0) # output from qubit 2 is measured\n",
    "  \n",
    "    # We'll run the program on a simulator\n",
    "    backend = Aer.get_backend('qasm_simulator')\n",
    "    # Since the output will be deterministic, we can use just a single shot to get it\n",
    "    job = execute(qc,backend,shots=1,memory=True)\n",
    "    output = job.result().get_memory()[0]\n",
    "  \n",
    "    return qc, output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Test the function\n",
    "for inp1 in ['0', '1']:\n",
    "    for inp2 in ['0', '1']:\n",
    "        qc, output = NAND(inp1, inp2)\n",
    "        print('NAND with inputs',inp1,inp2,'gives output',output)\n",
    "        display(qc.draw())\n",
    "        print('\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h3 style=\"font-size: 20px\">&#128211; OR gate</h3>\n",
    "\n",
    "Takes two binary strings as input and gives one as output.\n",
    "\n",
    "The output is '1' if either input is '1'."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def OR(inp1,inp2):\n",
    "    \"\"\"An OR gate.\n",
    "    \n",
    "    Parameters:\n",
    "        inpt1 (str): Input 1, encoded in qubit 0.\n",
    "        inpt2 (str): Input 2, encoded in qubit 1.\n",
    "        \n",
    "    Returns:\n",
    "        QuantumCircuit: Output XOR circuit.\n",
    "        str: Output value measured from qubit 2.\n",
    "    \"\"\"\n",
    "\n",
    "    qc = QuantumCircuit(3, 1) \n",
    "    qc.reset(range(3))\n",
    "    \n",
    "    if inp1=='1':\n",
    "        qc.x(0)\n",
    "    if inp2=='1':\n",
    "        qc.x(1)\n",
    "    \n",
    "    qc.barrier()\n",
    "   \n",
    "    # this is where your program for quantum OR gate goes\n",
    "\n",
    "\n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    \n",
    "    qc.barrier()\n",
    "    qc.measure(2, 0) # output from qubit 2 is measured\n",
    "  \n",
    "    # We'll run the program on a simulator\n",
    "    backend = Aer.get_backend('qasm_simulator')\n",
    "    # Since the output will be deterministic, we can use just a single shot to get it\n",
    "    job = execute(qc,backend,shots=1,memory=True)\n",
    "    output = job.result().get_memory()[0]\n",
    "  \n",
    "    return qc, output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Test the function\n",
    "for inp1 in ['0', '1']:\n",
    "    for inp2 in ['0', '1']:\n",
    "        qc, output = OR(inp1, inp2)\n",
    "        print('OR with inputs',inp1,inp2,'gives output',output)\n",
    "        display(qc.draw())\n",
    "        print('\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2 style=\"font-size:24px;\">Part 2: AND gate on Quantum Computer</h2>\n",
    "<br>\n",
    "\n",
    "Real quantum computers are not able to implement arbitary gates directly. Instead, everything needs to be compiled  (or 'transpiled') to the set of basic gates that the device can use. This usually consists of a set of single qubit rotations, as well as two qubit gates like `cx`.\n",
    "\n",
    "There are also limits on which `cx` gates can be used directly: only some pairs of control and target qubits are possible. To implement other `cx` gates, tricks such as using `swap` gates to effectively move information around must be used. The possible pairs of qubits on which `cx` gates can be applied is known as the 'connectivity' of the device.\n",
    "\n",
    "We'll now look at some examples. To make sure you don't end up in a queue for a busy device, we'll be using mock backends. These are designed to act exactly like real backends."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit.test.mock import FakeYorktown\n",
    "backend = FakeYorktown()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Upon executing the following cell you will be presented with a widget that displays all of the information about your choice of the backend.  You can obtain information that you need by clicking on the tabs. For example, backend status, number of qubits and the connectivity are under `configuration` tab, where as the `Error Map` tab will reveal the latest noise information for the system. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import qiskit.tools.jupyter\n",
    "\n",
    "backend"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The two system we are using (or at least pretending to) is `ibmqx2` (also known as `ibmq_yorktown`).\n",
    "\n",
    "Here's a circuit that applies an `AND` gate, compiled into single and two qubit gates (assuming full connectivity)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qc_and = QuantumCircuit(3)\n",
    "qc_and.ccx(0,1,2)\n",
    "print('AND gate')\n",
    "display(qc_and.draw())\n",
    "print('\\n\\nTranspiled AND gate with all the required connectiviy')\n",
    "qc_and.decompose().draw()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This ideal transpilation requires 6 `cx` gates.\n",
    "\n",
    "There are often optimizations that the transpiler can perform that reduce the overall gate count, and thus total length of the input circuits.  Note that the addition of swaps to match the device topology, and optimizations for reducing the length of a circuit are at odds with each other. In what follows we will make use of `initial_layout` that allows us to pick the qubits on a device used for the computation and `optimization_level`, an argument that allows selecting from internal defaults for circuit swap mapping and optimization methods to perform.\n",
    "\n",
    "You can learn more about transpile function in depth [here](https://qiskit.org/documentation/apidoc/transpiler.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Rather than actually running the AND function, let's just look at the transpiled circuits. The following function does this for a given set of inputs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# run the cell to define AND gate for real quantum system\n",
    "\n",
    "def AND(inp1, inp2, backend, layout):\n",
    "    \n",
    "    qc = QuantumCircuit(3, 1) \n",
    "    qc.reset(range(3))\n",
    "    \n",
    "    if inp1=='1':\n",
    "        qc.x(0)\n",
    "    if inp2=='1':\n",
    "        qc.x(1)\n",
    "        \n",
    "    qc.barrier()\n",
    "    qc.ccx(0, 1, 2) \n",
    "    qc.barrier()\n",
    "    qc.measure(2, 0) \n",
    "  \n",
    "    qc_trans = transpile(qc, backend, initial_layout=layout, optimization_level=3)\n",
    "    \n",
    "    return qc_trans"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h4 style=\"font-size: 16px\">Three qubits on <code>ibmqx2</code> with the triangle connectivity</h4>\n",
    " \n",
    "First, examine `ibmqx2` using the widget introduced earlier. Find the best set of three qubits to use for the `AND` gate, making best use of the connectivity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# run this cell for the widget\n",
    "backend"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>&#128211; Assign your choice of layout to the list variable <code>layout</code> in the cell below</p>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Assign your choice of the initial_layout to the variable layout1 as a list \n",
    "# ex) layout = [0,2,4]\n",
    "layout = "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Compile the `AND` gate on `ibmqx2` by running the cell below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for input1 in ['0','1']:\n",
    "    for input2 in ['0','1']:\n",
    "        qc_trans1 = AND(input1, input2, backend, layout)\n",
    "                \n",
    "        print('For input '+input1+input2)\n",
    "        print('# of nonlocal gates =',qc_trans1.num_nonlocal_gates())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.9.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
