Replacing KatScript components

For added flexibility when creating or adjusting models, it is possible to replace components in a model. To avoid subtle bugs and difficult to follow code, ‘replacing’ will always result in a new katscript string that you can use to build a new model. Your existing model will not be touched in any way!

We will first look at the intended usecase of this functionality: extending simplified models with extra complexity. In the example below we prepared a Michelson model (but could also be a full interferometer), with a designated nothing component as a ‘placeholder’.

from finesse import Model

model = Model(
    """
l l1

s LA l1.p1 PLACEHOLDER.p1
nothing PLACEHOLDER
s LB PLACEHOLDER.p2 central_bs.p1

bs central_bs R=0.5 T=0.5
s LW central_bs.p2 WE.p1 L=100
m WE R=0.9 T=0.1
s LN central_bs.p3 NE.p1 L=100
m NE R=0.9 T=0.1
pd pd1 central_bs.p4.o
"""
)
model.plot_graph(network_type="component", layout="dot")
../_images/bf703eb88978c4597f1554539a30c0d803ecc4a45f5718b8387217e29178f0b7.svg

We can insert an entire input mode cleaner by replacing the nothing component with some input mode cleaner Katscript.

IMC_KATSCRIPT = """
bs bs1 R=0.9 T=0.1
s s1 bs1.p2 bs2.p1
bs bs2 R=0.9 T=0.1
s s2 bs2.p2 bs3.p2
bs bs3 R=0.9 T=0.1
s s3 bs1.p3 bs3.p1
"""

new_kat = model.PLACEHOLDER.replace(IMC_KATSCRIPT, optical_ports=["bs1.p1", "bs3.p3"])
print(new_kat)
l l1

s LA l1.p1 bs1.p1

bs bs1 R=0.9 T=0.1
s s1 bs1.p2 bs2.p1
bs bs2 R=0.9 T=0.1
s s2 bs2.p2 bs3.p2
bs bs3 R=0.9 T=0.1
s s3 bs1.p3 bs3.p1
s LB bs3.p3 central_bs.p1

bs central_bs R=0.5 T=0.5 L=(1-(central_bs.R+central_bs.T))
s LW central_bs.p2 WE.p1 L=100
m WE R=0.9 T=0.1 L=(1-(WE.R+WE.T))
s LN central_bs.p3 NE.p1 L=100
m NE R=0.9 T=0.1 L=(1-(NE.R+NE.T))
pd pd1 central_bs.p4.o

This way you can prepare a simplified ‘base’ model for your interferometer where you can ‘turn on’ different parts to model more realistically. The downside to this approach is you will have to split up some of your spaces to insert nothing components.

The rest of this page continues with a more detailed explanation of the replace functionality. After building your model, the replace method is available as an attribute to the component you want to create. Currently you can only replace 1 component (i.e. one katscript line) at a time, but you are allowed to insert as much katscript as you want

model = Model(
    """
l laser_1 P=5
"""
)
new_kat = model.laser_1.replace("m m1")
print(new_kat)
m m1

You can see that the method returns a string. You would have to parse this into a new model if you want to use it.

new_model = Model(new_kat)

You can also call the replace method on the Model directly, in which case you have to pass the component you want to replace (either as a string or Python object). The advantage is that your editor will recognize the method.

model.replace(model.laser_1, "m m1")
'm m1'
model.replace("laser_1", "m m1")
'm m1'

Most of the time references to a component are not limited to a single line, since components are always connected to other components. Let’s try a more involved example: replacing the first mirror in a cavity with a beamsplitter

model = Model(
    """
l l1
s s1 l1.p1 m1.p1
m m1
s s2 m1.p2 m2.p1
m m2
"""
)
model.plot_dcfields_graph(add_fields=False)
../_images/97500b40f4a63f759825ced243f526dab2cc7dd97f720342e1d8909dfd3a5bd6.svg
PosixPath('/tmp/tmpr9iby_if.svg')
new_kat = model.m1.replace("bs bs1", optical_ports=["bs1.p1", "bs1.p3"])
print(new_kat)
l l1
s s1 l1.p1 bs1.p1
bs bs1
s s2 bs1.p3 m2.p1
m m2 R=0.5 T=0.5 L=0.0

There are a few things to unpack here. Firstly you can see that for the m2 component, the default RTL values have been added to the new katscript, even though they were not present in the original katscript. This is because the replace method relies on the unparsing functionality, which tries to be as unambiguous as possible.

Secondly, we had to specify some optical_ports. This is because the m1 component appeared in multiple lines, including the lines defining the spaces. If this is the case, you will have to provide a list of strings that will be used as the replacement ports for these optical ports. You only have to supply the ports that actually need replacing. For instance, in this example we didn’t need to provide any replacement for m1.mech, since it was not referenced anywhere. Below we will visualize the new model for clarity.

new_model = Model(new_kat)
new_model.plot_dcfields_graph(add_fields=False)
../_images/5401800e1785383c547a76d8a236ccf70c8a3a424145f3058ea283b29ebb0534.svg
PosixPath('/tmp/tmpsz31ung3.svg')

The above example will also work when using the link command. You will see another artifact from the unparsing: the link command gets unparsed into the explicit space components generated behind the scenes. The autogenerated names will still be based on the old component.

model = Model(
    """
l l1
link(l1, m1)
m m1
link(m1, m2)
m m2
"""
)
new_kat = model.m1.replace("bs bs1", optical_ports=["bs1.p1", "bs1.p3"])
print(new_kat)
l l1

bs bs1

m m2 R=0.5 T=0.5 L=0.0


space l1_p1__m1_p1 portA=l1.p1 portB=bs1.p1
space m1_p2__m2_p1 portA=bs1.p3 portB=m2.p1

The ports you are replacing will also automatically replace any nodes associated with that port. This behaviour is currenlty not configurable. Using the same example as before, we now also have 2 amplitude detectors placed at m1.p1.i and m1.p2.o. Because we are replacing these ports with bs1.p1 and bs1.p3, the detectors will automatically shift to bs1.p1.i and bs1.p3.o.

model = Model(
    """
l l1
s s1 l1.p1 m1.p1
m m1
s s2 m1.p2 m2.p1
m m2

ad ad_in m1.p1.i f=0
ad ad_out m1.p2.o f=0
"""
)
new_kat = model.m1.replace("bs bs1", optical_ports=["bs1.p1", "bs1.p3"])
print(new_kat)
l l1
s s1 l1.p1 bs1.p1
bs bs1
s s2 bs1.p3 m2.p1
m m2 R=0.5 T=0.5 L=0.0

ad ad_in bs1.p1.i f=0
ad ad_out bs1.p3.o f=0

Sometimes, the name of the component you are replacing will be mentioned in other lines of katscript. For example, when attaching a pendulum to a mirror, we use the connected_to argument to reference the mirror name. In these cases we need to provide the replace function with a component argument, which is the name of the new component the pendulum needs to be attached to. Let’s replace a suspended mirror with a suspended beamsplitter:

model = Model(
    """
l l1
s s1 l1.p1 m1.p1
m m1
pendulum pend1 connected_to=m1 mass=4.0
s s2 m1.p2 m2.p1
m m2

"""
)
new_kat = model.m1.replace(
    "bs bs1", component="bs1", optical_ports=["bs1.p1", "bs1.p3"]
)
print(new_kat)
l l1
s s1 l1.p1 bs1.p1
bs bs1
pendulum pend1 connected_to=bs1 mass=4.0
s s2 bs1.p3 m2.p1
m m2 R=0.5 T=0.5 L=0.0

Finally, we have an example that uses all features at once:

model = Model(
    """
l l1
s s1 l1.p1 m1.p1
m m1
pendulum pend1 connected_to=m1 mass=4.0
link(m1, m2)
m m2
ad ad_circ m1.p2.i f=0
xd m1_motion m1.mech.z
"""
)

new_kat = model.m1.replace(
    "bs bs1",
    component="bs1",
    optical_ports=["bs1.p1", "bs1.p3"],
    mechanical_ports=["bs1.mech"],
    verbose=True,
)
---
+++
@@ -1,11 +1,11 @@
l l1
-s s1 l1.p1 m1.p1
-m m1 R=0.5 T=0.5 L=0.0
-pendulum pend1 connected_to=m1 mass=4.0
+s s1 l1.p1 bs1.p1
+bs bs1
+pendulum pend1 connected_to=bs1 mass=4.0
m m2 R=0.5 T=0.5 L=0.0
-ad ad_circ m1.p2.i f=0
-xd m1_motion m1.mech.z
+ad ad_circ bs1.p3.i f=0
+xd m1_motion bs1.mech.z
-space m1_p2__m2_p1 portA=m1.p2 portB=m2.p1
+space m1_p2__m2_p1 portA=bs1.p3 portB=m2.p1
print(new_kat)
l l1
s s1 l1.p1 bs1.p1
bs bs1
pendulum pend1 connected_to=bs1 mass=4.0

m m2 R=0.5 T=0.5 L=0.0
ad ad_circ bs1.p3.i f=0
xd m1_motion bs1.mech.z


space m1_p2__m2_p1 portA=bs1.p3 portB=m2.p1