Boson Sampling
We are interested to simulate a boson sample with 14 photons and 60 modes, order of size comparable to what was done in Boson Sampling with 20 Input Photons and a 60-Mode Interferometer in a \(10^{14}\)-Dimensional Hilbert Space [1]
[1]:
from collections import Counter
import gzip
import pickle
import time
import random
import perceval as pcvl
from perceval.algorithm import Sampler
We define all the needed values below.
Perfect Boson sampling
[2]:
n = 14 #number of photons at the input
m = 60 #number of modes
N = 5000000 #number of samplings
Generating a Haar random Unitary with Perceval
[3]:
Unitary_60 = pcvl.Matrix.random_unitary(m) #creates a random unitary of dimension 60
A possible linear circuit realization of such matrix would be the following.
Here we define a 2-mode unitary circuit that we can use to decompose the 60 mode unitary
[4]:
mzi = (pcvl.BS() // (0, pcvl.PS(phi=pcvl.Parameter("φ_a")))
// pcvl.BS() // (1, pcvl.PS(phi=pcvl.Parameter("φ_b"))))
pcvl.pdisplay(mzi)
[4]:
Let us decompose the unitary into a Reck’s type circuit [2] - this makes a huge circuit…
[5]:
Linear_Circuit_60 = pcvl.Circuit.decomposition(Unitary_60, mzi,
phase_shifter_fn=pcvl.PS,
shape=pcvl.InterferometerShape.TRIANGLE)
pcvl.pdisplay(Linear_Circuit_60)
[5]:
Running Simulation
Now we choose the way to perform the simulation with Perceval. The number of photons is within what we could simulate with a Naive
backend (see here), however, the output space is far too big just to enumerate and store the states - so let us go with sampling using CliffordClifford2017
backend (see here).
[6]:
QPU = pcvl.Processor("CliffordClifford2017", Linear_Circuit_60)
Select a random input:
[7]:
#one can choose which mode he/she wants at input, or we can choose it randomly
def Generating_Input(n, m, modes = None):
"This function randomly chooses an input with n photons in m modes."
if modes == None :
modes = sorted(random.sample(range(m),n))
state = "|"
for i in range(m):
state = state + "0"*(1 - (i in modes)) +"1"*(i in modes)+ ","*(i < m-1)
return pcvl.BasicState(state + ">")
input_state = Generating_Input(n, m)
print("The input state: ", input_state)
QPU.with_input(input_state)
The input state: |0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,1,0,0,1,1,0,1,0,1,0,1>
Just to see that it outputs a statevectors of n photon(s) in m modes.
[10]:
# Keep all outputs
QPU.min_detected_photons_filter(0)
sampler = Sampler(QPU)
print("The sampled outputs are:")
for out_state in sampler.samples(10)["results"]:
print(out_state)
The sampled outputs are:
|0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,0,0,0,0,2,1,0,0,2,0,0,0,0,0,1,0,0,0>
|0,0,0,0,2,0,0,0,0,0,0,1,0,0,1,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,1,1,0,0,0,0,1,0,0,0,1,0>
|0,0,1,0,0,0,0,0,1,0,0,0,1,2,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,1,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0>
|0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,1,1,0,0,0,3,1,0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>
|1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,1,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0>
|1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,1,0,0,0,0,0,1>
|1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0>
|1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,3,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1,0>
|0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,2,0,0,0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0>
|1,0,0,1,0,0,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,1,0,0,1,0,0,0,1>
We carry out the sampling, we do it N times, it will take some time, let us save the results to a file:
[ ]:
# if we want to launch parallel process
worker_id=1
#store the input and the unitary
with open("%dphotons_%dmodes_%dsamples-worker%s-unitary.pkl" %(n,m,N,worker_id), 'wb') as f:
pickle.dump(Unitary_60, f)
with open("%dphotons_%dmodes_%dsamples-worker%s-inputstate.pkl" %(n,m,N,worker_id), 'w') as f:
f.write(str(input_state)+"\n")
with gzip.open("%dphotons_%dmodes_%dsamples-worker%s-samples.txt.gz" %(n,m,N,worker_id), 'wb') as f:
start = time.time()
for _ in range(N):
f.write((str(sampler.samples(1)["results"][0])+"\n").encode())
end = time.time()
f.write(str("==> %d\n" % (end-start)).encode())
f.close()
A little after (4 hours on a 3.1GHz Intel) - we do have 5M samples. We launched this on 32 threads for 2 days and collected 300 batches of 5M samples
Let us analyze the K-first mode bunching on these samples
[10]:
import gzip
[11]:
worker_id = 1
count = 0
bunching_distribution = Counter()
with gzip.open("%dphotons_%dmodes_%dsamples-worker%s-samples.txt.gz"%(n,m,N,worker_id), "rt") as f:
for l in f:
l = l.strip()
if l.startswith("|") and l.endswith(">"):
try:
st = pcvl.BasicState(l)
count+=1
bunching_distribution[st.photon2mode(st.n-1)]+=1
except Exception:
pass
print(count, "samples")
print("Bunching Distribution:", "\t".join([str(bunching_distribution[k]) for k in range(m)]))
5000000 samples
Bunching Distribution: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 1 0 4 7 12 15 37 17 72 26 160 202 425 645 1096 1305 1913 1695 2754 3818 5382 11044 11120 11836 15396 15035 38725 39583 60725 73354 91170 97192 109380 170639 266973 179713 364987 501164 543633 592287 700750 1085705
These numbers have been used on 300 samples for certification - see our article on Perceval for more details.
Boson sampling with non perfect sources
Let us explore around performing Boson sampling with a non perfect source. We declare a source with 90% brightness and purity. Here, we would reach the limits of the simulation if we use the same input as before, as for each photon at the entrance, we multiply by 3 the number of input states (0, 1 or 2 photons at the input), leading to \(3^n\) states.
[10]:
3 ** n
[10]:
4782969
[11]:
source = pcvl.Source(emission_probability=0.9, multiphoton_component=0.1)
QPU = pcvl.Processor("CliffordClifford2017", Linear_Circuit_60, source)
QPU.with_input(pcvl.BasicState([0, 1, 1] + (m - 3) * [0]))
We can see what is the source distribution, so how likely a state at the input of the linear circuit will be.
[12]:
pcvl.pdisplay(QPU.source_distribution, precision=1e-7, max_v=20)
state | probability |
---|---|
|0,{_:0},{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.731684 |
|0,0,{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.0855385 |
|0,{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.0855385 |
|0,{_:0}{_:2},{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.0381629 |
|0,{_:0},{_:0}{_:4},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.0381629 |
|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.01 |
|0,0,{_:0}{_:4},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.0044615 |
|0,{_:0}{_:2},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.0044615 |
|0,{_:0}{_:2},{_:0}{_:4},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> | 0.0019905 |
Now we can launch again starting from cell 8 to use this imperfect QPU with the same nominal input, or from cell 7 for another random input.
Error Mitigation in Boson Sampling by Photon recycling
Photon recycling, our new error mitigation feature, is a statistical algorithm designed to reduce the effects of photon loss in the output distribution from a linear optical quantum circuit (Reference: [3]). It computes an approximate probability distribution for the ideal case of n photons by computing recycled probabilities from the lossy output samples. The following section demonstrates the use of this feature in Perceval on a lossy simulation of Boson Sampling. A 10 mode random unitary circuit with 3 input photons and an imperfect source with low emission probability is used in the example.
[5]:
Unitary_10 = pcvl.Matrix.random_unitary(10) # a random unitary of dimension 10
Linear_Circuit_10 = pcvl.Circuit.decomposition(Unitary_10, mzi,
phase_shifter_fn=pcvl.PS,
shape=pcvl.InterferometerShape.TRIANGLE)
[11]:
lossy_source = pcvl.Source(emission_probability=0.3) # imperfect source
# Constructing a Processor with the lossy source and the 10 mode random unitary circuit
lossy_qpu = pcvl.Processor("SLOS", Linear_Circuit_10, lossy_source)
lossy_input = pcvl.BasicState([1, 1, 1] + (Linear_Circuit_10.m - 3) * [0]) # Input state with 3 photons
lossy_qpu.with_input(lossy_input)
lossy_qpu.min_detected_photons_filter(1)
# n-2 photons in min detected filter gives the best approximate for output probabilities with photon recycling
Computing and displaying the lossy output samples with photon count less than expected.
[12]:
# Sampler to create a lossy output sampling
lossy_sampler = Sampler(lossy_qpu)
lossy_output_samples = lossy_sampler.sample_count(100000)['results']
[13]:
for out_state in lossy_output_samples:
if out_state.n < 3:
print(out_state, ':', lossy_output_samples[out_state])
|1,0,0,0,0,0,0,0,0,0> : 1738
|0,1,0,0,0,0,0,0,0,0> : 11765
|0,0,1,0,0,0,0,0,0,0> : 5860
|0,0,0,1,0,0,0,0,0,0> : 7654
|0,0,0,0,1,0,0,0,0,0> : 4582
|0,0,0,0,0,1,0,0,0,0> : 4757
|0,0,0,0,0,0,1,0,0,0> : 10890
|0,0,0,0,0,0,0,1,0,0> : 9218
|0,0,0,0,0,0,0,0,1,0> : 7273
|0,0,0,0,0,0,0,0,0,1> : 3207
|2,0,0,0,0,0,0,0,0,0> : 29
|1,1,0,0,0,0,0,0,0,0> : 434
|1,0,1,0,0,0,0,0,0,0> : 116
|1,0,0,1,0,0,0,0,0,0> : 206
|1,0,0,0,1,0,0,0,0,0> : 100
|1,0,0,0,0,1,0,0,0,0> : 80
|1,0,0,0,0,0,1,0,0,0> : 151
|1,0,0,0,0,0,0,1,0,0> : 46
|1,0,0,0,0,0,0,0,1,0> : 204
|1,0,0,0,0,0,0,0,0,1> : 78
|0,2,0,0,0,0,0,0,0,0> : 1710
|0,1,1,0,0,0,0,0,0,0> : 535
|0,1,0,1,0,0,0,0,0,0> : 1184
|0,1,0,0,1,0,0,0,0,0> : 818
|0,1,0,0,0,1,0,0,0,0> : 449
|0,1,0,0,0,0,1,0,0,0> : 858
|0,1,0,0,0,0,0,1,0,0> : 617
|0,1,0,0,0,0,0,0,1,0> : 1385
|0,1,0,0,0,0,0,0,0,1> : 547
|0,0,2,0,0,0,0,0,0,0> : 412
|0,0,1,1,0,0,0,0,0,0> : 1034
|0,0,1,0,1,0,0,0,0,0> : 323
|0,0,1,0,0,1,0,0,0,0> : 170
|0,0,1,0,0,0,1,0,0,0> : 682
|0,0,1,0,0,0,0,1,0,0> : 700
|0,0,1,0,0,0,0,0,1,0> : 437
|0,0,1,0,0,0,0,0,0,1> : 197
|0,0,0,2,0,0,0,0,0,0> : 773
|0,0,0,1,1,0,0,0,0,0> : 437
|0,0,0,1,0,1,0,0,0,0> : 236
|0,0,0,1,0,0,1,0,0,0> : 607
|0,0,0,1,0,0,0,1,0,0> : 771
|0,0,0,1,0,0,0,0,1,0> : 500
|0,0,0,1,0,0,0,0,0,1> : 189
|0,0,0,0,2,0,0,0,0,0> : 208
|0,0,0,0,1,1,0,0,0,0> : 161
|0,0,0,0,1,0,1,0,0,0> : 611
|0,0,0,0,1,0,0,1,0,0> : 276
|0,0,0,0,1,0,0,0,1,0> : 626
|0,0,0,0,1,0,0,0,0,1> : 334
|0,0,0,0,0,2,0,0,0,0> : 310
|0,0,0,0,0,1,1,0,0,0> : 1080
|0,0,0,0,0,1,0,1,0,0> : 854
|0,0,0,0,0,1,0,0,1,0> : 343
|0,0,0,0,0,1,0,0,0,1> : 122
|0,0,0,0,0,0,2,0,0,0> : 1306
|0,0,0,0,0,0,1,1,0,0> : 1537
|0,0,0,0,0,0,1,0,1,0> : 960
|0,0,0,0,0,0,1,0,0,1> : 317
|0,0,0,0,0,0,0,2,0,0> : 894
|0,0,0,0,0,0,0,1,1,0> : 936
|0,0,0,0,0,0,0,1,0,1> : 317
|0,0,0,0,0,0,0,0,2,0> : 207
|0,0,0,0,0,0,0,0,1,1> : 387
|0,0,0,0,0,0,0,0,0,2> : 111
As expected, the imperfect source led to significant losses in the output sampling. Our Some of this loss can be recovered by using photon recycling as shown in the cell below to obtain a mitigated distribution that is closer to the ideal case.
[19]:
from perceval.error_mitigation import photon_recycling
# Ideal photon count = 3 in this case
mitigated_dist = photon_recycling(lossy_output_samples, ideal_photon_count=lossy_input.n)
pcvl.pdisplay(mitigated_dist, max_v=10) # displaying only the 10 highest probable states in the mitigated distribution
state | probability |
---|---|
|0,0,0,0,0,0,1,1,1,0> | 0.027285 |
|0,0,0,0,0,1,1,1,0,0> | 0.026835 |
|0,1,0,0,0,0,1,0,1,0> | 0.025997 |
|0,1,0,0,0,0,1,1,0,0> | 0.024874 |
|0,1,0,1,0,0,0,0,1,0> | 0.023755 |
|0,1,0,0,0,0,0,1,1,0> | 0.023057 |
|0,0,0,1,0,0,1,1,0,0> | 0.022692 |
|0,0,1,0,0,0,1,1,0,0> | 0.022161 |
|0,1,0,1,0,0,1,0,0,0> | 0.021077 |
|0,1,0,0,1,0,0,0,1,0> | 0.020601 |
References
[1] Hui Wang, et al. Boson Sampling with 20 Input Photons and a 60-Mode Interferometer in a \(10^{14}\)-Dimensional Hilbert Space. Physical Review Letters, 123(25):250503, December 2019. Publisher: American Physical Society.
[2] Michael Reck, Anton Zeilinger, Herbert J Bernstein, and Philip Bertani. Experimental realization of any discrete unitary operator. Physical review letters, 73(1):58, 1994.
[3] James Mills and Rawad Mezher. Mitigating photon loss in linear optical quantum circuits: classical postprocessing methods outperforming postselection. arxiv.