# Getting started with Perceval

In this notebook, we aim to reproduce the $$\mathsf{CNOT}$$ Gate to evaluate its performance while demonstrating key features of Perceval. We use as basis the implementation from [1].

[5]:

import perceval as pcvl
import sympy as sp
import numpy as np


## Ralph CNOT Gate

We start by describing the circuit as defined by the paper above- it is a circuit on six modes (labelled from 0 to 5 from top to bottom) consisting of five beam splitters. Modes 1 and 2 contain the control system while modes 3 and 4 encode the target system. Modes 0 and 5 are unoccupied ancillary modes.

[6]:

cnot = pcvl.Circuit(6, name="Ralph CNOT")
cnot.add((0, 1), pcvl.BS.H(pcvl.BS.r_to_theta(1/3), phi_tl = -np.pi/2, phi_bl = np.pi, phi_tr = np.pi / 2))
cnot.add((2, 3), pcvl.BS.H(pcvl.BS.r_to_theta(1/3), phi_tl = -np.pi/2, phi_bl = np.pi, phi_tr = np.pi / 2))
pcvl.pdisplay(cnot)

[6]:


We can then simulate this circuit using the Naive backend on four different input states corresponding to the two-qubit computational basis states. We use Analyzer to analyse the performance of the gate. Using heralds, we can get a better visualisation of what the circuit actually does.

[7]:

p = pcvl.Processor("Naive", cnot)
p.set_postprocess(lambda s: (s[1] or s[2]) and (s[3] or s[4]))

pcvl.pdisplay(p, recursive = True)

[7]:

[8]:

states = {
pcvl.BasicState([1, 0, 1, 0]): "00",
pcvl.BasicState([1, 0, 0, 1]): "01",
pcvl.BasicState([0, 1, 1, 0]): "10",
pcvl.BasicState([0, 1, 0, 1]): "11"
}

ca = pcvl.algorithm.Analyzer(p, states)
ca.compute(expected={"00": "00", "01": "01", "10": "11", "11": "10"})
pcvl.pdisplay(ca)
print("performance=%s, fidelity=%.3f%%" % (pcvl.simple_float(ca.performance)[1], ca.fidelity * 100))

00 01 10 11
00 1 0 0 0
01 0 1 0 0
10 0 0 0 1
11 0 0 1 0
performance=1/9, fidelity=100.000%


Beyond the actual logic function, what is interesting with this gate us that it produces entangled states that we will be trying to check with CHSH experiment when the source is not perfect.

## Checking for entanglement with CHSH experiment

https://en.wikipedia.org/wiki/File:Two_channel_bell_test.svg

To reproduce this Bell test protocol, we define a new circuit which uses the $$\mathsf{CNOT}$$ gate implemented above as a sub-circuit. The parameters $$a$$ and $$b$$ describe the measurement bases used by players $$A$$ and $$B$$ in the runs of the Bell-test. We define a photon source with a brightness of 40% and a purity of 99% that will be used by the Processor.

[9]:

source = pcvl.Source(emission_probability=0.40, multiphoton_component=0.01)

QPU = pcvl.Processor("Naive", 4, source)

# Remove postprocess function as it would mean nothing to the new QPU
p.clear_postprocess()

a = pcvl.Parameter("a")
b = pcvl.Parameter("b")

pcvl.pdisplay(QPU, recursive = True)

[9]:


We start by setting the values of the two parameters to 0, meaning that the beam splitters after the $$\mathsf{CNOT}$$ effectively act as the identity.

[10]:

a.set_value(0)
b.set_value(0)


We now state that our photons will be inputted on ports 1 and 2 (using the 0-index convention, and not counting heralded modes).

[11]:

QPU.with_input(pcvl.BasicState([0, 1, 1, 0]))


We now detail the different state vectors that are the probabilistic inputs to the circuit. The most frequent input is the empty state, followed by two states with only a single photon on either of the input ports, then by the expected nominal input $$|0,1,1,0,0,0\rangle$$. Here, the heralded modes are shown because if there were a photon on them, they would also have the imperfect source. They are represented at the end of the state (the two last modes) as they have been added after the declaration of the processor.

[12]:

pcvl.pdisplay(QPU.source_distribution, precision=1e-4)

state probability
|0,0,0,0,0,0> 9/25
|0,0,{_:0},0,0,0> 0.2395
|0,{_:0},0,0,0,0> 0.2395
|0,{_:0},{_:0},0,0,0> 0.1594
|0,0,{_:0}{_:1},0,0,0> 4.8193e-4
|0,{_:0}{_:1},0,0,0,0> 4.8193e-4
|0,{_:0}{_:1},{_:0},0,0,0> 3.2064e-4
|0,{_:0},{_:0}{_:1},0,0,0> 3.2064e-4
|0,{_:0}{_:1},{_:0}{_:2},0,0,0>0

We can then check the output state distribution corresponding to this input distribution. By default, since our input state had 2 photons, only states having photons on at least two modes are kept. This can be changed using mode_post_selection.

[13]:

output_distribution=QPU.probs()["results"]
pcvl.pdisplay(output_distribution, max_v=10)

state probability
|0,0,1,1> 0.247801
|1,1,0,0> 0.247801
|1,0,1,0> 0.247801
|0,1,0,1> 0.247801
|0,1,1,0> 0.003736
|1,0,0,1> 0.00074714
|0,1,2,0> 0.000497093
|0,1,1,1> 0.000497093
|1,1,1,0> 0.000497093
|0,2,1,0> 0.000497093

Let us run now the experiment with increasing value of g2 in the range $$[0, 0.2]$$ with a brightness of $$0.15$$ and check the CHSH inequality.

[14]:

from tqdm.auto import tqdm
x = np.arange(0, 20, 0.5)
y = []

for g2 in tqdm(x):
Es = []
for va in [0, sp.pi/2]:
a.set_value(va)
for vb in [sp.pi/4, 3*sp.pi/4]:
b.set_value(vb)
Npp, Npm, Nmp, Nmm = 0, 0, 0, 0
source = pcvl.Source(emission_probability=.15, multiphoton_component=g2/100)
QPU2 = pcvl.Processor("Naive", 4, source)
# Only the source of the big QPU remains
QPU2.with_input(pcvl.BasicState([0, 1, 1, 0]))
# we add a post-selection on the processor, there needs to be exactly one in mode 0,1 and one in mode 2,3
QPU2.set_postprocess(lambda o: (o[0]+o[1]==1) and (o[2]+o[3]==1))
output_distribution=QPU2.probs()["results"]
for output_bs, prob in output_distribution.items():
output_state = output_bs
if (output_state[0] == 1 and output_state[2] == 1):
Npp = prob
if (output_state[0] == 1 and output_state[3] == 1):
Npm = prob
if (output_state[1] == 1 and output_state[2] == 1):
Nmp = prob
if (output_state[1] == 1 and output_state[3] == 1):
Nmm = prob
E = (Npp-Npm-Nmp+Nmm)/(Npp+Npm+Nmp+Nmm)
Es.append(E)

S = Es[0]-Es[1]+Es[2]+Es[3]
print("g2=",g2/100, "S=", S)
y.append(S)

g2= 0.0 S= 2.82842712474619
g2= 0.005 S= 2.7926153612581373
g2= 0.01 S= 2.757298040245726
g2= 0.015 S= 2.7224649920120614
g2= 0.02 S= 2.6881063238088334
g2= 0.025 S= 2.654212410542609
g2= 0.03 S= 2.6207738857392093
g2= 0.035 S= 2.5877816329598575
g2= 0.04 S= 2.5552267774696427
g2= 0.045 S= 2.5231006783080483
g2= 0.05 S= 2.491394920629044
g2= 0.055 S= 2.4601013083623045
g2= 0.06 S= 2.4292118571395154
g2= 0.065 S= 2.3987187875083835
g2= 0.07 S= 2.3686145184000984
g2= 0.075 S= 2.338891660848145
g2= 0.08 S= 2.3095430119332683
g2= 0.085 S= 2.28056154897453
g2= 0.09 S= 2.251940423920364
g2= 0.095 S= 2.2236729579593097
g2= 0.1 S= 2.19575263631623
g2= 0.105 S= 2.1681731032549507
g2= 0.11 S= 2.1409281572493692
g2= 0.115 S= 2.114011746338283
g2= 0.12 S= 2.087417963639817
g2= 0.125 S= 2.061141043033512
g2= 0.13 S= 2.035175354988402
g2= 0.135 S= 2.0095154025476605
g2= 0.14 S= 1.9841558174433032
g2= 0.145 S= 1.9590913563550196
g2= 0.15 S= 1.9343168972953644
g2= 0.155 S= 1.9098274361218834
g2= 0.16 S= 1.8856180831640268
g2= 0.165 S= 1.861684059972091
g2= 0.17 S= 1.8380206961697065
g2= 0.175 S= 1.8146234264183838
g2= 0.18 S= 1.7914877874790276
g2= 0.185 S= 1.7686094153746597
g2= 0.19 S= 1.7459840426455249
g2= 0.195 S= 1.7236074956965992

[15]:

import matplotlib.pyplot as plt
plt.title("CHSH value with purity")
plt.xlabel("g2 (%)")
plt.ylabel("bell inegality")
plt.axhline(y=2, linewidth=2, color="red", label= 'horizontal-line')
plt.plot(x, y, color ="green")
plt.grid(color='b', dashes=(3, 2, 1, 2))
plt.show()


Beyond 13% of g2, we are crossing the value $$2$$, ie not violating anymore the $$|CHSH|\le 2$$ inegality!

### Reference

[1] T. C. Ralph, N. K. Langford, T. B. Bell, and A. G. White. Linear optical controlled-NOT gate in the coincidence basis. Physical Review A, 65(6):062324, June 2002. Publisher: American Physical Society.