{
"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": [
"\nTime-domain photonic circuits\n=============================\n\n*Authors: Fabian Laudenbach, Josh Izaac, Theodor Isacsson, and Nicolas\nQuesada*\n\nIn this tutorial, we will introduce the basic concepts of time-domain photonic circuits\n--- the same technology underpinning Xanadu's :doc:`quantum computational advantage hardware `, Borealis [[#advantage2022]_] --- \nusing the time-domain module of StrawberryFields, ``strawberryfields.tdm``. \nTime-domain multiplexing allows for the creation of massive quantum systems having \nmillions of entangled modes as shown in a number of recent experimental demonstrations [[#takeda2019]_],\n[[#yoshikawa2016]_], [[#larsen2019]_], [[#asavanant2019]_].\nTo motivate this architecture we follow Takeda et al. [[#takeda2019]_] and\nstudy the one loop setup shown in the picture below:\n\n![](/tutorials/images/oneloop.svg)\n\n :align: center\n :width: 75%\n :target: javascript:void(0);\n\nWe will first write a short standard StrawberryFields program implementing\nthis. Then we will progressively modify the way this circuit is\nimplemented until we are able to write it directly as a :class:`~strawberryfields.TDMProgram`.\n\nHaving understood the basics of time-domain programs we will then construct a generic\nsingle-loop function that can be used to simulate arbitrary long programs using only two\nmodes! We will use this machinery to investigate bipartite EPR states and then\nmultipartite GHZ states. An important take away of this tutorial is that one can sample\nefficiently Gaussian circuits containing millions of modes with the equivalent\ncomputation effort of sampling Gaussian circuits with just a handful of modes.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"An instruction set for the time-domain architecture\n---------------------------------------------------\n\nTo introduce the time domain architecture we will simulate the setup in\nthe figure. We can summarize the setup as the following set of\nintructions:\n\n1. The ``i+1`` mode is squeezed by a certain amount ``r``.\n2. The ``i+1`` mode undergoes a beamsplitter transformation with its\n preceding mode, labeled by ``i``, by a certain angle ``alpha[i]``.\n This beamsplitter has the effect of switching in and out of the loop\n the modes ``i+1`` and ``i`` respectively.\n3. The ``i+1`` mode has entered the loop after the beamsplitter\n transformation and is rotated by angle ``phi[i]``.\n4. After interacting with the ``i+1`` mode, the mode in the previous\n time bin ``i`` leaves the loop and then its rotated quadrature (by angle\n ``theta[i]``) is measured using a homodyne detector.\n\n\n\nThis circuit falls within the time-domain description because we are\nimplicitly assuming that the modes are flying through the optical elements\n(squeezer, beamsplitter, rotation, measurement) in the figure above.\nNote that the loop in the figure corresponds physically to a delay line,\ntypically implemented using propagation in free space or in an optical fiber.\nThus unlike in many other physical implementations\nour modes are not labeled by a position in space but by a time label that\ntells us when they arrived to a particular optical element.\n\nBefore actually trying to take advantage of the time-domain description\nof the circuit we will write a brute force simulation using the\nstandard :class:`~strawberryfields.Program`.\n\nWe will use ``n`` modes to represent ``n`` timebins and implement the\nsetup described by the figure using a standard StrawberryFields\nprogram.\n\nSince we will be using a Gaussian circuit this implies that we will be using\na covariance matrix of size ``2n`` times ``2n`` to represent our state. Thus,\nif we have for example ``n=1000`` modes we will have on the order of four million\nreal numbers stored in memory. We will however, just stick to ``n=20`` for this\nconcrete example.\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import strawberryfields as sf\nfrom strawberryfields.ops import Sgate, BSgate, Rgate, MeasureHomodyne\nimport numpy as np"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Below we assume that the parameters of the squeezing gate $(S)$\nand the angles of the beamsplitter $(BS)$ and rotation $R$\ndo not change with the timebin.\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# We set the seed to facilitate later comparison\n# since the samples are stochastic.\nnp.random.seed(42)\nn = 20\n\nr = 1.0\nlength = n - 1\nalpha = [np.pi / 4] * length\nphi = [0] * length\ntheta = [0] * length\n\nprog0 = sf.Program(n)\nwith prog0.context as q:\n for i in range(n - 1):\n Sgate(r) | q[i + 1]\n BSgate(alpha[i]) | (q[i], q[i + 1])\n Rgate(phi[i]) | q[i + 1]\n MeasureHomodyne(theta[i]) | q[i]\neng0 = sf.Engine(\"gaussian\")\nresult0 = eng0.run(prog0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can look at an unrolled version of the program above in terms of\na circuit diagram as\n\n|\n\n![](/tutorials/images/unrolled.svg)\n\n :align: center\n :width: 70%\n :target: javascript:void(0);\n\n|\n\nwhere, for clarity, we have reduced the number of displayed modes to ``n=5``.\n\nFinally, we can also look at the samples:\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(result0.samples)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this program we follow each and every time-bin mode (a total of\n``n=20`` modes) as they progress through the different operations.\nHowever, at most only two modes are interacting at the same time. The\nrest of the modes either have not been squeezed (and thus are in the\nvacuum state) or have been measured (and thus reset to vacuum).\n\nReducing simulation resources: shifting modes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nInstead of keeping track of the modes using labels telling us their time\nbin, we can label them in terms of the position that they occupy in the\ncircuit above. A convenient choice of labels is to look at the two modes\nthat enter into the beamsplitter. We can use ``0`` for the mode that\nenters in the top port of the beamsplitter and which has been\ncirculating in the loop. We can use the label ``1`` for the mode that\nhas just been squeezed and is about to enter the beamsplitter in the\nbottom port. These two modes, which have fixed positions in the circuit,\nwe call concurrent modes; they are, at all times, the only two modes\nthat are not in vacuum in the circuit.\n\nTo make a closed loop of modes going in and out of the circuit one can\nrecycle the mode that is measured (mode ``0``) and plug it back in as a\nvacuum mode re-entering the circuit. As it turns out the Gaussian\nbackend of Strawberry Fields automatically resets to vacuum a mode that\nhas been measured thus the resetting is automatically taken care of.\n\nTo implement the recycling we will need a function that takes the mode\nthat is measured (which sits at the beginning of the mode list ``q``)\nand moves it back to the end of the list after it is measured.\nThis functionality is\nprovided by the function ``shift_by`` in the module\n``strawberryfields.tdm`` as illustrated below:\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from strawberryfields.tdm import shift_by\n\nl = [0, 1, 2, 3, 4]\nshift_by(l, 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We are now ready to simulate an ``n=20`` mode circuit using only two\nconcurrent modes:\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"np.random.seed(42) # We set the seed to facilitate comparison\nprog1 = sf.Program(2)\nn = 20\nwith prog1.context as q:\n for i in range(n - 1):\n Sgate(r) | q[1]\n BSgate(alpha[i]) | (q[0], q[1])\n Rgate(phi[i]) | q[1]\n MeasureHomodyne(theta[i]) | q[0]\n # Note that only position 0 of q is measured\n q = shift_by(q, 1)\neng1 = sf.Engine(\"gaussian\")\nresult1 = eng1.run(prog1)\nprint(result1.samples)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Even though we measured ``n-1`` times we only get 2 values. This is\nbecause the ``samples`` attribute only contains the last ever measured\nvalue(s) in our modes; since we only have two concurrent modes we only get\ntwo numbers. To obtain the complete sample record we use the attribute\n``samples_dict``, which contains all measured samples inside of a dict.\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(result1.samples_dict)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we obtain the same samples as in ``result0.samples`` except\nthat they are ordered in a slightly different arrangement. We can put\nthem in the correct order by using ``reshape_samples``\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from strawberryfields.tdm import reshape_samples\n\nreshape_samples(result1.samples_dict, [0], [2], n - 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The second argument ``[0]`` gives the label of the\nconcurrent mode where a measurement happened,\nwhile the third argument ``[2]`` indicates, for this simple one loop program,\nthat there are only two concurrent modes in a single loop.\nFinally, the last argument gives the information on the number of\ntime bins measured. Notice that it is ``n-1`` and not ``n``.\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All the measurement outcomes are now attached to the only mode\nthat we ever measured, namely ``0``. Note that in ``result1.samples_dict``\nthe samples are attached to different modes. This is an artifact of the\nrecycling.\n\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By recycling the modes we are now able to simulate very long circuits\nwith just two modes. Going back to our hypothetical one thousand mode example,\nwe would need to keep in memory millions of real numbers, instead by using\na time-domain strategy where we only worry about concurrent modes we only ever\nneed to consider covariances matrices of size four by four, which results\nin massive speed and memory gains!\n\n\nThe TDMProgram\n~~~~~~~~~~~~~~\n\nRather than performing the mode shifting and sample reshaping manually,\nStrawberry Fields provides the :class:`~strawberryfields.TDMProgram` program class\nfor automating this procedure, making it easy to construct and simulate time-domain\nmultiplexing algorithms.\n\nIn this class one needs to specify the number of\nactive modes (``N=2`` for the example above) and then the class takes\ncare of automatically shifting the modes and reshaping the samples. Thus\nthe program above could have been rewritten as\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"np.random.seed(42) # We set the seed to facilitate comparison\nN = 2 # Number of concurrent modes\nprog2 = sf.TDMProgram(N=N)\n\nwith prog2.context(alpha, phi, theta) as (p, q):\n Sgate(r, 0) | q[1]\n BSgate(p[0]) | (q[0], q[1])\n Rgate(p[1]) | q[1]\n MeasureHomodyne(p[2]) | q[0]\neng2 = sf.Engine(\"gaussian\")\nresult2 = eng2.run(prog2)\nprint(result2.samples)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In a ``TDMProgram`` we need to specify the number of concurrent modes\n``N``. Then we pass to the ``context`` the sequences of\nthe different parameters that will be used in the program, in our case\nthe lists ``alpha``, ``phi`` and ``theta``. The three variables in the\nlist ``p`` will cycle through the corresponding values of these lists.\nFor example, ``p[0]`` will go through the values of ``alpha`` as the\nsimulation progresses.\n\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A single-loop architecture\n--------------------------\n\nWe will now write a simple function that\nencapsulates the single loop architecture using a ``TDMprogram``. Then we\nwill use it to generate samples of interesting quantum states and\ninvestigate their correlations.\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def singleloop(r, alpha, phi, theta, shots):\n \"\"\"Single loop program.\n\n Args:\n r (float): squeezing parameter\n alpha (Sequence[float]): beamsplitter angles\n phi (Sequence[float]): rotation angles\n theta (Sequence[float]): homodyne measurement angles\n shots (int): number of times the circuit is sampled\n Returns:\n list: homodyne samples from the single loop simulation\n \"\"\"\n\n prog = sf.TDMProgram(N=2)\n with prog.context(alpha, phi, theta) as (p, q):\n Sgate(r, 0) | q[1]\n BSgate(p[0]) | (q[0], q[1])\n Rgate(p[1]) | q[1]\n MeasureHomodyne(p[2]) | q[0]\n eng = sf.Engine(\"gaussian\")\n result = eng.run(prog, shots=shots)\n # We only want the samples from concurrent mode 0\n return result.samples_dict[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can use the function we just developed to reproduce for the fourth\ntime the results of our very simple experiment:\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"np.random.seed(42) # We set the seed to facilitate comparison\nsingleloop(r, alpha, phi, theta, 1) # We want 1 shot"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Generation of EPR states\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With the single loop architecture we can easily generate\nEinstein-Podolsky-Rosen (EPR) states [[#einstein1935]_]. In this section we will create\nthis bipartite state and probe it using homodyne measurements.\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"r = 2.0\nalpha = [np.pi / 4, 0]\nphi = [0, np.pi / 2]\ntheta = [0, 0] # We will measure the positions by setting theta = [0,0]\nshots = 2500\n\nsamplesEPRxx = singleloop(r, alpha, phi, theta, shots)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To process the samples we simply transpose the array in which they\nare stored. This gives us easier access to the values corresponding\nto the two halves of an EPR state.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"samplesxx = samplesEPRxx.T\nx0 = samplesxx[0]\nx1 = samplesxx[1]\n\n# We now want to look at the samples\nimport matplotlib.pyplot as plt\n\nplt.figure(figsize=(5, 5))\nplt.plot(x0, x1, \".\")\nplt.xlabel(r\"$x_0$\")\nplt.ylabel(r\"$x_{1}$\")\nplt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can put this observation in more quantitative terms by looking at the\nvariance of $x_1-x_0$:\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"sample_var_xx = np.var(x0 - x1)\nsample_var_xx"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As it turns out the variance of the operator $N_1 = x_1 - x_0$ is\nrelated to the amount of squeezing used to prepare the state. One can\neasily find that $\\text{Var}(N_1) = 2 e^{-2 r}$.\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"expected_var_xx = 2 * np.exp(-2 * r)\nexpected_var_xx"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the literature, the operator $N$ for which one finds a relation\nlike the one above, is called a *Nullifier* of the state. A way to\nunderstand this name is that the variance of this operator approaches\nzero (or *nullifies*) as the squeezing in the state grows [[#takeda2019]_].\n\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The EPR state has a second nullifier, $N_2 = p_1+p_2$. We can\nconfirm this by running our circuit again, but this time measuring\nin the $P$ quadrature\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"theta = [\n np.pi / 2,\n np.pi / 2,\n] # Now we homodyne the p-quadratures by setting thr angle to pi/2\nsamplesEPRpp = singleloop(r, alpha, phi, theta, shots)\n\nsamplespp = samplesEPRpp.T\np0 = samplespp[0]\np1 = samplespp[1]\n\nplt.figure(figsize=(5, 5))\nplt.plot(p0, p1, \".\")\nplt.xlabel(r\"$p_0$\")\nplt.ylabel(r\"$p_{1}$\")\nplt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now calculate the sample variance and the expected variance,\nexcept for the effect of finite size statistics, they are almost identical\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"np.var(p0 + p1), 2 * np.exp(-2 * r)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Generating GHZ states\n~~~~~~~~~~~~~~~~~~~~~\n\nThe GHZ states (named after Greenberger, Horne and Zeilinger) [[#greenberger2007]_] can be\nthought as an $n$-partite generalization of the EPR states [[#vanloock1999]_]. We\nwill prepare these states using the single loop architecture\nencapsulated in the function ``singleloop`` defined before.\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"n = 10\nvac_modes = 1 # This is an ancilla mode that is used at the beginning of the protocol\nshots = 1\nr = 4\nalpha = [\n np.arccos(np.sqrt(1 / (n - i + 1))) if i != n + 1 else 0\n for i in range(n + vac_modes)\n]\nalpha[0] = 0.0\nphi = [0] * (n + vac_modes)\nphi[0] = np.pi / 2\ntheta = [0] * (\n n + vac_modes\n) # We will measure first all the states in the X quadrature\n\nsingleloop(r, alpha, phi, theta, shots)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that all the *sampled* positions are the\nsame. We can also sample our state in the $p$ basis\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"theta = [np.pi / 2] * (n + vac_modes) # Now we measure in p quadrature\n\nsamplep = singleloop(r, alpha, phi, theta, shots)\nsamplep"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There does not seem to be anything interesting going on when we look at\nthe $p$ quadratures, yet if we consider the sum of the sampled\nvalues of the momenta we find that the momenta add up to approximately\nzero:\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"np.sum(samplep[1:]) # Note that we exclude the first element, the \"vacuum mode\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Indeed, like for the EPR state defined before, one can introduce\nnullifiers for the GHZ states such as\n$N_k = x_k - x_n$ for $1 \\leq k < n$,\n\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now generate many samples and statistically verify that the variances\nof the nullifier decay exponentially with the amount of squeezing\n\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Collect x-samples\nshots = 1000\ntheta = [0] * (n + vac_modes)\nsamplesGHZx = singleloop(r, alpha, phi, theta, shots)\n\nnullifier_X = lambda sample: (sample - sample[-1])[vac_modes:-1]\nval_nullifier_X = np.var([nullifier_X(x) for x in samplesGHZx], axis=0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The sample variances of the X nullifiers are\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"val_nullifier_X"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"While their expected value is\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"2 * np.exp(-2 * r)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Conclusion and final remarks\n----------------------------\nWe have introduced the basic ideas of time-domain circuits by gradually\ntransforming a simple one-loop ``n``-mode optical setup from a brute\nforce simulator to a time-domain simulator using the :class:`~strawberryfields.TDMProgram`.\nUsing the later class we achieve significant computational savings: a brute force\nGaussian simulator requires resources scaling quadratically with the number of modes,\na time-domain approach requires only constant (and very modest) resources.\nWe have used this highly efficient implementation to study the correlations\nof EPR and GHZ states, the canonical bipartite and multipartite entangled\nstates in the continuous-variable domain.\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"References\n----------\n\n.. [#advantage2022]\n\n `Quantum computational advantage with a programmable photonic processor `__.\n\n.. [#takeda2019]\n\n S. Takeda, T. Kan and A. Furusawa. On-demand photonic entanglement synthesizer.\n Science Advances, 2019. doi:10.1126/sciadv.aaw4530 .\n\n.. [#yoshikawa2016]\n\n J. Yoshikawa, S. Yokoyama, T. Kaji, C. Sornphiphatphong, Y. Shiozawa, K. Makino and A. Furusawa.\n Generation of one-million-mode continuous-variable cluster state by unlimited time-domain multiplexing.\n APL Photonics, 2016. doi:10.1063/1.4962732 .\n\n.. [#larsen2019]\n\n M. V. Larsen, X. Guo, C. R. Breum, J. S. Neergaard-Nielsen and U. L. Andersen.\n Deterministic generation of a two-dimensional cluster state.\n Science, 2019. doi:10.1126/science.aay4354 .\n\n.. [#asavanant2019]\n\n W.Asavanant, Y. Shiozawa, S. Yokoyama, B. Charoensombutamon, H. Emura, R. N. Alexander, S. Takeda, J. Yoshikawa, N. C. Menicucci, H. Yonezawa and A. Furusawa.\n Generation of time-domain-multiplexed two-dimensional cluster state.\n Science, 2019. doi:10.1126/science.aay2645 .\n\n.. [#einstein1935]\n\n A. Einstein, B. Podolsky and N. Rosen. Can quantum-mechanical description of physical reality be considered complete?\n Physical Review, 1935. doi:10.1103/PhysRev.47.777 .\n\n.. [#greenberger2007]\n\n D. M. Greenberger, M. A. Horne and A. Zeilinger. Going beyond Bell's Theorem\n arXiv:0712.0921, 2007.\n\n.. [#vanloock1999]\n\n P. van Loock, S. L. Braunstein. Multipartite Entanglement for Continuous Variables: A Quantum Teleportation Network\n Physical Review Letters, 2000. doi:10.1103/PhysRevLett.84.3482 .\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.15"
}
},
"nbformat": 4,
"nbformat_minor": 0
}