{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5b05ec19",
   "metadata": {
    "tags": [
     "remove-cell"
    ]
   },
   "source": [
    "#### Latex Headers\n",
    "$$\\newcommand{\\ket}[1]{\\left|{#1}\\right\\rangle}$$\n",
    "$$\\newcommand{\\bra}[1]{\\left\\langle{#1}\\right|}$$\n",
    "$$\\newcommand{\\braket}[2]{\\left\\langle{#1}\\middle|{#2}\\right\\rangle}$$\n",
    "$$\\newcommand{\\adagger}[0]{\\hat{a}^{\\dagger}}$$\n",
    "$$\\newcommand{\\ahat}[0]{\\hat{a}}$$\n",
    "$$\\newcommand{\\gtwo}[0]{g^{(2)}}$$\n",
    "$$\\newcommand{\\H}[0]{\\ket{H}}$$\n",
    "$$\\newcommand{\\V}[0]{\\ket{V}}$$\n",
    "$$\\newcommand{\\D}[0]{\\ket{D}}$$\n",
    "$$\\newcommand{\\AD}[0]{\\ket{AD}}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbe4a6fb",
   "metadata": {},
   "source": [
    "# LAB -- QKD (Week 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ce85b07",
   "metadata": {},
   "source": [
    "Now that you know how to operate the quED, we want you to take your knowledge of QKD and generate a key via BB84. To do this efficiently, you'll be developing Python scripts to perform taking the data and to do the post-processing. We've provided you with some barebones scripts to get you started. You should verify that they work as expected, and you can make any changes you wish.\n",
    "\n",
    "## Process for this lab\n",
    "\n",
    "quTools provides implementation details on how to do BB84 with the quED that can be found on pages 11 - 13 of their [QKD manual](https://www.qutools.com/files/quED/quED-QKD-manual.pdf). In terms of what you'll accomplish in your lab sessions, you'll follow these steps:\n",
    "\n",
    "1. Verify that Alice is sending an appropriate [number of photons](#Mean-photon-number) to Bob. \n",
    "1. Establish a method for Alice to [encode bits](#Encode-bits) and Bob to decode them\n",
    "1. Based on your setup, construct a [model](#Build-a-model) and estimate the secret key rate (SKR) you expect to see.\n",
    "1. Generate a raw key, including [taking data](#Take-data) and [sifting the bits](#Bit-sifting)\n",
    "1. Perform post-processing with provided [Information Reconciliation/Error Correction](#Information-Reconciliation/Error-Correction) and [Privacy Amplification](#Privacy-Amplification) scripts.\n",
    "\n",
    "## Aims in the lab\n",
    "\n",
    "As you work on the above process, keep in mind the following topics you'll need to discuss in your write-up:\n",
    "\n",
    "1. Justify the security of your set-up\n",
    "1. Explain how you developed your model of SKR\n",
    "1. Report your experimental SKR and how it compares to the model. Specifically, discuss the error rate you see versus what you expect.\n",
    "1. Propose steps for improving SKR. Consider how improving the loss of the system would affect the SKR."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cea63d30-afb1-4a88-8875-99383ef1aa06",
   "metadata": {
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "## Lab Timeline\n",
    "\n",
    "This lab is not explicitly broken up by day.  Typically, the first day of this lab is used to develop the needed codebase for your QKD measurements (adapting from what is provided).  This would include code for calibration, and the sending, receiving, and sifting of the secret key.  \n",
    "\n",
    "The second day is then used to send and recieve data, learning about the importance of various settings in the bit error rate and efficiency of sending your secret key.  It would also be used to complete the remainder of the lab, including the exercises at the end on error correction and privacy amplification."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bba5a044",
   "metadata": {
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "## Mean photon number\n",
    "\n",
    "Last week you spent some time finding $g^{(2)}(0)$ of the SPDC source and looking at the mean number of photons in a pulse. The motivation for doing that was to verify we had an appropriate source for our QKD setup. This is important because...\n",
    "\n",
    "### BB84's security is based on the no-cloning theorem\n",
    "\n",
    "The no-cloning theorem states that for a given quanta in a superposition of eigenstates, it's impossible to produce a perfect copy. For more information, see eg [Wootters and Zurek 1982](\"https://www.nature.com/articles/299802a0\") or [Dieks 1982](\"https://www.sciencedirect.com/science/article/pii/0375960182900846?via%3Dihub\"). In short, it means that if eavesdropper Eve listens in by: (1) intercepting the photons Alice sends to Bob; (2) measuring them; and (3) then pretending to be Alice by sending identical photons to Bob, she'll create errors in the system that Alice and Bob can detect.\n",
    "\n",
    "When Alice sends more than one photon to Bob, Eve can perform a photon number splitting attack, which you can read more about in [Brassard, Lutkenhaus, Mor, Sanders 2000](\"https://doi.org/10.1103/PhysRevLett.85.1330\")\n",
    "\n",
    "The HBT set-up showed us that using just one output from the SPDC isn't a true single photon source. So, we can approximate that by modeling it as a weak pulse with Poissonian statistics.\n",
    "\n",
    "Recall that for a Poisson distribution with mean value $\\lambda$ the probability of obtaining value k is given by\n",
    "\n",
    "$$P(k) = e^{-\\lambda}\\frac{\\lambda^k}{k!}$$\n",
    "\n",
    "We can adjust $\\lambda$ by adjusting the laser current and the pulse duration for the pump laser.\n",
    "\n",
    "```{note}\n",
    "**Implementation Note** You need to play with the repetition rate, laser current, and pulse duration to ensure that you are only sending around 1 photon per transmission event.  Otherwise your protocol will be vulnerable to eavesdropping and would not be a true implementation of BB84.  \n",
    "\n",
    "**Important** When switching to pulsed mode the repetition rate shown might not be what is set on the instrument.  You should hit enter in that box to send the setting to the instrument and ensure what you see is what is actually being used!\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "dccb5bd1",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The probability of getting more than 1 photons with mean 0.5 is 0.09020401043104986\n"
     ]
    }
   ],
   "source": [
    "# Estimate the probability of getting more than k photons\n",
    "import scipy\n",
    "from scipy.stats import poisson\n",
    "\n",
    "\n",
    "\n",
    "def gt(kk,mu):\n",
    "    xx = 1-poisson.cdf(k=kk, mu=mu)\n",
    "    return xx\n",
    "\n",
    "k = 1 # default is 1: how often does Alice send > 1 photon?\n",
    "mu = 0.5 # set the mean value (the above text uses lambda instead of mu)\n",
    "prob = gt(k,mu)\n",
    "\n",
    "print(f\"The probability of getting more than {k} photons with mean {mu} is {prob}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ef0fae8",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "## Encode bits\n",
    "\n",
    "As you recall from your reading, BB84 uses linear polarization to encode bits. The two bases are $\\left[ \\H, \\V \\right]$ (horizontal and vertical) and $\\left[ \\D, \\AD \\right]$ (diagonal and anti-diagonal). For the sake of this lab, the default is that photons in the state $\\H$ or $\\D$ are `0` while photons in $\\V$ or $\\AD$ are `1`. \n",
    "\n",
    "```{note}\n",
    "**Implementation Note** The default setup with the quED has Bob using a linear polarizer, **not** a polarizing beamsplitter. \n",
    "\n",
    "Keep this in mind while modifying the code below!\n",
    "```\n",
    "\n",
    "```{note}\n",
    "**Implementation Note** Note in the example code provided there is a way to implement integration over multiple trials.  This integration violates the assumptions of BB84 (do you see why?) and should only be used for debugging the system. (Integration reduces errors so you can check that your polarizer and HWP settings are indeed working as you expect them to.) \n",
    "```\n",
    "\n",
    ":::{figure-md} BB84_setup\n",
    "<img src=\"FIGURES/quED/qued-bb84-setup.png\" width=\"600\">\n",
    "\n",
    "Setup for BB84 using the QuED.\n",
    ":::\n",
    "\n",
    "As you can see in the above figure, with the default quED implementation Bob's terminal is simply a polarizer and a detector. That means that for each bit, Bob has to choose a basis and a value to perform his measurement. \n",
    "\n",
    "Consider an example with a lossless system. If Alice sends a bit with value `1` in the $\\left[ \\D, \\AD \\right]$ basis and Bob decides to measure in the $\\left[ \\D, \\AD \\right]$ basis but has his polarizer set to $\\D$ (which is `0`) he won't get a click. Even though Alice and Bob are using the same basis, they won't get a bit for their raw key from that photon.\n",
    "\n",
    "#### Assign random values\n",
    "\n",
    "The first step is generating the random bits that Alice will send Bob as well as generating the order of which basis Alice will use to transmit each bit and which basis and bit Bob will use to measure it. For the sake of the course, we'll use Python's pseudorandom number generator to make all the choices. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d9b246fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import necessary Libraries\n",
    "\n",
    "# Generate the basis and bit values for each bit from Alice and Bob\n",
    "\n",
    "import numpy as np\n",
    "import random\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "LENGTH = 10;\n",
    "\n",
    "# Are we using PBS and 2 detectors? The default is False\n",
    "PBS  = False\n",
    "\n",
    "# Assign bit valuess\n",
    "bH = 0\n",
    "bV = 1\n",
    "bD = 0\n",
    "bA = 1\n",
    "\n",
    "# Initialize the random bases for Alice and Bob. Let 0 be HV and 1 be AD\n",
    "basis_Alice = np.zeros((LENGTH))\n",
    "basis_Bob = np.zeros((LENGTH))\n",
    "\n",
    "# Now initialize the bit values for Alice\n",
    "bits_Alice = np.zeros((LENGTH))\n",
    "bits_Bob = np.zeros((LENGTH)) \n",
    "\n",
    "# Now set the correct values for Alice and Bob's rotation\n",
    "angles_Alice = np.zeros((LENGTH))\n",
    "angles_Bob = np.zeros((LENGTH))\n",
    "for x in range (LENGTH):\n",
    "    basis_Alice[x] = random.randint(0,1)\n",
    "    basis_Bob[x] = random.randint(0,1)\n",
    "    bits_Alice[x] = random.randint(0,1)\n",
    "    bits_Bob[x] = random.randint(0,1)\n",
    "    angles_Alice[x] = 22.5*basis_Alice[x] - 45*bits_Alice[x]\n",
    "    if PBS:\n",
    "        angles_Bob[x] = 22.5*basis_Bob[x] # this will rotate Bob's HWP as needed. You'll measure from 2 detectors\n",
    "    else:\n",
    "        angles_Bob[x] = 45*basis_Bob[x] - 90*bits_Bob[x] # this will tells us how to rotate the linear polarizer\n",
    "\n",
    "# print('Alices Basis :',basis_Alice)\n",
    "# print('Alices Bits  :',bits_Alice)\n",
    "# print(\"Alices Angles: %s\" %(angles_Alice))\n",
    "# print('Bob basis: ', basis_Bob)\n",
    "# print('Bob bits:  ',bits_Bob)\n",
    "# print('Bob angles:',angles_Bob)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50922335",
   "metadata": {},
   "source": [
    "#### Set correct polarization\n",
    "\n",
    "Unless you want to rotate the half wave plate (HWP) and polarizer manually for every single bit, you're going to want to make sure you can control polarization optics via a Python script. We've provided a basic script for you below, which you can edit as you see fit.\n",
    "\n",
    "```{note}\n",
    "Before you test the script, you need to make sure that your HWP and polarizers are lined up where you think they are, ie if you send $0^{\\circ}$ to the HWP, how do you know if it's actually at $0^{\\circ}$? For the quED set up, you can set the angle to $0^{\\circ}$ through the script or the quTools software and then manually rotate the polarization to get the optics where you want them. Ask one of the instruction staff to show you how to do that if you're unfamiliar with it.\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df7a66de",
   "metadata": {},
   "source": [
    "## Build a model\n",
    "\n",
    "Now that you know how to encode bits, it would be helpful to estimate what you expect to see in terms of a secret key rate.\n",
    "\n",
    "To start with, consider the following questions to determine the raw key:\n",
    "\n",
    "* How fast can Alice encode photons?\n",
    "* How often does Alice send Bob a photon? You should take into account both the rep rate of the pump pulses and also how often the pulse sends 0 photons.\n",
    "* How often will Bob make a measurement of the correct basis and the correct polarization?\n",
    "\n",
    "After you've done that, consider how many bits you sacrifice for error correction and privacy amplification, which you may want to wait to do once you've taken data and performed the post-processing."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d709bba0",
   "metadata": {},
   "source": [
    "## Take data\n",
    "\n",
    "Now you have all the pieces you need to take data! The cell immediately below this one is ready for you to define a function and get the data you need to create the raw key."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "94769a06-f990-498d-86a6-989a8bb48a97",
   "metadata": {
    "tags": [
     "hide-output"
    ]
   },
   "outputs": [
    {
     "ename": "ModuleNotFoundError",
     "evalue": "No module named 'lxml'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[1], line 5\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01murllib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mrequest\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m urlopen\n\u001b[1;32m      4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrequests\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmodels\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m PreparedRequest\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mlxml\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m html\n\u001b[1;32m      6\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mre\u001b[39;00m\n\u001b[1;32m      7\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mtime\u001b[39;00m\n",
      "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'lxml'"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "import numpy as np\n",
    "from urllib.request import urlopen\n",
    "from requests.models import PreparedRequest\n",
    "from lxml import html\n",
    "import re\n",
    "import time\n",
    "\n",
    "# User Parameters\n",
    "integrationTimeValue = 1000 # Integration time in ms, min = 100, max = 10000\n",
    "pauseTimeValue = 1 # Pause time for motion in s\n",
    "url = 'http://169.254.69.140:8080/?'\n",
    "\n",
    "# Accessible parameters\n",
    "diodeCurrent = \"ild\"\n",
    "motor1 = \"pm1\"\n",
    "motor2 = \"pm2\"\n",
    "integrationTime = \"int\"\n",
    "countRate = \"cnt\"\n",
    "moterRef = \"mref\"\n",
    "\n",
    "def quED_Access(url, action, param, reply = 0, value = []):\n",
    "    \"\"\"\n",
    "    Function that accesses QED via an ethernet connection\n",
    "    Inputs:\n",
    "        url - IP adddress of instrument (find in settings)\n",
    "        action - 'set' or 'get'\n",
    "        param - the parameter to set or get (see below)\n",
    "        reply - gives response output text, default false, true to debug\n",
    "        value - value to pass in, default is empty\n",
    "    Outputs:\n",
    "        finalData.response.text - raw string response from instrument\n",
    "        finalData.name - name of channel measured\n",
    "        finalData.data - data from measured channel\n",
    "    \"\"\"\n",
    "    \n",
    "    class finalData:\n",
    "        pass\n",
    "    \n",
    "    # For reading values out\n",
    "    if (value == []):\n",
    "        params = {'action':action,'param':param}\n",
    "        req = PreparedRequest()\n",
    "        req.prepare_url(url, params)\n",
    "        response = requests.get(req.url)\n",
    "    \n",
    "    # For setting values\n",
    "    else:\n",
    "        params = {'action':action,'param':param,'value':value}\n",
    "        req = PreparedRequest()\n",
    "        req.prepare_url(url, params)\n",
    "        response = requests.get(req.url)\n",
    "    \n",
    "    if reply == 1:\n",
    "        if action == 'set':\n",
    "            print(response.text.split(\"<body>\")[1].split(\"</body>\")[0])\n",
    "        else:\n",
    "            print(response.text)\n",
    "    \n",
    "    # For case where we measure count data\n",
    "    if param == 'cnt':\n",
    "        rawArray = response.text.split('<br>')\n",
    "        truncArray = rawArray[2:-2] \n",
    "        numElem = len(truncArray)\n",
    "        dataArray = np.zeros(numElem)\n",
    "        labelArray  = ['0' for i in range(numElem)]\n",
    "\n",
    "        for elem in range(numElem-1):\n",
    "            extractLine = truncArray[elem].split(':')\n",
    "            labelArray[elem] = (extractLine[0])\n",
    "            dataArray[elem] = int(extractLine[1])\n",
    "\n",
    "        finalData.name = labelArray\n",
    "        finalData.data = dataArray\n",
    "    \n",
    "    finalData.response = response\n",
    "    return finalData\n",
    "\n",
    "#-- Some examples using this code below...\n",
    "output = quED_Access(url, 'set', 'pm1', reply = 1, value = [45])\n",
    "output = quED_Access(url, 'set', 'pm2', reply = 1, value = [-22.5])\n",
    "output = quED_Access(url, 'get', 'cnt', reply = 0)\n",
    "output.data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae203bb1-fd63-418d-b9c7-77fc528fd0f6",
   "metadata": {
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "The following code was set up for running a QKD experiment and is provided to you for convenience.  It assumes that Bob has access to two detectors from a combination of his own half waveplate and polarizing beamsplitter -- however in your case this has been simplified to just a polarizer.  The experiment still works, but what are the consequences?  **Discuss this in your lab writeup.**\n",
    "\n",
    "**You need to modify this code for it to work for you!**  First of all, you need to take into account your particular setup with only a polarizer.  Second, you might consider if a delay is needed to ensure the moving elements get to where they need to be before a measurement takes place (you can try if this is necessary or not).  Third, if there is an offset in angles you found in calibration, you will want to account for this somehow.  Basically, take this as an example, but you are responsible for developing it into the final, working version."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f88c8d5a-5b86-4d46-802e-e5fb4d943d06",
   "metadata": {},
   "outputs": [],
   "source": [
    "def QKD_exp(alice_bases, bob_bases, int_time, det_H_index, det_V_index, num_trials):\n",
    "\n",
    "    detector_H = []\n",
    "    detector_V = []\n",
    "    \n",
    "    # Change the integration time\n",
    "    output = quED_Access(url, 'set', 'int', reply = 1, value = [int_time])\n",
    "    \n",
    "    for ind in range(len(alice_bases)):\n",
    "        ## Preparaing both Alice's and Bob's polarization bases\n",
    "        alice_basis = alice_bases[ind]\n",
    "        bob_basis = bob_bases[ind]\n",
    "        # Rotate Alice's HWP\n",
    "        output = quED_Access(url, 'set', 'pm1', reply = 1, value = [alice_basis])\n",
    "        # Rotate Bob's HWP\n",
    "        output = quED_Access(url, 'set', 'pm2', reply = 1, value = [bob_basis])\n",
    "        temp_H = []\n",
    "        temp_V = []\n",
    "        for k in range(num_trials):\n",
    "            # Grab coincidence counts between detector X and Y\n",
    "            output = quED_Access(url, 'get', 'cnt', reply = 0)\n",
    "            counts_H = output.data[det_H_index]\n",
    "            counts_V = output.data[det_V_index]\n",
    "            temp_H.append(counts_H)\n",
    "            temp_V.append(counts_V)\n",
    "\n",
    "        detector_H.append(np.mean(temp_H))\n",
    "        detector_V.append(np.mean(temp_V))\n",
    "    \n",
    "    return detector_H, detector_V\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1684632-dcd7-4011-9a5f-48084181baa6",
   "metadata": {
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "Below is some convenience code you can modify for testing that things are working as planned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab8b5831-8c4c-4f14-8677-5e1b33351c01",
   "metadata": {},
   "outputs": [],
   "source": [
    "## Setup\n",
    "# some random angles for testing...\n",
    "alice_bases = np.array([0,22.5,45])\n",
    "bob_bases = np.array([22.5,0,45])\n",
    "int_time = 100 # in ms\n",
    "det_H_index = 0 # detector in the transmission path of the PBS\n",
    "det_V_index = 1 # detector in the reflection path of the PBS\n",
    "num_trials = 30\n",
    "\n",
    "detector_H, detector_V = QKD_exp(alice_bases, bob_bases, int_time, det_H_index, det_V_index, num_trials)\n",
    "print(detector_H)\n",
    "print(detector_V)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3431a5b2",
   "metadata": {},
   "source": [
    "## Bit sifting\n",
    "\n",
    "Now that Alice has finished transmitting and Bob has finished measuring, they need to find cases where Alice sent a photon in the same polarization as Bob measured.\n",
    "\n",
    "In short, the sifting needs to step through the following questions:\n",
    "   1. Did Bob get a click?\n",
    "   1. If yes, did he have the same basis as Alice?\n",
    "and then discard any bits that don't meet both criteria.\n",
    "\n",
    "As before, some code is provided below to get you started."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76932abb",
   "metadata": {
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "## Information Reconciliation/Error Correction\n",
    "\n",
    "Alice and Bob want to make sure that any errors in their key are corrected, but they don't want Eve to get too much information. The figure of merit for information reconiciliation/error correction is bit error rate (BER), the ratio of bits that are incorrect.\n",
    "\n",
    "A basic error correction scheme is provided below. It is adapted from \"Error Correction for Quantum Key Distribution\" in Loepp and Wootters [book available online with MIT Library sign in](https://www.cambridge.org/core/books/abs/protecting-information/quantum-cryptography-revisited/8A88AA6C8C88E8A07E535FACEDE3C310).  You should read this section as well as the discussion on privacy amplification.  This is not meant to be a general introduction to error correction, but rather to provide an example approach so that you can see the basics of how error correction might be used.\n",
    "\n",
    "It first estimates the raw BER by having Alice and Bob compare the first `length_reveal` bits over a classical channel. Eve gets all the information Alice and Bob share over the classical channel, so Alice and Bob throw away those bits as they are no longer secret.\n",
    "\n",
    "**Write-up question:** Where do the errors come from? How is that reflected in your model? Think about the limitations of the polarization optics and dark counts, as a place to start.\n",
    "\n",
    "Another form of error correction is available [here](https://cascade-python.readthedocs.io/en/latest/protocol.html), although we haven't implemented it in the code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "eb6d6c15",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.09\n"
     ]
    }
   ],
   "source": [
    "# Estimate the q BER by revealing some of the bits.\n",
    "\n",
    "length_reveal = 100\n",
    "\n",
    "# errors = (bits_tx_sifted[0:length_reveal - 1] != bits_rx_sifted[0:length_reveal - 1]).astype(int)\n",
    "# errors_total = errors.sum()\n",
    "# BER = 1.0/length_reveal * errors.sum()\n",
    "\n",
    "Al_all = np.random.randint(low = 0, high = 2, size = (length_reveal+30,))\n",
    "\n",
    "# give Bob some errors for testing\n",
    "err_vec = np.random.choice(2,length_reveal+30,p=[0.9,0.1])\n",
    "Bob_all = Al_all ^ err_vec\n",
    "\n",
    "key_test = Al_all[0:length_reveal]\n",
    "Bob_test = Bob_all[0:length_reveal]\n",
    "BERest =  np.mean(key_test != Bob_test)\n",
    "print(BERest)\n",
    "\n",
    "# Discard those bits\n",
    "Al_all = Al_all[length_reveal:]\n",
    "Bob_all = Bob_all[length_reveal:]\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16820445",
   "metadata": {
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "Now that Alice and Bob have an estimate of the quantum BER, they can evaluate to what degree Eve is interfering in their key distribution and if error correction coding will help them. If the qBER is too high (for this particular scheme, the cutoff is when qBER $\\geq 0.14$), the error-correction code will actually make the BER worse. This scheme can correct 1 error in every block of 7 bits.\n",
    "\n",
    "The below section uses a very simple one way Hamming code following the example in the section \"Error Correction for Quantum Key Distribution\" in Loepp and Wootters [book available online with MIT Library sign in](https://www.cambridge.org/core/books/abs/protecting-information/quantum-cryptography-revisited/8A88AA6C8C88E8A07E535FACEDE3C310). This allows Alice and Bob to reveal a minimum amount of information to Eve while doing error correction; the privacy amplification step then removes the information they did reveal to Eve.\n",
    "\n",
    "The Hamming code works in the following way: Bob and Alice take their raw key (`a` for Alice and `b` for Bob) and break it into blocks of 7 bits. Each block is multiplied by a 3x7 Hamming matrix `H`. For Alice, this results in `Ha` and for Bob `Hb`. Then Alice sends `Ha` to Bob. Bob finds `Ha - Hb = He` where `e` is the error vector, ie the vector that indicates whether `a == b` for each element. From there, Bob finds `e` and can use that to locate and then correct his bits. For a more in-depth explanation, I highly recommend checking out the linked section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "93a12ee1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Working out the Hamming Error matrix once\n",
    "\n",
    "n = 7\n",
    "k = np.log2(n+1).astype(int)\n",
    "\n",
    "H = np.array([[1,1,1,0,1,0,0],\n",
    "              [1,1,0,1,0,1,0],\n",
    "              [0,1,1,1,0,0,1]])\n",
    "\n",
    "# Define the error correction functions we'll need\n",
    "\n",
    "def errCor(err_mat,H,a,b,n):\n",
    "    # Multiply Alice's bits a by H\n",
    "    at = np.matrix.transpose(a)\n",
    "    Hat = np.matmul(H,at)\n",
    "    Hat = np.remainder(Hat,2) # we're doing bitwise operation, so we need to eliminate the overflow\n",
    "\n",
    "    # Multiply Bob's bits b by H\n",
    "    bt = np.matrix.transpose(b)\n",
    "    Hbt = np.matmul(H,bt)\n",
    "    Hbt = np.remainder(Hbt,2)\n",
    "    \n",
    "    # Find Hb - Ha = s \n",
    "    s = Hat ^ Hbt\n",
    "    sT = np.matrix.transpose(s)\n",
    "    \n",
    "    # s = He where e is the error vector.\n",
    "    # If we know s, we can find which row of the error matrix it matches\n",
    "    # Based on that, we know where the error is\n",
    "    x = np.where((err_mat==sT).all(axis=1))[0][0].astype(int)\n",
    "    err_vec = np.zeros((1,n))\n",
    "\n",
    "    if x < n: \n",
    "     err_vec[0,x] = 1\n",
    "     \n",
    "    ev = err_vec.astype(int)\n",
    "    ev = np.reshape(ev,(1,n))\n",
    "\n",
    "    \n",
    "    # Bob now has a corrected vector\n",
    "    # bc = binSub(b,err_vec)\n",
    "    bc = b ^ ev\n",
    "    return bc\n",
    "\n",
    "def makeErrMat(n,k,H):\n",
    "    err_mat = np.zeros((n+1,k))\n",
    "\n",
    "    for ii in range(n):\n",
    "        err_vec = np.zeros((n,1))\n",
    "        err_vec[ii] = 1\n",
    "        HeT = np.matmul(H,err_vec)\n",
    "        HeTT = np.matrix.transpose(HeT)\n",
    "        # print('err loc',err_vec)\n",
    "        # print('HeT', HeT)\n",
    "        err_mat[ii] = HeTT    \n",
    "\n",
    "    err_mat = err_mat.astype(int)\n",
    "    return err_mat\n",
    "\n",
    "err_mat = makeErrMat(n,k,H)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "b447faf9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.0\n"
     ]
    }
   ],
   "source": [
    "# do error correction for each block\n",
    "\n",
    "# The following is an example script\n",
    "LL = 31\n",
    "\n",
    "Al_all = np.random.randint(low = 0, high = 2, size = (LL,))\n",
    "\n",
    "\n",
    "\n",
    "err_all = np.zeros((LL)).astype(int)\n",
    "err_all[3] = 1\n",
    "err_all[26] = 1\n",
    "err_all[18] = 1\n",
    "\n",
    "\n",
    "Bob_all = Al_all ^ err_all\n",
    "\n",
    "\n",
    "# We've skipped sifting for now\n",
    "sk = Bob_all.size\n",
    "\n",
    "cutoff =  sk % n # find key length modulo n\n",
    "\n",
    "# elimate the \"cut-off\" bits\n",
    "Bob = Bob_all[0:-cutoff]\n",
    "\n",
    "# reshape Alice and Bob's bits into a 2d matrix\n",
    "a = np.array([[1,0,0,0,1,1,1]])\n",
    "b = np.array([[1,0,0,0,1,1,0]])\n",
    "\n",
    "\n",
    "Bob = np.reshape(Bob,(-1,n))\n",
    "Al  = np.reshape(Al_all[0:-cutoff],(-1,n))\n",
    "Al[1,:] = a\n",
    "Bob[1,:] = b\n",
    "\n",
    "shape = np.shape(Al)\n",
    "Bob_corr = np.zeros((shape)).astype(int) #initialize matrix for Bob's error correction\n",
    "words = shape[0]\n",
    "\n",
    "for ii in range(words):\n",
    "    Al_word = Al[ii,:]\n",
    "    Bob_word = Bob[ii,:]\n",
    "    Bob_word_corr = errCor(err_mat, H, Al_word,Bob_word,n)\n",
    "    Bob_corr[ii,:] = Bob_word_corr\n",
    "    \n",
    "error = np.mean(Al != Bob_corr)\n",
    "print(error)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "594d176d",
   "metadata": {
    "tags": [],
    "user_expressions": []
   },
   "source": [
    "## Privacy Amplification\n",
    "\n",
    "In this set-up, Eve isn't interacting with the optical set-up, so the only information she has is what Alice and Bob share via the classical channel. \n",
    "So, following pg 184 from \"Error Correction for Quantum Key Distribution\" in Loepp and Wootters [book available online with MIT Library sign in](https://www.cambridge.org/core/books/abs/protecting-information/quantum-cryptography-revisited/8A88AA6C8C88E8A07E535FACEDE3C310), we simply get rid of the last 3 bits of each of the 7-word chunks, and Eve will have no information about Alice and Bob's shared key.  As discussed in the reference, this removes the problematic bits about which information might have been inadvertently shared.  \n",
    "\n",
    "There are a variety of ways to perform error correction and privacy amplification. The privacy amplification needs to take into account how the error correction is done: if Alice and Bob reveal more information (which would allow the error correction to work for a worse qBER) they need to eliminate more bits to thwart Eve.  Again, this exercise is only meant to introduce a basic approach."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba84b383",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Take the giant matrix of error-correct keys, and just skip the last few columns (or rows)\n",
    "Bob_corr = Bob_corr[:,0:4]\n",
    "Al = Al[:,0:4]\n",
    "\n",
    "# Then reshape the key into one long string of bits\n",
    "Bob_key = np.reshape(Bob_corr,(1,-1))\n",
    "Al_key = np.reshape(Al,(1,-1))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "174fe77b",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "In your write-up, think about one way you could improved the SKR using commerically available components. How practical is the component for the speed-up? "
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "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.11.4"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {},
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}