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.
You can quickly get help for particular syntax in an interactive Python environment by
import finesse print(finesse.syntax("mirror"))
m / mirror / 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
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.
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 can use integer, floating point, scientific and complex notation. The imaginary
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
m can be used instead of
bs instead of
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
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
.; 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
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
The following operators are supported:
Supported expression functions¶
The following mathematical functions are supported:
Degrees to radians
Radians to degrees
Functions that require multiple arguments (currently only
arctan2) should have those
arguments separated by a comma:
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.
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:
Used to represent null.
Used to represent higher order mode sets.
Used to represent scales.
Used to represent modulation types.
Used to represent filter types.
Cavity and beam properties.
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
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.
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_analysis() methods of
KatSpec, respectively. These
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_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 0x7fc2015e8bc0> 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.
If you serialize KatScript that uses custom directives, before parsing it later you must ensure the directives are again registered with the same arguments.