{ "cells": [ { "cell_type": "markdown", "id": "7d428b7117a2a2fe", "metadata": {}, "source": [ "# Quantum teleportation using feed-forward" ] }, { "cell_type": "markdown", "id": "61dad5341013c224", "metadata": {}, "source": [ "The goal of this notebook is to use perceval's feed-forward ability to demonstrate the quantum teleportation algorithm \\[1\\] on a photonic simulated circuit using dual rail encoding." ] }, { "cell_type": "markdown", "id": "1916284b04197f03", "metadata": {}, "source": [ "## I. Definition of the problem" ] }, { "cell_type": "markdown", "id": "60118172", "metadata": {}, "source": [ "The idea of the protocol is the following:\n", "\n", "Say that Alice has a generic qubit of the form\n", "\n", "$$|\\psi\\rangle = \\alpha |0\\rangle + \\beta |1\\rangle \\;$$\n", "\n", "that they want to send to a distant receiver called Bob. Since Bob is distant, we want to avoid transporting physical systems from Alice to Bob (only classical light will do).\n", "\n", "Before the start of the algorithm, Alice and Bob need to share a maximally entangled Bell state. For this example, we choose\n", "\n", "$$|\\phi^+\\rangle = \\frac{1}{\\sqrt{2}} (|0_A 0_B\\rangle + |1_A 1_B\\rangle) \\;$$\n", "\n", "The first qubit is accessible to Alice and the second to Bob. We now drop the subscript $A$ and $B$ for clarity. \n", "\n", "The composite system is then\n", "\n", "$$|\\psi\\rangle \\otimes |\\phi^+\\rangle = (\\alpha |0\\rangle + \\beta |1\\rangle) \\otimes \\frac{1}{\\sqrt{2}} (|00\\rangle + |11\\rangle)$$\n", "\n", "The algorithm is the following:\n", "\n", "Alice performs a CNOT using the first qubit as control and the second as target, then applies a Hadamard gate to the first qubit.\n", "\n", "![quantum_teleportation_circuit](../_static/img/quantum_teleportation_circuit.jpg)\n", "\n", "At the end, the composite system can be written as\n", "\n", "\\begin{align}\n", "& \\frac{1}{2}|00\\rangle \\otimes (\\alpha |0\\rangle + \\beta |1\\rangle) \\\\\n", "+& \\frac{1}{2}|01\\rangle \\otimes (\\beta |0\\rangle + \\alpha |1\\rangle) \\\\\n", "+& \\frac{1}{2}|10\\rangle \\otimes (\\alpha |0\\rangle - \\beta |1\\rangle) \\\\\n", "+& \\frac{1}{2}|11\\rangle \\otimes (- \\beta |0\\rangle + \\alpha |1\\rangle)\n", "\\end{align}\n", "\n", "Then Alice measures their two qubits and send the results to Bob using a classical channel.\n", "Firstly, if the second qubit is measured to be 1, Bob needs to apply a X gate to their qubit.\n", "Then, if the first qubit is measured to be 1, Bob needs to apply a Z gate to their qubit.\n", "\n", "After these corrections, Bob's qubit is guaranteed to be the original qubit of Alice $|\\psi\\rangle$." ] }, { "cell_type": "markdown", "id": "57318a20fc7eeb69", "metadata": {}, "source": [ "## II. Translation to Perceval" ] }, { "cell_type": "code", "execution_count": 1, "id": "4ec2a415e1cb1f04", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "import perceval as pcvl\n", "from perceval import catalog, Experiment" ] }, { "cell_type": "markdown", "id": "7208ddca25dafd45", "metadata": {}, "source": [ "### Starting state" ] }, { "cell_type": "markdown", "id": "17b862dbe9a08975", "metadata": {}, "source": [ "First, we need to create the input state $|\\psi\\rangle \\otimes |\\phi^+\\rangle$ for this algorithm. For demonstration purpose, we choose $\\alpha$ and $\\beta$ randomly." ] }, { "cell_type": "code", "execution_count": 2, "id": "initial_id", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.749*|1,0>+(0.464-0.473I)*|0,1>\n" ] } ], "source": [ "# Creation of the qubit to transmit\n", "alpha = np.random.random()\n", "beta = np.random.random() * np.exp(2 * np.pi * 1j * np.random.random())\n", "# alpha |0> + beta |1> in dual rail encoding\n", "to_transmit = pcvl.BasicState([1, 0]) * alpha + pcvl.BasicState([0, 1]) * beta\n", "to_transmit.normalize()\n", "\n", "alpha = to_transmit[pcvl.BasicState([1, 0])] # Normalized\n", "beta = to_transmit[pcvl.BasicState([0, 1])]\n", "\n", "print(to_transmit)" ] }, { "cell_type": "code", "execution_count": 3, "id": "b480ea69ffc07543", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.707*|1,0,1,0>+0.707*|0,1,0,1>\n" ] } ], "source": [ "# Creation of the quantum channel\n", "sg = pcvl.StateGenerator(pcvl.Encoding.DUAL_RAIL)\n", "bell_state = sg.bell_state(\"phi+\")\n", "print(bell_state)" ] }, { "cell_type": "code", "execution_count": 4, "id": "41cbc6dac613658f", "metadata": {}, "outputs": [], "source": [ "input_state = to_transmit * bell_state" ] }, { "cell_type": "markdown", "id": "884bebccd4e671b9", "metadata": {}, "source": [ "### Tomography" ] }, { "cell_type": "markdown", "id": "6a78aa77d2475c6b", "metadata": {}, "source": [ "Since we will only return probabilities and not quantum amplitudes, we will not have access to the relative phase between $|0\\rangle$ and $|1\\rangle$. However, we saw in the paragraph that, we need correction to teleport the state. Because we want to see the correctness of the teleportation, we code below a state tomography for 1 qubit to be able to make we have teleported the correct state.\n", "\n", "Since a qubit is defined up to a global rotation, we consider that $\\alpha$ is a real non-negative number." ] }, { "cell_type": "code", "execution_count": 5, "id": "5bcf056c9f390892", "metadata": {}, "outputs": [], "source": [ "# Needed if the number of modes is bigger than 2\n", "def squash_results(res: pcvl.BSDistribution, first_mode: int) -> pcvl.BSDistribution:\n", " \"\"\"Sum the output probabilities to keep only the mode of interest and the following.\"\"\"\n", " bsd = pcvl.BSDistribution()\n", " for state, prob in res.items():\n", " bsd[state[first_mode:first_mode + 2]] += prob\n", " return bsd\n", "\n", "def tomography(exp: pcvl.Experiment, first_mode: int = 0) -> pcvl.StateVector:\n", " computer = pcvl.SimulatedComputer(\"SLOS\")\n", "\n", " with computer.acquire():\n", " # First using identity, we get alpha ** 2 and |beta| ** 2\n", " res = pcvl.ExecutionFactory(computer, exp).probs()[\"results\"]\n", " res = squash_results(res, first_mode)\n", "\n", " alpha = res[pcvl.BasicState([1, 0])] ** .5\n", " if alpha == 0:\n", " return pcvl.StateVector(pcvl.BasicState([0, 1]))\n", "\n", " exp = exp.copy()\n", " # We do the same, but we add a H gate at the end for the qubit we are interested in\n", " exp.add(first_mode, pcvl.BS.H())\n", " res = pcvl.ExecutionFactory(computer, exp).probs()[\"results\"]\n", " res = squash_results(res, first_mode)\n", "\n", " p0 = res[pcvl.BasicState([1, 0])] # 1/2 |alpha + beta| ** 2\n", " p1 = res[pcvl.BasicState([0, 1])] # 1/2 |alpha - beta| ** 2\n", "\n", " # By writing beta = x + i y, we get\n", " x = (p0 - p1) / (2 * alpha)\n", "\n", " exp = exp.copy()\n", " # We do the same, but we multiply by i the amplitudes of qubit |1> before applying the H gate\n", " exp.add(first_mode + 1, pcvl.PS(np.pi / 2))\n", " exp.add(first_mode, pcvl.BS.H())\n", " res = pcvl.ExecutionFactory(computer, exp).probs()[\"results\"]\n", " res = squash_results(res, first_mode)\n", "\n", " p0 = res[pcvl.BasicState([1, 0])] # 1/2 |alpha + i beta| ** 2\n", " p1 = res[pcvl.BasicState([0, 1])] # 1/2 |alpha - i beta| ** 2\n", "\n", " y = (p0 - p1) / (2 * alpha)\n", " beta = x + 1j * y\n", "\n", " return alpha * pcvl.BasicState([1, 0]) + beta * pcvl.BasicState([0, 1])" ] }, { "cell_type": "markdown", "id": "44ad7639024e5118", "metadata": {}, "source": [ "We can now test this algorithm on our original qubit using an identity circuit." ] }, { "cell_type": "code", "execution_count": 6, "id": "937d6da2124fc11c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.749*|1,0>+(0.464-0.473I)*|0,1>" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "experiment = Experiment(2)\n", "\n", "experiment.min_detected_photons_filter(1)\n", "experiment.with_input(to_transmit)\n", "\n", "tomography(experiment)" ] }, { "cell_type": "markdown", "id": "3597044aedd3249c", "metadata": {}, "source": [ "We get the same state so the tomography process works." ] }, { "cell_type": "markdown", "id": "13cf7a685228ff18", "metadata": {}, "source": [ "### Circuit" ] }, { "cell_type": "markdown", "id": "f18e201949542957", "metadata": {}, "source": [ "Now we need to define the circuit on which the operations will take place. Since we need quantum gates and feed-forward operations, we use an `Experiment` object.\n", "\n", "First, we define the photonic circuit that acts on three qubits and therefore has six modes.\n", "\n", "Since the qubits on which the CNOT is applied will only perform 1-qubit gates *in the quantum circuit*, we can use a postprocessed CNOT instead of a heralded CNOT." ] }, { "cell_type": "code", "execution_count": 7, "id": "46c1994bcba8184f", "metadata": {}, "outputs": [], "source": [ "experiment = Experiment(6)\n", "experiment.add(0, catalog[\"postprocessed cnot\"].build_experiment())\n", "experiment.add(0, pcvl.BS.H());" ] }, { "cell_type": "markdown", "id": "2a6e8e7857d28509", "metadata": {}, "source": [ "Now we need to add the feed-forwarded components. Perceval provides two configurators that link measurements to circuits or experiments.\n", "\n", "Both are defined by the number of modes they measure, the number of empty modes between the measured modes and the circuit they configure (an integer called `offset`), and a default configuration used whenever a measurement does not match one of the defined cases.\n", "\n", "The measured modes need to be classical modes. Thus, we add detectors before adding the configurators.\n", "\n", "The X gate corresponds to a permutation for a dual rail encoding if we measure $|1\\rangle$, or an empty circuit if we measure $|0\\rangle$. Thus, we use an `FFCircuitProvider`, which links a measured state to a circuit or experiment." ] }, { "cell_type": "code", "execution_count": 8, "id": "84ab63365eb0278f", "metadata": {}, "outputs": [], "source": [ "# 2 measured modes\n", "# offset = 0 means that there is 0 empty modes between the measured modes and the circuit\n", "# the default circuit is an empty circuit\n", "ff_X = pcvl.FFCircuitProvider(2, 0, pcvl.Circuit(2))\n", "\n", "# Now if we measure a logical state |1>, we need to perform a permutation of the modes\n", "ff_X.add_configuration([0, 1], pcvl.PERM([1, 0]))\n", "\n", "# Add perfect detectors to the modes that will be measured\n", "experiment.add(2, pcvl.Detector.pnr())\n", "experiment.add(3, pcvl.Detector.pnr())\n", "experiment.add(2, ff_X);" ] }, { "cell_type": "markdown", "id": "4ab9a62af4dff3cc", "metadata": {}, "source": [ "The Z gate corresponds to a $\\pi$ shift on the second mode. Thus, we are going to use a `FFConfigurator` that uses a parametrized circuit and links the measured states to a mapping of values for these parameters." ] }, { "cell_type": "code", "execution_count": 9, "id": "34c780e885758455", "metadata": {}, "outputs": [], "source": [ "phi = pcvl.P(\"phi\")\n", "# Like Circuits and Experiments, configurators support chained `add` calls.\n", "ff_Z = pcvl.FFConfigurator(2, 3, pcvl.PS(phi), {\"phi\": 0}).add_configuration([0, 1], {\"phi\": np.pi})\n", "\n", "experiment.add(0, pcvl.Detector.pnr())\n", "experiment.add(1, pcvl.Detector.pnr())\n", "experiment.add(0, ff_Z);" ] }, { "cell_type": "markdown", "id": "88f450e84ce7f9a2", "metadata": {}, "source": [ "We can check that we defined our experiment correctly. With `recursive=True`, we can expose the inner circuit of the `FFConfigurator`." ] }, { "cell_type": "code", "execution_count": 10, "id": "cd64eca26d10b8cd", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "FFC\n", "\n", "\n", "\n", "U(FFC)\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "FFC\n", "\n", "\n", "Φ=phi\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "0\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pcvl.pdisplay(experiment, recursive=True)" ] }, { "cell_type": "markdown", "id": "be92f0ddd80ad218", "metadata": {}, "source": [ "## III. Simulation" ] }, { "cell_type": "markdown", "id": "5324d67209a5f0ed", "metadata": {}, "source": [ "Now that we have both the input state and the experiment, we can run the algorithm and check that it works." ] }, { "cell_type": "code", "execution_count": 11, "id": "a438b878ced2cfe8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'results': {\n", "\t|1,0,1,0,0,1>: 0.10972542108564676\n", "\t|0,1,1,0,1,0>: 0.14027457891435324\n", "\t|0,1,1,0,0,1>: 0.10972542108564677\n", "\t|1,0,0,1,1,0>: 0.14027457891435324\n", "\t|1,0,1,0,1,0>: 0.14027457891435324\n", "\t|0,1,0,1,1,0>: 0.14027457891435324\n", "\t|1,0,0,1,0,1>: 0.10972542108564677\n", "\t|0,1,0,1,0,1>: 0.10972542108564677\n", "}, 'global_perf': 0.11111111111111113}\n" ] } ], "source": [ "experiment.min_detected_photons_filter(3)\n", "\n", "# Since we use a custom (non-BasicState) input state, add the post-processed CNOT heralds manually.\n", "input_state *= pcvl.BasicState([0, 0])\n", "experiment.with_input(input_state)\n", "\n", "computer = pcvl.SimulatedComputer(\"SLOS\")\n", "factory = pcvl.ExecutionFactory(computer, experiment)\n", "with computer.acquire():\n", " res = factory.probs()\n", "print(res)" ] }, { "cell_type": "markdown", "id": "78778a2ac0806cd1", "metadata": {}, "source": [ "Notice that when using feed-forward, it is not possible to split the perfs into \"physical_perf\" and \"logical_perf\". In our case, the \"global_perf\" corresponds to the CNOT gate performance: $1 / 9 \\approx 0.111$.\n", "\n", "For the results, we don't need to know what was measured by Alice, so we need to squash the resulting probabilities to keep only the two last modes." ] }, { "cell_type": "code", "execution_count": 12, "id": "3e6a8afe9b080f15", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", "\t|0,1>: 0.43890168434258703\n", "\t|1,0>: 0.561098315657413\n", "}\n" ] } ], "source": [ "print(squash_results(res[\"results\"], 4))" ] }, { "cell_type": "markdown", "id": "e656019381c106b2", "metadata": {}, "source": [ "We can now apply our tomography process to check that Bob's qubit is now the initial qubit that Alice wanted to transmit." ] }, { "cell_type": "code", "execution_count": 13, "id": "beea8ac653d00512", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.749*|1,0>+(0.464-0.473I)*|0,1>\n" ] } ], "source": [ "print(tomography(experiment, 4))" ] }, { "cell_type": "markdown", "id": "6ca68df83bec38d0", "metadata": {}, "source": [ "Tadaaaa! We get the state that we wanted to transmit. Pretty to cool to teleport state in photonics right?" ] }, { "cell_type": "markdown", "id": "71027fa0c349b9d1", "metadata": {}, "source": [ "## References\n", "\n", "> [1] C. H. Bennett, G. Brassard, C. Crépeau, R. Jozsa, A. Peres and W. K. Wootters, “Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels”, [Phys. Rev. Lett.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.70.1895) **70**, 1895 (1993)." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }