2-mode Grover’s search algorithm

We implement in this notebook a 2-mode optical realization of Grover’s search algorithm, based on Kwiat et al. (2000). Grover’s search algorithm: An optical approach. Journal of Modern Optics, 47(2–3), 257–266.

Introduction

Motivation

Searching for a specific item (called the marked item) in an unstructured list of \(N\) items requires \(O(N)\) accesses to the list classically. Grover showed in 1996 that is possible for a quantum computer to achieve this using only \(O\left(\sqrt{N}\right)\) iterations.

Algorithm summary

For a list of size \(N\), Grover’s algorithm requires \(\log (N)\) qubits. The algorithm starts by setting each qubit in the superposition state \(\frac{1}{\sqrt{2}}\left(|0\rangle+|1\rangle\right)\). Then it applies \(O\left(\sqrt{N}\right)\) iterations of a subroutine called inversion-about-mean, whose goal is to skew this initial uniform superposition state towards the desired marked state such the probability of measuring the marked state is amplified. This subroutine requires the application of an oracle unitary, which applies a relative \(\pi\) phase shift only to the quantum state encoding the item we are looking for in the database.

Kwiat et al. implementation details

The optical implementation of Kwiat et al. uses the polarization and path degree of freedom of a single beam to achieve a 2-qubit optical implementation of Grover’s search algorithm. Although \(N=4\) here, calculations show that only a single application of the inversion-about-mean subroutine is required.

In an effort to reduce the number of optical components used in the experimental setup, the authors work with a compiled version of the circuit, which we will reproduce here using Perceval.

Perceval implementation

Initialisation

[1]:
from tabulate import tabulate
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt

import perceval as pcvl
import perceval.lib.phys as phys

We create in Perceval a circuit with two spatial modes, \(a\) and \(b\) denoting resapectively the lower and upper spatial modes. For clarity, the different equivalent encodings for each of the four basis states are given below in order: - marked item encoding: \(\left|"00"\right\rangle\), \(\left|"01"\right\rangle\), \(\left|"10"\right\rangle\), \(\left|"11"\right\rangle\) - Kwiat et al. path and polarization encoding: \(\left|aH\right\rangle\), \(\left|aV\right\rangle\), \(\left|bH\right\rangle\), \(\left|bV\right\rangle\) - Perceval path and polarization encoding: \(\left|0, 1:H\right\rangle\), \(\left|0, 1:V\right\rangle\), \(\left|1:H, 0\right\rangle\), \(\left|1:V, 0\right\rangle\)

We first define these states in Perceval:

[12]:
states = [pcvl.AnnotatedBasicState("|0,{P:H}>"),
          pcvl.AnnotatedBasicState("|0,{P:V}>"),
          pcvl.AnnotatedBasicState("|{P:H},0>"),
          pcvl.AnnotatedBasicState("|{P:V},0>"),
         ]

We use the following unitary matrix to represent the beamsplitters:

[13]:
BS = phys.BS(theta=sp.pi/4, phi_a=0, phi_b=sp.pi/2, phi_d=0)
pcvl.pdisplay(BS.U)
$\left[\begin{matrix}\frac{\sqrt{2}}{2} & - \frac{\sqrt{2}}{2}\\\frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2}\end{matrix}\right]$

The half-wave plates are defined in the article as:

[14]:
def HWP(xsi):
    hwp = pcvl.Circuit(m=1)
    hwp.add(0, phys.HWP(xsi)).add(0, phys.PS(-sp.pi/2))
    return hwp

pcvl.pdisplay(HWP(sp.pi/2))
[14]:
../_images/notebooks_2-mode_Grover_algorithm_14_0.svg

Circuit Construction

We divide the compiled circuit of Kwiat et al. in three parts: state initialization, oracle and inversion-about-mean. However, due to the compilation, each individual part does not act exactly as described in the introduction.

#### State initialization circuit

[15]:
init_circuit = pcvl.Circuit(m=2, name="Initialization")
init_circuit.add(0, HWP(sp.pi/8))
init_circuit.add((0, 1), BS)
init_circuit.add(0, phys.PS(-sp.pi))

pcvl.pdisplay(init_circuit)
[15]:
../_images/notebooks_2-mode_Grover_algorithm_18_0.svg

#### Oracle

The oracle circuit can be initialised so that any one of the four list elements are marked. This is controlled via the integer parameter \(mark \in [0, 3]\).

[16]:
def oracle(mark):
    """Values 0, 1, 2 and 3 for parameter 'mark' respectively mark the elements "00", "01", "10" and "11" of the list."""
    oracle_circuit = pcvl.Circuit(m=2, name='Oracle')
    # The following dictionnary translates n into the corresponding component settings
    oracle_dict = {0: (1, 0), 1: (0, 1), 2: (1, 1), 3: (0, 0)}
    PC_state, LC_state = oracle_dict[mark]
    # Mode b
    if PC_state == 1:
        oracle_circuit.add(0, HWP(0))
    oracle_circuit.add(0, phys.PR(sp.pi/2))
    if LC_state == 1:
        oracle_circuit.add(0, HWP(0))
    # Mode a
    if LC_state == 1:
        oracle_circuit.add(1, HWP(0))
    if PC_state == 1:
        oracle_circuit.add(1, HWP(0))
    return oracle_circuit

pcvl.pdisplay(oracle(0))
[16]:
../_images/notebooks_2-mode_Grover_algorithm_20_0.svg

#### Inversion about mean

[17]:
inversion_circuit = pcvl.Circuit(m=2, name='Inversion')
inversion_circuit.add((0, 1), BS)
inversion_circuit.add(0, HWP(sp.pi/4))
inversion_circuit.add((0, 1), BS)

pcvl.pdisplay(inversion_circuit)
[17]:
../_images/notebooks_2-mode_Grover_algorithm_22_0.svg

Detection

The article also uses a detection circuit of the form:

[18]:
detection_circuit = pcvl.Circuit(m=4, name='Detection')
detection_circuit.add((0, 1), phys.PBS())
detection_circuit.add((2, 3), phys.PBS())

pcvl.pdisplay(detection_circuit)
[18]:
../_images/notebooks_2-mode_Grover_algorithm_24_0.svg

However, Perceval allows us to filter out the photon’s polarization state, meaning that there is no need to expand the circuit to four output spatial modes.

Final circuit setup

As above, the value of parameter ‘mark’ indicates which element of the list needs to be found.

[19]:
def grover_circuit(mark):
    grover_circuit = pcvl.Circuit(m=2, name='Grover')
    grover_circuit.add((0,1), init_circuit).add((0,1), oracle(mark)).add((0,1), inversion_circuit)
    return grover_circuit

print('Grover optical circuit for searching database element "00":')
pcvl.pdisplay(grover_circuit(0), recursive=True)
Grover optical circuit for searching database element "00":
[19]:
../_images/notebooks_2-mode_Grover_algorithm_27_1.svg

Grover algorithm execution

We can finally simulate Grover’s algorithm for marked database elements “00”, “01”, “10” and “11”.

[20]:
# Circuit simulation
simulator_backend = pcvl.BackendFactory().get_backend('Naive')
input_state = pcvl.AnnotatedBasicState("|{P:H},0>")
results_list = []  # probability amplitudes storage

for mark in range(4):
    sim = simulator_backend(grover_circuit(mark))
    ca = pcvl.CircuitAnalyser(sim,
                              input_states=[input_state],
                              output_states=states,
                             )
    ca.compute()
    results_list.append(ca._distribution[0])

# Plot data
labels = ['"00"', '"01"', '"10"', '"11"']
state_0_prob_list = results_list[0]
state_1_prob_list = results_list[1]
state_2_prob_list = results_list[2]
state_3_prob_list = results_list[3]
x = np.arange(4)  # label locations
width = 0.1  # the width of the bars

fig, ax = plt.subplots(dpi=150)
rects_0 = ax.bar(x - 3*width/2, state_0_prob_list, width, label=str(states[0]))
rects_1 = ax.bar(x - width/2, state_1_prob_list, width, label=str(states[1]))
rects_2 = ax.bar(x + width/2, state_2_prob_list, width, label=str(states[2]))
rects_3 = ax.bar(x + 3*width/2, state_3_prob_list, width, label=str(states[3]))

ax.set_xlabel('Marked database element')
ax.set_ylabel('Detection probability')
ax.set_xticks(x, labels)
ax.legend()
ax.grid(True, axis='x')
plt.show()
../_images/notebooks_2-mode_Grover_algorithm_29_0.png

As demonstrated by the graph above, Grover’s algorithm indeed finds the marked database element!

Reference

Kwiat et al. Grover’s search algorithm: An optical approach. Journal of Modern Optics, 47(2–3), 257–266 (2000).