gtrace Tutorial

This Jupyter notebook walks you through how to use the gtrace package.

Import modules

First, we import related modules

[1]:
import gtrace.beam as beam  # Gaussian beam module
import gtrace.optcomp as opt  # Optical components
import gtrace.draw as draw  # A module to draw results into CAD files
from gtrace.draw.tools import drawAllBeams, drawAllOptics, transAll, rotateAll #Utility functions for drawing
import gtrace.draw.renderer as renderer
from gtrace.unit import *  # A convenience module to represent various modules
import gtrace.optics.gaussian as gauss # A utility module for Gaussian beams
from gtrace.nonsequential import non_seq_trace #Non-sequential trace
from gtrace.optics.geometric import vector_normalize #A function to normalize a vector

import numpy as np  #Numpy
pi = np.pi  #Just for brevity

Coordinates and directions

In gtrace, everything is put on a 2D plane and we use a Cartesian coordinate system with x- and y-axis.

Sometimes, you may want to specify a direction in the 2D space, such as a beam propagation direction or the direction of a mirror surface representated by its normal vector. You can use either a unit vector or an angle to specify a direction. When using an angle, it is always measured from the X-axis in counter-clockwise. Therefore, a unit vector having a direction angle \(\theta\) has components \((\cos\theta, \sin\theta)\).

Gaussian beam object

A Gaussian beam object:

9a53575b828b47d0a14314e47f681090

[2]:
#q-parameter of the beam
q0 = gauss.Rw2q(ROC=np.inf, w=0.3*mm)

#Create a GaussianBeam object.
b0 = beam.GaussianBeam(q0=q0, wl=1064*nm, length=30*cm, P=1.0)

#Set the direction angle of the beam to 10deg from the global x-axis.
b0.dirAngle = deg2rad(10)

#Set the position of the origin of the beam
b0.pos = (0.0, 0.0)

Define mirrors

Mirror object:

d452ac5c6d4a4a80a0e435ea019adf7c

[5]:
M1 = opt.Mirror(HRcenter=[50*cm, 10*cm], normAngleHR=pi,
                 diameter=25*cm, thickness=10*cm,
                 wedgeAngle=deg2rad(0.25), inv_ROC_HR=1./(120*cm),
                 inv_ROC_AR=0,
                 Refl_HR=0.9, Trans_HR=1-0.9,
                 Refl_AR=500*ppm, Trans_AR=1-500*ppm,
                 n=1.45, name='M1')

M2 = opt.Mirror(HRcenter=[0*cm, 18*cm], normAngleHR=deg2rad(5.0),
                 diameter=15*cm, thickness=5*cm,
                 wedgeAngle=deg2rad(0.25), inv_ROC_HR=-1./(350*cm),
                 inv_ROC_AR=0,
                 Refl_HR=0.9, Trans_HR=1-0.9,
                 Refl_AR=500*ppm, Trans_AR=1-500*ppm,
                 n=1.45, name='M2')

M3 = opt.Mirror(HRcenter=[30*cm, 30*cm], normAngleHR=deg2rad(21.3),
                 diameter=15*cm, thickness=5*cm,
                 wedgeAngle=deg2rad(1), inv_ROC_HR=1./(350*cm),
                 inv_ROC_AR=0,
                 Refl_HR=0.9, Trans_HR=1-0.9,
                 Refl_AR=500*ppm, Trans_AR=1-500*ppm,
                 n=1.45, name='M3')

Sequential beam trace

Prepare a dictionary to save beams

[6]:
beamDict = {}

Hit the mirror M1 with the beam b1 from the HR side

[7]:
beams = M1.hitFromHR(b0, order=2)

Returned object (beams) is a dictionary containing the resulting beams. The names of the beams are the following.

3d72e08db72f466fa3037603c1a8395b

Trace the remaining beam path

[9]:
#Save the incident beam to the beam dictionary
beamDict['input_beam'] = beams['input']

#Save other beams
beamDict['M1s1'] = beams['s1']
beamDict['M1t1'] = beams['t1']
beamDict['M1s2'] = beams['s2']
beamDict['M1s3'] = beams['s3']
beamDict['M1t2'] = beams['t2']

#Reflected beam from M1
b = beams['r1']

#Hit M2
beams = M2.hitFromHR(b, order=2)

#Save the beam from M1 to M2
beamDict['M1toM2'] = beams['input']
beamDict['M2s1'] = beams['s1']
beamDict['M2t1'] = beams['t1']

#Reflected beam from M2
b = beams['r1']

#Hit M3 from AR
beams = M3.hitFromAR(b, order=2)

#Save beams
beamDict['M2toM3'] = beams['input']
beamDict['M3s1'] = beams['s1']
beamDict['M3t1'] = beams['t1']

Draw the results

[10]:
#Create a canvas object
cnv = draw.Canvas()

#Add a layer to the canvas
cnv.add_layer("main_beam", color=(0,0,0))

#Draw all the beams in beamDict
drawAllBeams(cnv, list(beamDict.values()), drawWidth=True, sigma=3.0, drawPower=False,
                 drawROC=False, drawGouy=False, drawOptDist=False, layer='main_beam',
                    fontSize=0.01)

#Draw the mirror
drawAllOptics(cnv, [M1,M2,M3])

#Save the result as a DXF file
renderer.renderDXF(cnv, 'SeqTrace.dxf')

The generated DXF file looks like this.

98fae6d64b6041feacd1f2c3e6d205e0

Non-sequential trace

In the above example, we instructed which beam should hit which optic explicitly. Here, we only specify an input beam and a set of optics. Then let the beam go around until specfied criteria are met. This is called “Non-Sequential Trace”.

Perform non-sequential trace

[11]:
# Trace beams until the power is less than 1e-6 or 30th order internal reflections
beams = non_seq_trace([M1,M2,M3], b0, order=30, power_threshold=1e-6)

Draw results

[13]:
#Create a canvas object
cnv = draw.Canvas()

#Add a layer to the canvas
cnv.add_layer("main_beam", color=(0,0,0))

#Draw all beams

drawAllBeams(cnv, beams, drawWidth=True, sigma=3.0, drawPower=False,
                 drawROC=False, drawGouy=False, drawOptDist=False, layer='main_beam',
                    fontSize=0.01)

#Draw the mirror
drawAllOptics(cnv, [M1,M2,M3])

#Save the result as a DXF file
renderer.renderDXF(cnv, 'NonSeq.dxf')
[49]:
a=cnv.layers['main_beam']
[52]:
b=a.shapes[0]
[54]:
b.stop
[54]:
(0.4999415205870579, 0.0881531788559148)

The generated DXF file looks like this.

09b397500d204933997431c9f97967a4

KAGRA Input Mode Cleaner

Parameters

[14]:
nsilica = 1.44967

MC_Dia = 10.0*cm
MC_Thick = 3.0*cm
MCe_ROC = 37.3

MCi_Refl = 0.9937
MCo_Refl = 0.9937
MCe_Refl = 0.9999
AR_Refl = 0.1/100

pos_MCi = np.array([-0.25, 0.0])
pos_MCo = np.array([0.25, 0.0])
pos_MCe = np.array([0, 2.63986994e+01])

Define MC mirrors

[15]:
MCi = opt.Mirror(HRcenter=[0,0], normAngleHR=0.0,
                 diameter=MC_Dia, thickness=MC_Thick,
                 wedgeAngle=-deg2rad(2.5), inv_ROC_HR=0.0,
                 Refl_HR=MCi_Refl, Trans_HR=1-MCi_Refl,
                 Refl_AR=AR_Refl, Trans_AR=1-AR_Refl,
                 n=nsilica, name='MCi')

MCo = opt.Mirror(HRcenter=[0,0], normAngleHR=0.0,
                 diameter=MC_Dia, thickness=MC_Thick,
                 wedgeAngle=deg2rad(2.5), inv_ROC_HR=0.0,
                 Refl_HR=MCo_Refl, Trans_HR=1-MCo_Refl,
                 Refl_AR=AR_Refl, Trans_AR=1-AR_Refl,
                 n=nsilica, name='MCo')

MCe = opt.Mirror(HRcenter=[0,0], normAngleHR=0.0,
                 diameter=MC_Dia, thickness=MC_Thick,
                 wedgeAngle=deg2rad(2.5), inv_ROC_HR=1.0/MCe_ROC,
                 Refl_HR=MCe_Refl, Trans_HR=1-MCe_Refl,
                 Refl_AR=AR_Refl, Trans_AR=1-AR_Refl,
                 n=nsilica, name='MCe')

Put MC mirrors

[16]:
#Put mirrors in position
MCi.HRcenter = pos_MCi
MCo.HRcenter = pos_MCo
MCe.HRcenter = pos_MCe

#Align the mirrors to form a triangular cavity
v1 = vector_normalize(pos_MCi - pos_MCo)
v2 = vector_normalize(pos_MCe - pos_MCo)
MCo.normVectHR = (v1+v2)/2

v2 = vector_normalize(pos_MCe - pos_MCi)
MCi.normVectHR = (-v1+v2)/2

MCe.normVectHR = np.array([0.0, -1.0])

MC eigen mode

Now we compute the eigen mode of the MC by tracing a beam round trip of the MC then extracting the ABCD matrix

[17]:
#Test beam (beam parameters does not matter)
b = beam.GaussianBeam(q0=gauss.Rw2q(ROC=np.inf, w=1*mm), wl=1064*nm)

#Put the beam on the surface of MCi
b.pos = MCi.HRcenter

#Direct the beam to the center of MCo
b.dirVect = MCo.HRcenter - b.pos

#Hit MCo
beams = MCo.hitFromHR(b)
b = beams['r1']

#Hit MCe
beams = MCe.hitFromHR(b)
b = beams['r1']

#Hit MCi
beams = MCi.hitFromHR(b)
b = beams['r1']

#Extract the round trip ABCD matrix in the horizontal direction
A = b.Mx[0,0]
B = b.Mx[0,1]
C = b.Mx[1,0]
D = b.Mx[1,1]
#q-parameter of the MC eigenmode beam
qxMC = 1.0/((D-A)/(2*B)-1j*np.sqrt(4-(A+D)**2)/(2*B))

#Extract the round trip ABCD matrix in the vertical direction
A = b.My[0,0]
B = b.My[0,1]
C = b.My[1,0]
D = b.My[1,1]
#q-parameter of the MC eigenmode beam
qyMC = 1.0/((D-A)/(2*B)-1j*np.sqrt(4-(A+D)**2)/(2*B))

#Update the q-parameter of b
b.qx = qxMC
b.qy = qyMC

#Name it bMC
bMC = b.copy()

Waist size and position of the MC eigenmode (measured from the MCi HR surface)

[18]:
bMC.waist()
[18]:
{'Waist Size': (0.002388588001545699, 0.002388775565011432),
 'Waist Position': (0.25000000000000416, 0.25000000000000505)}

Trace the beam in the MC again

[19]:
beamDict = {}

#Hit MCo
beams = MCo.hitFromHR(b, order=1)
beamDict['MCitoMCo'] = beams['input']
beamDict['MCos1'] = beams['s1']
beamDict['MCot1'] = beams['t1']

b = beams['r1']

#Hit MCe
beams = MCe.hitFromHR(b, order=1)
beamDict['MCotoMCe'] = beams['input']
beamDict['MCes1'] = beams['s1']
beamDict['MCet1'] = beams['t1']

b = beams['r1']

#Hit MCi
beams = MCi.hitFromHR(b, order=1)
beamDict['MCetoMCi'] = beams['input']
beamDict['MCis1'] = beams['s1']
beamDict['MCit1'] = beams['t1']

Draw MC beams

[20]:
#Create a canvas object
cnv = draw.Canvas()

#Add a layer to the canvas
cnv.add_layer("main_beam", color=(0,0,0))

#Draw all beams

drawAllBeams(cnv, list(beamDict.values()), drawWidth=True, sigma=3.0, drawPower=False,
                 drawROC=False, drawGouy=False, drawOptDist=False, layer='main_beam',
                    fontSize=0.01)

#Draw the mirror
drawAllOptics(cnv, [MCi,MCo,MCe])

#Save the result as a DXF file
renderer.renderDXF(cnv, 'MC.dxf')

The generated DXF file looks like this:

16f27829935f4d569e154b2116536754

[ ]: