{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e83e47cb",
   "metadata": {},
   "source": [
    "# Quantum information and quantum computing - Problem set 11\n",
    "\n",
    "### _Problem 1_ : Repetition quantum error correction code"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "585af575",
   "metadata": {},
   "source": [
    "In this notebook we will explore a simple type of error correction on quantum computers that takes advantage of repetitions. In this case, we are going to use $n=3$ physical qubits in order to represent the information contained in a single, logical qubit.\n",
    "\n",
    "We do it by repeating the information contained in a single qubit on three qubits. Be careful that repeating does not mean \"cloning\". In fact, given a state\n",
    "\n",
    "\\begin{equation}\n",
    "|\\psi \\rangle = \\alpha |0\\rangle + \\beta |1 \\rangle\n",
    "\\end{equation}\n",
    "\n",
    "the repeated state is \n",
    "\n",
    "\\begin{equation}\n",
    "\\alpha |000 \\rangle + \\beta |111 \\rangle\n",
    "\\end{equation}\n",
    "\n",
    "and not $|\\psi \\rangle \\otimes |\\psi \\rangle \\otimes |\\psi \\rangle$, which is in general prevented by the no-cloning theorem.\n",
    "\n",
    "Therefore what we want to check is if the state is repeated in each of the three qubits.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4da9e2f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# First, import all the useful methods\n",
    "\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt   \n",
    "import math\n",
    "\n",
    "\n",
    "from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister\n",
    "from qiskit_aer import QasmSimulator"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0382f59",
   "metadata": {},
   "source": [
    "1) The first thing we want to create is a circuit that, using ancilla qubits , can spot errors by measuring the error syndromes. For $n=3$ we will need $m=2$ ancilla qubits, together with the classical registers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30ad7809",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the circuits\n",
    "\n",
    "cq = QuantumRegister(3,'code')\n",
    "aq = QuantumRegister(2,'ancilla') \n",
    "sb = ClassicalRegister(2)\n",
    "\n",
    "qc = QuantumCircuit(cq,aq,sb)\n",
    "\n",
    "## TO DO: write the circuit to spot errors\n",
    "\n",
    "# Print the circuit\n",
    "qc.draw('mpl', style={'name': 'iqx'})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc5b3da2",
   "metadata": {},
   "source": [
    "Remember that you want a circuit that does the following: if the outcome of the measure is $00$, we know that no error occurred. If it's $01$ we know the first qubit was flipped, $10$ means the third qubit was fliped and, finally, $11$ means that the second qubit was flipped."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b9ea6c0",
   "metadata": {},
   "source": [
    "2) Now we implement the circuit on the `qasm_simulator` and see if it recognize the flips correctly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1439b24b",
   "metadata": {},
   "outputs": [],
   "source": [
    "## Call the backend\n",
    "\n",
    "backend = QasmSimulator()\n",
    "shots = 2048"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6666d929",
   "metadata": {},
   "source": [
    "As a first thing, we are going to see what appens if we do nothing to it"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1b8baf9",
   "metadata": {},
   "outputs": [],
   "source": [
    "results = backend.run(qc, shots=shots).result()\n",
    "answer = results.get_counts()\n",
    "\n",
    "plt.suptitle(\"Syndrome measurement\")\n",
    "plt.bar(answer.keys(), answer.values(), color='royalblue')\n",
    "plt.show()\n",
    "\n",
    "print(answer)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "319a041b",
   "metadata": {},
   "source": [
    "Now flip each of the qubits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f87ca989",
   "metadata": {},
   "outputs": [],
   "source": [
    "init = QuantumRegister(3,\"code\")\n",
    "initc = QuantumCircuit(init, QuantumRegister(2), ClassicalRegister(2))\n",
    "\n",
    "## First qubit flipped\n",
    "initc.x(init[0])\n",
    "## Second qubit flipped\n",
    "#initc.x(init[1])\n",
    "## Third qubit flipped\n",
    "#initc.x(init[2])\n",
    "\n",
    "\n",
    "# Execute\n",
    "results = backend.run(initc.compose(qc), shots=shots).result()\n",
    "answer = results.get_counts()\n",
    "\n",
    "plt.suptitle(\"Syndrome measurement for flip on single qubit\")\n",
    "plt.bar(answer.keys(), answer.values(), color='royalblue')\n",
    "plt.show()\n",
    "\n",
    "print(answer)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1caf41b4",
   "metadata": {},
   "source": [
    "And check if our circuit recognizes correctly the bit flips.\n",
    "\n",
    "3) Now we are going to import a noise model from a real device in order to see how effective is the QECC we created to recognize errors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ef61384",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit.providers.fake_provider import GenericBackendV2\n",
    "from qiskit_aer.noise import NoiseModel\n",
    "\n",
    "simulator = GenericBackendV2(num_qubits=5)\n",
    "noise_model = NoiseModel.from_backend(simulator)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e4fdac0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: create a circuit that initializes the circuit in |111>\n",
    "\n",
    "init = QuantumRegister(3,\"code\")\n",
    "initc = QuantumCircuit(init, QuantumRegister(2), ClassicalRegister(2))\n",
    "\n",
    "\n",
    "# and execute the circuit with noise model\n",
    "\n",
    "\n",
    "results = simulator.run(initc.compose(qc), backend=simulator, shots=shots,noise_model = noise_model).result()\n",
    "answer = results.get_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e7c7ddf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Print the results\n",
    "\n",
    "plt.suptitle(\"Syndrome measurement with noise model\")\n",
    "plt.bar(answer.keys(), answer.values(), color='royalblue')\n",
    "plt.show()\n",
    "\n",
    "print(answer)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe7082ab",
   "metadata": {},
   "source": [
    "Did some error occurr during this simple initialisation?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6b48451",
   "metadata": {},
   "source": [
    "4) Now we initialize the circuit with a generic initial state, as an example we create the state $\\alpha|0\\rangle+ \\beta|1\\rangle$ using a generic unitary on the first qubit and then we encode it in the repetition code "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d99695b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "init = QuantumRegister(3,\"code\")\n",
    "initc = QuantumCircuit(init, QuantumRegister(2), ClassicalRegister(2))\n",
    "initc.u(0.1,0.1,0.1,init[0])  ## Use some parameters \\theta, \\phi, \\lambda of your choice\n",
    "\n",
    "## TO DO: write a circuit to create the repetition state on 3 qubits\n",
    "initc.barrier()\n",
    "\n",
    "generic = initc.compose(qc)\n",
    "\n",
    "generic.draw('mpl', style={'name': 'iqx'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05c613c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Execute\n",
    "results = simulator.run(generic, shots=shots,noise_model = noise_model).result()\n",
    "answer = results.get_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "614de8ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Print the results\n",
    "\n",
    "plt.suptitle(\"Syndrome measurement with noise model\")\n",
    "plt.bar(answer.keys(), answer.values(), color='royalblue')\n",
    "plt.show()\n",
    "\n",
    "print(answer)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3182f9d",
   "metadata": {},
   "source": [
    "Did we get more or less error this time?\n",
    "\n",
    "5) In general, gates are the source of errors during the time of our computation. Adding more gates means adding noise and therefore errors (remember the error rates indicated in the IBM Q Experience homepage). In particular, two-qubits gates are the gates with the higher error rate, this is the reason why we are going to implement an initialization circuits that ends with the preparation of |000> and contains some CNOTs in order to spot how the errors are more likely to appear in this case"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20ce44a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Long initialization circuit to recreate |000>\n",
    "\n",
    "init = QuantumRegister(3,\"code\")\n",
    "initc = QuantumCircuit(init, QuantumRegister(2), ClassicalRegister(2))\n",
    "\n",
    "## TO DO: Create a long circuit that starts with |000> and ends with |000> but\n",
    "#  with arbitrary number of gates in between. See how the error increases when the depth increases\n",
    "\n",
    "# append it at the beginning of our QECC\n",
    "\n",
    "final = initc.compose(qc)\n",
    "\n",
    "\n",
    "final.draw('mpl', style={'name': 'iqx'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ae0c799",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Execute\n",
    "results = simulator.run(final, shots=shots,noise_model = noise_model).result()\n",
    "answer = results.get_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "492d01d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Print the results\n",
    "\n",
    "plt.suptitle(\"Syndrome meaurement for a long circuit\")\n",
    "plt.bar(answer.keys(), answer.values(), color='royalblue')\n",
    "plt.show()\n",
    "\n",
    "print(answer)"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "formats": "ipynb,md"
  },
  "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.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
