Trace verbosity

The trace command in Finesse 2 provided options for printing various beam tracing and coupling coefficient information associated with a file. On this page we will detail the replacements (and improvements) for these options.

For a brief summary of the Finesse 2 trace options and how these translate into the new features, see the table below. The descriptions of each integer, n, in terms of the Finesse 2 behaviour can be found in the old syntax reference.

Old trace option, n

New Finesse equivalent

1

The Model.modes() method.

2

Print the ModelElement.info_parameter_table() of Cavity objects.

4

See Model.mismatches_table().

8

Display the BeamTraceSolution via printing return of Model.beam_trace().

16

See Model.space_gouys_table().

32

Use KnmDetector objects.

64

Use ModeMismatchDetector objects.

128

Draw the TraceForest via printing Model.trace_forest.

We will now go into a bit more detail on each of these options, in terms of the recommended ways to use them. For all of the following sections we will employ the example of a Fabry-Perot Michelson interferometer with aLIGO-like geometrical parameters for the arm cavities:

import finesse
finesse.configure(plotting=True)

model = finesse.Model()
model.parse("""
# Radii of curvature of arm cavity mirrors
var XROC1 -1934
var XROC2 2245
var YROC1 -1934
var YROC2 2245

l L0 P=1
link(L0, BS)
bs BS

s sy BS.p2 ITMY.p1 L=10
m ITMY R=0.99 T=0.01 Rc=[YROC1, YROC1]
s LY ITMY.p2 ETMY.p1 L=4k
m ETMY R=0.99 T=0.01 Rc=[YROC2, YROC2]

s sx BS.p3 ITMX.p1 L=10
m ITMX R=0.99 T=0.01 Rc=[XROC1, XROC1]
s LX ITMX.p2 ETMX.p1 L=4k
m ETMX R=0.99 T=0.01 Rc=[XROC2, XROC2]

cav cavXARM ITMX.p2
cav cavYARM ITMY.p2
""")

Note that the use of variables here for the radii of curvature of the mirrors allows us to ensure that these mirrors remain non-astigmatic; as the references will update for both planes whenever these variables change. We also separate these into X-arm and Y-arm variables such that updating one set doesn’t affect the other arm cavity. If it was useful to do so, we could simply have a single pair of RoC values instead — thereby ensuring both arm cavities remain equivalent.

Each section heading below describes the task with the old ‘trace’ command equivalent in parentheses.

Listing TEM modes used (‘trace 1’)

One can simply query the modes included in a model with the Model.modes() function. In the case of our example above, we have:

print(model.modes())
[[0 0]]

Note that the existence of the Cavity objects here automatically switches on the modal basis with only the fundamental (pure Gaussian) mode modelled. The modal mode can be explicitly turned off also using Model.modes():

print(model.modes("off"))
None

If we select additional modes via Model.modes() and print the attribute again then we will see this reflected in the output:

model.modes(maxtem=2)
print(model.modes())
[[0 0]
 [1 0]
 [0 1]
 [2 0]
 [1 1]
 [0 2]]

See also

Selecting the modes to model for a comprehensive guide on mode selection.

Accessing cavity parameters (‘trace 2’)

There are several ways to obtain properties of Cavity objects. To display a verbose table of all the parameters (in their current state) associated with a cavity:

print(model.cavXARM.info_parameter_table())
┌───────────────────────┬─────────────────────────────────┐
│ Description           │ Value                           │
╞═══════════════════════╪═════════════════════════════════╡
│ FSR                   │ 37474.05725                     │
├───────────────────────┼─────────────────────────────────┤
│ Loss                  │ 0.01990000000000003             │
├───────────────────────┼─────────────────────────────────┤
│ Finesse               │ 312.5845222828291               │
├───────────────────────┼─────────────────────────────────┤
│ FWHM                  │ 119.88455786717795              │
├───────────────────────┼─────────────────────────────────┤
│ Storage time          │ 0.002655136673535981            │
├───────────────────────┼─────────────────────────────────┤
│ Pole                  │ 59.94227893358897               │
├───────────────────────┼─────────────────────────────────┤
│ Round trip length     │ 8000.0                          │
├───────────────────────┼─────────────────────────────────┤
│ Waist size            │ [0.01195054 0.01195054]         │
├───────────────────────┼─────────────────────────────────┤
│ Waist position        │ [-1837.21538864 -1837.21538864] │
├───────────────────────┼─────────────────────────────────┤
│ Stability (m-factor)  │ [0.67018515 0.67018515]         │
├───────────────────────┼─────────────────────────────────┤
│ Stability (g-factor)  │ [0.83509258 0.83509258]         │
├───────────────────────┼─────────────────────────────────┤
│ Round trip gouy phase │ [312.08135656 312.08135656]     │
├───────────────────────┼─────────────────────────────────┤
│ Mode separation       │ [4988.07218818 4988.07218818]   │
├───────────────────────┼─────────────────────────────────┤
│ Resolution            │ [41.60729519 41.60729519]       │
├───────────────────────┼─────────────────────────────────┤
│ Stable                │ True                            │
├───────────────────────┼─────────────────────────────────┤
│ Critically stable     │ False                           │
└───────────────────────┴─────────────────────────────────┘

In this case this displays the parameters associated with our cavXARM cavity.

For those parameters which have two values, e.g. the “waist size”, the first value represents the quantity in the tangential plane whilst the second value is for the sagittal plane.

Accessing the actual values, rather than displaying them, is as simple as “calling” the relevant property of the Cavity class. For example, if we want to get the eigenmode of cavYARM in the tangential plane and do something with the value (e.g. access the wavefront RoC of this mode), we can do:

qx_yarm = model.cavYARM.qx
# Do something the the BeamParam qx_yarm, e.g. get the RoC of the wavefront
print(qx_yarm.Rc)
-1934.0

Note

To obtain any of the parameters of the cavity during a simulation, use the CavityPropertyDetector.

Current mode mismatch parameters (‘trace 4’)

Printing the current mode mismatch state (see BeamParam.mismatch() for a description of the figure-of-merit mismatch quantity used) of a model is simple:

print(model.mismatches_table())
┌──────────╥──────────────┬──────────────┐
│ Coupling ║ Mismatch (x) │ Mismatch (y) │
└──────────╨──────────────┴──────────────┘

Of course, this gives an empty table for this example currently as both arm cavities are equivalent in terms of geometric parameters and the interferometer is symmetric. For illustration, we can alter one of these cavities temporarily to show the resulting mode mismatch that then occurs at the central beam splitter (where the trace trees intersect):

# Temporarily change the RoC of ITMY using a context manager
with model.temporary_parameters():
    model.YROC1.value = -2000
    print(model.mismatches_table())
┌────────────────────╥──────────────┬──────────────┐
│      Coupling      ║ Mismatch (x) │ Mismatch (y) │
╞════════════════════╬══════════════╪══════════════╡
│ BS.p1.i -> BS.p2.o ║       0.0075 │       0.0075 │
├────────────────────╫──────────────┼──────────────┤
│ BS.p2.i -> BS.p1.o ║       0.0075 │       0.0075 │
├────────────────────╫──────────────┼──────────────┤
│ BS.p2.i -> BS.p4.o ║       0.0075 │       0.0075 │
├────────────────────╫──────────────┼──────────────┤
│ BS.p4.i -> BS.p2.o ║       0.0075 │       0.0075 │
└────────────────────╨──────────────┴──────────────┘

If you want to access the actual mismatch values themselves, you can use Model.detect_mismatches(). For example, we can access just the mismatch for the coupling BS.p1.i -> BS.p2.o with:

with model.temporary_parameters():
    model.spaces.LX.L = 4100
    mismatches = model.detect_mismatches()
    print(mismatches[(model.BS.p1.i, model.BS.p2.o)])
{'x': 0.04390880807812923, 'y': 0.04390880807812923}

As you can see, this returns a dictionary of the mismatch values for this node coupling in both the tangential (x) and sagittal (y) planes.

Beam parameters for every node (‘trace 8’)

Displaying the values for the beam parameters at each node can be done via printing the BeamTraceSolution object — which is returned when performing a Model.beam_trace() and is also stored in the Model.last_trace attribute.

Again, using our example:

# Execute a beam trace using current model state
trace = model.beam_trace()
# Draw the trace solution in a forest format
print(trace)

Internal trace of cavity: cavXARM (q = -1837.22 + 421.68j, g = 0.8350925761717988)

    o ITMX.p2.o: [q = -1837.22 + 421.68j]
    ╰──o ETMX.p1.i: [q = 2162.78 + 421.68j]
       ╰──o ETMX.p1.o: [q = -2162.78 + 421.68j]
          ╰──o ITMX.p2.i: [q = 1837.22 + 421.68j]

Internal trace of cavity: cavYARM (q = -1837.22 + 421.68j, g = 0.8350925761717988)

    o ITMY.p2.o: [q = -1837.22 + 421.68j]
    ╰──o ETMY.p1.i: [q = 2162.78 + 421.68j]
       ╰──o ETMY.p1.o: [q = -2162.78 + 421.68j]
          ╰──o ITMY.p2.i: [q = 1837.22 + 421.68j]

Dependency: cavXARM

    o ETMX.p1.i: [q = 2162.78 + 421.68j]
    ╰──o ETMX.p2.o: [q = 2162.78 + 421.68j]

Dependency: cavXARM

    o ITMX.p2.i: [q = 1837.22 + 421.68j]
    ╰──o ITMX.p1.o: [q = 1837.22 + 421.68j]
       ╰──o BS.p3.i: [q = 1847.22 + 421.68j]
          ├──o BS.p4.o: [q = 1847.22 + 421.68j]
          ╰──o BS.p1.o: [q = 1847.22 + 421.68j]
             ╰──o L0.p1.i: [q = 1847.22 + 421.68j]

Dependency: cavYARM

    o ETMY.p1.i: [q = 2162.78 + 421.68j]
    ╰──o ETMY.p2.o: [q = 2162.78 + 421.68j]

Dependency: cavYARM

    o ITMY.p2.i: [q = 1837.22 + 421.68j]
    ╰──o ITMY.p1.o: [q = 1837.22 + 421.68j]
       ╰──o BS.p2.i: [q = 1847.22 + 421.68j]

The order in which these solution trees are displayed corresponds exactly to the order in which the beam traces were performed on the model. The TraceDependency object associated with each trace tree is printed above the tree.

The BeamTraceSolution provides a dict-like interface for accessing beam parameters, and so obtaining such values is simple. For example, if we want the beam parameter at the laser output from our above trace:

# Get the beam parameters in both planes
qx, qy = trace[model.L0.p1.o]

# Equivalent but alternative syntax:
qx = trace[model.L0.p1.o].qx
qy = trace[model.L0.p1.o].qy

# As both qx and qy are BeamParam instances we can then access any
# beam parameter property of these or just print them, e.g:
print(qx)
BeamParam(w0=11.951 mm, z=-1.8472 km, w=53.697 mm, Rc=-1.9435 km)

Access Gouy phases for all spaces (‘trace 16’)

One can print all the Gouy phases accumulated over each individual space, using the current state of the model, via:

print(model.space_gouys_table())
┌──────────────╥────────────────┬────────────────┐
│    Space     ║ Gouy (x) [deg] │ Gouy (y) [deg] │
╞══════════════╬════════════════╪════════════════╡
│ L0_p1__BS_p1 ║         0.0000 │         0.0000 │
├──────────────╫────────────────┼────────────────┤
│      sy      ║         0.0676 │         0.0676 │
├──────────────╫────────────────┼────────────────┤
│      LY      ║       156.0407 │       156.0407 │
├──────────────╫────────────────┼────────────────┤
│      sx      ║         0.0676 │         0.0676 │
├──────────────╫────────────────┼────────────────┤
│      LX      ║       156.0407 │       156.0407 │
└──────────────╨────────────────┴────────────────┘

This shows each space in the model with the corresponding Gouy phases in both the tangential (x) and sagittal (y) planes. The values are in degrees by default, but can be switched to radians as shown by the parameter list of Model.space_gouys_table().

If you want to access the actual Gouy phase values themselves, you can use Model.compute_space_gouys(). For example, we can access just the Gouy phase accumulated over the space sy with:

gouys = model.compute_space_gouys()
print(gouys[model.spaces.sy])
{'x': 0.0676471163098868, 'y': 0.0676471163098868}

See also

For tracking the accumulated Gouy phase over an arbitrary length sequence of spaces during a simulation, use the Gouy detector.

And for outside of a simulation, you can use Model.acc_gouy() to calculate the Gouy phase accumulated over a given path. Another recommended method for doing this (and much more at the same time) is Model.propagate_beam() which returns a PropagationSolution containing methods and attributes for obtaining Gouy phases accumulated over paths and sub-paths; along with many more useful features.

Coupling coefficients of components (‘trace 32’)

The simplest way to obtain the values of coupling coefficients, for couplings at any component, is to add corresponding KnmDetector instances to a model.

For example, let’s misalign ITMX in our interferometer by rotating it by 0.5% of the cavity divergence angle in the yaw degree of freedom. Then we will add a KnmDetector for probing the scattering matrix on reflection from this mirror at the inner-surface:

# Add a KnmDetector for K22 matrix of ITMX
model.parse("knmd K22_itmx ITMX 22")
# Temporarily misalign ITM.xbeta by 0.5% of the cavXARM divergence angle
with model.temporary_parameters():
    model.ITMX.xbeta = model.cavXARM.qx.divergence * 0.5 / 100
    # Run the model with no parameter sweep
    out = model.run()

Our out object now contains an entry "K22_itmx" which consists of the mode scattering matrix data for the specified coupling. We can wrap this up neatly using a KnmMatrix and print it, for example:

from finesse.knm.matrix import KnmMatrix

# The from_buffer method needs to know what higher-order modes were used so pass this as second arg
K22_itmx = KnmMatrix.from_buffer(out["K22_itmx"], model.modes())

print(K22_itmx)
K[0][0]: 0 0 -> 0 0 = (0.9990013731832287+0j)
K[1][0]: 0 0 -> 1 0 = (-2.976989016666019e-18+0.04465716662614314j)
K[2][0]: 0 0 -> 0 1 = 0j
K[3][0]: 0 0 -> 2 0 = (-0.0014115661870374835-4.2185087357482133e-19j)
K[4][0]: 0 0 -> 1 1 = (-0+0j)
K[5][0]: 0 0 -> 0 2 = 0j
K[0][1]: 1 0 -> 0 0 = (-1.0900798791148438e-17-0.04465716662614314j)
K[1][1]: 1 0 -> 1 0 = (-0.997005117137333+2.2137960734299366e-16j)
K[2][1]: 1 0 -> 0 1 = 0j
K[3][1]: 1 0 -> 2 0 = (1.7478612144409567e-17-0.06309167114051635j)
K[4][1]: 1 0 -> 1 1 = (-0+0j)
K[5][1]: 1 0 -> 0 2 = 0j
K[0][2]: 0 1 -> 0 0 = 0j
K[1][2]: 0 1 -> 1 0 = (-0+0j)
K[2][2]: 0 1 -> 0 1 = (0.9990013731832287-2.2182286522803378e-16j)
K[3][2]: 0 1 -> 2 0 = 0j
K[4][2]: 0 1 -> 1 1 = (2.6513310074939703e-17+0.044657166626143135j)
K[5][2]: 0 1 -> 0 2 = 0j
K[0][3]: 2 0 -> 0 0 = (-0.0014115661870374838+4.455108644135821e-19j)
K[1][3]: 2 0 -> 1 0 = (1.7215857375126575e-17+0.06309167114051635j)
K[2][3]: 2 0 -> 0 1 = 0j
K[3][3]: 2 0 -> 2 0 = (0.9950108556023098+2.2093679232833226e-16j)
K[4][3]: 2 0 -> 1 1 = (-0+0j)
K[5][3]: 2 0 -> 0 2 = 0j
K[0][4]: 1 1 -> 0 0 = 0j
K[1][4]: 1 1 -> 1 0 = (-0+0j)
K[2][4]: 1 1 -> 0 1 = (-1.0900798791148438e-17-0.04465716662614314j)
K[3][4]: 1 1 -> 2 0 = 0j
K[4][4]: 1 1 -> 1 1 = (-0.997005117137333+6.64138822028981e-16j)
K[5][4]: 1 1 -> 0 2 = 0j
K[0][5]: 0 2 -> 0 0 = 0j
K[1][5]: 0 2 -> 1 0 = (-0+0j)
K[2][5]: 0 2 -> 0 1 = 0j
K[3][5]: 0 2 -> 2 0 = 0j
K[4][5]: 0 2 -> 1 1 = (-0+0j)
K[5][5]: 0 2 -> 0 2 = (0.9990013731832289+2.2182286522803383e-16j)

The displayed output here gives the coupling coefficients for the modes n m -> n' m' as shown.

Mode mismatches during a simulation (‘trace 64’)

Obtaining mode mismatch values during a simulation can be achieved via the use of ModeMismatchDetector objects. Using our interferometer above, we can, for example, scan over the RoC of ITMX during a simulation and detect the corresponding mode mismatch which occurs from port 2 to port 4 of the beam splitter (due to the intersecting trace trees from cavXARM and cavYARM at this component):

model.parse("""
# This will detect mode mismatch from BS.p2.i -> BS.p4.o
mmd mm_bs_x BS.p2.i BS.p4.o direction=x
mmd mm_bs_y BS.p2.i BS.p4.o direction=y

# Sweep the RoC of the ITM in the x-arm
xaxis(XROC1, lin, -1900, -2000, 100)
""")

out = model.run()
out.plot("mm_bs_x", "mm_bs_y");
../_images/trace_verbosity_15_0.svg

The resulting plot shows the requested mode mismatch quantity as a function of the swept parameter. One can specify the plane to detect in via the direction argument of this detector, as described in ModeMismatchDetector. The mismatch quantities are the same in both planes in this case as scanning XROC1 changes both the tangential and sagittal planes of the RoC of the ITM in the X-arm.

Nodes found during the beam tracing (‘trace 128’)

This option was already essentially covered by the beam trace solution printing section earlier, however one can also access the underlying TraceForest structure of a model itself via the Model.trace_forest attribute. You can print this to get a representation of how the beam traces are structured in the current model state:

print(model.trace_forest)

Dependency: cavXARM [Cavity]

  o ITMX.p2.o
  ╰──o ETMX.p1.i
     ╰──o ETMX.p1.o
        ╰──o ITMX.p2.i

Dependency: cavYARM [Cavity]

  o ITMY.p2.o
  ╰──o ETMY.p1.i
     ╰──o ETMY.p1.o
        ╰──o ITMY.p2.i

Dependency: cavXARM [Cavity]

  o ETMX.p1.i
  ╰──o ETMX.p2.o

  o ITMX.p2.i
  ╰──o ITMX.p1.o
     ╰──o BS.p3.i
        ├──o BS.p4.o
        ╰──o BS.p1.o
           ╰──o L0.p1.i

Dependency: cavYARM [Cavity]

  o ETMY.p1.i
  ╰──o ETMY.p2.o

  o ITMY.p2.i
  ╰──o ITMY.p1.o
     ╰──o BS.p2.i

This gives all of the trace trees (with the associated optical nodes) in the order in which beam traces will traverse them during the beam tracing algorithms.

One can easily change the priority of TraceDependency instances of the model and see how this is reflected in the new forest structure. For example, here we set cavYARM as the highest priority now such that this will be traced before cavXARM:

# Gives cavYARM the highest priority (one can also set
# priority manually via model.cavYARM.priority = value)
model.cavYARM.take_priority()

# We can check the tracing order via this method call
print("Trace order = ", model.trace_order_names)

# Perform a new beam trace so that trace_forest gets re-planted
model.beam_trace()

print(model.trace_forest)
Trace order =  ['cavYARM', 'cavXARM']

Dependency: cavYARM [Cavity]

  o ITMY.p2.o
  ╰──o ETMY.p1.i
     ╰──o ETMY.p1.o
        ╰──o ITMY.p2.i

Dependency: cavXARM [Cavity]

  o ITMX.p2.o
  ╰──o ETMX.p1.i
     ╰──o ETMX.p1.o
        ╰──o ITMX.p2.i

Dependency: cavYARM [Cavity]

  o ETMY.p1.i
  ╰──o ETMY.p2.o

  o ITMY.p2.i
  ╰──o ITMY.p1.o
     ╰──o BS.p2.i
        ├──o BS.p1.o
        │   ╰──o L0.p1.i
        ╰──o BS.p4.o

Dependency: cavXARM [Cavity]

  o ETMX.p1.i
  ╰──o ETMX.p2.o

  o ITMX.p2.i
  ╰──o ITMX.p1.o
     ╰──o BS.p3.i

And now we can see that the ordering has changed as expected.

Todo

Link to a separate page covering trace ordering in more details once written.