Squeezer characterization

In this tutorial we collect photon-number event samples from the X8_01 chip in an effort to characterize its 4 non-degenerate squeezers A similar task is automatically performed each morning to record the stability of our hardware. We begin with the necessary imports.

From hereon, we refer to one wavelength mode of the two mode squeezed vacuum states produced by our squeezers as the signal, and the other wavelength mode the idler, in keeping with standard terminology.

import numpy as np
import strawberryfields as sf

Collecting the hardware samples

To start with, let’s define a general function to execute a job on the X8_01 chip that returns information needed to characterize the squeezers.

The function run_job accepts a unitary and set of squeezing amplitudes for the 4 squeezers (presently limited to 0 or 1), and requested number of samples. It first creates a Strawberry Fields program implementing the supplied unitary and squeezing amplitudes, and subsequently runs it on X8_01, returning a results object that we can use to view the resulting samples.

We write the function somewhat generally, even though here we will always run it on X8_01, a chip with 4 squeezers and 4 spatial modes.

def run_job(
    job_name="default_name",
    n_spatial_modes=4,
    unitary=None,
    squeezing_amplitudes=None,
    n_samples=500000,
):

    prog = sf.Program(n_spatial_modes * 2, name=job_name)

    # If no unitary is provided, default to the identity
    if unitary is None:
        unitary = np.identity(n_spatial_modes)

    # If no squeezing amplitudes are provided, default to all of them being off
    if squeezing_amplitudes is None:
        squeezing_amplitudes = [0] * n_spatial_modes

    with prog.context as q:
        for i in range(n_spatial_modes):
            sf.ops.S2gate(squeezing_amplitudes[i]) | (q[i], q[i + n_spatial_modes])
        for qumodes in (q[:n_spatial_modes], q[n_spatial_modes:]):
            sf.ops.Interferometer(unitary) | qumodes
        sf.ops.MeasureFock() | q

    eng = sf.RemoteEngine("X8_01")

    return eng.run(prog, shots=n_samples, disable_port_permutation=True)

With this function defined, it is easy to collect samples that will allow us to characterize each squeezer.

squeezer0 = run_job(job_name="squeezer0_char", squeezing_amplitudes=[1, 0, 0, 0])

Analysis

The samples span 8 (4 spatial x 2 frequency) detector channels, and we can easily calculate the mean photon number of each.

n_means = np.mean(squeezer0.samples, axis=0)
print(f"The mean photon numbers <n> of each channel are:\n{n_means}")

Out:

The mean photon numbers <n> of each channel are:
[0.30781  0.01342  0.018746 0.022222 0.311482 0.02154  0.01833  0.018452].

From which we see that the signal and idler arms of squeezer 0 have been directed to the detectors in channel 0 and channel 4, as expected when the implemented unitary is the identity. More relevant to the present example, we can use such pairs of channels to determine that we have indeed produced quantum light, using a quantity known as the quantum noise reduction factor or NRF. Each squeezer generates an entangled mode pair, and we expect the photon numbers of each half of this pair to be correlated. As detailed in [1] and references therein, we can measure this as the variance of the photon number difference between signal and idler channels of a squeezer being suppressed below what would be expected for uncorrelated modes with Poisson photon statistics or, more precisely, \(\text{NRF}=V_{n_{S} - n_{I}}/ \langle n_{S} + n_{I} \rangle\), and is \(\geq 1\) for classical states.

def NRF(sig, idl):
    return np.var(sig - idl) / np.mean(sig + idl)

NRF_sq0 = NRF(squeezer0.samples[:, 0], squeezer0.samples[:, 4])

print(f"The NRF of squeezer 0 is {NRF_sq0:.2f}.")

Out:

The NRF of squeezer 0 is 0.86.

The fact that it is less than 1 indicates that squeezer 0 has produced non-classical light.

An additional squeezer property of interest is the temporal mode structure of the generated light. Ideally, each squeezer should generate squeezing in a single temporal mode, as any multi-modedness can effectively act as a source of unwanted noise in various applications. As detailed in [2], we can measure such multi-modedness via a second-order correlation, or \(g^{(2)}\), measurement, via \(g^{(2)} = 1 + 1 / K\) where \(K\) is a quantity known as the Schmidt number. The Schmidt number counts the effective number of modes, and is therefore 1 if there is a single temporal mode. Thus, the nearer to 2 one measures \(g^{(2)}\) to be, the nearer to a single temporal mode the squeezer is operating.

def g2(samples):
    return (np.mean(samples ** 2) - np.mean(samples)) / np.mean(samples) ** 2


print(f"The g2 of the signal arm of squeezer 0 is {g2(squeezer0.samples[:, 0]):.2f}.")
print(f"The g2 of the idler arm of squeezer 0 is {g2(squeezer0.samples[:, 4]):.2f}.")

Out:

The g2 of the signal arm of squeezer 0 is 1.79.
The g2 of the idler arm of squeezer 0 is 1.78.

We note that similar data can be taken for the 3 other squeezers.

squeezer1 = run_job(job_name="squeezer1_char", squeezing_amplitudes=[0, 1, 0, 0])
squeezer2 = run_job(job_name="squeezer2_char", squeezing_amplitudes=[0, 0, 1, 0])
squeezer3 = run_job(job_name="squeezer3_char", squeezing_amplitudes=[0, 0, 0, 1])

print(f"The NRF of squeezer 1 is {NRF(squeezer1.samples[:, 1], squeezer1.samples[:, 5]):.2f}.")
print(f"The NRF of squeezer 2 is {NRF(squeezer2.samples[:, 2], squeezer2.samples[:, 6]):.2f}.")
print(f"The NRF of squeezer 3 is {NRF(squeezer3.samples[:, 3], squeezer3.samples[:, 7]):.2f}.")

Out:

The NRF of squeezer 1 is 0.89.
The NRF of squeezer 2 is 0.85.
The NRF of squeezer 3 is 0.87.
print(f"The g2 of the signal arm of squeezer 1 is {g2(squeezer1.samples[:, 1]):.2f}.")
print(f"The g2 of the idler arm of squeezer 1 is {g2(squeezer1.samples[:, 5]):.2f}.")
print(f"The g2 of the signal arm of squeezer 2 is {g2(squeezer2.samples[:, 2]):.2f}.")
print(f"The g2 of the idler arm of squeezer 2 is {g2(squeezer2.samples[:, 6]):.2f}.")
print(f"The g2 of the signal arm of squeezer 3 is {g2(squeezer3.samples[:, 3]):.2f}.")
print(f"The g2 of the idler arm of squeezer 3 is {g2(squeezer3.samples[:, 7]):.2f}.")

Out:

The g2 of the signal arm of squeezer 1 is 1.84.
The g2 of the idler arm of squeezer 1 is 1.81.
The g2 of the signal arm of squeezer 2 is 1.83.
The g2 of the idler arm of squeezer 2 is 1.83.
The g2 of the signal arm of squeezer 3 is 1.68.
The g2 of the idler arm of squeezer 3 is 1.73.

References

1

V.D. Vaidya, B. Morrison, L.G. Helt, R. Shahrokhshahi, D.H. Mahler, M.J. Collins, K. Tan, J. Lavoie, A. Repingon, M. Menotti, N. Quesada, R.C. Pooser, A.E. Lita, T. Gerrits, S.W. Nam, and Z. Vernon. “Broadband quadrature-squeezed vacuum and nonclassical photon number correlations from a nanophotonic device.” arXiv:1904.07833 (2019).

2

A. Christ, K. Laiho, A. Eckstein, K. N. Cassemiro, and C. Silberhorn. “Probing multimode squeezing with correlation functions.” New Journal of Physics 13, 033027 (2011).

Total running time of the script: ( 0 minutes 0.000 seconds)

Gallery generated by Sphinx-Gallery