Single-photon Simulation

The following notebook demonstrates the use of the emulator to simulate the propagation of a single photon in a quantum photonic processor. This can be used to find the unitary implemented by a system.

First import required modules and some additional tools.

[1]:
from collections import Counter

import matplotlib.pyplot as plt

import lightworks as lw
from lightworks import State, emulator

A general N is then defined, which is used to adjust the number of modes used for the circuit throughout the notebook.

[2]:
N = 8

Can then create a random unitary matrix which is programmed onto the chip.

[3]:
U = lw.random_unitary(N)

circuit = lw.Unitary(U)

Simulator

First we try using the simulation part of the emulator. This allows the simulation of a number of input states, finding the probability amplitudes for the given outputs. To set this up, we specify the generated circuit to the simulator class.

In this case we will choose to try to recreate the unitary matrix, which can be done by creating a list of all single photon input and output states.

[4]:
# Create all single photon states by adding 1 to list when i=j, otherwise add 0
states = [State([int(i == j) for j in range(N)]) for i in range(N)]

sim = lw.Simulator(circuit, states, states)

# Calculate probability amplitude
backed = emulator.Backend("permanent")
results = backed.run(sim)

U_calc = results.array

The resultant matrix probability amplitude matrix can be visualized below, and we see that the unitary is correctly replicated. Note that the unitary needs to be transposed for the plotting below to show identical matrices.

[5]:
fig, ax = plt.subplots(1, 2, figsize=(12, 6))

ax[0].imshow(abs(U.T))
ax[1].imshow(abs(U_calc))
ax[0].set_title("U")
ax[1].set_title("Calculated U")

plt.show()
../_images/examples_single_photon_demo_9_0.png

It is also possible to directly view the probability amplitudes in a nicer form using the display_as_dataframe function included with the results object. In this case the values are converted to probabilities with the conv_to_probability keyword.

[6]:
results.display_as_dataframe(conv_to_probability=True)
[6]:
|1,0,0,0,0,0,0,0> |0,1,0,0,0,0,0,0> |0,0,1,0,0,0,0,0> |0,0,0,1,0,0,0,0> |0,0,0,0,1,0,0,0> |0,0,0,0,0,1,0,0> |0,0,0,0,0,0,1,0> |0,0,0,0,0,0,0,1>
|1,0,0,0,0,0,0,0> 0.054219 0.030692 0.369378 0.333889 0.000770 0.025639 0.143519 0.041894
|0,1,0,0,0,0,0,0> 0.011680 0.264869 0.064209 0.118370 0.289810 0.093067 0.005352 0.152644
|0,0,1,0,0,0,0,0> 0.018689 0.090437 0.147608 0.141004 0.165931 0.124955 0.262464 0.048911
|0,0,0,1,0,0,0,0> 0.116603 0.046318 0.111925 0.126841 0.094749 0.425849 0.049754 0.027963
|0,0,0,0,1,0,0,0> 0.154861 0.208638 0.147460 0.015421 0.188167 0.086908 0.115069 0.083477
|0,0,0,0,0,1,0,0> 0.142784 0.007850 0.044895 0.063095 0.072830 0.041421 0.295332 0.331795
|0,0,0,0,0,0,1,0> 0.031335 0.322487 0.047759 0.104681 0.068356 0.129004 0.059216 0.237163
|0,0,0,0,0,0,0,1> 0.469830 0.028710 0.066768 0.096700 0.119388 0.073157 0.069294 0.076153

Sampler

Alternatively, the Sampler class can be used to emulate the process of measuring samples from the system. To initialise, this requires the generated circuit, the single input state that we want to look at, and the number of samples to collect. In this case we input a single photon on mode 0.

[7]:
sampler = lw.Sampler(circuit, State([1] + [0] * (N - 1)), n_samples=100)

Once the sampler has been set up, we can then sample from it many times by running on the backend.

[8]:
results = backed.run(sampler)

mode_locs = []
for state, count in results.items():
    loc = state.s.index(1)
    mode_locs += [loc] * count

# Count number of times we measure a photon on each mode
counted = Counter(mode_locs)

The counts can then be converted into probabilities and compared to the expected values from the unitary. As we only choose to look at 100 samples here, the measured and expected distributions tend to vary as not enough samples have been taken to see convergence.

[9]:
x = range(N)

# Convert counts to a list and normalise to total count numbers
p_calc = [counted[i] / sum(counted.values()) for i in x]

# Find expected distribution
p_exp = abs(U.T[0, :]) ** 2

plt.figure(figsize=(7, 6))
plt.bar(x, p_calc, label="Measured", alpha=1)
plt.bar(
    x,
    p_exp,
    label="Expected",
    edgecolor="Black",
    fill=False,
    linestyle="dashed",
    linewidth=1.5,
)
plt.xlabel("Output mode")
plt.ylabel("Counts")
plt.legend()
plt.show()
../_images/examples_single_photon_demo_17_0.png