Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

Update II  Sample code for simulating boson-sampling experiments has been added (as an answer).

This code exploits new Mathematica capabilities relating to both empirical and smooth distributions; in particular KolmogorovSmirnovTest[__] finds use.

Update I  Multiple optimizations that were suggested by members "ssch" and Simon Woods have in aggregate yielded a ~5X code-speedup; and these optimizations now are incorporated in the example code.

Thank you both very much.

Further improvements are welcome, needless to say. In particular, for nxn matrix arguments, a further O(n) speedup can be achieved (in principle) by exploiting the Gray code structure of "[Delta]PermutationList". However, this would come at the cost of substantially increased code complexity and generally larger round-off error.


For research in BosonSampling (for example) it is desirable to compute matrix permanents by the fastest feasible algorithm. The appended Mathematica code uses Glynn's formula to compute the complex-valued matrix permanent. This code computes the permanent of a 20x20 matrix in ~250 ms (on a 2.93 GHz MacBook Pro laptop)

The Question Asked  Can further speed gains be achieved in numerical computation of the (complex-valued) matrix permanent?

The matrices of interest typically have dimension 10x10 to 25x25, and speed-of-execution for repeated permanent evaluations at fixed matrix-dimension is the sole figure-of-merit.

Suggestions for improvements will cheerfully be adopted!

--- code follows ---


BeginPackage["PermanentCode`"];

Permanent::usage = "\<\
Permanent[mArg_List/;MatrixQ[a]] is computed by Glynn's formula.

The algorithm requires O(m^2 2^m) operations, where m is the dimension 
of the matrix arg.

When the argument is numeric, compiled C-code is executed.

When the argument is non-numeric, a \"Permanent::symbolic\" message 
is issued, and the permanent is calculated symbolically.

Implementation Notes: 

(1) Glynn's formula is simplified with a view to speed-by-simplicity 
    (at negligible cost in formal efficiency); in brief the algorithm
    is implemented as a sequence of BLAS-compatible calls to built-in 
    Mathematica (BLAS) functions.

(2) At present the algorithm does not fully exploit the Gray-code 
    structure of the permutation list \[Delta]PermutationList.    

URL: http://en.wikipedia.org/wiki/Computing_the_permanent#Glynn_formula
URL: http://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms\>";

Permanent::symbolic = "\<\
ADVISORY: Permanent[_] argument is a non-numeric `1`\[Cross]`2` matrix\>";
On[Permanent::symbolic];

\[Delta]PermutationList::usage = "\<\
List of Gray-code permutations, saved in-memory 
for use by Permanent[_]'s Glynn formula.\>";

classicalPermanent::usage = "\<\
classicalPermanent[_] computes the matrix permanent
(slowly!) by expansion of the index permutation\
\>";

Begin["`Private`"];

classicalPermanent[mArg_] := Block[
    {rowList,colPerms},
    rowList = Table[i,{i,1,mArg//Length}];
    colPerms = rowList//Permutations;
    Map[
        (MapThread[mArg[[#1,#2]]&,{rowList,#}]//
          Times@@#&)&,
        colPerms
    ]//Plus@@#&
];

\[Delta]PermutationList[1] = {{1}};
\[Delta]PermutationList[m_Integer]/;(m>1) := (
    (* Conserve memory by purging irrelevant DownValues.
       These rules may exist for arbitrarily large arguments, 
       so a pattern-matched undefine "=." is applied *)
    (\[Delta]PermutationList//DownValues)[[All,1]]//
      ReplaceAll[#,HoldPattern[\[Delta]PermutationList[a_]]:>a]&//ReleaseHold//
        Select[#,(IntegerQ[#]&&(#!=1)&&(#!=m-1))&]&//
          Map[(\[Delta]PermutationList[#]=.;)&,#]&;
    (* now define-and-return the requested \[Delta]PermutationList *)
    \[Delta]PermutationList[m] = \[Delta]PermutationList[m-1]//
    (* idiom: the pipe holds \[Delta]PermutationList[m-1], so conserve 
       memory by deleting its DownValue immediately *)
    (If[m>2,\[Delta]PermutationList[m-1]=.;];#)&//(
          (* reflect DownValue[m-1] in Gray-code order *)
          Map[({1,1}~Join~(#//Rest))&,#] ~ Join ~
          Map[({1,-1}~Join~(#//Rest))&,#//Reverse]
      )&
);

Permanent[ (* numeric evaluation *)
    mArg_List/;(MatrixQ[mArg,NumericQ])
] := compiledGlynnAlgorithm[
        \[Delta]PermutationList[mArg//Length],
        mArg
    ]//Total[#[[1 ;; ;; 2]]] - Total[#[[2 ;; ;; 2]]]&//
      #/2^((mArg//Length)-1)&

compiledGlynnAlgorithm = Compile[{
        {d, _Integer, 1},
        {a, _Complex, 2}
    },
    Apply[Times,(d.a)],
    CompilationTarget -> "C", 
    RuntimeAttributes -> {Listable},
    Parallelization -> True
];

Permanent[ (* symbolic evaluation *)
    mArg_List/;
        (
            MatrixQ[mArg] &&
            (!MatrixQ[mArg,NumericQ]) &&
            (mArg//Length//Message[Permanent::symbolic,#,#]&;True)
        )
] := Map[
    Apply[Times,(#.mArg)]&,
    \[Delta]PermutationList[mArg//Length]
]//Total[#[[1 ;; ;; 2]]] - Total[#[[2 ;; ;; 2]]]&//
  #/2^((mArg//Length)-1)&;

End[];
EndPackage[];

Code to validate and benchmark

nPerm = 4;
Table[\[DoubleStruckCapitalC][i,j],{i,1,nPerm},{j,1,nPerm}]//
  Permanent[#]-classicalPermanent[#]&//
    Expand//
      If[
          #===0,
          Print["VALIDATED: ",nPerm,"\[Cross]",nPerm," symbolic permanent"];,
          Print["ERROR: ",nPerm,"\[Cross]",nPerm," symbolic permanent"];
      ]&;

nPerm = 5;
Table[\[DoubleStruckCapitalC][i,j],{i,1,nPerm},{j,1,nPerm}]//
  Permanent[#]-classicalPermanent[#]&//
    Expand//
      If[
          #===0,
          Print["VALIDATED: ",nPerm,"\[Cross]",nPerm," symbolic permanent"];,
          Print["ERROR: ",nPerm,"\[Cross]",nPerm," symbolic permanent"];
      ]&;

nPerm = 6;
nPerm//{#,#}&//(
            1*RandomVariate[NormalDistribution[0,1],#]+
            I*RandomVariate[NormalDistribution[0,1],#]
  )*1/Sqrt[2]&//
    {Permanent[#],classicalPermanent[#]}&//
      (#[[1]]-#[[2]])/Sqrt[#[[2]]\[Conjugate]*#[[2]]]&//
      If[
          Abs[#]<1000*10^(-$MachinePrecision),
          Print["VALIDATED: ",nPerm,"\[Cross]",nPerm," compiled numeric permanent"];,
          Print["ERROR: ",nPerm,"\[Cross]",nPerm," compiled numeric permanent"];
      ]&;

nPerm = 7;
nPerm//{#,#}&//(
            1*RandomVariate[NormalDistribution[0,1],#]+
            I*RandomVariate[NormalDistribution[0,1],#]
  )*1/Sqrt[2]&//
    {Permanent[#],classicalPermanent[#]}&//
      (#[[1]]-#[[2]])/Sqrt[#[[2]]\[Conjugate]*#[[2]]]&//
      If[
          Abs[#]<100*10^(-$MachinePrecision),
          Print["VALIDATED: ",nPerm,"\[Cross]",nPerm," compiled numeric permanent"];,
          Print["ERROR: ",nPerm,"\[Cross]",nPerm," compiled numeric permanent"];
      ]&;

Print["--------------"];
Print["*** first Permanent[_] evaluation ***"];
Do[
nPerm//{#,#}&//(
            1*RandomVariate[NormalDistribution[0,1],#]+
            I*RandomVariate[NormalDistribution[0,1],#]
  )*1/Sqrt[2]&//(
        (* first call stores Gray-code array *)
        (Permanent[#]//AbsoluteTiming)//First//1000*#&//Round//
          Print["Benchmark: Permanent[ ",nPerm,"\[Cross]",nPerm," ] took ",#," ms"]&;
    )&;,{nPerm,20,12,-1}];

Print["--------------"];
Print["*** second Permanent[_] evaluation ***"];
Do[
nPerm//{#,#}&//(
            1*RandomVariate[NormalDistribution[0,1],#]+
            I*RandomVariate[NormalDistribution[0,1],#]
  )*1/Sqrt[2]&//(
        (* second call runs fast *)
        Permanent[#]; 
        (Permanent[#]//AbsoluteTiming)//First//1000*#&//Round//
          Print["Benchmark: Permanent[ ",nPerm,"\[Cross]",nPerm," ] took ",#," ms"]&;
    )&;,{nPerm,20,12,-1}];

Print["--------------"];
Print["*** (large) Permanent[ 25\[Cross]25 ] evaluation ***"];

25//{#,#}&//(
            1*RandomVariate[NormalDistribution[0,1],#]+
            I*RandomVariate[NormalDistribution[0,1],#]
  )*1/Sqrt[2]&//(
        (Permanent[#]//AbsoluteTiming)//First//Round//
          Print["Benchmark: Permanent[ ",25,"\[Cross]",25," ] took ",#," s"]&;
        (Permanent[#]//AbsoluteTiming)//First//Round//
          Print["Benchmark: Permanent[ ",25,"\[Cross]",25," ] took ",#," s"]&;
    )&;

Results of validating and benchmarking

VALIDATED: 4\[Cross]4 symbolic permanent
VALIDATED: 5\[Cross]5 symbolic permanent
VALIDATED: 6\[Cross]6 compiled numeric permanent
VALIDATED: 7\[Cross]7 compiled numeric permanent
--------------
*** first Permanent[_] evaluation ***
Benchmark: Permanent[ 20\[Cross]20 ] took 1908 ms
Benchmark: Permanent[ 19\[Cross]19 ] took 926 ms
Benchmark: Permanent[ 18\[Cross]18 ] took 428 ms
Benchmark: Permanent[ 17\[Cross]17 ] took 235 ms
Benchmark: Permanent[ 16\[Cross]16 ] took 120 ms
Benchmark: Permanent[ 15\[Cross]15 ] took 52 ms
Benchmark: Permanent[ 14\[Cross]14 ] took 22 ms
Benchmark: Permanent[ 13\[Cross]13 ] took 13 ms
Benchmark: Permanent[ 12\[Cross]12 ] took 8 ms
--------------
*** second Permanent[_] evaluation ***
Benchmark: Permanent[ 20\[Cross]20 ] took 250 ms
Benchmark: Permanent[ 19\[Cross]19 ] took 118 ms
Benchmark: Permanent[ 18\[Cross]18 ] took 55 ms
Benchmark: Permanent[ 17\[Cross]17 ] took 27 ms
Benchmark: Permanent[ 16\[Cross]16 ] took 16 ms
Benchmark: Permanent[ 15\[Cross]15 ] took 22 ms
Benchmark: Permanent[ 14\[Cross]14 ] took 10 ms
Benchmark: Permanent[ 13\[Cross]13 ] took 4 ms
Benchmark: Permanent[ 12\[Cross]12 ] took 1 ms
--------------
*** (large) Permanent[ 25\[Cross]25 ] evaluation ***
Benchmark: Permanent[ 25\[Cross]25 ] took 75 s
Benchmark: Permanent[ 25\[Cross]25 ] took 11 s

Note that the initial evaluation is slower than subsequent evaluations, because initial evaluation creates Gray-code tables that are retained for subsequent use.

share|improve this question

4 Answers 4

up vote 13 down vote accepted

Looking at CompilePrint[compiledGlynnAlgorithm] there are some CopyTensor in it which aren't really needed. There's also a few CoerceTensor in there when it might be faster to just coerce the integer matrix once at the beginning.

By slightly adjusting the function all CopyTensor and CoerceTensor go away giving a small increase in speed:

compiledGlynnAlgorithmAlt = Compile[{
    {d, _Complex, 2}, {a, _Complex, 2}}, 
   Total@Map[Apply[Times, (#.a)*#] &, d],
   CompilationTarget -> "C",
   RuntimeAttributes -> {Listable},
   Parallelization -> True];


n = 20;
rc = RandomComplex[{-I - 1, I + 1}, {n, n}];
a = compiledGlynnAlgorithmAlt[δGrayCodeList[n], rc]; // AbsoluteTiming
b = compiledGlynnAlgorithm[δGrayCodeList[n], rc]; // AbsoluteTiming
a == b
(* {0.582192, Null} *)
(* {0.690600, Null} *)
(* True *)

Some more performance can be squeezed out by caching the resulting sign of each row in δGrayCodeList[n] the result is no longer exactly the same, but the relative difference is small:

δGrayCodeListSigns[n_] := δGrayCodeListSigns[n] = Times @@@ δGrayCodeList[n]

compiledGlynnAlgorithmKnownSign = 
  Compile[{{d, _Integer, 2}, {a, _Complex, 2}, {s, _Integer, 1}},
   s.Map[ Apply[Times, (#.a)] &, d]
   , CompilationTarget -> "C"
   , RuntimeAttributes -> {Listable}];

n = 20;
rc = RandomComplex[{-I - 1, I + 1}, {n, n}];

a = compiledGlynnAlgorithmAlt[δGrayCodeList[n], rc]; // AbsoluteTiming
b = compiledGlynnAlgorithm[δGrayCodeList[n], rc]; // AbsoluteTiming
c = compiledGlynnAlgorithmKnownSign[
      δGrayCodeList[n], rc, δGrayCodeListSigns[n]
    ]; // AbsoluteTiming

Abs[c - b]/Abs[b]

(* {0.565806, Null} *)
(* {0.614640, Null} *)
(* {0.430388, Null} *)
(* 2.49266*10^-13 *)
share|improve this answer
    
I affirm your speed-up of approximately 10% &amp; will award this the "answer" if nothing better appears. Thank you "ssh"! –  John Sidles Dec 2 '13 at 19:38
    
Your answer is "accepted". Thank you, "ssch". –  John Sidles Dec 3 '13 at 2:17
    
The code now cache's the sign, with a further speed-up that for Gray code ordering, the sign-list is a strictly alternating sequence of zeros and ones. –  John Sidles Dec 3 '13 at 10:58

You might get a speed up by restricting compiledGlynnAlgorithm to work on just one row of the Gray Code list, allowing the Listable and Parallelization to come into play. I say "might" because the speed up will depend on the details of your hardware.

Redefine compiledGlynnAlgorithm like so (note that it now takes a one dimensional list for d):

compiledGlynnAlgorithm = Compile[{{d, _Integer, 1}, {a, _Complex, 2}},
  Apply[Times, (d.a) d], 
   CompilationTarget -> "C", RuntimeAttributes -> {Listable}, Parallelization -> True]

And put the Total into Permanent

Permanent[mArg_List /; (MatrixQ[mArg, NumericQ])] := 
  Total@compiledGlynnAlgorithm[δGrayCodeList[mArg // Length], mArg] // 
    #/2^((mArg // Length) - 1) &;

a bit more speed

As ssch suggested, a little more performance can be squeezed out by exploiting the fact that the product of a given row of the Gray Code list is either 1 or -1. Furthermore, these occur alternately. So we can redefine compiledGlynnAlgorithm to remove the multiplication by d:

compiledGlynnAlgorithm = Compile[{{d, _Integer, 1}, {a, _Complex, 2}},
  Apply[Times, (d.a)],
  CompilationTarget -> "C", RuntimeAttributes -> {Listable}, Parallelization -> True]

and modify Permanent to Total the odd and even rows of the result separately:

Permanent[mArg_List /; (MatrixQ[mArg, NumericQ])] :=
 Module[{x},
  x = compiledGlynnAlgorithm[δGrayCodeList[mArg // Length], mArg];
  (Total[x[[;; ;; 2]]] - Total[x[[2 ;; ;; 2]]]) // #/2^((mArg // Length) - 1) &]

On my machine this gives about a factor of 3.5 speed increase over the original code for a 20x20 matrix.

share|improve this answer
    
Simon Woods, your optimizations are outstanding, and I have incorporated them (with thanks) into the code. Thank you very much. –  John Sidles Dec 3 '13 at 11:42

Here is a variant adapted from this MathGroup thread

permanentC = 
  Compile[{{m, _Real, 2}}, With[{len = Length[m]}, (-1)^len*Module[
      {s = {0.}, u = 0.},
      Do[
       s = N[IntegerDigits[n, 2, len]];
       u += (-1)^Round[Total[s]]*(Times @@ (m.s)),
       {n, 2^len - 1}];
      u]], CompilationTarget -> "C"];

I checked it on the test set below.

SeedRandom[11111];
testmats = Table[RandomInteger[1, {n, n}], {n, 8, 20, 2}];

It is slightly faster than Permament from the original post. It is also slightly wronger, so to speak. The issue is cancellation error, and for the larger dimensions it shows up in the last few places. The culprit is the Times @@ (m.s) part. Those get large and we lose digits on cancelling. Possibly there is a way to reorder things so as to avoid this numeric pitfall, but offhand I don't see it.

share|improve this answer
    
When I adapted this compiled code to accept _Complex input matrices, the resulting execution times were ~2.4X longer than with the "ssch" approach. Hence, this approach is not preferred (AFAICT). –  John Sidles Dec 2 '13 at 22:33

Edits

Per the comments, the bugfix RandomChoice[__] $\Rightarrow$ RandomSelection[__] is implemented.

Also the simulated distribution of $|permanent|^2$ is fitted to LogNormalDistribution[mu,sigma]; textual diagnostics are expanded; entropy cost is reported; links to Mathematica Distribution tutorials are provided.


Distributions in Mathematica

Having upgraded to Mathematica 10.0.0, it turns out that the various built-in symbols associated to the Wolfram language tutorials Nonparametric Statistical Distributions, Derived Statistical Distributions, and Hypothesis Tests greatly facilitate the numerical simulation of boson-sampling experiments using the above-provided PermanentCode` package.

Mathematica's built-in hypothesis test KolgomorovSmirnovTest is particularly valuable; the Wikipedia entry Kolmogorov–Smirnov test provides a good introduction.

Permanents in quantum physics

The appended Mathematica code simulates the permanent-distribution of the output photons of the experiment describe by Lund et al "Boson sampling from a Gaussian state" (see PRL 2014 and arXiv:1305.4346).

Open questions

This example is preparatory to a planned TCS StackExchange question Can Kolmogorov-Smirnov tests collapse the polynomial hierarchy?; this question is inspired by conjectures set forth by Scott Aaronson and Alex Arkhipov in "The Computational Complexity of Linear Optics" (see ToC 2013 and arXiv:1011.3245 ).

Baseline capabilities

The code provided establishes baseline computational capabilities to the simulation side of the following Permanent Entropy challenge:

The Permanent Entropy challenge  Experimentally sample a permanent distribution at lower entropy-cost than indistinguishably simulating that distribution by a classical computation.

Example code

The following code generates, on a MacBook Pro, about $12000\ \text{Joule/Kelvin}$ of wall-plug entropy in indistinguishably simulating — as assessed by Kolmogorov-Smirnov test — $k=32$ samples of $n=20$-photon scattering into a $m=400$-mode boson-sampling apparatus, whose scattering matrix is chosen Haar-randomly.

Thanks to Mathematica's built-in Distribution-handling symbols, the bulk of the code is devoted to generating nice-looking graphics.

Graphical output

permanent distributions and simulations

Note  A figure key appears at the end of the following textual output.

Textual output

... begin Haar-random matrix construction ...
... Haar-random matrix constructed, begin sampling permanents ...
boson-sampling is 000% done; elapsed time 00:00:14; 03:48:22 remaining
boson-sampling is 001% done; elapsed time 00:02:14; 03:40:45 remaining
    etc;
boson-sampling is 099% done; elapsed time 03:56:31; 00:02:27 remaining
boson-sampling is 100% done; elapsed time 03:58:58; 00:00:00 remaining
... permanent sampling done, begin computing distributions ...
... distributions done, begin CDFs ...
... CDFs done, begin k-sample simulations ...
... k-sample simulations done, plot the results (be patient) ...
... finished plotting

simulation entropy cost = (11955*Joule)/Kelvin

permanent distribution fitted to:
LogNormalDistribution[0.03810299650724991, 1.5231961079779779]

Alice's raw boson-sampling modes-detected data
{4, 12, 23, ...},{22, 24, 49, ...}
{18, 26, 35, ...},{6, 17, 59, ...}
{14, 29, 33, ...},{7, 32, 34, ...}
{4, 8, 21, ...},{44, 45, 98, ...}
{11, 31, 55, ...},{8, 44, 101, ...}
...

Bob's raw boson-sampling modes-detected data
{4, 10, 17, ...},{28, 29, 34, ...}
{74, 96, 115, ...},{11, 16, 46, ...}
{18, 21, 47, ...},{2, 16, 28, ...}
{29, 66, 77, ...},{5, 8, 20, ...}
{7, 13, 20, ...},{34, 49, 83, ...}
...

Kolmogorov-Smirnov (KS) tests ...
one-sample "A"    KS test: p = 0.348142
one-sample "B"    KS test: p = 0.824036
two-sample "AvsB" KS test: p = 0.835661

    Figure key:

       solid black: InverseCDF[EmpiricalDistribution] 
                    (smoothed) of |permanent|^2
        red dotted: InverseCDF[LogNormalDistribution] 
                    (fitted) of |permanent|^2
       gray dashed: InverseCDF[EmpiricalDistribution]
                    (smoothed) of |determinant|^2    

Mathematica code

As usual, making the graphics look nice takes the most code:

Needs["PermanentCode`"]; (* provided in question asked *)

(* ------------------------------------------------- *)
(* --- set boson-sampling simulation parameters --- *)
(* ------------------------------------------------- *)

nPhoton = 20;         (* number of photons detected *)
nSampleMax = 10^5;    (* upper bound to matrix samples; 
                         nSampleMax >= 10^5 is recommended *)
tSampleMax = 6*3600;  (* time-used upper bound in seconds *)
kSample = 32;         (* number of Kolmogorov-Smirnov samples *)

nominalPower = 250 Watt; (* nominal processor power at full load *)
nominalTemperature = 300 Kelvin; (* nominal heat-sink temperature *)  


(* ---------------------------------------- *)
(* --- construct Haar-random unitaries --- *)
(* ---------------------------------------- *)
"... begin Haar-random matrix construction ..."//Print;

mNode = nPhoton^2;
iSeed = 2^nPhoton;

SeedRandom[iSeed];
Umatrix = RandomVariate[NormalDistribution[],{mNode,mNode}] + 
    I * RandomVariate[NormalDistribution[],{mNode,mNode}]//
    SingularValueDecomposition[#,mNode]&//
    #[[1]].
      (DiagonalMatrix[RandomReal[{0,2*Pi},{mNode}]//Exp[I*#]&]).
      ConjugateTranspose[#[[3]]]&;

(* ------------------------------------------------- *)
(* --- set the scale of the median |permanent|^2 --- *)
(* ------------------------------------------------- *)
"... Haar-random matrix constructed, begin sampling permanents ..."//Print;

PessoanPostulate::usage = "\<\
Per the boson-sampling experiments of Lund et al. \"Boson sampling 
from a gaussian state\" (PRL 2014, see Figure 1), let $n$ be the 
number of photons detected among $m=n^2$ output modes.  Then for 
a Haar-distributed unitary scattering the median value of the 
squared permanent is (empirically) $2^(2-n^2/m)\\Gamma(n+1)/m^n$.\
\>";

(* the first two terms are heuristic; the remaining term  
   is a numerically-fitted improvement that in most cases 
   has negligible practical implications *)
PessoanPostulate = Gamma[nPhoton+1]/mNode^nPhoton * 
    2^(2-nPhoton^2/mNode) (* * 
    (* term commented-out *)
    2^(
        -0.6288239555022707 + 
        +0.0363892632845249*nPhoton + 
        +0.6797969693300729*nPhoton^3/mNode^2 +
        -0.2824925592014731*nPhoton^4/mNode^3
    ) *) ;

(* ------------------------------ *)
(* --- pretty-print utilities --- *)
(* ------------------------------ *)

padIntegerString[iArg_?NumberQ,nArg_Integer] := 
    NumberForm[iArg//Round,nArg,NumberPadding->{"0",""}]//
  ToString//Characters//{
        Take[#,1;;-nArg-1]//StringJoin//
          StringReplace[#,RegularExpression["^[0]*"] -> ""]&,
        Take[#,-nArg;;]//StringJoin
    }&//StringJoin;

toTime[arg_?NumberQ] := {arg}//
  ((#//First//{Quotient[#,60],Mod[#,60]}&)~Join~(#//Rest))&//
  ((#//First//{Quotient[#,60],Mod[#,60]}&)~Join~(#//Rest))&//
    Map[{":",padIntegerString[#,2]}&,#]&//
      Flatten//Rest//StringJoin@@#&;

(* ------------------------------------------------------ *)
(* --- sample combinatorically random output channels --- *)
(* ------------------------------------------------------ *)

permEstimatedRMS = Sqrt[PessoanPostulate//N];
amplitudeScale = permEstimatedRMS^(-1.0/nPhoton);

SeedRandom[iSeed+1];
{
    sample$Permanent,
        sample$Determinant,
    sample$Powerproduct,
        sample$Rows,
    sample$Columns
} = For[
    iSample=0;
    lastCalibration = iSample;
    startTime = zeroTime = AbsoluteTime[];,
    iSample<nSampleMax,
    iSample++,
    rowIndexList = RandomSample[Range[mNode],nPhoton]//Sort;
    colIndexList = RandomSample[Range[mNode],nPhoton]//Sort;
    Umatrix[[rowIndexList,colIndexList]]//
        (* from a superabundance of caution, rescale 
           such that the computed permament is \[ScriptCapitalO](1) *)
        (#*amplitudeScale)&//{
            #//Permanent//#*#\[Conjugate]&//Re,
            #//Det//#*#\[Conjugate]&//Re,
            #.#\[HermitianConjugate]//Diagonal//Times@@#&//Re
        }&//#*PessoanPostulate&//
          #~Join~{rowIndexList,colIndexList}&//
            Sow;
    (* provide status messages at timely intervals *)
    If[(iSample == Max[nSampleMax/1000//Round,1]) || 
        (iSample == nSampleMax-1) ||
        ((iSample > 0) &&
          (0==Mod[iSample,nSampleMax/100//Round])),
        statusString = "boson-sampling is " <>
            (100*iSample/nSampleMax//padIntegerString[#,3]&) <> 
            "% done; elapsed time " <>
            ((AbsoluteTime[]-zeroTime)//toTime) <> "; " <>
            (((AbsoluteTime[]-startTime)/(iSample-lastCalibration+1))*
                (nSampleMax-(iSample+1))//toTime) <> " remaining";
        (* uncomment to periodically pipe status to /tmp *)
        "printf '%s\\n' \"" <> statusString <> 
            "\" >/tmp/permanentStatus.txt"//Run;
        If[ (0 == Mod[iSample,nSampleMax/20//Round]) ||
            (iSample <= nSampleMax/30) || 
            ((nSampleMax-iSample) <= nSampleMax/30),
            statusString//Print;
        ];
        lastCalibration = iSample;
        startTime = AbsoluteTime[];
    ];
]//Hold//
  TimeConstrained[#//ReleaseHold,tSampleMax]&//
    Reap//Last//Last//
      (* Sort[#]& yields increasing permanent *) 
      Sort[#]&//Transpose[#]&;

samplePermanentNormed = sample$Permanent//
  Map[(1/PessoanPostulate)*#&,#]&;

sampleDeterminantNormed = sample$Determinant//
  Map[(1/PessoanPostulate)*#&,#]&;

(* ----------------------------------------------------------- *)
(* --- construct distributions both empirical and smoothed --- *)
(* ----------------------------------------------------------- *)
"... permanent sampling done, begin computing distributions ..."//Print;

(* Note: all distributions are of Log[normed |perm|^2] *)

empiricalPermanent$D = {samplePermanentNormed,samplePermanentNormed}//
  Rule[#[[1]],(#[[2]]//Log[#]&)]&//
    EmpiricalDistribution;

(* bootstrap a smooth distribution; see e.g. Wikipedia's discussion 
   URL: http://en.wikipedia.org/wiki/Bootstrapping_%28statistics%29 *)
smoothPermanent$D = empiricalPermanent$D//
  RandomVariate[#,{10*(samplePermanentNormed//Length)//Round}]&//
    SmoothKernelDistribution[#,0.1]&;

empiricalDeterminant$D = {samplePermanentNormed,sampleDeterminantNormed}//
  Rule[#[[1]],(#[[2]]//Log[#]&)]&//
    EmpiricalDistribution;

(* bootstrap a smooth distribution *)
smoothDeterminant$D = empiricalDeterminant$D//
  RandomVariate[#,{10*(sampleDeterminantNormed//Length)//Round}]&//
    SmoothKernelDistribution[#,0.05]&;

weightedPermanentData = samplePermanentNormed//
      WeightedData[#//Log[#]&,#]&;

fittedPermanent$D = weightedPermanentData//
  Module[{\[Mu]\[FilledDiamond], \[Sigma]\[FilledDiamond]},
      EstimatedDistribution[#,
          NormalDistribution[\[Mu]\[FilledDiamond], \[Sigma]\[FilledDiamond]]]
  ]&;

(* ------------------------------------------------ *)
(* --- construct inverse distributions and CDFs --- *)
(* ------------------------------------------------ *)
"... distributions done, begin CDFs ..."//Print;

permanent$CDF = smoothPermanent$D//CDF;
permanent$InverseCDF = smoothPermanent$D//InverseCDF;
fittedPermanent$InverseCDF = fittedPermanent$D//InverseCDF;
determinant$InverseCDF = smoothDeterminant$D//InverseCDF;

(* ------------------------------------------------------ *)
(* --- simulate k-sample experiments by Alice and Bob --- *)
(* ------------------------------------------------------ *)
"... CDFs done, begin k-sample simulations ..."//Print;

SeedRandom[iSeed+2];

CDFList::usage = "\<\
CDFList is a list of Rules -- intended for use with Nearest[_] --
that satisfies the relations

    sample$Permanent[[ CDFList[[i,2]] ]] \[TildeTilde] 
           InverseCDF[empiricalPermanent$D][ CDFList[[i,1]] ]

The CDFList makes it easy to simulate boson-sampling experiments.\
\>";

CDFList = sample$Permanent//
  FoldList[Plus,#]&//
    Times[#,1/(#//Last)]&//
      MapIndexed[Rule,#]&;

(* Simulate Alice's raw data *)
AlicesRawData = RandomReal[{0,1},kSample]//
  Map[(Nearest[CDFList,#]//Flatten//First)&,#]&//
    {sample$Rows[[#]],sample$Columns[[#]]}&//
      Transpose;

(* Compute Alice's processed data *)
AlicesLogNormedPermanentData = AlicesRawData//
  Map[
      (Umatrix[[ #[[1]] , #[[2]] ]]//
        (#*amplitudeScale)&//
          Permanent//#*#\[Conjugate]&//Re//Log)&,#
  ]&;

simulationEntropy = (AbsoluteTime[]-zeroTime) *
    (nominalPower/Watt)/(nominalTemperature/Kelvin)//
   Round//#*Joule/Kelvin&;

(* Simulate Bob's raw data *)
BobsRawData = RandomReal[{0,1},kSample]//
  Map[(Nearest[CDFList,#]//Flatten//First)&,#]&//
    {sample$Rows[[#]],sample$Columns[[#]]}&//
      Transpose;

(* Compute Bob's processed data *)
BobsLogNormedPermanentData = BobsRawData//
  Map[
      (Umatrix[[ #[[1]] , #[[2]] ]]//
        (#*amplitudeScale)&//
          Permanent//#*#\[Conjugate]&//Re//Log)&,#
  ]&;

(* ----------------------------- *)
(* --- plot it all up nicely --- *)
(* ----------------------------- *)
"... k-sample simulations done, plot the results (be patient) ..."//Print;

nPlotPts = 1000;

range = samplePermanentNormed//Log[#]&//Sort//
  (* don't plot outliers *)
  {#[[4;;5]]//Mean,#[[-5;;-4]]//Mean}&//
    Map[permanent$CDF,#]&;

thePermanentPointList = Range[1/2,nPlotPts]/nPlotPts//
  Select[#,(#>range[[1]])&]&//
    Select[#,(#<range[[2]])&]&//
      Map[{#,permanent$InverseCDF[#]/Log[10]}&,#//N]&;

theFittedPermanentPointList = Range[1/2,nPlotPts]/nPlotPts//
  Select[#,(#>range[[1]])&]&//
    Select[#,(#<range[[2]])&]&//
      Map[{#,fittedPermanent$InverseCDF[#]/Log[10]}&,#//N]&;

theDeterminantPointList = Range[1/2,nPlotPts]/nPlotPts//
  Select[#,(#>range[[1]])&]&//
    Select[#,(#<range[[2]])&]&//
      Map[{#,determinant$InverseCDF[#]/Log[10]}&,#//N]&;

smoothPlot = {
    thePermanentPointList,
    theFittedPermanentPointList,
    theDeterminantPointList
}//
  ListPlot[#,
    PlotJoined->True,
    PlotRange->{{0,1},{-4,2}},
    PlotStyle->{
      Directive[Black,AbsoluteThickness[1.8],Opacity[1]],
      Directive[Red,AbsoluteThickness[1.8],Dotted,Opacity[1]],
      Directive[Gray,Dashed,AbsoluteThickness[1.8],Opacity[0.6]]
    },
    AspectRatio->0.9,
    AxesOrigin->{0.5,0.0},
    Ticks->{{None,None},{None,None}},
    AxesStyle->Directive[Black,AbsoluteThickness[1.2]],
    Frame->True,
    FrameStyle->Directive[Black,AbsoluteThickness[1.2]],
    FrameTicks -> {
        {{
          Range[-4,2,1],
          {"0.0001","0.001","0.01","0.1","1","10","100"}
        }//Transpose,None},
        {{
          Range[0,1,0.2],
          {"0","0.2","0.4","0.6","0.8","1"}
        }//Transpose,None}
    },
    FrameTicksStyle->Directive[Black,AbsoluteThickness[0.6],FontSize->Medium],
    GridLines -> {
        Range[0,1,0.1],
        Outer[#1+#2&,Range[-4,1,1],Range[1,10,1]//Log[10,#]&]//
          Flatten
    },
    GridLinesStyle->Directive[Black,AbsoluteThickness[0.6],Opacity[0.35]]
  ]&;

AliceBobInverseCDFPlot = {
    AlicesLogNormedPermanentData,
    BobsLogNormedPermanentData
}//
  Map[((#//EmpiricalDistribution//
        InverseCDF[#]&)[x])&,#]&//
    Map[#/Log[10]&,#]&//
  Plot[#,{x,0,1},
      Exclusions -> None, Frame -> None, GridLines -> None, Axes -> None,
      PlotPoints->400,MaxRecursion->3,
      PlotRange->{{0,1},{-4,2}},
      PlotStyle->{
          Directive[RGBColor[0.8,0.65,0.0],AbsoluteThickness[1.2],Opacity[1]],
          Directive[RGBColor[0.0,0.65,0.8],AbsoluteThickness[1.2],Opacity[1]]
      }
  ]&;

AliceBobRegionPlot = {
    AlicesLogNormedPermanentData,
    BobsLogNormedPermanentData
}//
  Map[((#//EmpiricalDistribution//
        InverseCDF[#]&)[x])&,#]&//
  Map[#/Log[10]&,#]&//
  RegionPlot[
        {
          y<#[[1]] && y>#[[2]],
          y<#[[2]] && y>#[[1]]
        },
        {x,0,1},{y,-3,3},
        Background -> None,
        Frame -> None, GridLines -> None, Axes -> None,
        PlotRange->{{0,1},{-4,2}},
        PlotPoints->200,MaxRecursion->2,
        BoundaryStyle -> None,
        PlotStyle -> {
          Directive[RGBColor[1.0,0.75,0.0],Opacity[0.125]],
          Directive[RGBColor[0.0,0.75,1.0],Opacity[0.125]]
        }
  ]&;

"\<... finished plotting\>"//Print;

Print[""];
simulationEntropy//InputForm//
  Print["simulation entropy cost = ",#]&;

Print[""];
fittedPermanent$D//
  ReplaceAll[#,NormalDistribution->LogNormalDistribution]&//
    (Print["permanent distribution fitted to:"];
     Print[#//InputForm];)&;

Print["\nAlice's raw boson-sampling modes-detected data"];
AlicesRawData//#[[1;;5,All,1;;3]]&//
  Map[Print[#[[1]]~Join~{"..."},",",#[[2]]~Join~{"..."}]&,#]&;
Print["..."];

Print["\nBob's raw boson-sampling modes-detected data"];
BobsRawData//#[[1;;5,All,1;;3]]&//
  Map[Print[#[[1]]~Join~{"..."},",",#[[2]]~Join~{"..."}]&,#]&;
Print["..."];

{AlicesRawData,BobsRawData}//
  Intersection@@#&//Length//
    If[#>0,
        Print[""];
        Print["Warning: there were ",#," simulation collisions"];
    ]&;

Print[""];
Print["Kolmogorov-Smirnov (KS) tests ..."];
{{"one-sample \"A\"   ","one-sample \"B\"   ","two-sample \"AvsB\""},{
    KolmogorovSmirnovTest[AlicesLogNormedPermanentData,smoothPermanent$D],
        KolmogorovSmirnovTest[BobsLogNormedPermanentData,smoothPermanent$D],
    KolmogorovSmirnovTest[AlicesLogNormedPermanentData,BobsLogNormedPermanentData]
}}//Transpose//
  Map[Print[#[[1]]," KS test: p = ",#[[2]]]&,#]&;

Print[""];
"\<\
Figure key:\n
   solid black: InverseCDF[EmpiricalDistribution] 
                (smoothed) of |permanent|^2
    red dotted: InverseCDF[LogNormalDistribution] 
                (fitted) of |permanent|^2
   gray dashed: InverseCDF[EmpiricalDistribution]
                (smoothed) of |determinant|^2\
\>"//Print;

Show[smoothPlot,AliceBobRegionPlot,AliceBobInverseCDFPlot,
    PlotLabel -> Style[
        "boson-sampling Kolmogorov-Smirnov analysis \n(n="<>
            (nPhoton//ToString)<>
            " photons, k=" <>
            (kSample//ToString)<>
            " detections, m=" <>
            (mNode//ToString)<>
            " modes)",
        FontSize->Medium,
        Black
    ],
    Background -> None,
    FrameLabel->{
      Style["\<\
cumulative probability\n\
(from \>" <> (samplePermanentNormed//Length//ToString) <>
" U-matrix samples)",FontSize->Medium,Black],
      Style["inverse CDF of\n|perm|^2\[Cross]2^((n^2/m)-2)\[Cross]m^n/n!",
      FontSize->Medium,Black]
    }
]//TraditionalForm
share|improve this answer
    
Followup The bugfix now is implemented: Oct 21 at 22:06 In the above code RandomChoice[__] should be RandomSelection[__] ... –  John Sidles 21 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.