The hafnian of a matrix is defined by\n\n .. math:: \\text{Haf}(A) = \\frac{1}{n!2^n}\\sum_{\\sigma \\in S_{2N}}\\prod_{i=1}^N A_{\\sigma(2i-1)\\sigma(2i)}\n\n where $S_{2N}$ is the set of all permutations of $2N$ elements. In graph theory, the\n hafnian calculates the number of perfect `matchings\n

\n\nIn the above, the single mode squeeze states all apply identical squeezing $z=r$, the\nparameters of the beamsplitters and the rotation gates determine the unitary $U$, and finally\nthe detectors perform Fock state measurements on the output modes. As with boson sampling, for\n$N$ input modes, we must have a minimum of $N+1$ columns in the beamsplitter array\n[[4]_].\n\nSimulating this circuit using Strawberry Fields is easy; we can simply read off the gates from left\nto right, and convert it into the Blackbird circuit language.\n\nTo begin, we create the boson sampling quantum program using Strawberry Fields:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n\n# set the random seed\nnp.random.seed(42)\n\n# import Strawberry Fields\nimport strawberryfields as sf\nfrom strawberryfields.ops import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Unlike the :doc:`run_boson_sampling`, we will directly apply a Gaussian unitary\nto the circuit using the :class:`~strawberryfields.ops.Interferometer` operation.\nFirst, we must define the unitary matrix we would like to embed in the circuit:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# define the linear interferometer\nU = np.array([\n [ 0.219546940711-0.256534554457j, 0.611076853957+0.524178937791j,\n -0.102700187435+0.474478834685j,-0.027250232925+0.03729094623j],\n [ 0.451281863394+0.602582912475j, 0.456952590016+0.01230749109j,\n 0.131625867435-0.450417744715j, 0.035283194078-0.053244267184j],\n [ 0.038710094355+0.492715562066j,-0.019212744068-0.321842852355j,\n -0.240776471286+0.524432833034j,-0.458388143039+0.329633367819j],\n [-0.156619083736+0.224568570065j, 0.109992223305-0.163750223027j,\n -0.421179844245+0.183644837982j, 0.818769184612+0.068015658737j]\n])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use this to now construct the circuit:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# create the 4 mode Strawberry Fields program\ngbs = sf.Program(4)\n\nwith gbs.context as q:\n # prepare the input squeezed states\n S = Sgate(1)\n S | q[0]\n S | q[1]\n S | q[2]\n S | q[3]\n\n # linear interferometer\n Interferometer(U) | q" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A couple of things to note in this particular example:\n\n..\n\n1. To prepare the input single mode squeezed vacuum state $\\ket{z}$ where $z = 1$, we\n apply a squeezing gate :class:`~strawberryfields.ops.Sgate` to each of the modes (initially in\n the vacuum state).\n\n..\n\n2. Next we apply the linear interferometer to all four modes, using the decomposition operator\n :class:`~strawberryfields.ops.Interferometer`, and the unitary matrix ``U``. This operator\n decomposes the unitary matrix representing the linear interferometer into single mode\n rotation gates :class:`~strawberryfields.ops.Rgate`, and two-mode beamsplitters\n :class:`~strawberryfields.ops.BSgate`. After applying the interferometer, we will denote the\n output state by $\\ket{\\psi'}$.\n\n .. note::\n\n You can view the decomposed beamsplitters and rotation gates which correspond to the linear\n interferometer ``U`` by calling :meth:`eng.print_applied()\n

Repeat this tutorial with\n\n 1. A Fock backend such as ``'fock'`` instead of the Gaussian backend.\n\n 2. Different beamsplitter and rotation parameters.\n\n 3. Input states with *differing* squeezed values\n $r_i$. You will need to modify the code to take into account the fact that the output\n covariance matrix determinant must now be calculated.