{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# This cell is added by sphinx-gallery\n# It can be customized to whatever you like\n%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\nStudying realistic bosonic qubits using the bosonic backend\n===========================================================\n\n

#### Note

This tutorial is third in a series on the bosonic backend. For an introduction\n to the backend, see :doc:part 1 . To learn about\n how the backend can be used to sample non-Gaussian states, see\n :doc:part 2 . These tutorials accompany\n our research paper [[#bourassa2021]_].

\n\nIn a previous :doc:tutorial , we briefly introduced\nGottesman-Kitaev-Preskill (GKP) states [[#gottesman2001]_] as an example of\nnon-Gaussian states which can be simulated using the bosonic\nbackend. Here, we use the bosonic backend to take a deep dive into\nthe advantages of using GKP states as a means for encoding a qubit in a\nphotonic mode. GKP qubits have a key property that we will\nexplore in this tutorial: a universal set of qubit gates and measurements can be\napplied using the already-familiar Gaussian gates and measurements from\nprevious :doc:tutorials .\n\nAfter providing more intuition for what ideal and practical GKP states\nlook like in phase space, we will show how to apply qubit gates and\nmeasurements, and how such operations perform under realistic, noisy\nconditions. While some of these simulations have been performed before,\nmany of these, especially the two-qubit Clifford gates and the T-gate\nteleportation circuits have eluded simulation because of their computational\ncost. The bosonic backend brings these simulations to within our reach.\n\nWe assume that readers of this tutorial have some familiarity with phase\nspace methods like the :doc:Wigner function  and\nbasic CV gates _,\nas well as basic qubit gates _.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "GKP states as qubits\n--------------------\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In an ideal world\n~~~~~~~~~~~~~~~~~\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the GKP encoding, the Wigner function of the ideal qubit\n$|0\\rangle_{\\rm gkp}$ state consists of a linear combination of\nDirac delta functions centred at half-integer multiples of\n$\\sqrt{\\pi\\hbar}$ in phase space [[#gottesman2001]_]:\n\n\\begin{align}W^0_{\\rm gkp}(q,p) = \\sum_{s,t = -\\infty}^{\\infty} (-1)^{st}\\delta\\left(p-\\frac{s\\sqrt{\\pi\\hbar}}{2}\\right)\\delta\\left(q-t\\sqrt{\\pi\\hbar}\\right).\\end{align}\n\nThe GKP qubit $|1\\rangle_{\\rm gkp}$ state can be obtained by\nshifting the $|0\\rangle_{\\rm gkp}$ state by\n$\\sqrt{\\pi\\hbar}$ in the position quadrature [[#gottesman2001]_].\n\nBelow is a visualization of the ideal $|0\\rangle_{\\rm gkp}$ and $|1\\rangle_{\\rm gkp}$\nstates in phase space using the bosonic backend [[#footnote1]_]. Each blue (red) dot\nrepresents a positive (negative) delta function.\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Import relevant libraries\nimport strawberryfields as sf\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib import colors, colorbar\n\n# Set the scale for phase space\nsf.hbar = 1\nscale = np.sqrt(sf.hbar * np.pi)\n\n# Create a GKP |0> state\nprog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP() | q\n\neng = sf.Engine(\"bosonic\")\ngkp_0 = eng.run(prog).state\n\n# Create a GKP |1> state\nprog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP() | q\n sf.ops.Xgate(np.sqrt(np.pi * sf.hbar)) | q\n\neng = sf.Engine(\"bosonic\")\ngkp_1 = eng.run(prog).state\n\n# Get the phase space coordinates of the delta functions for the two states\nq_coords_0 = gkp_0.means().real[:, 0]\np_coords_0 = gkp_0.means().real[:, 1]\nq_coords_1 = gkp_1.means().real[:, 0]\np_coords_1 = gkp_1.means().real[:, 1]\n\n# Determine whether the delta functions are positively or negatively weighted\ndelta_sign_0 = np.sign(gkp_0.weights().real)\ndelta_sign_1 = np.sign(gkp_1.weights().real)\n\n# Plot the locations and signs of the deltas\nfig,ax = plt.subplots(1, 2)\nax.scatter(q_coords_0 / scale,\n p_coords_0 / scale,\n c=delta_sign_0,\n cmap=plt.cm.RdBu, vmin=-1.5, vmax=1.5)\nax.scatter(q_coords_1 / scale,\n p_coords_1 / scale,\n c=delta_sign_0,\n cmap=plt.cm.RdBu, vmin=-1.5, vmax=1.5)\nfor i in range(2):\n ax[i].set_xlim(-3.5, 3.5)\n ax[i].set_ylim(-3.5, 3.5)\n ax[i].set_xlabel(r'$q$ (units of $\\sqrt{\\pi\\hbar}$ )', fontsize=15)\n ax[i].set_aspect(\"equal\")\nax.set_title(r'$|0\\rangle_{\\rm gkp}$ Wigner function', fontsize=15)\nax.set_title(r'$|1\\rangle_{\\rm gkp}$ Wigner function', fontsize=15)\nax.set_ylabel(r'$p$ (units of $\\sqrt{\\pi\\hbar}$ )', fontsize=15)\nfig.tight_layout()\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Like standard qubit\n$|0\\rangle$ and $|1\\rangle$ states, these two states are orthogonal:\nwherever the $|0\\rangle_{\\rm gkp}$ state has a column of\ndelta functions with alternating signs, the $|1\\rangle_{\\rm gkp}$ state\nhas a column of all positive delta functions, and vice versa. This\nresults in a zero overlap between the $|0\\rangle_{\\rm gkp}$ and\n$|1\\rangle_{\\rm gkp}$ Wigner functions, meaning they are orthogonal.\n\nThe Wigner function for a general qubit state\n\n\\begin{align}|\\psi\\rangle = \\cos{\\tfrac{\\theta}{2}} \\vert 0 \\rangle_{\\rm gkp} + e^{-i \\phi} \\sin{\\tfrac{\\theta}{2}} \\vert 1 \\rangle_{\\rm gkp}\\end{align}\n\nalso consists of a weighted linear combination of delta functions\nat half-integer multiples of $\\sqrt{\\pi\\hbar}$ in phase space,\nwith the weights depending $\\theta$ and $\\phi$ [[#alvarez2020]_, [#bourassa2021]_].\n\nOne might have already noticed a problem:\nsince their Wigner functions consist of a linear combination of\ndeltas, ideal GKP states have infinite energy, cannot be normalized, and are\ntherefore unphysical!\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's get physical\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Phyiscal, normalizable and finite-energy GKP states can be constructed\nby applying a Fock damping operator to the ideal states:\n\n\\begin{align}|k^\\epsilon\\rangle_{\\rm gkp} = e^{-\\epsilon \\hat{n}} |k\\rangle_{\\rm gkp}, k=0,1,\\end{align}\n\nwhere $\\hat{n}$ is the photon number operator. For\nany realistic application calling for GKP states, finite-energy GKP\nstates will need to be employed.\n\nThrough a mathematical derivation best enjoyed with coffee \u2615 and a snack \ud83c\udf6a\n[[#bourassa2021]_], we find the effect of the finite-energy operator on\nthe GKP Wigner functions to be as follows:\n\n1. Each delta function is replaced with a Gaussian function of non-zero variance.\n\n2. The location of each of these new Gaussian peaks is contracted towards the origin.\n\n3. The weights of the Gaussians are damped the further the peak is located from the origin.\n\nAs we saw in the :doc:introductory tutorial , Wigner functions that can be\nexpressed as a linear combination of Gaussian functions in phase space\nare the bread and butter of the bosonic backend. Now we have shown\nexactly how GKP states fall neatly into that form.\n\nFor these new sets of Gaussian peaks, the variance, the location shift,\nand the damping of the weights all depend on the finite-energy parameter $\\epsilon$. Let's\nvisualize how the Wigner function changes as we vary $\\epsilon$:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Choose some values of epsilon\nepsilons = [0.05, 0.1, 0.2, 5]\n\n# Pick a region of phase space to plot\nquad = np.linspace(-3.5, 3.5, 200) * scale\n\nwigners = []\n\nfor epsilon in epsilons:\n\n # Create a GKP |0> state\n prog = sf.Program(1)\n\n with prog.context as q:\n sf.ops.GKP(epsilon=epsilon) | q\n\n eng = sf.Engine(\"bosonic\")\n gkp = eng.run(prog).state\n\n # Calculate the Wigner function\n wigner = gkp.wigner(mode=0, xvec=quad, pvec=quad)\n wigners.append(wigner)\n\n# Plot the results\nfig, axs = plt.subplots(1, 5, figsize=(16, 4), gridspec_kw={\"width_ratios\": [1, 1, 1, 1, 0.05]})\ncmap = plt.cm.RdBu\ncmax = np.real_if_close(np.amax(np.array(wigners)))\nnorm = colors.Normalize(vmin=-cmax, vmax=cmax)\ncb1 = colorbar.ColorbarBase(axs, cmap=cmap, norm=norm, orientation=\"vertical\")\nfor i in range(4):\n axs[i].contourf(\n quad / scale,\n quad / scale,\n wigners[i],\n levels=60,\n cmap=plt.cm.RdBu,\n vmin=-cmax,\n vmax=cmax,\n )\n axs[i].set_title(r'$\\epsilon =$'+str(epsilons[i]), fontsize=15)\n axs[i].set_xlabel(r'$q$ (units of $\\sqrt{\\pi\\hbar}$ )', fontsize=15)\naxs.set_ylabel(r'$p$ (units of $\\sqrt{\\pi\\hbar}$ )', fontsize=15)\ncb1.set_label(\"Wigner function\", fontsize=15)\nfig.tight_layout()\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that as $\\epsilon$ increases, the variance of each peak\ngrows, the peaks get closer and closer to the origin, and the weights\n(which govern how large the Wigner function gets) drop exponentially\naway from the origin. In the limit as $\\epsilon\\rightarrow\\infty$,\nthe Fock damping gets so strong that all we are left with is the vacuum\nstate, as can be seen in the last plot.\n\nSince the delta spikes are now Gaussians, the\n$|0^\\epsilon\\rangle_{\\rm gkp}$ and\n$|1^\\epsilon\\rangle_{\\rm gkp}$ states are no longer exactly\northogonal, which will lead to a small but inherent readout error\nwhen distinguishing the computational-basis states.\nAs compared to ideal GKP qubits, finite-energy qubits are noisier;\nconsequently, we can refer to this type of imperfection as \"finite-energy noise\".\n\nNow that we've established that realistic GKP states fall under the\nsimulation capabilities of the bosonic backend, let's go through the\nset of qubit measurements and gates for a GKP state. We'll see that the\nonly resources one needs in addition to GKP states are Gaussian\ntransformations and measurements [[#baragiola2019]_], which we also know to be easily\nsimulated using the bosonic backend.\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Qubit Pauli measurements: CV homodyne measurements\n--------------------------------------------------\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first example of correspondence between GKP qubit operations and\nGaussian resources are Pauli measurements. For GKP qubits, they can be\nimplemented by performing homodyne measurements and processing the\noutcomes. The association is:\n\n===== == ============\nPauli Homodyne\n===== == ============\nZ $q$\nX $p$\nY $q-p$\n===== == ============\n\nThe outcomes of these homodyne measurements are rounded or \"binned\" to\nthe nearest $n\\sqrt{\\pi\\hbar}$, $n\\in\\mathbb{Z}$. Then, the\nparity of $n$ is taken to determine the value of the Pauli\nmeasurement. Note that the Pauli Y measurements can be achieved by\nperforming a homodyne measurement along $(q - p)/\\sqrt{2}$ and rescaling by $\\sqrt{2}$.\n\nAs an example, we look at the marginal distributions of homodyne outcomes\nfor GKP $|+^\\epsilon\\rangle_{\\rm gkp}$ to confirm Pauli measurements can be performed with homodyne.\nAlternatively, we could have directly simulated samples like in the :doc:last tutorial .\n\nThe angles $\\theta,\\phi$ that specify the qubit state can be passed to the\nsf.ops.GKP() operation via the state parameter.\n\nThe homodyne outcomes that fall into blue (red) bins can be interpreted as a Pauli operator\neigenvalue measurement with outcome $+1$ ($-1$).\nThe bins can be used to provide the expectation value of each Pauli operator:\nwe integrate the marginal distributions, each multiplied by an appropriate weighting function\nthat changes the sign of the distribution depending on the Pauli operator eigenvalue associated\nwith the bin.\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create a GKP |+> state\nplus = [np.pi / 2, 0]\nprog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP(state=plus, epsilon=0.08) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\ndef calc_and_plot_marginals(state, mode):\n '''Calculates and plots the p, q-p, and q quadrature marginal\n distributions for a given circuit mode. These can be used to determine\n the Pauli X, Y, and Z outcomes for a GKP qubit.\n\n Args:\n state (object): a strawberryfields BaseBosonicState object\n mode (int): index for the circuit mode\n '''\n # Calculate the marginal distributions.\n # The rotation angle in phase space is specified by phi\n marginals = []\n phis = [np.pi / 2, -np.pi / 4, 0]\n quad = np.linspace(-5, 5, 400) * scale\n for phi in phis:\n marginals.append(state.marginal(mode, quad, phi=phi))\n\n # Plot the results\n paulis = [\"X\", \"Y\", \"Z\"]\n homodynes = [\"p\", \"q-p\", \"q\"]\n expectations = np.zeros(3)\n\n fig, axs = plt.subplots(1, 3, figsize=(12, 4))\n for i in range(3):\n if i == 1:\n # Rescale the outcomes for Pauli Y\n y_scale = np.sqrt(2 * sf.hbar) / scale\n axs[i].plot(quad * y_scale, marginals[i] / y_scale, 'k-')\n axs[i].set_xlim(quad * y_scale, quad[-1] * y_scale)\n\n # Calculate Pauli expectation value\n # Blue bins are weighted +1, red bins are weighted -1\n bin_weights = 2 * (((quad * y_scale - 0.5) // 1) % 2) - 1\n integrand = (marginals[i] / y_scale) * bin_weights\n expectations[i] = np.trapz(integrand, quad * y_scale)\n else:\n axs[i].plot(quad / scale, marginals[i] * scale, 'k-')\n axs[i].set_xlim(quad / scale, quad[-1] / scale)\n\n # Calculate Pauli expectation value\n # Blue bins are weighted +1, red bins are weighted -1\n bin_weights = 2 * (((quad / scale - 0.5) // 1) % 2) - 1\n integrand = (marginals[i] * scale) * bin_weights\n expectations[i] = np.trapz(integrand, quad / scale)\n\n # Color the qubit bins blue and red\n for j in range(-10, 10):\n axs[i].axvspan((2 * j - 0.5), (2 * j + 0.5), alpha=0.2, facecolor='b')\n axs[i].axvspan((2 * j + 0.5), (2 * j + 1.5), alpha=0.2, facecolor='r')\n\n axs[i].set_title(\"Homodyne data for Pauli \" + paulis[i] +\n \"\\n\" + r'$\\langle$'+paulis[i]+r'$\\rangle$='+\n str(np.around(expectations[i],2)))\n axs[i].set_xlabel(homodynes[i] + r' (units of $\\sqrt{\\pi\\hbar}$ )', fontsize=15)\n axs.set_ylabel(\"Marginal distribution\", fontsize=15)\n fig.tight_layout()\n plt.show()\n\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see from the $p$-homodyne measurement outcomes that the\nresults are mostly within the blue bins, so there will be a high chance\nof reading out a $+1$ eigenvalue from a Pauli X measurement, as would be\nexpected for a standard qubit $|+\\rangle$ state.\n\nFor the other two homodyne measurements, the peaks of the marginal\ndistributions are effectively evenly distributed between $+1$ and $-1$ bins,\njust like Pauli Y and Z measurements on a standard qubit\n$|+\\rangle$ state.\n\nOne of the strengths of the bosonic backend is the ease of applying\nGaussian transformations to states. For qubits encoded in CV systems, a\nmajor source of noise is loss or dissipation. Let's see how the\ndistributions of homodyne outcomes change after we apply the\n:class:~strawberryfields.ops.LossChannel operation:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create a GKP |+> state and apply loss\nprog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP(state=plus, epsilon=0.08) | q\n sf.ops.LossChannel(0.85) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\n#Calculate and plot marginals\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that the peaks in the homodyne distribution get broadened and\nshifted towards the origin, resulting in outcomes falling outside of the\ncorrect bins. This corresponds to qubit readout errors.\n\nFrom this, we see the bosonic backend can be used to study noise and develop\nstrategies to deal with realistic effects on bosonic qubits.\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Single-qubit Clifford gates\n---------------------------\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With Pauli measurements under our belt, we can move on to the\nsingle-qubit Clifford gates. As a summary, the association between\nGKP single-qubit Clifford gates and qubits is [[#footnote2]_]:\n\n============ == ==================================================\nQubit gates CV gates\n============ == ==================================================\nPauli X $q$-displacement by $\\sqrt{\\pi\\hbar}$\nPauli Z $p$-displacement by $\\sqrt{\\pi\\hbar}$\nHadamard Rotation by $\\pi/2$\nPhase Quadratic phase gate of strength 1\n============ == ==================================================\n\nWe'll demonstrate this mapping in the next few subsections.\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Qubit Pauli X and Z gates: CV $q$ and $p$ displacements\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe simplest examples are the qubit Pauli X _\nand Z _ gates,\nwhich for the GKP encoding correspond to displacements _\nin phase space by $\\sqrt{\\pi\\hbar}$ along $q$ and $p$, respectively.\nThese can be implemented using the :class:~strawberryfields.ops.Xgate and :class:~strawberryfields.ops.Zgate operations.\n\nBelow, we take a GKP qubit with $\\theta=\\phi=np.pi/4$ and plot its marginal distributions along\n$p$, $q-p$, and $q$ before and after applying a\nbit-phase flip. Recall that, once binned, this data provides the outcome\nof the Pauli measurements: blue (red) bins correspond to Pauli $+1$ ($-1$) eigenvalues.\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create a GKP state\nprog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP(state=[np.pi / 4, np.pi / 4], epsilon=0.08) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\n#Calculate and plot marginals\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Run it again but apply a bit-phase flip this time\nprog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP(state=[np.pi / 4, np.pi / 4], epsilon=0.08) | q\n sf.ops.Xgate(np.sqrt(np.pi * sf.hbar)) | q\n sf.ops.Zgate(np.sqrt(np.pi * sf.hbar)) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\n#Calculate and plot marginals\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For $p$ and $q$ (associated with Pauli X and\nZ), we see that the regions of the distributions originally in\nthe $+1$ (blue) bins get shifted to $-1$ (red) bins and vice versa. The\ndistribution of $q-p$ remains unchanged. This confirms the gates\nact as expected, since the probabilities of $+1$ and $-1$ outcomes from Pauli\nX and Z measurements get swapped by the bit-phase flip, while the Pauli\nY is invariant!\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Qubit Hadamard gate: CV rotation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe GKP Hadamard _\ngate can be implemented by a $\\pi/2$\nrotation _\nin phase space. We've already indirectly seen this gate in action when\ntoggling between different homodyne measurements. In the previous\nexample, if a $\\pi/2$ rotation were applied to the state, the\ndistribution of outcomes in $q$ and $p$ are interchanged,\nwhich means the the statistics of the Pauli X and Z\noutcomes are swapped as well, just as expected from a qubit Hadamard\ngate!\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Qubit phase gate: CV quadratic phase gate\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe GKP qubit phase _\ngate maps to the CV quadratic phase _\ngate, which can be implemented in Strawberry Fields with the existing\n:class:~strawberryfields.ops.Pgate. This is a Gaussian operation, but unlike\nrotations, the CV phase gate requires a squeezing operation _.\n\nLet's see the GKP phase gate in action on a qubit\n$|+^\\epsilon\\rangle_{\\rm gkp}$ state:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create a GKP |+> state and apply a phase gate\nprog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP(state=plus, epsilon=0.08) | q\n sf.ops.Pgate(1) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As expected, the homodyne outcomes associated with the Pauli Y\nmeasurement now fall entirely in the $+1$ (blue) bins, reflecting that a\nstandard qubit phase gate converts\n$\\hat{S}|+\\rangle = |+i\\rangle$.\n\nAs mentioned, the CV phase gate requires inline squeezing. In photonics,\na feasible technique to do this is to apply measurement-based squeezing\n[[#filip2005]_], which is essentially a method to teleport a squeezing\noperation onto a state. Briefly, the steps to do this are:\n\n1. Interfere an ancillary squeezed vacuum state at a beamsplitter with the target state.\n\n2. Perform a homodyne measurement on the ancillary mode.\n\n3. Apply a feedforward displacement to the target mode based on the homodyne outcome.\n\nThe quality of measurement-based squeezing depends on the level of\nsqueezing in the ancillary mode, and the efficiency of the homodyne\ndetection.\n\nWhile all backends of Strawberry Fields have implemented the ideal\ninline squeezing operation through :class:~strawberryfields.ops.Sgate, to be able to study the\neffects of realistic noise on bosonic qubits, for the bosonic\nbackend, we have also implemented measurement-based squeezing as\n:class:~strawberryfields.ops.MSgate.\n\n:class:~strawberryfields.ops.MSgate performs the teleportation circuit described above. The user\ncan specify whether they would like the map corresponding to the average\napplication of the teleportation circuit, or whether they want to\nsimulate a single-shot run and keep the homodyne sample on the ancillary\nmode.\n\nBelow we examine how the quality of the GKP qubit phase gate changes\nonce we replace the inline squeezing in the CV phase gate with\nmeasurement-based squeezing. We look first at the average map of the\ngate.\n\nFirst, we define a phase gate that uses measurement-based squeezing.\nr_anc and eta_anc are the level of squeezing and detection efficiency for\nthe ancilla. avg specifies whether to apply the average or single-shot map.\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def MPgate(q, s, r_anc, eta_anc, avg):\n '''Applies a CV phase gate to a given mode using measurement-based squeezing.\n\n Args:\n q (object): label for the mode in the strawberryfields program\n s (float): the ideal phase gate parameter\n r_anc (float): level of squeezing in the ancillary mode\n eta_anc (0 <= float <= 1): detector efficiency for the ancillary mode\n avg (bool): True (False) applies the average (single-shot) mapping\n '''\n r = np.arccosh(np.sqrt(1 + s ** 2 / 4))\n theta = np.arctan(s / 2)\n phi = - np.pi / 2 * np.sign(s) - theta\n sf.ops.MSgate(r, phi, r_anc, eta_anc, avg) | q\n sf.ops.Rgate(theta) | q" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we create a GKP $|+^\\epsilon\\rangle_{\\rm gkp}$ state and\napply a realistic phase gate (average map):\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "prog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP(state=plus, epsilon=0.08) | q\n MPgate(q, 1, r_anc=1.2, eta_anc=0.95, avg=True)\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What happened? The peaks stayed essentially in the same places as in the\nprevious simulation, but now they've become broader! Since more of each\npeak falls outside the correct bins, this will have an impact on\nlikelihood of obtaining the correct Pauli measurement outcomes.\n\nLet's take a look at the single-shot implementation of the\nmeasurement-based squeezing operation in the phase gate by setting avg to False:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "prog = sf.Program(1)\n\nwith prog.context as q:\n sf.ops.GKP(state=plus, epsilon=0.08) | q\n MPgate(q, 1, r_anc=1.2, eta_anc=0.95, avg=False)\n\neng = sf.Engine(\"bosonic\")\nresults = eng.run(prog)\ngkp = results.state\n\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On the face of it, these marginal distributions look very different!\nLuckily, the Pauli outcomes only depend on how much of the marginal\ndistributions fall inside the correct bins, and less on the shapes and\nheights of the peaks within those bins. We notice that the homodyne\ndistribution used for the Pauli Y measurement still falls primarily in\nthe $+1$ (blue) bins, although the peaks have broadened as compared to the\nideal CV phase gate.\n\nAs promised, the homodyne outcome used for the feedforward operation can\nbe retrieved from the Results object output by the circuit:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(results.ancillae_samples)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dictionary key 0 denotes the mode where measurement-based\nsqueezing was applied, and the list entry contains the result of the\nhomodyne measurement on the ancillary state used in the feedforward\ndisplacement of the measurement-based squeezing gate.\n\nWe've again seen through this example that the bosonic backend is\nuseful for studying realistic noise effects present in photonic circuits\napplied to bosonic qubits. Next we move to two-qubit Clifford gates.\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Two-qubit Clifford gates\n------------------------\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we understand single-qubit Clifford gates, we can move to\ntwo-qubit entangling gates. Another boon to the GKP encoding is the fact\nthat certain two-qubit entangling gates such as the qubit\nCNOT _\nand CZ _ gates\ncorrespond to deterministic Gaussian operations, specifically the CV\nCX _\nand CZ _ gates:\n\n============ == =======================\nQubit gates CV gates\n============ == =======================\nCNOT CX gate of strength 1\nCZ CZ gate of strength 1\n============ == =======================\n\nAs a simple demonstration of a two-qubit entangling gate, we apply a GKP\nCNOT gate on $|+^\\epsilon\\rangle_{\\rm gkp}$ and $|0^\\epsilon\\rangle_{\\rm gkp}$.\nFor standard qubits, this yields the entangled state\n$\\frac{1}{\\sqrt{2}}(|0,0\\rangle+|1,1\\rangle)$.\n\nAs a sanity check that this is indeed the state produced from the CNOT\ngate, we project the first qubit into the $|0\\rangle$ state by\nmeasuring $q$-homodyne (corresponding to a qubit Pauli Z\nmeasurment) and post-select on $q_1=0$, which forces the outcome\nof the first mode into a $+1$ (blue) bin. When we plot the marginal\ndistribution of the second qubit after the measurement on the first qubit, we see that the\ndistribution of $q_2$ outcomes fall mainly into the $+1$ (blue) bins,\nmeaning the Pauli Z outcome on the second qubit will also be $+1$,\nas expected.\n\nBelow, we create $|+^\\epsilon\\rangle_{\\rm gkp}\\otimes|0^\\epsilon\\rangle_{\\rm gkp}$ and apply a CNOT gate.\nThen, we measure $q$-homodyne on the first mode, post-selecting on 0\nvia the select parameter:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "prog = sf.Program(2)\n\nwith prog.context as q:\n sf.ops.GKP(state=plus, epsilon=0.1) | q\n sf.ops.GKP(epsilon=0.1) | q\n sf.ops.CXgate(1) | (q,q)\n sf.ops.MeasureHomodyne(0, select=0) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\ncalc_and_plot_marginals(gkp, 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can do the same simulation, but this time postselect\n$q_1=\\sqrt{\\pi\\hbar}$, forcing the\nPauli Z outcome of the first qubit to be $-1$, since that value falls in a red bin.\nWhen we plot the marginal\ndistributions of the second qubit, we see that the distribution of\noutcomes in $q_2$ now fall in the $-1$ (red) bins, indicating the\ncorrect qubit state was created by the two-qubit gate.\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "prog = sf.Program(2)\n\nwith prog.context as q:\n sf.ops.GKP(state=plus, epsilon=0.1) | q\n sf.ops.GKP(epsilon=0.1) | q\n sf.ops.CXgate(1) | (q,q)\n sf.ops.MeasureHomodyne(0, select=np.sqrt(np.pi * sf.hbar)) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\ncalc_and_plot_marginals(gkp, 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The CV CX _\ngate used to implement the GKP qubit CNOT gate requires\nbeamsplitters, rotations and inline squeezing.\nWhile we did not examine it here, the same realistic\nnoise effects we examined for the other operations -- be it loss or\nmeasurement-based squeezing -- could be simulated for two qubit gates\nusing the bosonic backend.\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Non-Clifford T gate\n-------------------\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To complete the set of universal gates for GKP qubits, we need one\nnon-Clifford gate. As it turns out, the GKP\nqubit T _\ngate can be applied through gate teleportation by using an ancillary GKP qubit magic state\n(a non-Pauli eigenstate) and Gaussian operations [[#gottesman2001]_].\n\nThe steps to apply the T gate build on all the previous examples we've\nseen:\n\n1. Entangle the ancillary magic state and the data state with a qubit CNOT gate, i.e. a CV CX gate.\n\n2. Perform a Pauli Z measurement on the ancillary mode via a binned CV $q$-homodyne measurement.\n\n3. Based on the outcome of the Pauli measurement, apply a feedforward qubit phase gate (a CV phase gate) to the data mode.\n\nIdeally, the GKP qubit magic state one would use is given by\n$|M\\rangle_{\\rm gkp}=\\frac{1}{\\sqrt{2}}(e^{-i\\pi/8}|0\\rangle_{\\rm gkp} + e^{i\\pi/8}|1\\rangle_{\\rm gkp})$.\nNext, we will see the effect of using a finite-energy GKP\n$|M^{\\epsilon}\\rangle_{\\rm gkp}$ for gate teleportation.\n\nWe apply the qubit T gate to the state\n$|M^{\\epsilon}\\rangle_{\\rm gkp}$ itself, knowing that for\nstandard qubits $\\hat{T}|M\\rangle=|+i\\rangle$. For this\nsimulation, we will postselect on a homodyne outcome for the ancillary\nmode that will cause the feedforward phase gate to be applied.\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Define a binning function to get the Pauli Z outcome for the feedforward\ndef gkp_binning(x):\n '''Bins a homodyne outcome to a binary 0 or 1 according to the GKP bin structure.\n Pauli +1 (-1) corresponds to 0 (1).\n\n Args:\n x (float): homodyne outcome value\n '''\n term_1 = (x + np.sqrt(np.pi) / 2) / np.sqrt(4 * np.pi)\n term_2 = (x + np.sqrt(np.pi) / 2) // np.sqrt(4 * np.pi)\n return (term_1 - term_2) // 0.5\n\n# Create GKP magic states and perform the T gate teleportation circuit\nmagic = [np.pi / 2, - np.pi / 4]\nprog = sf.Program(2)\n\nwith prog.context as q:\n sf.ops.GKP(state=magic, epsilon = 0.1) | q\n sf.ops.GKP(state=magic, epsilon = 0.1) | q\n sf.ops.CXgate(1) | (q, q)\n sf.ops.MeasureHomodyne(0, select=np.sqrt(np.pi * sf.hbar)) | q\n sf.ops.Pgate(gkp_binning(q.par)) | q\n\neng = sf.Engine(\"bosonic\")\ngkp = eng.run(prog).state\n\ncalc_and_plot_marginals(gkp, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We find that the output homodyne distributions match what we expect\nfor the $|+i\\rangle$ qubit state: the\ndistribution along $q-p$ falls mainly within $+1$ (blue) bins, while\nthe other quadratures fall evenly between red and blue bins.\n\nAn interesting effect to note is that the peaks are broader in some\nquadratures than others. This demonstrates that through the gate\nteleportation process, the data qubit picked up some of the\nfinite-energy noise from the ancillary magic state. These types of realistic\neffects are easy to study using the bosonic backend!\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Conclusion\n----------\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this tutorial, we showed the translation between the universal set of\nGKP qubit gates and their CV Gaussian phase space implementations. We\nperformed simulations using the bosonic backend, a tool specialized to\nstudy how bosonic qubits transform under the types of Gaussian\ntransformations and measurements necessary for the GKP encoding.\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "References and Footnotes\n------------------------\n\n.. [#bourassa2021]\n\n J. Eli Bourassa, Nicol\u00e1s Quesada, Ilan Tzitrin, Antal Sz\u00e1va, Theodor Isacsson,\n Josh Izaac, Krishna Kumar Sabapathy, Guillaume Dauphinais, and Ish Dhand.\n Fast simulation of bosonic qubits via Gaussian functions in phase space.\n arXiv:2103.05530 _, 2021.\n\n.. [#gottesman2001]\n\n Daniel Gottesman, Alexei Kitaev, and John Preskill. Encoding a qubit in an oscillator.\n doi:10.1103/PhysRevA.64.012310.\n\n.. [#footnote1]\n\n For the advanced reader: when we call sf.ops.GKP(), we are still only creating a\n finite-energy GKP state. However, the locations of peaks in that state are close enough\n to the ideal state, and the signs of the weights match the ideal state, so we can use it\n for a pedagogical illustration of the ideal GKP states.\n\n.. [#alvarez2020]\n\n L. Garc\u00eda-\u00c1lvarez, A. Ferraro, and G. Ferrini. From the Bloch Sphere to Phase-Space\n Representations with the Gottesman-Kitaev-Preskill Encoding. doi:10.1007/978-981-15-5191-8_9.\n\n.. [#baragiola2019]\n\n Ben Q. Baragiola, Giacomo Pantaleoni, Rafael N. Alexander, Angela Karanjai, and Nicolas C. Menicucci.\n All-Gaussian Universality and Fault Tolerance with the Gottesman-Kitaev-Preskill Code.\n doi:10.1103/PhysRevLett.123.200502.\n\n.. [#footnote2]\n\n A note for the keen reader: An extra advantage of the GKP encoding is that there is in\n fact a one-to-many mapping of qubit to CV gates. Taking Pauli gates as an exmaple, a\n displacement by any odd multiple of $\\sqrt{\\pi\\hbar}$ along $q$ ($p$) will effect\n a Pauli X (Z) gate. The direction of displacements can be varied in the circuit compilation to\n avoid too much energy being injected into the state, which would happen if the displacements\n were always in the same direction.\n\n.. [#filip2005]\n\n Radim Filip, Petr Marek, and Ulrik L. Andersen. Measurement-induced continuous-variable\n quantum interactions. doi:10.1103/PhysRevA.71.042308.\n\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.17" } }, "nbformat": 4, "nbformat_minor": 0 }