KatScript

Finesse includes a built-in definition language called KatScript that provides an alternative way to build models without using the Python API. It is concise, simple, and easy to use. The following contains a brief introduction to KatScript syntax and the available instructions.

Supported elements, commands and analyses are described in their corresponding sections. There are also command, element and analysis indices for quick reference.

Quick reference

You can quickly get help for particular syntax in an interactive Python environment by calling the syntax() function:

import finesse
print(finesse.syntax("mirror"))
m / mirror: m name R=none T=none L=none phi=0 Rc=inf xbeta=0 ybeta=0 misaligned=falseNone

From the Command line interface you can also use:

$ kat3 syntax mirror
$ kat3 syntax pd*  # Wildcards are allowed too.

A quick example

Optical models are built by specifying components, detectors, commands, and other types of statement on separate lines:

# Example of 10 W of laser power incident upon a Fabry-Perot cavity.
l l1 P=10
s s1 l1.p1 m1.p1
m m1 R=0.99 T=0.01
s s2 m1.p2 m2.p1
m m2 R=0.99 T=0.01
pd pd1 m2.p1.i
modes(even, maxtem=10)

To use KatScript in Python you will need to wrap it in a call to Model.parse():

from finesse import Model

model = Model()

# Parse the simple model from above
model.parse(
    """
    # Example of 10 W of laser power incident upon a Fabry-Perot cavity.
    l l1 P=10
    s s1 l1.p1 m1.p1
    m m1 R=0.99 T=0.01
    s s2 m1.p2 m2.p1
    m m2 R=0.99 T=0.01
    pd pd1 m2.p1.i
    modes(even, maxtem=10)
    """
)

Most statements are formed from a sequence of space-delimited values; these represent elements of the model. Elements always have unique names, which must be defined after the element type, which is specified first. The element type is typically, but not always, represented by a short one or two character name. The arguments following the element name are used to customise the element, such as to set the reflectivity and transmissivity in the case of the mirrors.

The last line is a command which sets some properties of the model or simulation. In this case, it’s setting which higher order modes to simulate. Commands use “functional” form, with comma-delimited parameters inside parentheses.

Language fundamentals

Whitespace and comments

Whitespace can be simple spaces, tabs and newlines. Comments start with #. Everything including and after # on the same line is ignored by the parser.

# This is a comment!

Whitespace is sometimes important in KatScript, for instance between parameters of elements.

Types, names, and keywords

Component types (like mirror), names (like m1) and keywords (like lin) are written directly, without quotation marks:

mirror m1  # A mirror named 'm1'.

User-defined names have to start with a letter, but can otherwise contain integer numbers and underscores.

Numbers

Numbers can use integer, floating point, scientific and complex notation. The imaginary unit is j. Numbers can include _ between digits to assist clarity, e.g. 1_000_000. SI prefices are supported, but cannot be combined with scientific notation. Integers with leading zeros, e.g. 01, are not allowed.

Some valid number examples:

# Typical number forms.
1
1.
1.2
-3.141
6.5+2.0j

# Scientific notation.
1.1e7   # 11000000.0
7.6e-2  # 0.076

# SI prefices. Any of p, n, u, m, k, M, G, and T are supported.
1m  # 0.001
1k  # 1000.0

# Separation of digits using `_`.
1_234_567       # 1234567
1_234.5_678     # 1234.5678
1_234e6         # 1234000000.0
1_234j          # 1234j

Components and detectors

Component and detector definitions, collectively referred to as elements, for the most part mirror their Python API signatures. Both positional and keyword arguments are supported. Types of element can be referred to using either their full name or an abbreviated short form. They are defined using syntax of the form element name arg1 arg2 arg3=val3 ...:

# Definition with positional arguments (Finesse 2 style): a mirror named "m1" with
# reflectivity R=0.99 and transmissivity T=0.01.
mirror m1 0.99 0.01

# Definition with keyword arguments.
# Note that only two of "R", "T" and "L" are required, and the third is calculated
# from the first two. You can still specify all three, but they must all add up to
# 1.
mirror m2 R=0.99 T=0.01

# You can mix positional and keyword arguments as long as they make sense. Like in
# Python, positional arguments must always come before keyword arguments.
mirror m3 1 0 Rc=[1, 1.1]  # Rcx and Rcy defined together.

Element definitions cannot span multiple lines.

Many components and detectors have aliases that can be used instead of longer names. For example, m can be used instead of mirror, or bs instead of beamsplitter. Available aliases are listed for each instruction in the syntax references for Model elements, Commands, and Actions, Analyses, and Solutions.

Commands and analyses

Commands set some special behaviour in the model. Analyses define one or more procedures to perform on the model defined in the script. Both commands and analyses are defined using syntax in the form command(arg1, arg2, ...):

# Command used to set the higher order modes to simulate.
modes(maxtem=3)

# The `modes` command supports various different types of input. You can for example
# specify individual modes...
modes([[1, 0], [0, 1], [2, 0]])

# ...or odd modes up to some maximum order.
modes(odd, maxtem=9)

The arguments inside the parentheses in command and analysis definitions can span across multiple lines, and may contain comments:

modes(
    even,
    # Set maximum order.
    maxtem=10
)

Some analyses can contain other analyses; this is used for example to build a set of nested procedures to perform on the model:

# Xaxis function to perform a linear sweep over the value of m1.phi.
xaxis(m1.phi, lin, 0, 10k, 500)

# A nested action tree.
series(
    # First sweep m1.R...
    xaxis(m1.R, lin, 0, 1, 10),
    # ...then L0.P.
    xaxis(L0.P, lin, 10, 100, 20)
)

Ports and nodes

Some components define ports which act as reference points in the model to which detectors and other components may be connected or certain properties such as beam sizes may be defined. Ports themselves contain nodes which provide access to the underlying directions. For a full explanation, see The port and node system. Some brief examples are shown below:

# Spaces connect directly to components' ports.
l l1 P=1
s s1 l1.p1 m1.p1 L=1
m m1 0.99 0.01
s s1 m1.p2 m2.p1 L=1
m m2 0.99 0.01

# Detectors connect to the nodes within ports, allowing the direction to detect to
# be specified.
pd circ m2.p1.i  # Incoming beam at port p1
pd trans m2.p2.o  # Outgoing beam at port p2

Paths

Elements and their attributes and parameters can be addressed elsewhere in models using their path. Elements are simply referred to by their name; for example, a mirror named “m1” has path m1. Attribute and parameter paths are formed by combining their owner’s path with the attribute or parameter name, separated by a .; for example, a mirror’s phi model parameter has path m1.phi. Ports and nodes are also addressable this way: the mirror’s first port has path m1.p1 and the incoming node of that port has path m1.p1.i.

Commands and analyses do not have names and can thus not be addressed using paths.

Paths are typically useful when specifying spaces, detectors, analyses, and parameter references. Spaces can be used to connect components by referring to the paths of their ports:

m m1 R=0.99 T=0.01
m m2 R=0.99 T=0.01
s s1 m1.p2 m2.p1 L=10  # Connect port 2 of m1 to port 1 of m2.

Detectors can “look at” a particular port or node in a model:

l l1 P=10
m m1 R=0.99 T=0.01
s s1 l1.p1 m1.p1
pd pd1 m1.p1.i  # Look at the power incident on m1 on the port 1 side
pd pd2 m1.p2.o  # Look at the power outgoing from m1 on the port 2 side
ad a m1.p2.o f=0

Certain analyses can sweep model parameters:

l l1 P=1
m m1 R=0.99 T=0.01
m m2 R=0.99 T=0.01
s s1 m1.p2 m2.p1 L=10
xaxis(l1.P, lin, 0, 10, 100)  # Sweep l1's power from 0 to 10 W over 100 points.

Expressions and references

Parameters can be given values that contain expressions:

m m2 R=0.5 T=0.5 phi=cos(2*pi)

When used in element definitions, expressions cannot contain spaces unless they are enclosed in parentheses:

# Allowed:
l l2 P=l1.P/10
l l2 P=(l1.P / 10)
l l2 P=cos(l1.P / 10)  # Function arguments can also have whitespace.

# NOT allowed:
# l l2 P=l1.P / 10

Supported operators

The following operators are supported:

Operator

Description

+

Addition

-

Subtraction

*

Multiplication

/

Division

//

Floor division

**

Exponentiation

Supported expression functions

The following mathematical functions are supported:

Function

Description

abs

Absolute value

neg

Negation

pos

Positive

conj

Conjugate

real

Real part

imag

Imaginary part

exp

Exponential

sin

Sine

arcsin

Arcsine

cos

Cosine

arccos

Arccosine

tan

Tangent

arctan2

(Two-argument) arctangent

sqrt

Square root

radians

Degrees to radians

degrees

Radians to degrees

Functions that require multiple arguments (currently only arctan2) should have those arguments separated by a comma:

arctan2(1, 0)

References

Expressions in KatScript can make references to model parameters using their path. The value of the target model parameter will then be substituted whenever the expression is evaluated. Since model parameters can themselves be set to expressions, this lets model parameters be linked:

m m1 0.99 0.01
m m2 R=m1.R T=m1.T  # m2's R and T parameters take their values from those of m1.

References can be included within larger expressions:

l l1 P=1
l l2 P=2*l1.P  # Set l2's power to twice that of l1.

References can also refer to other parameters within the same element:

m m1 R=1-m1.T T=0.01  # Set R to ensure R + T = 1.

Model parameter values containing references (either directly or via expressions) get updated on each step of simulations, allowing for a single axis sweep to update many parameters indirectly. This is a powerful feature of KatScript, allowing for compact descriptions of complex models.

Keywords

The following keywords exist in KatScript and serve special purposes. These do not need to be specified in quotes when used as element or function arguments:

none

Used to represent null.

even, odd, x, y, off

Used to represent higher order mode sets.

lin, log

Used to represent scales.

am, pm

Used to represent modulation types.

lowpass, highpass, bandpass, bandstop

Used to represent filter types.

div, gouy, l, length, loss, finesse, fsr, fwhm, pole, modesep, q, g, rc, stability, s, resolution, w, w0, z, r, tau

Cavity and beam properties.

Arrays

Some parts of the syntax, like the Rc parameter of a mirror or the modes command, support arrays as inputs. Arrays in KatScript are defined using comma-delimited lists of values contained within square brackets:

m m1 R=1 T=0 Rc=[1, 1.1]  # Radius of curvature in tangential (x) and sagittal (y) planes.

Arrays can also contain expressions:

m m1 R=1 T=0 Rc=[1, 1.1]
m m2 R=1 T=0 Rc=[m1.Rcx, 1.2]

Arrays can also contain other arrays. This can be useful for defining the modes to simulate:

modes([[1, 0], [0, 1], [2, 0]])  # Simulate only these higher order modes.

Generating KatScript from a model

Once you’ve parsed a script into a model, you can continue to make changes using the Python API. If you wish to then regenerate KatScript for the updated model, you can use Model.unparse() or Model.unparse_file(). Note that this does not (yet) guarantee to preserve the original parse order.

from finesse import Model

model = Model()

# Parse a simple model.
model.parse(
    """
    l l1 P=10
    s s1 l1.p1 m1.p1
    m m1 R=0.995 T=1-m1.R
    s s2 m1.p2 m2.p1 L=10
    m m2 R=0.9999 T=1-m2.R
    pd pd1 m2.p1.i
    pd pd2 m2.p2.o
    """
)

# Change the cavity length.
model.spaces.s2.L = 9

# Unparse the model.
print(model.unparse())

    l l1 P=10
    s s1 l1.p1 m1.p1
    m m1 R=0.995 T=(1-m1.R)
    s s2 m1.p2 m2.p1 L=9.0
    m m2 R=0.9999 T=(1-m2.R)
    pd pd1 m2.p1.i
    pd pd2 m2.p2.o
    
# Items below could not be matched to original script, or were not present when the model was originally parsed.




Warning

KatScript generation is an experimental feature and likely contains bugs. It is not (yet!) guaranteed to generate KatScript that parses back to the same model state. Use it with care!

Registering custom KatScript directives

New elements, commands and analyses can be registered using the register_element(), register_command(), and register_analysis() methods of KatSpec, respectively. These methods require ItemAdapter instances as their first argument, which are low-level objects that give a large degree of control over how the new directive should behave. For ordinary elements and analyses the convenience functions make_element() and make_analysis() are available to simplify the process.

The module variable KATSPEC holds an instance of KatSpec intended for use in user code to register new elements, commands, and analyses at runtime. Directives registered using this object become available in the parse/unparse functions and methods throughout Finesse automatically:

from finesse import Model
from finesse.components.nothing import Nothing
from finesse.script.spec import KATSPEC, make_element

# Create a new element type.
class MyElement(Nothing):
    """My custom element."""

# Register a KatScript directive for the new element.
KATSPEC.register_element(make_element(MyElement, "myelement"))

# It's now available in `Model.parse()`:
model = Model()
model.parse("myelement myel1")

print(model.info())
<finesse.model.Model object at 0x7f259466a0a0>

1 optical mode
==============

    - [0 0]

1 component
===========

    - MyElement('myel1')

0 detectors
===========

0 cavities
==========

0 locking loops
===============

The registered directive is available for the rest of the current Python kernel’s lifetime.

Warning

If you serialize KatScript that uses custom directives, before parsing it later you must ensure the directives are again registered with the same arguments.