{ "cells": [ { "cell_type": "markdown", "id": "0a97c59f", "metadata": {}, "source": [ "# Detailed walkthrough\n", "\n", "In this tutorial, we will try to cover any basic code to get our hands on Perceval.\n" ] }, { "cell_type": "markdown", "id": "4ad79fd5", "metadata": {}, "source": [ "## I. Introduction \n", "\n", "### 1. Perceval installation " ] }, { "cell_type": "code", "execution_count": 1, "id": "7537273d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'0.10.0'" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import perceval as pcvl\n", "pcvl.__version__" ] }, { "cell_type": "code", "execution_count": 2, "id": "28abc3e4", "metadata": {}, "outputs": [], "source": [ "from perceval.components.unitary_components import PS, BS, PERM \n", "import numpy as np\n", "\n", "## Use the symbolic skin for display\n", "from perceval.rendering.circuit import DisplayConfig, SymbSkin\n", "DisplayConfig.select_skin(SymbSkin)" ] }, { "cell_type": "markdown", "id": "8817daed", "metadata": { "tags": [] }, "source": [ "### 2. BasicStates \n", "\n", "In Linear Optical Circuits, photons can have many discrete degrees of freedom, called modes. \n", "It can be the frequency, the polarisation, the position, or all of them.\n", "\n", "We represent these degrees of freedom with Fock states. If we have $n$ photons over $m$ modes, the Fock state $|s_1,s_2,...,s_m\\rangle$ means we have $s_i$ photons in the $i^{th}$ mode. Note that $\\sum_{i=1}^m s_i =n$.\n", "\n", "In Perceval, we will use the module `pcvl.BasicState`" ] }, { "cell_type": "code", "execution_count": 3, "id": "435b102b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Those are the same states\n", "There is 0 photon in mode 0\n", "There is 2 photon in mode 1\n", "There is 0 photon in mode 2\n", "There is 1 photon in mode 3\n" ] } ], "source": [ "## Syntax of different BasicState (list, string, etc)\n", "bs1 = pcvl.BasicState('|0,2,0,1>')\n", "bs2 = pcvl.BasicState([0, 2, 0, 1])\n", "\n", "if bs1==bs2:\n", " print(\"Those are the same states\")\n", "\n", "## You can iterate on modes\n", "for i, photon_count in enumerate(bs2):\n", " print(f\"There is {photon_count} photon in mode {i}\")\n" ] }, { "cell_type": "markdown", "id": "5fe29a50", "metadata": {}, "source": [ "### 3. LO-Components \n", "\n", "The linear optical components are the elementary blocks which will act on our Fock states.\n", "\n", "It's important to know all the possible components that can be found in Perceval and understand their effects.\n", "\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "f3dad8b0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "PERM\n", "PERM([2, 0, 1])\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}0 & 1 & 0\\\\0 & 0 & 1\\\\1 & 0 & 0\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Permutation\n", "\n", "perm=PERM([2,0,1])\n", "\n", "print(perm.name)\n", "print(perm.describe())\n", "pcvl.pdisplay(perm.definition())\n", "pcvl.pdisplay(perm)" ] }, { "cell_type": "code", "execution_count": 5, "id": "1d0cf3b2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "PS\n", "PS(phi=pi)\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}e^{i \\phi}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=pi\n", "\n", "0\n", "0\n", "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Phase shifter\n", "ps = PS(phi=np.pi)\n", "\n", "print(ps.name)\n", "print(ps.describe()) \n", "pcvl.pdisplay(ps.definition()) \n", "pcvl.pdisplay(ps) # A pdisplay call on a circuit/processor needs to be the last line of a cell\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "d71c153b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "BS.Rx() unitary matrix\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & i e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\i e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "BS.H() unitary matrix\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & - e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "BS.Ry() unitary matrix\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & - e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "BS displays its convention as a small label\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Beam splitters\n", "\n", "bs_rx = BS.Rx() # By default a beam splitter follows the Rx gate convention, so bs=BS() has the same matrix\n", "\n", "# But other conventions exist too:\n", "bs_h = BS.H() \n", "bs_ry = BS.Ry()\n", "\n", "## Check the difference in the unitary definition:\n", "print(\"BS.Rx() unitary matrix\")\n", "pcvl.pdisplay(bs_rx.definition())\n", "print(\"BS.H() unitary matrix\")\n", "pcvl.pdisplay(bs_h.definition())\n", "print(\"BS.Ry() unitary matrix\")\n", "pcvl.pdisplay(bs_ry.definition())\n", "print(\"BS displays its convention as a small label\")\n", "pcvl.pdisplay(bs_ry)" ] }, { "cell_type": "code", "execution_count": 7, "id": "cd1e84bb", "metadata": {}, "outputs": [ { "data": { "text/html": [ "$\\left[\\begin{matrix}e^{0.392699081698724 i}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "$\\left[\\begin{matrix}0.92388 + 0.382683 i\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "A default beam-splitter:\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}\\frac{\\sqrt{2}}{2} & \\frac{\\sqrt{2} i}{2}\\\\\\frac{\\sqrt{2} i}{2} & \\frac{\\sqrt{2}}{2}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "A Beam-Splitter with a numerical value for theta:\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}\\cos{\\left(5 \\right)} & i \\sin{\\left(5 \\right)}\\\\i \\sin{\\left(5 \\right)} & \\cos{\\left(5 \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "$\\left[\\begin{matrix}0.283662 & - 0.958924 i\\\\- 0.958924 i & 0.283662\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "A Phase Shifter with a symbolic value for phi:\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}e^{i \\psi}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "A beam-splitter with a symbolic variable...\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}\\cos{\\left(\\frac{toto}{2} \\right)} & i \\sin{\\left(\\frac{toto}{2} \\right)}\\\\i \\sin{\\left(\\frac{toto}{2} \\right)} & \\cos{\\left(\\frac{toto}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "... set to a numerical value\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}0.283662 & - 0.958924 i\\\\- 0.958924 i & 0.283662\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Modified parameters...\n", " Parameter(name='toto', value=3.141592653589793, min_v=0.0, max_v=12.566370614359172)\n", " Parameter(name='tata', value=None, min_v=0.0, max_v=6.283185307179586)\n", "... and successfully modified Beam-Splitter:\n" ] }, { "data": { "text/html": [ "$\\left[\\begin{matrix}6.12323399573677 \\cdot 10^{-17} e^{i \\left(tata + 5.28318530717959\\right)} & 1.0 i e^{5.28318530717959 i}\\\\1.0 i e^{i tata} & 6.12323399573677 \\cdot 10^{-17}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# You can ask for the symbolic matrix value of your component with the attribute U\n", "my_ps = PS(phi=np.pi/8)\n", "pcvl.pdisplay(my_ps.U)\n", "# And for the numerical value with the method compute_unitary\n", "pcvl.pdisplay(my_ps.compute_unitary())\n", "print(\"\")\n", "\n", "# If you do it for a Beam-Splitter, you can see that by default theta=pi/2, and the phi's are 0\n", "print(\"A default beam-splitter:\")\n", "pcvl.pdisplay(BS().compute_unitary()) #this is a balanced Beamsplitter\n", "print(\"\")\n", "\n", "# To control the value of the parameters of a component, several choices are possible: \n", "# - by setting a numerical value during the creation of the component\n", "print(\"A Beam-Splitter with a numerical value for theta:\")\n", "bs_rx = BS.Rx(theta=10)\n", "pcvl.pdisplay(bs_rx.U)\n", "pcvl.pdisplay(bs_rx.compute_unitary())\n", "print(\"\")\n", "\n", "# - by using the syntax pcvl.P to create a symbolic variable \n", "# (note that you cannot compute the numerical value of your component anymore)\n", "print(\"A Phase Shifter with a symbolic value for phi:\")\n", "ps = PS(phi=pcvl.P('\\psi'))\n", "pcvl.pdisplay(ps.U)\n", "print(\"\")\n", "\n", "# - you can still modify the value of a symbolic variable after its creation\n", "# This is not true for a numerical variable!\n", "print(\"A beam-splitter with a symbolic variable...\")\n", "bs_rx = BS(theta=pcvl.P('toto'))\n", "pcvl.pdisplay(bs_rx.U)\n", "bs_rx.assign({'toto':5})\n", "bs_rx.assign({'toto':10})\n", "print(\"... set to a numerical value\")\n", "pcvl.pdisplay(bs_rx.compute_unitary())\n", "print(\"\")\n", "\n", "# To check which parameters can be modified, you can call the method get_parameters\n", "# You can also directly change the output of get_parameters to change the values of the parameters\n", "bs_rx = BS(theta=pcvl.P('toto'), phi_tl = pcvl.P('tata'), phi_tr = -1)\n", "parameters = bs_rx.get_parameters()\n", "parameters[0].set_value(np.pi)\n", "print(\"Modified parameters...\")\n", "for param in parameters:\n", " print(\" \", param)\n", "print(\"... and successfully modified Beam-Splitter:\")\n", "pcvl.pdisplay(bs_rx.U)" ] }, { "cell_type": "code", "execution_count": 8, "id": "b04b1194", "metadata": {}, "outputs": [ { "data": { "text/html": [ "$\\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & i e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\i e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=phi_tl\n", "\n", "\n", "Φ=phi_bl\n", "\n", "Θ=theta\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_tr\n", "\n", "\n", "Φ=phi_br\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## to understand the conventions, you can note that a BS.Rx with the 4 phases phi (top left/right and bottom left/right) can be represented like that \n", "\n", "bs_rx_circuit=pcvl.Circuit(2) // (0,PS(phi=pcvl.P(\"phi_tl\"))) // (1,PS(phi=pcvl.P(\"phi_bl\"))) // BS(theta=pcvl.P('theta')) // (0,PS(phi=pcvl.P(\"phi_tr\"))) // (1,PS(phi=pcvl.P(\"phi_br\")))\n", "\n", "\n", "pcvl.pdisplay(bs_rx_circuit.U)\n", "\n", "# we can check it's the same as bs_rx.definition()\n", "pcvl.pdisplay(bs_rx_circuit)\n", "\n", "## For this cell, we needed the syntax to builds circuits... Good transition !" ] }, { "cell_type": "markdown", "id": "bb4cd4d2", "metadata": {}, "source": [ "## II. LO-Circuits \n", "\n", "From the LO-components, we can build a LO-circuit, i.e. a sequence of those components acting on our different modes.\n", "\n", "### 1. Syntax" ] }, { "cell_type": "code", "execution_count": 9, "id": "2ab8b90e", "metadata": {}, "outputs": [ { "data": { "text/html": [ "$\\left[\\begin{matrix}\\frac{\\sqrt{2} e^{1.5707963267949 i}}{2} & \\frac{\\sqrt{2} i e^{1.5707963267949 i}}{2} & 0\\\\\\frac{i e^{i \\phi}}{2} & \\frac{e^{i \\phi}}{2} & \\frac{\\sqrt{2} i}{2}\\\\- \\frac{e^{i \\phi}}{2} & \\frac{i e^{i \\phi}}{2} & \\frac{\\sqrt{2}}{2}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=phi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "circuit = pcvl.Circuit(3) # Create a 3 mode circuit\n", "\n", "\n", "circuit.add(0, BS())\n", "circuit.add(0, PS(phi=np.pi/2)).add(1, PS(phi=pcvl.P('phi'))).add(1, BS())\n", "\n", "# Equivalent syntax:\n", "# circuit // BS() // PS(phi=np.pi/2) // (1, PS(phi=pcvl.P('phi'))) // (1, BS())\n", "\n", " \n", "pcvl.pdisplay(circuit.U)\n", "pcvl.pdisplay(circuit)" ] }, { "cell_type": "markdown", "id": "0efeef18", "metadata": {}, "source": [ "The syntax ``pcvl.P('phi')`` allows you to use parameters in the circuit, where you can assign a value or not. The behavior of the parameters of a circuit is similar to the case of the components.\n", "\n", "For instance, you can use :" ] }, { "cell_type": "code", "execution_count": 10, "id": "1230c948", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Parameter(name='phi', value=None, min_v=0.0, max_v=6.283185307179586)]\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "params=circuit.get_parameters()\n", "print(params) #list of the parameters\n", "\n", "# the value is None, but we can change that with :\n", "\n", "params[0].set_value(np.pi)\n", "pcvl.pdisplay(circuit)\n" ] }, { "cell_type": "markdown", "id": "75e4440d", "metadata": {}, "source": [ "### 2. Mach-Zehnder Interferometers\n", "\n", "The beamsplitter's angle $\\theta$ can also be defined as a parameter.\n", "\n", "However, as the reflexivity depends on the mirror, it's hard to have adaptibility on the angle. \n", "Therefore, in practice, we use a [Mach-Zehnder Interferometer](https://en.wikipedia.org/wiki/Mach%E2%80%93Zehnder_interferometer). \n", "\n", "The beamsplitter with a parameterised $\\theta$ is therefore implemented with a parameterised phase shifter $\\phi$ between two fixed beamsplitters.\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "8598d2e3", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## TO-DO: build a circuit implementing the mzi\n", "\n", "mzi = pcvl.Circuit(2) // BS() // (1,PS(phi=pcvl.P(\"phi\"))) // BS()\n", "\n", "\n", "## TO-DO: Check that the parameterised phase allows you to change the reflexivity of your MZI\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "## We create a list of all different values for theta\n", "X = np.linspace(0, 2*np.pi, 1000)\n", "Y = []\n", "for theta in X:\n", " phase = mzi.get_parameters()[0]\n", " phase.set_value(theta)\n", " Y.append(abs(mzi.compute_unitary()[0,0])**2) #compute_unitary is numerical, so far faster that mzi.U, which uses symbolic expressions.\n", " \n", "plt.plot(X, Y)\n", "plt.xlabel(\"phi\")\n", "plt.ylabel(\"R\")\n", "plt.show()\n", "\n", "pcvl.pdisplay(mzi)\n", "## Note: If you need to create a BS directly from the reflexivity value, please use:\n", "## BS(BS.r_to_theta(reflectivity_value))\n", "## However, be aware that only theta value is stored inside the BS object" ] }, { "cell_type": "markdown", "id": "9d8e718b", "metadata": {}, "source": [ "### 3. Universal Circuits\n", "\n", "An operation on the modes of our circuit can also be expressed as a unitary.\n", "\n", "For three modes, the unitary $U=\\begin{pmatrix}\n", "a_{1,1} & a_{1,2} & a_{1,3}\\\\\n", "a_{2,1} & a_{2,2} & a_{2,3} \\\\ \n", "a_{3,1} & a_{3,2} & a_{3,3}\n", "\\end{pmatrix}$ performs the following operation on the Fock state basis:\n", "\n", "$$\\begin{array}{rcl}\n", "|1,0,0\\rangle & \\mapsto& a_{1,1}|1,0,0\\rangle + a_{1,2}|0,1,0\\rangle + a_{1,3}|0,0,1\\rangle\\\\\n", "|0,1,0\\rangle & \\mapsto& a_{2,1}|1,0,0\\rangle + a_{2,2}|0,1,0\\rangle + a_{2,3}|0,0,1\\rangle\\\\\n", "|0,0,1\\rangle & \\mapsto& a_{3,1}|1,0,0\\rangle + a_{3,2}|0,1,0\\rangle + a_{3,3}|0,0,1\\rangle\n", "\\end{array}$$" ] }, { "cell_type": "markdown", "id": "78150c03", "metadata": {}, "source": [ "Since 1994, we know that any $U$ on the modes can be implemented as an LO-circuit [Reck's et al](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58).\n", "\n", "This decomposition can be done easily in Perceval using beamsplitters and phase-shifters as follows. " ] }, { "cell_type": "code", "execution_count": 12, "id": "a043bb05", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=5.584389\n", "\n", "\n", "Φ=4.707071\n", "\n", "\n", "Φ=3.40112\n", "\n", "Θ=5.119244Φ_tr=0.249691\n", "\n", "Rx\n", "\n", "\n", "Θ=8.251993Φ_tr=2.085961\n", "\n", "Rx\n", "\n", "\n", "Θ=4.742105Φ_tr=0.125058\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## From any unitary\n", "n = 3\n", "U = pcvl.Matrix.random_unitary(n)\n", "\n", "circuit_u = pcvl.Circuit.decomposition(U, BS(theta=pcvl.P('theta'),phi_tr=pcvl.P('phi')), phase_shifter_fn=PS)\n", "\n", "pcvl.pdisplay(circuit_u)" ] }, { "cell_type": "code", "execution_count": 13, "id": "a5e7c453", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The error between the two unitaries is 8.546891021709984e-09\n" ] } ], "source": [ "print(\"The error between the two unitaries is\", np.linalg.norm(U-circuit_u.compute_unitary()))" ] }, { "cell_type": "code", "execution_count": 14, "id": "20b31e3f", "metadata": {}, "outputs": [], "source": [ "## TO-DO: decompose your unitary with only phase shifters and balanced beamsplitters.\n", "mzi=pcvl.Circuit(2) // BS() // PS(pcvl.P(\"phi1\")) // BS() // PS(pcvl.P(\"phi2\"))\n", "\n", "circuit_u=pcvl.Circuit.decomposition(U,mzi,phase_shifter_fn=PS)\n", "\n", "## Note: you can use a MZI. Be careful to put the phase on the right, as the full layer of phase_shifter_fn is on the left of the circuit\n" ] }, { "cell_type": "code", "execution_count": 15, "id": "d77fa5a7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The error between the two unitaries is 3.4586946619580422e-09\n" ] } ], "source": [ "## TO-DO: check the norm of the difference to be sure it has worked well \n", "print(\"The error between the two unitaries is\",np.linalg.norm(U-circuit_u.compute_unitary()))" ] }, { "cell_type": "markdown", "id": "b6990985", "metadata": {}, "source": [ "### 4. Black Box\n", "\n", "To improve readibility, the circuit can be constructed in multiple steps which are then combined as black boxes. This will also help when we'll need generic operations.\n" ] }, { "cell_type": "code", "execution_count": 16, "id": "cf442c25", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "BELLSTATEPREPAR.\n", "\n", "\n", "\n", "UPPERMZI\n", "\n", "\n", "\n", "LOWERMZI\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pre_MZI = (pcvl.Circuit(4, name=\"Bell State Prepar.\")\n", " .add(0, BS())\n", " .add(2, BS())\n", " .add(1, PERM([1, 0])))\n", "\n", "upper_MZI = (pcvl.Circuit(2, name=\"upper MZI\")\n", " .add(0, PS(phi=pcvl.P('phi_0')))\n", " .add(0, BS())\n", " .add(0, PS(phi=pcvl.P('phi_2')))\n", " .add(0, BS()))\n", "\n", "lower_MZI = (pcvl.Circuit(2, name=\"lower MZI\")\n", " .add(0, PS(phi=pcvl.P('phi_1')))\n", " .add(0, BS())\n", " .add(0, PS(phi=pcvl.P('phi_3')))\n", " .add(0, BS()))\n", "\n", "chip = (pcvl.Circuit(4)\n", " .add(0, pre_MZI)\n", " .add(0, upper_MZI, merge=False)\n", " .add(2, lower_MZI, merge=False))\n", "\n", "pcvl.pdisplay(chip)" ] }, { "cell_type": "code", "execution_count": 17, "id": "74c28a38", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "BELL STATE PREPAR.\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "UPPER MZI\n", "\n", "\n", "Φ=phi_0\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "LOWER MZI\n", "\n", "\n", "Φ=phi_1\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_3\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## You can still display the inside of black boxes with:\n", "pcvl.pdisplay(chip, recursive=True)" ] }, { "cell_type": "markdown", "id": "1bd8e2f0", "metadata": {}, "source": [ "## III. Simulation \n", "\n", "Up to this point, we have focused on creating circuits.\n", "It's time to learn how to sample from them or describe their output distribution, on many different inputs.\n", "\n" ] }, { "cell_type": "markdown", "id": "190ae9ac", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] }, "source": [ "### 1. Computing probabilities\n", "\n", "For this part, we will take the [Hong-Ou-Mandel](https://en.wikipedia.org/wiki/Hong%E2%80%93Ou%E2%80%93Mandel_effect) experience as an example.\n", "\n", "It's one of the simplest experiments and yet it is very useful.\n", "\n", "Making two indistinguishable photons, one in each mode, enter one balanced beamsplitter $BS=\\frac{1}{\\sqrt{2}} \\left[\\begin{matrix}1 & 1\\\\1& -1\\end{matrix}\\right]$, we expect the outcome to be:\n", "\n", "$$|1,1\\rangle \\mapsto \\frac{|2,0\\rangle - |0,2\\rangle}{\\sqrt{2}} $$\n", "\n", "We will show how to verify this in the next steps using the Naive backend to recover the full probability distribution." ] }, { "cell_type": "code", "execution_count": 18, "id": "73e9230b", "metadata": {}, "outputs": [], "source": [ "## TO-DO: build the circuit with the convention above\n", "\n", "\n", "circuit = pcvl.Circuit(2) // BS.H()\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 19, "id": "2818570a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0.7071067811865477+0j)\n", "(-0.7071067811865477+0j)\n", "0.5000000000000002\n" ] }, { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
|1,1>|0,2> |2,0>
|1,1> 01/2 1/2
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Syntax to compute the amplitudes\n", "backend = pcvl.BackendFactory.get_backend(\"Naive\")\n", "backend.set_circuit(circuit)\n", "backend.set_input_state(pcvl.BasicState([1,1]))\n", "print(backend.prob_amplitude(pcvl.BasicState([2,0]))) #note that it's the amplitude !\n", "print(backend.prob_amplitude(pcvl.BasicState([0,2])))\n", "print(backend.probability(pcvl.BasicState([0,2])))\n", "\n", "\n", "## We can also use the Analyser module to compute a table of probabilities\n", "## The Analyser uses a Processor to work with. A Processor aims at simulating a photonic source plugged into a circuit\n", "## with a given backend.\n", "## The main syntax is :\n", "## >>> p = pcvl.Processor(backend_name, circuit, source)\n", "p = pcvl.Processor(\"Naive\", BS())\n", "analyzer = pcvl.algorithm.Analyzer(p, [pcvl.BasicState([1,1])], '*')\n", "pcvl.pdisplay(analyzer)\n" ] }, { "cell_type": "code", "execution_count": 20, "id": "fcf74970", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
|0,2,0> |0,1,1> |2,0,0> |1,1,0> |1,0,1> |0,0,2>
|1,1,0> 0.043518 0.302556 0.072713 0.54482 0.014496 0.021897
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "## TO-DO: Choose a random unitary 3x3 U, and output the table probablities when the input |1,1,0> passes through the LO-Circuit of unitary U.\n", "\n", "randU=pcvl.Unitary(pcvl.Matrix.random_unitary(3)) \n", "input=pcvl.BasicState([1,1,0])\n", "\n", "p=pcvl.Processor(\"SLOS\", randU) #We can put in the processor a pcvl.Unitary instead of a circuit ! We don't need to use pcvl.decomposition\n", "analyzer= pcvl.algorithm.Analyzer(p, [input], '*')\n", "pcvl.pdisplay(analyzer)" ] }, { "cell_type": "markdown", "id": "cd69cf36", "metadata": {}, "source": [ "### 2. Sampling\n", "\n", "Although it's crucial to compute the output distribution, it's not what we can expect from a photonic chip. Indeed, realistically, we only can obtain a single sample from the distribution each time we run the circuit. This can be done using the backend SLOS.\n", "\n" ] }, { "cell_type": "code", "execution_count": 21, "id": "ab11d846", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " |2,0>: 499\n", " |0,2>: 501\n", "}\n" ] } ], "source": [ "p = pcvl.Processor(\"SLOS\", BS())\n", "p.with_input(pcvl.BasicState([1,1]))\n", "\n", "# The sampler holds 'probs', 'sample_count' and 'samples' calls. You can use the one that fits your needs!\n", "sampler = pcvl.algorithm.Sampler(p) \n", "\n", "# A sampler call will return a Python dictionary containing sampling results, and two performance scores\n", "# sample_count = sampler.sample_count(1000)\n", "# sample_count contains {'results': , 'physical_perf': float [0.0 - 1.0], 'logical_perf': float [0.0 - 1.0]}\n", "sample_count = sampler.sample_count(1000)\n", "print(sample_count['results'])\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "ecbaaa5f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " |1,1,0>: 552\n", " |2,0,0>: 73\n", " |0,1,1>: 301\n", " |0,0,2>: 20\n", " |0,2,0>: 44\n", " |1,0,1>: 10\n", "}\n" ] } ], "source": [ "## TO-DO: implement the code to sample from the 3x3 Unitary of earlier\n", "\n", "p=pcvl.Processor(\"CliffordClifford2017\",randU)\n", "p.with_input(pcvl.BasicState([1,1,0]))\n", "p.min_detected_photons_filter(0) # Do not filter out any output state\n", "\n", "sampler = pcvl.algorithm.Sampler(p) \n", "sample_count = sampler.sample_count(1000)\n", "print(sample_count['results'])\n", "\n", "## Question: how many states do we have for 3 modes and 2 photons?\n", "## There are 6 different states\n", "\n", "\n", "## Question : how many states do we have for m modes and n photons? \n", "## There are m+n-1 choose n different states. Cf Bar and Star problems." ] }, { "cell_type": "markdown", "id": "a2ec95a5", "metadata": {}, "source": [ "Note : to approximate with decent precision a distribution over $M$ different states, we would need $M^2$ samples. This can be shown by [Hoeffding's inequality](https://en.wikipedia.org/wiki/Hoeffding%27s_inequality). " ] }, { "cell_type": "markdown", "id": "e07db4a7", "metadata": {}, "source": [ "### 3. Performance and output state filtering\n", "\n", "Perceval Processors have a built-in way of computing performance scores.\n", "\n", "There are two different performance scores:\n", "* Physical performance\n", "* Logical performance\n", "\n", "These performance scores help measure the real duration of a data acquisition on a real QPU.\n", "\n", "#### a. Physical performance\n", "\n", "This score is related to the number of detections (on a QPU: number of clicks). It drops output states where photons have been lost, or finish in the same mode.\n", "\n", "For instance, an imperfect source makes this score drop.\n", "\n", "However, you can choose not to filter any output state by lowering the expected clicks with:\n", "> proc.min_detected_photons_filter(0)\n", "\n", "##### Processor.min_detected_photons_filter method\n", "\n", "Perceval aims at being an interface for the QPU and as such, proc.min_detected_photons_filter(int k) post selects on having at least k photons detected (for threshold detection: photons on at least k different modes). By default, this value is set to n where n is the expected number of input photons. This is useful for retrieving a logical interpretation, making sure that no photon has been lost due to noise and coherent with the use of threshold detectors. However, for various applications (for instance machine learning where we use the full Fock space and resolve the number of photons, you will have to set it to 0 (and you may introduce you own post selection scheme if needed)." ] }, { "cell_type": "code", "execution_count": 23, "id": "4f53cd46", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Physical perf of perfect processor = 1\n", "Physical perf of imperfect processor = 0.08999999999999997\n", "Physical perf of imperfect processor (without selection) = 1\n" ] } ], "source": [ "# Create an empty circuit (each input mode is directly connected to a detector without interacting with any other)\n", "empty_circuit = pcvl.Circuit(4)\n", "\n", "perfect_proc = pcvl.Processor(\"Naive\", empty_circuit)\n", "imperfect_proc = pcvl.Processor(\"Naive\", empty_circuit, pcvl.Source(emission_probability=0.3))\n", "\n", "# Set the same input in both processors\n", "input_state = pcvl.BasicState([1,0,1,0])\n", "perfect_proc.with_input(input_state)\n", "imperfect_proc.with_input(input_state)\n", "\n", "perfect_sampler = pcvl.algorithm.Sampler(perfect_proc)\n", "perfect_probs = perfect_sampler.probs()\n", "imperfect_sampler = pcvl.algorithm.Sampler(imperfect_proc)\n", "imperfect_probs = imperfect_sampler.probs()\n", "\n", "print('Physical perf of perfect processor =', perfect_probs['physical_perf'])\n", "print('Physical perf of imperfect processor =', imperfect_probs['physical_perf']) # source emission probability**2\n", " \n", "# You can still disable output state filtering\n", "imperfect_proc.min_detected_photons_filter(0)\n", "imperfect_probs = imperfect_sampler.probs()\n", "print('Physical perf of imperfect processor (without selection) =', imperfect_probs['physical_perf'])" ] }, { "cell_type": "markdown", "id": "2e2915c3", "metadata": {}, "source": [ "#### b. Logical performance\n", "\n", "This performance computation is set up by heralded modes and/or post-selection function set in a processor.\n", "\n", "Depending on the circuit used, on the post-selection function, you may observe that physical and logical performance score interact. So, if you're interested on a theoretical gate performance, you should disable physical post-selection with:\n", "> proc.min_detected_photons_filter(0)\n", "\n", "Here is a quick example of the heralding / post-selection syntax in Perceval. You will see the result later on in this notebook." ] }, { "cell_type": "code", "execution_count": 24, "id": "e874af1d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "With herald only\n", "Logical perf = 0.75\n", "{\n", " |1,0>: 0.02859547920896832\n", " |0,1>: 0.9714045207910317\n", "}\n", "With herald + post-selection function\n", "Logical perf = 0.7285533905932737\n", "{\n", " |0,1>: 1.0\n", "}\n" ] } ], "source": [ "circuit = pcvl.Circuit(3) // BS() // (1, BS()) // BS()\n", "p = pcvl.Processor(\"Naive\", circuit)\n", "p.add_herald(2,0) # Third mode is heralded (0 photon in, 0 photon expected out)\n", "\n", "# After a mode is heralded, you must not take it into account when setting an input to the processor\n", "p.with_input(pcvl.BasicState([1, 0]))\n", "sampler = pcvl.algorithm.Sampler(p)\n", "probs = sampler.probs()\n", "print(\"With herald only\")\n", "print(\"Logical perf =\", probs['logical_perf'])\n", "print(probs['results'])\n", "\n", "# A post-selection function can be created like this:\n", "postselect_func = pcvl.PostSelect(\"[1] == 1\") # meaning we required 1 photon detection in mode #1\n", "\n", "p.set_postselection(postselect_func) # Add post-selection\n", "probs = sampler.probs()\n", "print(\"With herald + post-selection function\")\n", "print(\"Logical perf =\", probs['logical_perf'])\n", "print(probs['results'])" ] }, { "cell_type": "markdown", "id": "58b02992", "metadata": {}, "source": [ "### 4. Variational algorithm\n", "\n", "In variational algorithms, the samples from a quantum circuit allow us to approximate an expectation value, which is then used to determine the value of a loss function. This loss function is chosen such that minimising it yields a solution to a given problem. By changing the values of the parameters in our quantum circuit, we can search for this minimum.\n", "\n", "We won't go into the details of variational algorithms. However, it may be useful to see how to perform an optimisation with Perceval.\n", "\n", "We will use the library [scipy.optimise](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html#scipy.optimize.minimize).\n", "\n", "The following code solves the problem of finding an LO-Circuit which, given a Fock State $|1,1,1,1\\rangle$, maximises the probability of outputting $|4,0,0,0\\rangle$.\n", "The solution below works for an arbitrary $n$." ] }, { "cell_type": "code", "execution_count": 3, "id": "eaa4cab5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The maximum probability is 0.09374999914144536\n" ] }, { "data": { "text/plain": [ "0.09374999999999999" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import random\n", "from scipy import optimize\n", "\n", "# Data\n", "n = 4\n", "input = pcvl.BasicState([1]*n)\n", "output_to_max = pcvl.BasicState([n]+[0]*(n-1))\n", "backend = pcvl.BackendFactory.get_backend(\"SLOS\")\n", "\n", "# TO-DO: implement a generic circuit of size n with parameters. Code the loss function to maximise the good output. Launch the optimisation procedure. Output the probability and circuit obtained\n", "\n", "# We take a universal circuit\n", "circuit = pcvl.GenericInterferometer(n, \n", " lambda i: BS(theta=pcvl.P(f\"theta{i}\"),\n", " phi_tr=pcvl.P(f\"phi_tr{i}\")),\n", " phase_shifter_fun_gen=lambda i: PS(phi=pcvl.P(f\"phi{i}\")))\n", "param_circuit = circuit.get_parameters()\n", "params_init = [random.random()*np.pi for _ in param_circuit]\n", "\n", "\n", "def loss_function(params):\n", " for i, value in enumerate(params):\n", " param_circuit[i].set_value(value)\n", " backend.set_circuit(circuit)\n", " backend.set_input_state(input)\n", " return -backend.probability(output_to_max) # we want to maximise the prob, so we want to minimise the -prob\n", "\n", "\n", "# We run the otpimisation\n", "o = optimize.minimize(loss_function, params_init, method=\"Powell\")\n", "\n", "print(f\"The maximum probability is {-loss_function(o.x)}\") \n", "\n", "# For n=4, the probability should be 3/32 \n", "# The maximum can also be obtained with the Hadamard matrix :\n", "\n", "H4 = (1/2)*np.array([[1,1,1,1], [1,-1,1,-1], [1,1,-1,-1], [1,-1,-1,1]])\n", "backend.set_circuit(pcvl.Unitary(pcvl.Matrix(H4)))\n", "backend.set_input_state(input)\n", "backend.probability(output_to_max)" ] }, { "cell_type": "markdown", "id": "3a78cc87", "metadata": {}, "source": [ "### 5. To go further : connect to a chip\n", "\n", "Perceval is also connected to real/physical chips.\n", "Here's the syntax to sample directly from them ! \n", "\n", "[doc to connect to a chip](https://perceval.quandela.net/docs/notebooks/Remote%20computing.html)" ] }, { "cell_type": "markdown", "id": "c7ada850", "metadata": {}, "source": [ "## IV. Encoding Qubits " ] }, { "cell_type": "markdown", "id": "e84fc736", "metadata": {}, "source": [ "### 1. Path encoding\n", "\n", "To perform quantum computations using photons, we need an encoding: a correspondance between our Fock states and our qubit states.\n", "\n", "We therefore want to associate each qubit state with one of our Fock states.\n", "\n", "One natural way to encode qubits is the path encoding.\n", "A qubit is a two-level quantum state, so we will use two spatial modes to encode it: this is the dual-rail or path encoding.\n", "\n", "The logical qubit state $|0\\rangle_L$ will correspond to a photon in the upper mode, as in the Fock state $|1,0\\rangle$, while $|1\\rangle_L$ will be encoded as $|0,1\\rangle$.\n", "\n", "\n", "We can extend this to multiple qubits by having twice as many modes as there are qubits. For example the $3$-qubit state $\\frac{1}{\\sqrt{2}}(|000\\rangle_L+|111\\rangle_L)$ can be encoded with $3$ photons and $3\\times 2=6$ modes :\n", "$\\frac{1}{\\sqrt{2}}(|1,0,1,0,1,0\\rangle+|0,1,0,1,0,1\\rangle)$" ] }, { "cell_type": "markdown", "id": "4fbd0d6a", "metadata": {}, "source": [ "### 2. Single-qubit gates\n", "\n", "Using the dual-rail enconding, single-qubit gates only deal with one photon and are straightforward. Can you give the LO-circuits for the gates below?" ] }, { "cell_type": "markdown", "id": "9f1900e8", "metadata": {}, "source": [ "$$X=\\left[\\begin{matrix}0 & 1\\\\1& 0\\end{matrix}\\right]$$\n", "$$Y=\\left[\\begin{matrix}0 & -i\\\\i& 0\\end{matrix}\\right]$$\n", "$$Z=\\left[\\begin{matrix}1 & 0\\\\0& -1\\end{matrix}\\right]$$\n", "$$H=\\frac{1}{\\sqrt{2}} \\left[\\begin{matrix}1 & 1\\\\1& -1\\end{matrix}\\right]$$\n", "\n", "$$R_X=\\left[\\begin{matrix}\\cos{\\left(\\frac{\\theta}{2} \\right)} & -i \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\-i \\sin{\\left(\\frac{\\theta}{2} \\right)} & \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$$\n", "\n", "$$R_Y=\\left[\\begin{matrix}\\cos{\\left(\\frac{\\theta}{2} \\right)} & - \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\ \\sin{\\left(\\frac{\\theta}{2} \\right)} & \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$$\n", "\n", "$$R_Z=\\left[\\begin{matrix}e^{-i\\frac{\\theta}{2}} & 0 \\\\ 0 & e^{i\\frac{\\theta}{2}}\\end{matrix}\\right]$$" ] }, { "cell_type": "code", "execution_count": 26, "id": "626771f0", "metadata": {}, "outputs": [], "source": [ "## TO-DO: find the LO-circuits for each gate\n", "\n", "circuit_x = PERM([1,0]) #it's not the only way\n", "circuit_y = PERM([1,0]) // (0,PS(-np.pi/2)) // (1,PS(np.pi/2))\n", "circuit_z = pcvl.Circuit(2) // (1,PS(np.pi))\n", "circuit_h = BS.H()\n", "\n", "circuit_rx = pcvl.Circuit(2) // (0, PS(np.pi)) // BS.Rx(theta=pcvl.P(\"theta\")) // (0, PS(np.pi)) #Be careful for the minus ! We use a convention\n", "circuit_ry = BS.Ry(theta=pcvl.P(\"theta\"))\n", "circuit_rz = BS.H() // circuit_rx // BS.H() # Indeed, Rz = H Rx H. Of course, we would like to be able to have many parameters with the same name. It will come soon :)" ] }, { "cell_type": "markdown", "id": "26b5af58", "metadata": {}, "source": [ "### 3. Two-qubit gates\n", "\n", "On the other hand, in dual-rail encoding, it can be shown that two-qubit gates can't be deterministic, and have a probability to fail.\n", "\n", "There are two ways to detect that failure:\n", "\n", "- We can use additional photons called ancillas, which we can measure independently from the main circuit photons. Depending on the state obtained on the ancilla, we know whether the gate has succeeded or not on the main qubits. Those gates will be called heralded.\n", "- We can also directly measure the main circuit qubits, and depending on the result, assess whether the gate has succeeded or not. Those gates will be called postselected.\n", "\n", "The CNOT gate acts on two qubits, a control and a target, and flips the value of the target if the control qubit is in state $|1\\rangle_L$. In the following two exercices, we will see the two types of CNOT gates: \n", "- the postselected CNOT of [Ralph et al.](https://arxiv.org/abs/quant-ph/0112088)\n", "- the heralded CNOT of the [KLM protocol](https://arxiv.org/abs/quant-ph/0006088)" ] }, { "cell_type": "code", "execution_count": 27, "id": "4b308e53", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['klm cnot', 'postprocessed cnot', 'heralded cz', 'generic 2 mode circuit', 'mzi phase first', 'mzi phase last']\n" ] } ], "source": [ "## We introduce the component catalog. It contains both CNOT gates.\n", "from perceval.components import catalog\n", "print(catalog.list())" ] }, { "cell_type": "code", "execution_count": 28, "id": "09d7d85f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "POSTPROCESSED CNOT DOCUMENTATION\n", "---------------------------------\n", "\n", "CNOT gate with 2 heralded modes and a post-selection function\n", "\n", "Scientific article reference: https://journals.aps.org/pra/abstract/10.1103/PhysRevA.65.062324\n", "\n", "Schema:\n", " ╭─────╮\n", "ctrl (dual rail) ─────┤ ├───── ctrl (dual rail)\n", " ─────┤ ├─────\n", " │ │\n", "data (dual rail) ─────┤ ├───── data (dual rail)\n", " ─────┤ ├─────\n", " ╰─────╯\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "H\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "[ctrl]\n", "\n", "2\n", "3\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "\n", "0\n", "1\n", "[ctrl]\n", "\n", "2\n", "3\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "" ], "text/plain": [ "" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Ralph's et al. CNot\n", "\n", "print(catalog['postprocessed cnot'].doc)\n", "ralph_cnot = catalog['postprocessed cnot'].build_processor()\n", "## You can set its input state with a LogicalState\n", "ralph_cnot.with_input(pcvl.LogicalState([1, 0]))\n", "\n", "pcvl.pdisplay(ralph_cnot, recursive=True, render_size=1.25)" ] }, { "cell_type": "code", "execution_count": 29, "id": "1e407529", "metadata": {}, "outputs": [], "source": [ "## TO-DO: Check/Convince yourself that the circuit above is performing a CNOT in the dual rail encoding" ] }, { "cell_type": "code", "execution_count": 30, "id": "bd4192fa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " |0,1,0,1>: 1.0\n", "}\n", "Some output states were not selected because of heralds and post-processing => you can check the logical performance\n", "Logical performance = 0.11111111111111131\n" ] } ], "source": [ "## You can sample some output states\n", "ralph_cnot.min_detected_photons_filter(0)\n", "cnot_sampler = pcvl.algorithm.Sampler(ralph_cnot)\n", "samples = cnot_sampler.probs()\n", "print(samples['results'])\n", "print(\"Some output states were not selected because of heralds and post-processing => you can check the logical performance\")\n", "print(\"Logical performance = \", samples['logical_perf'])" ] }, { "cell_type": "code", "execution_count": 31, "id": "88579684", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[|1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>]\n", "Some output states were not selected because of heralds and post-processing => you can check the logical performance\n", "Logical performance = 0.05156442163200023\n" ] } ], "source": [ "## You can sample some output states\n", "h_cnot = catalog['klm cnot'].build_processor()\n", "cnot_sampler = pcvl.algorithm.Sampler(h_cnot)\n", "h_cnot.with_input(pcvl.LogicalState([0, 0]))\n", "\n", "samples = cnot_sampler.samples(10)\n", "print(samples['results'])\n", "print(\"Some output states were not selected because of heralds and post-processing => you can check the logical performance\")\n", "print(\"Logical performance =\", samples['logical_perf'])" ] }, { "cell_type": "code", "execution_count": 32, "id": "6648043a", "metadata": {}, "outputs": [], "source": [ "## TO-DO: Check it perfoms a CNOT, and explicit the difference between the two types of CNOT" ] }, { "cell_type": "markdown", "id": "bda1dc03", "metadata": {}, "source": [ "### Exercise\n", "\n", "The next circuit comes from the following [paper](https://quantum-journal.org/papers/q-2021-03-29-422/)." ] }, { "cell_type": "markdown", "id": "76bcc78f", "metadata": {}, "source": [ "![](../_static/img/tuto_circuit_to_reproduce.png)" ] }, { "cell_type": "code", "execution_count": 33, "id": "911f92c6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HERALDED CNOT DOCUMENTATION\n", "----------------------------\n", "\n", "CNOT gate with 4 heralded modes\n", "\n", "Scientific article reference: https://doi.org/10.1073/pnas.1018839108\n", "\n", "Schema:\n", " ╭─────╮\n", "ctrl (dual rail) ─────┤ ├───── ctrl (dual rail)\n", " ─────┤ ├─────\n", " │ │\n", "data (dual rail) ─────┤ ├───── data (dual rail)\n", " ─────┤ ├─────\n", " ╰─────╯\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "Θ=2.145993\n", "\n", "H\n", "\n", "Θ=2.145993\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.028622\n", "\n", "H\n", "\n", "\n", "\n", "Θ=1.028622\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "[ctrl]\n", "\n", "2\n", "3\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "1\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "1\n", "\n", "0\n", "1\n", "[ctrl]\n", "\n", "2\n", "3\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "1\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## KLM's CNOT\n", "\n", "print(catalog['klm cnot'].doc)\n", "knill_cnot = catalog['klm cnot'].build_processor()\n", "\n", "pcvl.pdisplay(knill_cnot, recursive=True)" ] }, { "cell_type": "code", "execution_count": 34, "id": "7039fcdc", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "RY1\n", "\n", "\n", "\n", "RZ4\n", "\n", "\n", "\n", "RY2\n", "\n", "\n", "\n", "RZ5\n", "\n", "\n", "\n", "RY3\n", "\n", "\n", "\n", "RZ6\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDEDCNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDEDCNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDEDCNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "RY7\n", "\n", "\n", "\n", "RZ10\n", "\n", "\n", "\n", "RY8\n", "\n", "\n", "\n", "RZ11\n", "\n", "\n", "\n", "RY9\n", "\n", "\n", "\n", "RZ12\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "1\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "1\n", "\n", "[herald4]\n", "0\n", "\n", "[herald5]\n", "1\n", "\n", "[herald6]\n", "0\n", "\n", "[herald7]\n", "1\n", "\n", "[herald8]\n", "0\n", "\n", "[herald9]\n", "1\n", "\n", "[herald10]\n", "0\n", "\n", "[herald11]\n", "1\n", "\n", "2\n", "3\n", "[ctrl]\n", "\n", "4\n", "5\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "1\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "1\n", "\n", "[herald4]\n", "0\n", "\n", "[herald5]\n", "1\n", "\n", "[herald6]\n", "0\n", "\n", "[herald7]\n", "1\n", "\n", "[herald8]\n", "0\n", "\n", "[herald9]\n", "1\n", "\n", "[herald10]\n", "0\n", "\n", "[herald11]\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# TO-DO : reproduce it in the encoding seen above\n", "\n", "# Let's try to implement that circuit properly.\n", "# First, the quantum gates, as coded above :\n", "\n", "Rx = lambda i: pcvl.Circuit(2) // (0,PS(np.pi)) // BS.Rx(theta=pcvl.P(f\"theta{i}\")) // (0,PS(np.pi)) #Be careful for the minus ! We use a convention\n", "Ry = lambda i: pcvl.Circuit(2,name=f\"Ry{i}\") // BS.Ry(theta=pcvl.P(f\"theta{i}\"))\n", "Rz = lambda i: pcvl.Circuit(2,name=f\"Rz{i}\") // BS.H() // circuit_rx // BS.H() \n", "cnot = catalog['klm cnot'].build_processor()\n", "\n", "# Our qubits in the dual rail encoding\n", "q1, q2, q3 = [0,1], [2,3], [4,5]\n", "\n", "p = pcvl.Processor(\"SLOS\",6)\n", "\n", "for i in range(3):\n", " p.add(2*i,Ry(i+1)).add(2*i,Rz(i+4))\n", "p.add(q1+q2, cnot)\n", "p.add(q1+q3, cnot)\n", "p.add(q2+q3, cnot)\n", "\n", "for i in range(3):\n", " p.add(2*i, Ry(i+7)).add(2*i, Rz(i+10))\n", "\n", "pcvl.pdisplay(p,recursive=False)" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }