Pykat: Python package for modelling precision optical interferometers

\textsc{Pykat} is a Python package which extends the popular optical interferometer modelling software \textsc{Finesse}. It provides a more modern and efficient user interface for conducting complex numerical simulations, as well as enabling the use of Python's extensive scientific software ecosystem. In this paper we highlight the relationship between \textsc{Pykat} and \textsc{Finesse}, how it is used, and provide an illustrative example of how it has helped to better understand the characteristics of the current generation of gravitational wave interferometers.


Introduction
In order to design the precision interferometers at the heart of advanced gravitational wave detectors, a multitude of bespoke and generic optical simulation software packages have been developed [1]. One of the most popular approaches to modelling these interferometers is to use the frequency domain picture, which allows the user to study the quasi-steady state behaviour of an interferometer. Such tools are used to model noise couplings, design control systems, and how defects in an interferometer affect the behaviour of both. As the physical complexity of these interferometers has increased to meet improved sensitivity goals, so has the complexity of the modelling packages to keep up with the issues the community has faced. Several packages using this frequency domain method have been developed over the years: Melody [2], Optickle [3], MIST [4], and Finesse [5,6].
The interferometers typically used for gravitational wave detection are well described by a set of linear coupling equations between the various components. This set of equations grows rapidly when the number of optical components, frequencies of the light fields, and higher-order spatial modes of those light Fig. 1. A schematic of the typical workflow a user has with Pykat. Initially the user must have a concrete idea of the physical setup they want to model, and then express this setup in kat-script. This script can then be loaded into a Pykat object which allows the user to run multiple simulations, perform more complex analyses, and generate plots. Behind the scenes, Pykat will seamlessly setup and call the Finesse binary to run the required simulations. fields increases. All of the above packages take in some description of the interferometer, construct a sparse matrix representing the set of equations describing the system, then solve it for various excitations and configurations.
Finesse is one of the most feature-rich packages available and has had extensive usage throughout the gravitational wave community. It is programmed in C, is free, open source, and cross platform. It consists of a single binary executable, called kat, that performs the required numerical computations. The user provides a text file, known as a kat-file, that describes both the model of an interferometer and the type of simulation to be executed, using a domain-specific language, kat-script. The binary then outputs a text file with the output data requested by the user. This interaction is performed entirely via a command-line interface from a system terminal.
Despite Finesse being very computationally efficient, the simplistic user interface began to become a hindrance as the complexity of the modelling tasks grew. However, due to the simple command-line interface it was easily called and manipulated via other higher level programming languages such as Bash, Perl, MATLAB, and eventually Python. The Python wrapper, Pykat, is the latest and by far the most advanced wrapper available. It enables a user to easily perform complex sets of modelling tasks efficiently and enables Finesse to be used in conjunction with the wide variety of Python packages, for example, for generating plots, for solving complex optimisation problems or for producing reduced-order models [7].
Pykat has grown extensively from what it was first conceived to be. Originally it aimed to just simply read kat-script and produce an object-oriented description of the model and simulation to produce a graphical user-interface (GUI). The object-oriented description however proved much more useful for developing and running simulations. We then developed Pykat to be much more focused on providing better interoperability with Finesse binary. As of today, the majority of all Finesse-based modelling tasks are now undertaken through Pykat.
In this article we highlight how Pykat works with Finesse to run simulations in a more efficient and procedural manner. We also provide an illustrative example of how Pykat was used in practice to better understand the behaviour of real gravitational wave interferometer.

Installation
Pykat is available from http://www.gwoptics.org/pykat/ with links to a git repository with the source code, including the most recent version under development. The release version can be installed through the Conda package management system https://anaconda.org/gwoptics/pykat or from PyPi https://pypi. org/project/PyKat/. At the time of writing Conda is the recommended method to install Pykat, because Conda also hosts the Finesse binaries for multiple platforms, and Finesse must be installed for Pykat to work.

Wrapping up Finesse with Python
Pykat should be thought of as a wrapper to run Finesse simulations. Thus, to effectively use it the user must be familiar with how Finesse works computationally, what it can and cannot compute, and how to use kat-script. For that, the best reference is the Finesse user manual [8] and the review article by Bond et al. [9]. Additional learning materials can also be found at http: //www.gwoptics.org/learn/, which contains online notebooks for learning about interferometry as well as the materials for several workshops we have taught using Pykat.
Kat-script has three distinct classes of instruction: components, detectors, and commands. Components are objects such as mirrors, beam splitters, lenses, etc. Components can be connected together to form a network. Each component has a number of nodes to represent the incoming and outgoing light fields; the edges of the network describe how that light goes from one component to the next. Detectors are objects that produce an output when a simulation is run. For example, if you want to measure the optical power at a particular location in the interferometer, you would place a 'photodiode' detector there. Detectors can be more abstract, however, such as for measuring particular properties of a laser beam's shape that would otherwise be difficult or impossible in a real setup. Commands are those instructions that configure a component or detector, or setup the type of simulation to run. The Python code for these three classes of instructions can be found in pykat.components, pykat.detectors, and pykat.commands.
When using Finesse in the traditional way, the user would write their kat-script into a text file and then from the command line call the binary to run it, kat my_simulation.kat. This would then produce a text file called my_simulation.out containing simulation output data and display a plot of the detector outputs as a function of some model parameter being varied. Fig. 1 shows an overview of how a user would typically interact with Pykat instead. Pykat aims to offer Python classes that represent all available kat-script instructions. Models and simulations can be built in Pykat through two interfaces: by parsing the kat-script; or by using the object-oriented interface.
Kat-script instructions are parsed into an instance of Pykat's Kat object. It is constructive for the user to imagine a Kat object like a kat-file that you would pass to the Finesse binary at the command-line, except the kat-file is dynamically generated by Pykat.
Parsing the kat-script converts the instructions into Python objects which are added to the Kat object which the user can interact with. These objects can be manipulated as required, such as changing properties, parsing further commands, etc. Once the user is ready a simulation can be run. The Kat object will first construct a kat-file from the objects contained within it, then pass it to the Finesse binary. Pykat and Finesse then communicate with each other during the running of a simulation via an interprocess communication pipe, allowing data and commands to be exchanged. Once completed, the outputted text file with the simulation result is loaded and processed into a KatRun object, which provides an easy interface for the user to access all the detector outputs via numpy arrays.
Consider the example in Listing 1 where we create a simple model with a single laser and a mirror. In this model we add two detectors to measure the complex amplitude of the light fields being reflected and transmitted through the mirror. These commands are parsed into a Kat object. We then change the mirror's reflectivity and transmissivity and run it. The output file is then accessed much like a dictionary using the detectors' names. This allows the user to quickly plot and manipulate any outputs.
Although this interface is likely more favoured by programmers, it does remove the more direct connection the user has to the Finesse binary. Thus, they must be mindful of what Finesse is actually running. For more complex modelling tasks, and for debugging, this information is sometimes required explicitly. The user can then quickly see the current kat-script that would be sent to Finesse by simplying printing the kat object, print(kat).
However, it is entirely possible to use a mix of both interfaces. While the example in Listing 1 shows a task that could in principle be accomplished using only Finesse and a single kat-script, users often require running multiple simulations to produce specific results, which would be a tedious manual task without Pykat. A common example of such a task is the optimisation of some experimental parameter. Using Pykat we can easily access a variety of packages for such a task, such as Scipy. Listing 3 depicts a toy optimisation problem where we wish to find the optimal transmissivity of the second mirror in the cavity to optimise the amount of power being transmitted. When run, the result should be that it is equal to the transmission of the first mirror [9], in what is known as an impedance matched cavity configuration. 1 import pykat 2 import scipy 3 4 base = pykat. finesse .kat () 5 base.parse( " " " 6 l l1 1 0 n1 7 s s1 1 n1 n2 8 m m1 0.99 0.01 0 n2 n3 9 attr m1 Rc -10 10 s s2 1 n3 n4 11 m m2 1 0 0 n4 n5 12 attr m2 Rc 10 13 cav c m1 n3 m2 n4  Finesse itself does not significantly benefit from parallelisation when running a single simulation, as many of the core computations must run sequentially: filling the matrices, inverting them, computing the detector outputs, and repeating this over a large parameter sweep. Typically users are often faced with having to explore some large parameter space, in these cases to improve run times multiple simulations must be performed in parallel. This requires creating many different kat-script files and passing them to the Finesse binary, which is cumbersome to perform manually. Pykat offers an easy to use interface for quickly running multiples simulations. The interface uses the Ipyparallel package, which allows it to utilise anything from the multiple cores on a single machine to distributing the tasks to an entire cluster. Listing 4 provides an overview of how this works. 1 import pykat 2 from pykat . parallel import parakat

Illustrative example: modelling noise couplings in advanced LIGO
A typical problem encountered by instrument scientists working on gravitational wave detectors is to understand how various sources of noise in the interferometer couple into the output channel that measures passing gravitational waves. Sometimes these couplings can be explained or explored with simple analytical models. However, as the complexity of the instrument has grown, simple models often no longer represent what is happening in reality. This is where Finesse becomes particularly useful as it allows us to simulate such complex setups.
In this example, we highlight a modelling task undertaken on measurements from one of the Advanced LIGO [10] interferometers. The issue at hand was understanding how the frequency noise of the input laser couples into the gravitational wave output channel. The measured transfer function for this noise coupling is shown in Fig. 2. Simple models predict that the frequency noise coupling drops as ∝ 1/f , however the measurement shows some structure around 100 Hz which smoothly rises above 1 kHz.
To model this type of problem we first have to produce a realistic model of the Advanced LIGO detectors. This has to include realistic defects that we know or expect to be present. Then Plotted for comparison are various model configurations that were found to have the best fit from the 100k simulations performed. The colour bar shows how much the shape of the laser beam generated by the input mode cleaner (IMC) is mismatched to that of the interferometer. For combinations of mirror astigmatism and IMC mismatch this noise a noise coupling similar shape to the measurement around 100 Hz and at 1-8 kHz.
we model different specific configurations and output how the frequency noise injected at the laser source propagates to the gravitational readout channel. Finally we can compare the result to the measured values and ask which configuration best fits the data.
A Finesse model that describes a realistic interferometer in a gravitational wave detector can easily reach 300+ instructions. When using a single kat-file and Finesse, such a model can become large and cumbersome to work with. Pykat has trivialised creating models of the advanced detectors, for example here we show how we can create an Advanced LIGO model and initialise it to be at its correct operating point in just a handful of lines: 1 import pykat 2 from pykat.ifo import aligo 3 import pykat.ifo.aligo.plot 4 # Create a new base model of the as designed 5 # Advanced LIGO interferometer 6 base = aligo. make_kat () 7 # This then sets up all the locks , error signals , 8 # and operating points for the interferometer model 9 aligo.setup(base) 10 # Finally plot the quantum noise limited strain 11 # sensitivity 12 aligo.plot. strain_sensitivity (base) 13 # Plot all the length sensing error signals 14 aligo.plot. error_signals (base)

Listing 5: Getting an Advanced LIGO model
This base model represents an ideal interferometer, no defects are included. The setup() function is optimising all the mirror positions to ensure they are at the correct operating point [9]. Simply put, this means the mirrors are held at the correct microscopic position to ensure that the optical fields resonate in the interferometer as designed.
From this idealised model of LIGO we can begin to introduce defects: incorrectly sized laser beams; deviations in mirror properties, such as optical loss, radii of curvature, or alignment; and errors in how well we microscopically position the mirrors. All these effects are likely to be present in the experiment, we have ranges of how strong each of these deviations might be but the exact amount and relative proportions of each is not well known.
In this case, given the number of free parameters, we chose to run a simple Monte-Carlo search computing the frequency noise coupling for each. Using the Pykat parallelisation features the parameter space was quickly explored using a computer cluster. For each of these potential defected configurations we have to make sure our interferometer still operates correctly; otherwise we may model the noise coupling in an interferometer state that cannot be used for gravitational wave detection. One important figure of merit is ensuring we can still effectively sense and control all the lengths of the interferometer, also known as locking the interferometer. Pykat includes functionality (See kat.IFO.lock_drag()) for performing a technique called lock-dragging [11] which allows a user to find a suitable operating point easily-which when not performed correctly, or at all, results in unphysical and incorrect models and is a common stumbling point for new users.
Of the order 100k simulations were run looking at different geometric configurations of the interferometer and input laser beam states. The configuration with the best-fits are shown in Fig. 2. It was found that generating a noise coupling with this form requires that some astigmatism must be present in the surface figure of the LIGO test mass optics, while the shape of the beam generated by the input mode cleaner must also differ by several percent compared to the interferometers beam shape, known as mode-mismatch [9]. This configuration might not be the exact state of the real interferometer, however it highlights features that can be investigated further experimentally, to either rule out or confirm the hypothetical source of this extra noise coupling. Clearly, performing this analysis over such a large parameter space using only Finesse would have been impractical.

Impact
In the past 20 years Finesse alone has had a significant impact in the field of gravitational wave science [12]. However, with Pykat we have significantly increased the capabilities and reach of our simulation software. First of all, Pykat has enabled significantly more complex modelling tasks to be undertaken with Finesse with ease. It achieves this by providing a more modern and adaptable user interface allowing Finesse to be connected with a wide variety of scientific packages in Python. Expert users can now code up their knowledge into functions, such as aligo.setup() shown in Listing 5, that other users can easily call on. It also significantly reduces errors from working with multiple kat-files, by providing the tools to do so in a more procedural manner.
The use of Pykat from Python scripts has enabled researchers to share their modelling via version controlled repositories with ease leading to increased research software sustainability [13]. In particular, Jupyter Notebooks [14] have provided a far more interactive format for conducting, documenting, and distributing numerical modelling tasks and results-which has led them to becoming the preferred method of using Pykat and Finesse now for many users.
Pykat was the key tool to leverage the entire Python ecosystem and to benefit from the rising popularity of the Python language in the science community. More importantly, the notebook format has enabled us to produce more engaging and interactive learning materials, not only for using Finesse, but also for teaching undergraduate students and early career researchers about precision interferometry in a more hands-on environment. This has resulted in an unprecedented amount of training in interferometer simulation for gravitational wave detection, through online resources or workshops. This is of particular importance for young researchers in new gravitational wave group. This year we created dedicated training material and organized a 'hackathon' as part of the effort to train students for the new LIGO detector planned in India [15]. Pykat is the key component around which such training and teaching is being developed. Multiple examples of similar workshops and the respective materials can be found at http://www.gwoptics.org/learn.

Conclusion
In this paper we have described the interferometer simulation software Pykat and the symbiotic relationship between Pykat and Finesse. Pykat provides a new modern and efficient Python interface that enables us to simulate precision optical experiments previously not possible. We outlines an illustrative example of how it enabled us to perform a complex modelling task to better understand current noise couplings in of one of the LIGO gravitational wave interferometers. The success of Pykat has enabled us to provide better learning materials for students, improved the software sustainability, and allows researchers to tackle more complex problems.
Although Pykat is primarily for modelling optical experiments using Finesse, it includes several other features that have not been discussed here. It has also become the home for several other computational tools which users may find helpful, such as, a Fast-Fourier-transform optical modelling tool, based on the software package Oscar [16], ABCD Gaussian beam propagation code, routines for computing higher-order-mode Gaussian beam scattering, and generating reduced-order-models for light scattering [7].
Looking forward, much of what has been learnt developing Pykat and Finesse will be combined and further improved upon in Finesse v3. The core engine in C code will be kept to ensure simulation times are kept to a minimum. Whereas all the parsing, user-interface and components will be written in Python to provide a more adaptable and open programming interface in Python for tackling future problems.

Declaration of competing interest
The authors declare that they have no known competing financial interests or personal relationships that could have appeared to influence the work reported in this paper.