Frequency dependant squeezing

Gravitational wave detectors use squeezed light (a special quantum state of light) to improve their sensitivity. Using just squeezed light however can only reduce the radiation pressure noise or the shot-noise components of the quantum noise not both simultaneously. Using a “filter cavity” [22] rotates the squeezed state in just the right way so that it suppress radiation pressure noise at low frequency and then rotates to suppress shot-noise at high frequency at the correct points. These additional cavities are being installed into current detectors and will be crucial to realising future generations of detectors—so we need to be able to model them.

In this example we will describe the multiple ways in which you can simulate a filter cavity. This can be done in a rigorous way or in a more approximate manner. Depending on what you want to achieve either option can be used.

To begin with we will need a model of a detector. For this example we will use a model model that is similar to Cosmic Explorer, a proposed 40km long detector.

import numpy as np
import finesse
import matplotlib.pyplot as plt
finesse.init_plotting()

base = finesse.Model()
base.parse("""
variable ITM_T 0.014
variable L_ARM 40k
variable FC_detune 7.3 # Hz

l l1 P=165
link(l1, PRM)
# ------------------------------------------------------------------------------
# Power recycling cavity
# ------------------------------------------------------------------------------
m PRM T=0.03 L=40u phi=0
s PRC PRM.p2 BS.p1 L=53.0
bs BS R=0.5 T=0.5
# ------------------------------------------------------------------------------
# X-arm
# ------------------------------------------------------------------------------
s lx BS.p3 ITMX.p1 L=0
m ITMX T=ITM_T L=0u phi=0
s LX ITMX.p2 ETMX.p1 L=L_ARM
m ETMX T=5u L=40u phi=0
# ------------------------------------------------------------------------------
# Y-arm
# ------------------------------------------------------------------------------
s ly BS.p2 ITMY.p1 L=0
m ITMY T=ITM_T L=0u phi=90
s LY ITMY.p2 ETMY.p1 L=L_ARM
m ETMY T=5u L=40u phi=90
# Signal recycling cavity
s SRC BS.p4 SRM.p1 L=20
m SRM T=0.02 L=0 phi=0
# ------------------------------------------------------------------------------
# Output optics
# ------------------------------------------------------------------------------
dbs OFI
link(SRM.p2, OFI.p1)
readout_dc AS OFI.p3.o
# ------------------------------------------------------------------------------
# suspensions
# ------------------------------------------------------------------------------
free_mass ITMX_sus ITMX mass=320
free_mass ETMX_sus ETMX mass=320
free_mass ITMY_sus ITMY mass=320
free_mass ETMY_sus ETMY mass=320
# ------------------------------------------------------------------------------
# Degrees of Freedom
# ------------------------------------------------------------------------------
dof STRAIN LX.dofs.h +1  LY.dofs.h -1

# signal generator
sgen sig STRAIN

qnoised NSR AS.p1.i nsr=True

fsig(1)
""")

First we make a model which just has a squeezer

model = base.deepcopy()
model.parse("""
sq sqz db=10
link(sqz, OFI.p2)
""")

We can plot the quantum noise limited sensitivity using the code below. In this code we switch the squeezer on and off and also rotate the squeezing angle to see how the quantum noise shape changes.

with model.temporary_parameters():
    # No squeezing
    model.sqz.db = 0
    model.sqz.angle = 0
    sol_no_sqz = model.run("xaxis(fsig, log, 1, 5k, 100)")
    # Squeezing
    model.sqz.db = 10
    model.sqz.angle = 90
    sol_shot_sqz = model.run("xaxis(fsig, log, 1, 5k, 100)")
    # Squeezing
    model.sqz.db = 10
    model.sqz.angle = 0
    sol_rp_sqz = model.run("xaxis(fsig, log, 1, 5k, 100)")

plt.loglog(sol_no_sqz.x1, abs(sol_no_sqz['NSR']), label='No squeezing')
plt.loglog(sol_shot_sqz.x1, abs(sol_shot_sqz['NSR']), label='Shot-noise squeezing')
plt.loglog(sol_rp_sqz.x1, abs(sol_rp_sqz['NSR']), label='Rad. pressure squeezing')
plt.legend()
plt.xlabel("Frequency [Hz]")
plt.ylabel("Sensitivity [h/sqrt{Hz}]");
../_images/frequency_dependant_squeezing_2_0.svg

We can see that by changing the squeezing angle we can improve the noise at high or low frequencies, but not both simultaneously.

The quick method using symbolics

A filter cavity is essentially just rotating the squeezing angle in the correct manner. Therefore we can approximate it by just using a symbolic equation and applying it to the squeezer elements angle parameter. Ignoring losses the equation we need is

\[\phi = \phi_0 + \arctan\left(\frac{\Omega^2 - \Delta^2}{\Omega^2 + \Delta^2}\right). \]

Here \(\phi_0\) is an offset to arctan method to ensure the correct DC rotation is used , \(\Omega = 2\pi f_{\mathrm{sig}}\), and \(\Delta\) is the detuning frequency which specifies at what frequency the squeezed state rotates. This should be around the corner frequency between the radiation pressure and shot noise contributions.

Note

This method is particularly useful if you are not interested in modelling the specific technical details of the filter cavity, such as the filter cavity losses, or mode matching from the cavity into the detector.

We can use normal numpy math function and apply them to the Finesse 3.0 symbolic variable references for the signal frequency and detuning frequency. Once that is done we just simply run the model.

with model.temporary_parameters():
    model.FC_detune.value = 7.2
    model.sqz.db = 10
    # Give the variable references shorter names
    fsig = model.fsig.f.ref
    delta_f = model.FC_detune.ref
    # set the squeezing angle to be a symbolic function
    model.sqz.angle = (
        45 + 180/np.pi * np.arctan((fsig**2-delta_f**2)/(fsig**2 + delta_f**2))
    )
    print(model.sqz.angle) # print to see how it turned out
    sol_fc_sqz = model.run("xaxis(fsig, log, 1, 5k, 100)")

plt.loglog(sol_no_sqz.x1, abs(sol_no_sqz['NSR']), label='No squeezing')
plt.loglog(sol_shot_sqz.x1, abs(sol_shot_sqz['NSR']), label='Shot-noise squeezing')
plt.loglog(sol_rp_sqz.x1, abs(sol_rp_sqz['NSR']), label='Rad. pressure squeezing')
plt.loglog(sol_fc_sqz.x1, abs(sol_fc_sqz['NSR']), label='Filter cavity')
plt.legend()
plt.xlabel("Frequency [Hz]")
plt.ylabel("Sensitivity [h/sqrt{Hz}]");
(45+(57.29577951308232*arctan(((fsig.f)**(2)-(FC_detune)**(2))/((fsig.f)**(2)+(FC_detune)**(2))))) degrees
../_images/frequency_dependant_squeezing_3_1.svg

Some experimentation will be needing in choosing \(\phi_0\) and \(\Delta\) correctly and once that is done we should see we reduce the noise across the whole frequency range.

An alternative method is to use a minimization routine at each signal frequency and optimise the noise with respect to the squeezing angle. We can easily add this using the minimize action on the xaxis pre_step event.

# Run an optimiser to find ideal rotation
sol_opt_sqz = model.run("xaxis(fsig, log, 1, 5k, 100, pre_step=minimize(NSR, sqz.angle))")

plt.loglog(sol_no_sqz.x1, abs(sol_no_sqz['NSR']), label='No squeezing')
plt.loglog(sol_fc_sqz.x1, abs(sol_fc_sqz['NSR']), label='Filter cavity (symbolic)')
plt.loglog(sol_opt_sqz.x1, abs(sol_opt_sqz['NSR']), label='Filter cavity (optimized)', ls='--')
plt.legend()
plt.xlabel("Frequency [Hz]")
plt.ylabel("Sensitivity [h/sqrt{Hz}]");
../_images/frequency_dependant_squeezing_4_0.svg

As can be seen the symbolic and optimised version give the same improvements. The optimal squeezing angle can be extracted from the minimizsation solutions using

# Extract every optimisisation solution (`x`) from all the pre-step minimize actions
opt_angle = sol_opt_sqz["pre_step", "minimize"].x
plt.semilogx(sol_opt_sqz.x1, opt_angle)
plt.xlabel("Frequency [Hz]")
plt.ylabel("Optimal squeezing angle [deg]");
../_images/frequency_dependant_squeezing_5_0.svg

The optimization method can be used to determine the ideal rotation. Consider a case where we have a detuned SRC:

with model.temporary_parameters():
    model.FC_detune.value = 7.2
    model.SRM.phi += 0.1 # Detune slightly
    model.sqz.db = 10
    # Give the variable references shorter names
    fsig = model.fsig.f.ref
    delta_f = model.FC_detune.ref
    # set the squeezing angle to be a symbolic function
    model.sqz.angle = (
        45 + 180/np.pi * np.arctan((fsig**2-delta_f**2)/(fsig**2 + delta_f**2))
    )
    sol_fc_sqz = model.run("xaxis(fsig, log, 1, 5k, 100)")
    # Run an optimiser to find ideal rotation
    model.sqz.angle = 0
    sol_opt_sqz = model.run("xaxis(fsig, log, 1, 5k, 100, pre_step=minimize(NSR, sqz.angle))")

    plt.loglog(sol_no_sqz.x1, abs(sol_no_sqz['NSR']), label='No squeezing')
    plt.loglog(sol_fc_sqz.x1, abs(sol_fc_sqz['NSR']), label='Filter cavity (symbolic)')
    plt.loglog(sol_opt_sqz.x1, abs(sol_opt_sqz['NSR']), label='Filter cavity (optimized)')
    plt.legend()
    plt.xlabel("Frequency [Hz]")
    plt.ylabel("Sensitivity [h/sqrt{Hz}]");
    plt.title("Detuned SRC");
    plt.figure()
    # Extract every optimisisation solution (`x`) from all the pre-step minimize actions
    opt_angle = sol_opt_sqz["pre_step", "minimize"].x
    plt.semilogx(sol_opt_sqz.x1, opt_angle)
    plt.xlabel("Frequency [Hz]")
    plt.ylabel("Squeezing angle [deg]")
    plt.title("Optimised squeezing angle")
../_images/frequency_dependant_squeezing_6_0.svg../_images/frequency_dependant_squeezing_6_1.svg

We can see that the optimised version still maintains a perfect reduction in quantum noise. The symbolic case shows how much a filer cavity would be affected by such a detuning. In such a case a single filter cavity cannot correct this squeezing misrotation.

A more realistic model

In some models we are also interested in the details of the filter cavity itself and the propagation between it and the rest of the interferometer. This requires actually specifying the filter cavity in KatScript and optimising its parameters. Here we add a squeezer, an isolator, followed by a linear cavity to a new model building on our base model

model = base.deepcopy() # make a copy so we can add extra features
model.parse("""
sq sqz db=10
dbs FI
m FC1 L=0 T=0.0015
s sFC FC1.p2 FC2.p1 L=4e3
m FC2 L=0 T=0 phi=-0.022

link(sqz, FI.p1)
link(FI.p3, FC1)
link(FI.p4, OFI.p2)
""")

Some experimentation and calculations are needed to determine the filter cavity finesse, detuning, and length. All these parameters combine to give the correct frequency dependent rotation to the squeezed light. Once these are found we can run a quick optimisation at the corner frequency a few times to fine tune the aforementioned parameters. More detailed optimisation could also be performed here if necessary over the full frequency range. Below we also compare to the previous results using minimize in the pre_step event.

model.sqz.db = 10
model.sqz.angle = 90
model.FC1.T = 0.0015
model.FC1.R = 1-model.FC1.T.ref # Make sure we update the R correctly when we change T
model.FC2.phi = -0.022
# Optimise the NSR at the corner frequency
model.fsig.f = 7.2
# Run the optimisation a few times to reach a better level
for _ in range(3):
    model.run("minimize(NSR, [FC1.T, FC2.phi, sFC.L])")
# finally compute the NSR
sol_fc2_sqz = model.run("xaxis(fsig, log, 1, 5k, 100)")

plt.loglog(sol_no_sqz.x1, abs(sol_no_sqz['NSR']), label='No squeezing')
plt.loglog(sol_fc2_sqz.x1, abs(sol_fc2_sqz['NSR']), label='Filter cavity model')
plt.loglog(sol_opt_sqz.x1, abs(sol_opt_sqz['NSR']), label='Filter cavity (ideal)', ls='--')
plt.legend()
plt.xlabel("Frequency [Hz]")
plt.ylabel("Sensitivity [h/sqrt{Hz}]");
print("FC1.T =", model.FC1.T)
print("FC2.phi =", model.FC2.phi)
print("FC L =", model.sFC.L)
FC1.T = 0.001647473115045308
FC2.phi = -0.023938740532193054 degrees
FC L = 3893.2089336620093 m
../_images/frequency_dependant_squeezing_8_1.svg

As we can see from the final plot and parameter optimisation results a more detailed model of a filter cavity can be created and added to a model. This is nearly reaching the ideal filter cavity response but a more careful selection of parameters is needed.

Including geometric effects would require specifying curvatures of the filter cavity mirrors as well as a mode matching telescope to the rest of the interferometer. If these details are not of interest then we would recommend using the symbolic filter cavity method, as this reduces the computational complexity and setup time.

Click to download example as python script

Click to download example as Jupyter notebook