{ "cells": [ { "cell_type": "markdown", "id": "7f6effb781342585", "metadata": {}, "source": [ "# Quantum States" ] }, { "cell_type": "markdown", "id": "b0357168bef55424", "metadata": {}, "source": [ "This tutorial shows how to handle quantum states in Perceval.\n", "\n", "First, the necessary import:" ] }, { "cell_type": "code", "execution_count": 1, "id": "1846601e344992c4", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "# or you can import each symbol, depending on your prefered coding style\n", "from perceval import BasicState, StateVector, SVDistribution, BSDistribution, BSCount, BSSamples" ] }, { "cell_type": "markdown", "id": "2fae428413e5493c", "metadata": {}, "source": [ "## I. BasicState\n", "\n", "In linear optics (LO) 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", "

\n", "Modes are using [0-based numbering](https://en.wikipedia.org/wiki/Zero-based_numbering) - so mode 0 is\n", "corresponding to the first one, and mode $(m-1)$ is corresponding to the $m$-th.

\n", "\n", "In Perceval, we will use the class `pcvl.BasicState`." ] }, { "cell_type": "code", "execution_count": 2, "id": "initial_id", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|0,2,0,1>\n", "Number of modes: 4\n", "Number of photons: 3\n", "bs1 and bs2 are the same states\n", "There is 0 photon in mode 0 (or equivalently bs1[0]=0).\n", "There is 2 photon in mode 1 (or equivalently bs1[1]=2).\n", "There is 0 photon in mode 2 (or equivalently bs1[2]=0).\n", "There is 1 photon in mode 3 (or equivalently bs1[3]=1).\n" ] } ], "source": [ "## Syntax of different BasicState (list, string, etc)\n", "bs1 = BasicState([0, 2, 0, 1])\n", "bs2 = BasicState('|0,2,0,1>') # Must start with | and end with >\n", "\n", "print(bs1)\n", "print(f\"Number of modes: {bs1.m}\")\n", "print(f\"Number of photons: {bs1.n}\")\n", "\n", "if bs1 == bs2:\n", " print(\"bs1 and bs2 are the same states\")\n", "\n", "## You can iterate on modes\n", "for i, photon_count in enumerate(bs1):\n", " print(f\"There is {photon_count} photon in mode {i} (or equivalently bs1[{i}]={bs1[i]}).\")" ] }, { "cell_type": "markdown", "id": "af7322010b167d02", "metadata": {}, "source": [ "A `BasicState` is actually more than just a Fock state representation." ] }, { "cell_type": "code", "execution_count": 3, "id": "9b324ca9d34528a4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " True\n", " True\n", " True\n" ] } ], "source": [ "# There are three kind of BasicStates\n", "\n", "photon_position = [0, 2, 0, 1]\n", "\n", "# FockState, all photons are indistinguishable\n", "bs1 = BasicState(photon_position) # Or equivalently BasicState(\"|0,2,0,1>\")\n", "print(type(bs1), isinstance(bs1, BasicState))\n", "\n", "# NoisyFockState, photons with the same tag are indistinguishable, photons with different tags are distinguishable (they will not interact)\n", "noise_index = [0, 1, 0]\n", "bs2 = BasicState(photon_position, noise_index) # Or equivalently BasicState(\"|0,{0}{1},0,{0}>\")\n", "print(type(bs2), isinstance(bs2, BasicState))\n", "\n", "# AnnotatedFockState, with custom annotations (not simulable in the general case, needs a conversion to something simulable first)\n", "bs3 = BasicState(\"|0,{lambda:925}{lambda:925.01},0,{lambda:925.02}>\")\n", "print(type(bs3), isinstance(bs3, BasicState))\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "cd65ad3ce682196b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reminder, bs1 = |0,2,0,1>\n", "bs1 * |1,2> = |0,2,0,1,1,2>\n", "A slice of bs1 (extract modes #1 & 2) = |2,0>\n", "bs1 with threshold detection applied = |0,1,0,1>\n" ] } ], "source": [ "# Basic methods are common between these types\n", "print(\"Reminder, bs1 =\", bs1)\n", "print(\"bs1 * |1,2> =\", bs1 * BasicState([1, 2])) # Tensor product\n", "print(\"A slice of bs1 (extract modes #1 & 2) =\", bs1[1:3]) # Slice\n", "print(\"bs1 with threshold detection applied =\", bs1.threshold_detection()) # Apply a threshold detection to the state, limiting detected photons to 1 per mode" ] }, { "cell_type": "markdown", "id": "ec59dd21de3157ba", "metadata": {}, "source": [ "## II. StateVector\n", "\n", "A `StateVector` is a complex superposition of `BasicState` with the same number of modes. It can represent any pure state in the Fock space" ] }, { "cell_type": "code", "execution_count": 5, "id": "8575795407f7140a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "State vector is normalized upon use and display: (0.432+0.259I)*|1,0,1,1>+0.864*|0,2,0,1>\n", "|1,0,1,1> has the complex amplitude (0.4319342127906801+0.25916052767440806j)\n", "|0,2,0,1> has the complex amplitude (0.8638684255813602+0j)\n", "(0.8638684255813602+0j)\n" ] } ], "source": [ "# StateVectors can be defined using arithmetic on BasicStates and other StateVectors\n", "sv = (0.5 + 0.3j) * BasicState([1, 0, 1, 1]) + BasicState([0, 2, 0, 1])\n", "\n", "# State vectors normalize themselves upon use\n", "print(\"State vector is normalized upon use and display: \", sv)\n", "\n", "for (basic_state, amplitude) in sv:\n", " print(basic_state, \"has the complex amplitude\", amplitude)\n", "\n", "# We can also access amplitudes as in a dictionary\n", "print(sv[pcvl.BasicState([0, 2, 0, 1])])" ] }, { "cell_type": "markdown", "id": "452ef27cc289356e", "metadata": {}, "source": [ "## III. Distributions\n", "\n", "Perceval contains a state vector distribution class that embodies a mixed state. All states defined in this distribution must have the same number of modes." ] }, { "cell_type": "code", "execution_count": 6, "id": "fa17c00336024203", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Five random samples according to the distribution: [0.707*|3,0>+0.707*|2,1>, 0.707*|3,0>+0.707*|2,1>, 0.707*|3,0>+0.707*|2,1>, 0.707*|3,0>+0.707*|2,1>, |1,2>]\n", "{\n", "\t|0>: 0.3333333333333333\n", "\t0.707*|1>+0.707*|2>: 0.6666666666666666\n", "} sum of probabilities 1.0\n", "Tensor product\n", "{\n", "\t0.707*|1,2,1>+0.707*|1,2,2>: 0.26666666666666666\n", "\t|1,2,0>: 0.13333333333333333\n", "\t0.707*|3,0,0>+0.707*|2,1,0>: 0.19999999999999998\n", "\t0.5*|3,0,1>+0.5*|3,0,2>+0.5*|2,1,1>+0.5*|2,1,2>: 0.39999999999999997\n", "}\n" ] } ], "source": [ "# A SVDistribution is a collection of StateVectors\n", "svd = SVDistribution({StateVector([1, 2]) : 0.4,\n", " BasicState([3, 0]) + BasicState([2, 1]) : 0.6})\n", "\n", "print(\"Five random samples according to the distribution:\", svd.sample(5))\n", "\n", "svd2 = SVDistribution({StateVector([0]) : 0.1,\n", " BasicState([1]) + BasicState([2]) : 0.2})\n", "svd2.normalize() # distributions have to be normalized to make sense\n", "print(svd2, \"sum of probabilities\", sum(svd2.values()))\n", "\n", "print(\"Tensor product\")\n", "print(svd * svd2) # Tensor product between distributions" ] }, { "cell_type": "markdown", "id": "4410cc950d933f0", "metadata": {}, "source": [ "## IV. Results\n", "\n", "When running a Perceval computation, you can expect the result type from the input and the command:\n", "\n", "Performing a state evolution returns\n", "* A `StateVector` from a pure state input\n", "* A `SVDistribution` from a mixed state input\n", "\n", "Most other Perceval computations return *measurements*. Three types exist to match the following commands:\n", "* `probs` (for \"probabilities\") returns a `BSDistribution`\n", "* `sample_count` returns a `BSCount`\n", "* `samples` returns a `BSSamples`\n", "\n", "These data structures only hold simple `FockState` instances without any noise index or annotation, as these data do not survive a measurement." ] }, { "cell_type": "code", "execution_count": 7, "id": "4c09f45da57f6f1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", "\t|1,2>: 0.4\n", "\t|2,1>: 0.2\n", "}\n", "Number of modes: 2\n", "|1,2> has the probability 0.6666666666666666 (or equivalently 0.6666666666666666)\n", "|2,1> has the probability 0.3333333333333333 (or equivalently 0.3333333333333333)\n", "Tensor product\n", "{\n", "\t|1,2,1>: 0.6666666666666666\n", "\t|2,1,1>: 0.3333333333333333\n", "}\n", "{\n", " |1,2>: 20\n", " |2,1>: 118\n", "}\n", "Total number of samples: 138\n" ] } ], "source": [ "# The BSDistribution is a collection of FockStates\n", "bsd = BSDistribution()\n", "bsd[BasicState([1, 2])] = 0.4\n", "bsd[BasicState([2, 1])] = 0.2\n", "\n", "print(bsd)\n", "print(\"Number of modes:\", bsd.m)\n", "\n", "bsd.normalize() # Not automatic on distributions\n", "\n", "# Distributions act much like python dictionaries\n", "for (state, probability) in bsd.items():\n", " print(state, \"has the probability\", probability, f\"(or equivalently {bsd[state]})\")\n", "\n", "print(\"Tensor product\")\n", "print(bsd * BasicState([1])) # Tensor product, also works between distributions\n", "\n", "bsc = BSCount()\n", "bsc[BasicState([1, 2])] = 20\n", "bsc[BasicState([2, 1])] = 118\n", "print(bsc)\n", "print(\"Total number of samples:\", bsc.total())" ] }, { "cell_type": "markdown", "id": "c763b109e9e24543", "metadata": {}, "source": [ "Functions exist to convert these measurement results from one type to another" ] }, { "cell_type": "code", "execution_count": 8, "id": "777dc447adf6bde2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bsd converted to a BSCount = {\n", " |1,2>: 10\n", " |2,1>: 10\n", "}\n", "bsc converted to a BSDistribution = {\n", "\t|1,2>: 0.14492753623188406\n", "\t|2,1>: 0.855072463768116\n", "}\n", "bsc converted to a BSSamples = [ |2,1>, |2,1>, |2,1>, |2,1>, |2,1>, |2,1>, |2,1>, |1,2>, |2,1>, |1,2>, |2,1>, ... (size=138) ]\n" ] } ], "source": [ "from perceval import probs_to_sample_count, sample_count_to_probs, sample_count_to_samples\n", "\n", "print(\"bsd converted to a BSCount =\", probs_to_sample_count(bsd, max_samples=20))\n", "print(\"bsc converted to a BSDistribution =\", sample_count_to_probs(bsc))\n", "print(\"bsc converted to a BSSamples =\", sample_count_to_samples(bsc)) # Here the sample order is generated by a Python random library" ] }, { "cell_type": "markdown", "id": "505a479e680b89b1", "metadata": {}, "source": [ "Now that you know how to define states in perceval, you are ready to go to the next part of the tutorial about LO circuits." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }