Calculating general transfer functions¶
There are three types of nodes in Finesse that inject and probe different types of signals (see also The port and node system):
Optical: Optical modes.
Mechanical: Mechanical degrees of freedom such as lengths and angles (pitch and yaw), and forces and torques.
Electrical: electrical signals such as outputs from photodetectors and
DegreeOfFreedom
components.
Note
Whilst differentiated in descriptions, internally electrical and mechanical
nodes are both SignalNode
attached to an element. Such as the laser amplitude signal laser.amp,
or mirror.mech.z. Signal nodes represent a small AC oscillations at the model.fsig
frequency.
The previous section described how transfer functions are computed from electrical and
mechanical nodes to electrical and mechanical nodes using the
FrequencyResponse
action. There are a total of four frequency response actions
which calculate transfer functions between optical nodes as well so that optical modes
can be probed directly. This is especially useful when analyzing quantum noise as
described in two_photon_transfer. The four actions are
FrequencyResponse
: {electrical,mechanical} -> {electrical,mechanical}
FrequencyResponse2
: optical -> {electrical,mechanical}
FrequencyResponse3
: optical -> optical
FrequencyResponse4
: {electrical,mechanical} -> optical
In this section we use these four actions to analyze a simple Fabry-Perot cavity with Pound-Drever-Hall [25] readout.
Setting up the model¶
import numpy as np
import finesse
import finesse.components as fc
import finesse.analysis.actions as fa
from finesse.plotting import bode
finesse.init_plotting()
Fmod_Hz = 9e6
model = finesse.Model()
ETM = fc.Mirror("ETM", T=0, L=0, Rc=2245)
ITM = fc.Mirror("ITM", T=0.014, L=0, Rc=1940)
Laser = fc.Laser("Laser", P=5e3)
Mod = fc.Modulator("Mod", f=Fmod_Hz, midx=0.2, order=1)
REFL = fc.ReadoutRF("REFL", ITM.p2.o, f=Fmod_Hz, output_detectors=True)
model.add((Laser, Mod, ETM, ITM, REFL))
model.link(Laser.p1, Mod, ITM.p2, ITM.p1, 4e3, ETM.p1)
model.phase_config(zero_k00=False)
If you are using higher order modes, you would also add a Cavity
to use the mode of the cavity to define the
modal basis. See Defining the modal basis for details. Here we manually set a
mismatched beam parameter for illustrative purposes so that there will be
coupling between the HOMs and the fundamental.
# Usually we would do this
# model.add(fc.Cavity("cavARM", model.ETM.p1.o))
# and not this
model.ITM.p1.i.q = finesse.BeamParam(w=55e-3, Rc=1943)
model.modes("even", maxtem=2)
print(model.mismatches_table())
┌──────────────────────╥──────────────┬──────────────┐
│ Coupling ║ Mismatch (x) │ Mismatch (y) │
╞══════════════════════╬══════════════╪══════════════╡
│ ETM.p1.i -> ETM.p1.o ║ 0.0033 │ 0.0033 │
├──────────────────────╫──────────────┼──────────────┤
│ ETM.p2.i -> ETM.p2.o ║ 0.0033 │ 0.0033 │
├──────────────────────╫──────────────┼──────────────┤
│ ITM.p1.i -> ITM.p1.o ║ 0.0001 │ 0.0001 │
├──────────────────────╫──────────────┼──────────────┤
│ ITM.p2.i -> ITM.p2.o ║ 0.0001 │ 0.0001 │
└──────────────────────╨──────────────┴──────────────┘
This is fair bit of mismatch, so we need to add a Lock
to make sure the cavity
is on the correct operating point before calculating transfer functions. See
Locking actions for details.
model.add(
finesse.locks.Lock("cav_lock", model.REFL.outputs.I, model.ETM.phi, 1e-4,
1e-6)
);
Calculating the transfer functions¶
The FrequencyResponseN
actions are called like
FrequencyResponseN(F_Hz, [input_nodes], [output_nodes], name="action name")
For FrequencyResponse{2,3,4}
which include optical nodes, input and output optical
nodes need to be listed as a tuple (node, freq)
where freq
is the frequency the
sideband is calculated at. This frequency must be a symbolic reference to the model’s
fsig.f
parameter.
Note
Positive frequencies correspond to upper sidebands while negative frequencies correspond to the conjugate of the lower sidebands, not the lower sidebands. Therefore, propagation of both result in a phase delay.
For example, with nodes defined as
fsig = model.fsig.f.ref
carrier_nodes = [
(node, +fsig),
(node, -fsig),
]
rf_sideband_nodes = [
(node, Fmod_Hz + fsig),
(node, Fmod_Hz - fsig),
(node, -Fmod_Hz + fsig),
(node, -Fmod_Hz - fsig),
]
the first node in carrier_nodes
is the upper sideband of the carrier and the second
node is the conjugate of the lower sideband of the carrier. The first two nodes listed
in rf_sideband_nodes
are the upper sideband and conjugate of the lower sideband
around the RF sideband at +Fmod_Hz
. The second two nodes are the upper and conjugate
of the lower sideband around the RF sideband at -Fmod_Hz
.
In our example we calculate the response at the reflection of the ITM to signals excited at or by the ETM for the different types of signals.
# Run the four FrequencyResponse actions
F_Hz = np.geomspace(1, 2e3, 200)
model.fsig.f = 1 # make sure to set this or you'll get a warning
fsig = model.fsig.f.ref # Make a shorthand reference to the fsig symbol
# FrequencyResponse actions need a symbolic reference to frequency
sol = model.run(
fa.Series(
# First lock the cavity
fa.RunLocks(),
# ETM motion to PDH readout
fa.FrequencyResponse(
F_Hz,
[model.ETM.mech.z],
[model.REFL.I.o, model.REFL.Q.o],
name="fresp",
),
# ETM optical fields to PDH readout
fa.FrequencyResponse2(
F_Hz,
[
(model.ETM.p1.o, +fsig), # upper sideband
(model.ETM.p1.o, -fsig), # conjugate lower sideband
],
[model.REFL.I.o, model.REFL.Q.o],
name="fresp2",
),
# ETM optical fields to ITM optical fields
fa.FrequencyResponse3(
F_Hz,
[
(model.ETM.p1.o, +fsig),
(model.ETM.p1.o, -fsig),
],
[
(model.ITM.p2.o, +fsig),
(model.ITM.p2.o, -fsig),
],
name="fresp3",
),
# ETM motion to ITM optical fields
fa.FrequencyResponse4(
F_Hz,
[model.ETM.mech.z],
[
(model.ITM.p2.o, +fsig),
(model.ITM.p2.o, -fsig),
],
name="fresp4",
),
)
)
The transfer functions are returned in the out
attribute of the solutions. The
dimensions returned for each action are
FrequencyResponse
: [frequencies, outputs, inputs]
FrequencyResponse2
: [frequencies, outputs, inputs, (input) HOMs]
FrequencyResponse3
: [frequencies, outputs, inputs, (output) HOMs, (input) HOMs]
FrequencyResponse4
: [frequencies, outputs, inputs, (output) HOMs]
The transfer functions computed with FrequencyResponse
can also be accessed
through the output and input keys like sol["fresp"]["out", "in"]
. In this example
each of the solutions has three HOMs where
for idx, hom in enumerate(model.homs):
print(f"index {idx} corresponds to HG{''.join(hom.astype(str))}")
index 0 corresponds to HG00
index 1 corresponds to HG20
index 2 corresponds to HG02
Frequency response of the fundamental mode¶
In this example, FrequencyResponse
calculates ETM mirror motion to the PDH
REFL readout. There are two outputs (the PDH quadratures) and one input (ETM motion).
Here we plot mirror motion to the I and Q PDH readout.
print("FrequencyResponse shape", sol["fresp"].out.shape)
axs = bode(F_Hz, sol["fresp"].out[..., 0, 0], db=False, label="REFL I")
bode(F_Hz, sol["fresp"].out[..., 1, 0], axs=axs, db=False, label="REFL Q", ls="--")
axs[0].set_ylabel("Magnitude [W / m]")
axs[0].set_title("ETM mirror motion to REFL readout");
FrequencyResponse shape (200, 2, 1)
As noted above, transfer functions calculated with FrequencyResponse
can also
be accessed by their input and output keys:
print(np.all(sol["fresp"]["REFL.I.o", "ETM.mech.z"] == sol["fresp"].out[..., 0, 0]))
print(np.all(sol["fresp"]["REFL.Q.o", "ETM.mech.z"] == sol["fresp"].out[..., 1, 0]))
True
True
In this example, FrequencyResponse2
calculates optical fields to PDH REFL
readout. It has the same two outputs as FrequencyResponse
and two inputs (the
upper and lower sidebands of the optical fields at the ETM). Here we plot the upper and
conjugate lower sidebands of the optical fields at the front of the ETM to the REFL I
PDH readout.
print("FrequencyResponce2 shape", sol["fresp2"].out.shape)
axs = bode(F_Hz, sol["fresp2"].out[..., 0, 0, 0], db=False, label="upper")
bode(F_Hz, sol["fresp2"].out[..., 0, 1, 0], axs=axs, db=False, label="lower", ls="--")
axs[0].set_ylabel("Magnitude [W / $\sqrt{\mathrm{W}}$]")
axs[0].set_title("ETM optical field to REFL I readout");
FrequencyResponce2 shape (200, 2, 2, 3)
In this example, FrequencyResponse3
calculates optical fields at the ETM to
optical fields at the ITM. There are two outputs (upper and lower sidebands of the
fields at the ITM) and two inputs (upper and lower sidebands of the fields at the ETM).
Here we plot the upper and conjugate lower sidebands at the front of the ETM to the
sidebands at the back of the ITM.
print("FrequencyResponse3 shape", sol["fresp3"].out.shape)
axs = bode(F_Hz, sol["fresp3"].out[..., 0, 0, 0, 0], db=False, label="upper")
bode(F_Hz, sol["fresp3"].out[..., 1, 1, 0, 0], axs=axs, db=False, label="lower", ls="--")
axs[0].set_ylabel("Magnitude [$\sqrt{\mathrm{W}} / \sqrt{\mathrm{W}}$]")
axs[0].set_title("ETM optical field to reflected optical field");
FrequencyResponse3 shape (200, 2, 2, 3, 3)
There is no mixing between the uppper and lower sidebands in this case:
print(np.allclose(sol["fresp3"].out[..., 1, 0, :, :], 0))
print(np.allclose(sol["fresp3"].out[..., 0, 1, :, :], 0))
True
True
In this example, FrequencyResponse4
calculates ETM mirror motion to optical
fields at the back of the ITM. There are two outputs (upper and lower sidebands of the
fields at the ITM) and one input (ETM motion). Here we plot ETM mirror motion to the
upper and conjugate lower sidebands at the back of the ITM.
print("FrequencyResponse4 shape", sol["fresp4"].out.shape)
axs = bode(F_Hz, sol["fresp4"].out[..., 0, 0, 0], db=False, label="upper")
bode(F_Hz, sol["fresp4"].out[..., 1, 0, 0], axs=axs, db=False, ls="--", label="lower")
axs[0].set_ylabel("Magnitude [$\sqrt{\mathrm{W}}$ / m]")
axs[0].set_title("Mirror motion to reflected optical field");
FrequencyResponse4 shape (200, 2, 1, 3)
Frequency response between HOMs¶
We can also look at transfer functions between HOMs. Here we plot the HG20 upper and conjugate lower sidebands at the front of the ETM to the HG00 upper and conjugate lower sidebands at the back of the ITM.
axs = bode(F_Hz, sol["fresp3"].out[..., 0, 0, 0, 1], db=False, label="upper")
bode(F_Hz, sol["fresp3"].out[..., 1, 1, 0, 1], axs=axs, db=False, label="lower", ls="--")
axs[0].set_ylabel("Magnitude [$\sqrt{\mathrm{W}} / \sqrt{\mathrm{W}}$]")
axs[0].set_title("ETM HG20 optical field to reflected HG00 optical field");
Here we plot mirror motion to the HG02 upper and conjugate lower sidebands at the back of the ITM.
axs = bode(F_Hz, sol["fresp4"].out[..., 0, 0, 2], db=False, label="upper")
bode(F_Hz, sol["fresp4"].out[..., 1, 0, 2], axs=axs, db=False, ls="--", label="lower")
axs[0].set_ylabel("Magnitude [$\sqrt{\mathrm{W}}$ / m]")
axs[0].set_title("Mirror motion to reflected HG02 optical field");
Similarly, the response of REFL.Q
to excitation of the conjugate lower HG02 mode at
the ETM is given by sol["fresp2"].out[..., 1, 1, 2]
, etc.