Commit 8f0c7dd2 authored by Andreas Freise's avatar Andreas Freise

Merge branch 'master' of git.ligo.org:finesse/pykat

parents 8af5d368 06aebbea
......@@ -20,13 +20,24 @@ Please cite pykat when used with::
Url = {http://www.gwoptics.org/pykat}
}
Examples and tutorials can be found at http://www.gwoptics.org/learn/
Please email finesse@star.sr.bham.ac.uk if you have any issues.
Installation
-------------
The easiest way to install PyKat is through PyPi::
The easiest way to install PyKat is through PyPi or Conda (Recommended)::
pip install pykat
conda install -c gwoptics pykat
The Conda installation has the advantage that it will also install the Finesse binaries too.
If you are a Windows user you also have the option to download the installer at https://pypi.python.org/pypi/PyKat.
You should now be able to open up a new Python terminal and type `import pykat`, the output should be::
......
......@@ -40,10 +40,12 @@ import pykat.finesse as finesse
import pykat.components as components
import pykat.detectors as detectors
import pykat.commands as commands
import pykat.style as style
from pykat.optics.gaussian_beams import BeamParam
from pykat.plotting import init_pykat_plotting
from pykat.style import use as set_plot_style
from .SIfloat import SIfloat
......@@ -89,4 +91,4 @@ SIlabel = {'yocto': 'y',# yocto
'giga': 'T',# giga
'tera': 'P',# tera
'peta': 'y',# peta
}
\ No newline at end of file
}
......@@ -372,11 +372,21 @@ class AbstractMirrorComponent(Component):
self._default_fsig_param = self.__phi
def setRTL(self, R=None, T=None, L=None):
"""
Sets the R, T, and/or L properties. At least two values should be specfified
as the the third can be assumed as:
R+T+L = 1
By setting one of the properties to None will set whether an m/m1/m2 (bs/bs1/bs2)
command is used with Finesse.
"""
if R is not None: self.R = R
if T is not None: self.T = T
if L is not None: self.L = L
def completeRTL(self, R=None, T=None, L=None):
setValues = sum(x is not None for x in [R,T,L])
if setValues == 3:
self.setRTL(R,T,L)
......@@ -1517,13 +1527,14 @@ class laser(Component):
self._requested_node_names.append(node)
self.__power = Param("P", self, SIfloat(P), canFsig=True, fsig_name="amp")
self.__f_offset = Param("f", self, f, canFsig=True, fsig_name="freq")
self.__f_offset = Param("f", self, None, canFsig=True, fsig_name="freq")
self.__phase = Param("phase", self, SIfloat(phase), canFsig=True, fsig_name="phase")
self.__w0 = Param("phase", self, None, canFsig=True, fsig_name="w0", isPutable=False, isPutter=False, isTunable=False)
self.__z = Param("phase", self, None, canFsig=True, fsig_name="z", isPutable=False, isPutter=False, isTunable=False)
self.__noise = AttrParam("noise", self, None)
self._svgItem = None
self.f = f
self._default_fsig_param = self.__f_offset
self._freeze()
......
......@@ -99,7 +99,7 @@ if USE_GUI:
from multiprocessing import Process, Manager
PYKAT_DATA = "PYKAT_DATA="
PYKAT_DATA = "#PYKAT_DATA="
NO_BLOCK = "NO_BLOCK"
pykat_web = "www.gwoptics.org/pykat"
......@@ -1212,9 +1212,10 @@ class kat(object):
katScript = "".join(self.generateKatScript())
katfile.writelines(katScript)
katfile.write("")
katfile.write(PYKAT_DATA + self._data2str())
katfile.flush()
if len(self.data) > 0:
katfile.write("")
katfile.write(PYKAT_DATA + self._data2str())
katfile.flush()
def saveScript(self, filename=None):
"""
......
......@@ -11,6 +11,8 @@ from scipy.optimize import brute
from scipy.optimize import fmin
from scipy.interpolate import interp1d
from scipy.optimize import minimize_scalar
from scipy.optimize import minimize
from scipy.misc import comb
# THE PLOTTING OPTION WILL BE REMOVED
import matplotlib.pyplot as plt
......@@ -101,9 +103,9 @@ def round_to_n(x, n):
factor = (10 ** power)
return round(x * factor) / factor
def vprint(verbose, printstr):
def vprint(verbose, printstr, end='\n'):
if verbose:
print(printstr)
print(printstr,end=end)
def BS_optical_path(thickness, n=1.44963098985906, angle=45.0):
"""
......@@ -234,7 +236,7 @@ def scan_optics_string(_optics, _factors, _varName='scan', target="phi", linlog=
#print()
return _tuneStr
def scan_f_cmds(DOF, linlog="lin", lower=10, upper=5000, steps=100):
def scan_f_cmds(DOF, linlog="log", lower=10, upper=5000, steps=100):
name = "_%s" % DOF.name
cmds = DOF.fsig(name, 1)
......@@ -595,11 +597,114 @@ def mismatch_scan_L(base, node, length, lower, upper, steps):
return out['qx'], out['qy']
def modematch(kat, components, cavs, node, verbose = False):
'''
Mode matches the cavity eigenmmodes for the cavities in cavs by varying the
components in components. Computes mode overlaps between the cavity eigenmodes.
Minimises the maximum cavity mismatch.
Inputs
--------
kat - kat-object to run
components - list of names of components to vary. Each entry must be a lens (varies f),
mirror (Rc), BS (Rc) or space (L).
cavs - list with names of cavities to match
node - name of node where to compute mismatches. Must be first or
last node in IFO for reliable result.
verbose - If true, prints the new optimised paramaters
Returns
--------
kat1 - kat-object with the optimised component parameters. Deepcopy of kat.
'''
Nc = len(cavs)
Np = len(components)
kat1 = kat.deepcopy()
# Storing parameters to tune, and their initial values, in lists
attrs = []
p0 = []
for c in components:
if isinstance(kat1.components[c], pykat.components.lens):
attrs.append(kat1.components[c])
p0.append(kat1.components[c].f.value)
elif (isinstance(kat1.components[c], pykat.components.mirror) or
isinstance(kat1.components[c], pykat.components.beamSplitter)):
attrs.append(kat1.components[c])
p0.append(kat1.components[c].Rc.value)
elif isinstance(kat1.components[c], pykat.components.space):
attrs.append(kat1.components[c])
p0.append(kat1.components[c].L.value)
# Switching off cavity commands for cavities not in cavs
for cav in kat1.getAll(pykat.commands.cavity):
if not cav.name in cavs:
cav.remove()
# Cost function
def func(p):
for k in range(Np):
if isinstance(attrs[k], pykat.components.lens):
attrs[k].f = p[k]
elif isinstance(attrs[k], pykat.components.space):
attrs[k].L = p[k]
elif (isinstance(attrs[k], pykat.components.mirror) or
isinstance(attrs[k], pykat.components.beamSplitter)):
attrs[k].Rc = p[k]
mmx, mmy, qs = pykat.ifo.mismatch_cavities(kat1, node)
mm = np.zeros(comb(Nc,2,exact=True), dtype=float)
cs = deepcopy(cavs)
k = 0
for c1 in cavs:
cs.pop(0)
for c2 in cs:
mm[k] = np.sqrt(mmx[c1][c2])*np.sqrt(mmy[c1][c2])
k += 1
# print(kat1.CPN_TL.f.value, kat1.CPW_TL.f.value, mm.mean())
return mm.max()
if verbose:
# Computing initial mismatch. Only for display
mmx, mmy, qs = pykat.ifo.mismatch_cavities(kat1, node)
mm0 = np.zeros(comb(Nc,2,exact=True), dtype=float)
cs = deepcopy(cavs)
k = 0
for c1 in cavs:
cs.pop(0)
for c2 in cs:
mm0[k] = np.sqrt(mmx[c1][c2])*np.sqrt(mmy[c1][c2])
k += 1
# Optimising
opts = {'xtol': 1.0, 'ftol': 1.0e-7, 'disp': False}
out = minimize(func, p0, method='Nelder-Mead', options=opts)
if not out['success']:
pkex.printWarning(out.message)
# Setting new parameters to kat-object
for k in range(Np):
if isinstance(attrs[k], pykat.components.lens):
attrs[k].f = out.x[k]
elif isinstance(attrs[k], pykat.components.space):
attrs[k].L = out.x[k]
elif (isinstance(attrs[k], pykat.components.mirror) or
isinstance(attrs[k], pykat.components.beamSplitter)):
attrs[k].Rc = out.x[k]
if verbose:
print('Maximum mismatch: {:.2e} --> {:.2e}'.format(mm0.max(), out.fun))
for c in components:
if isinstance(kat.components[c], pykat.components.lens):
print(' {}.f: {:.5e} m --> {:.5e} m'.format(c, kat.components[c].f.value, kat1.components[c].f.value ))
elif isinstance(kat.components[c], pykat.components.space):
print(' {}.L: {:.5e} m --> {:.5e} m'.format(c, kat.components[c].L.value, kat1.components[c].L.value ))
elif (isinstance(kat.components[c], pykat.components.mirror) or
isinstance(kat.components[c], pykat.components.beamSplitter)):
print(' {}.Rc = {:.5e} m --> {:.5e} m'.format(c, kat.components[c].Rc.value, kat1.components[c].Rc.value ))
return kat1
......
......@@ -26,6 +26,11 @@ import pkg_resources
from scipy.constants import c as clight
from scipy.optimize import fmin
from pykat.optics.hellovinet import hellovinet
from pykat.tools.lensmaker import lensmaker
from pykat.tools.compound_lens import combine
class ADV_IFO(IFO):
"""
This contains advanced Virgo specific methods for computing interferometer
......@@ -47,8 +52,6 @@ class ADV_IFO(IFO):
self._f4 = np.nan
self._f36M = np.nan
@property
def DARMoffset(self):
......@@ -363,7 +366,7 @@ class ADV_IFO(IFO):
self.kat.nodes.replaceNode(self.kat.sPRCin, 'nHAM2out', 'nLaserOut')
def remove_FI_OMC(self, removeFI=True, removeOMC=True):
def remove_OMC(self):
"""
Method for removing the OMC and the FI blocks in kat-objects having these
included. The FI block contains an ideal Faraday isolator as well as the
......@@ -379,19 +382,17 @@ class ADV_IFO(IFO):
removeOMC : Boolean
If True, the OMC is removed. Must be True if removeFI = True.
"""
self.kat.removeBlock('OMCpath')
self.kat.removeBlock('OMC')
self.kat.cavOMC1.remove()
self.kat.cavOMC2.remove()
if removeFI and not removeOMC:
raise pkex.BasePyKatException("Must remove OMC if removing FI")
if removeFI:
self.kat.nodes.replaceNode(self.kat.sSRM_FI, 'nFI2a', 'nAS')
self.kat.removeBlock('FI')
self.kat.removeBlock('OMC')
self.kat.cavOMC.remove()
elif removeOMC:
self.kat.nodes.replaceNode(self.kat.sOM3_OMC, 'nOMC_ICa', 'nAS')
self.kat.removeBlock('OMC')
self.kat.cavOMC.remove()
self.kat.nodes.replaceNode(self.kat.sOut,
self.kat.sOut.nodes[0],
self.kat.components[self.mirrors['SRMAR']].nodes[1].name)
def adjust_PRC_length(self, verbose=False):
"""
Adjust PRC length so that it fulfils the requirement
......@@ -492,7 +493,7 @@ class ADV_IFO(IFO):
else:
raise pkex.BasePyKatException("\033[91m offset_type must be DARM or MICH. \033[0m")
print("-- applying user-defined DC offset to {}:".format(offset_type))
vprint(verbose, "-- applying user-defined DC offset to {}:".format(offset_type))
_kat = self.kat
m = self.mirrors
......@@ -532,11 +533,11 @@ class ADV_IFO(IFO):
out = kat.run()
print("-- adjusting {} DCoffset based on light in dark port:".format(offset_type))
vprint(verbose, "-- adjusting {} DCoffset based on light in dark port:".format(offset_type))
waste_light = round(float(out[signame]),1)
print(" waste light in AS port of {:2} W".format(waste_light))
vprint(verbose, " waste light in AS port of {:2} W".format(waste_light))
#kat_lock = _kat.deepcopy()
DCoffset = self.find_DC_offset(5*waste_light, offset_type, verbose=verbose)
......@@ -550,6 +551,7 @@ class ADV_IFO(IFO):
This function directly alters the tunings of the associated kat object.
"""
m = self.mirrors
if offset_type == 'DARM' or offset_type == 'darm':
isDARM = True
......@@ -602,7 +604,7 @@ class ADV_IFO(IFO):
#kat.NI.phi = IXphi - phi/2.0
out = kat.run()
print(" ! ", out[self.B1.get_signal_name()], phi)
# print(verbose, " ! ", out[self.B1.get_signal_name()], phi)
return np.abs(out[self.B1.get_signal_name()] - AS_power)
......@@ -862,7 +864,127 @@ class ADV_IFO(IFO):
for _ in inspect.getmembers(self, lambda x: isinstance(x, Output)):
self.Outputs[_[0]] = _[1]
def thermal_lensing(self, thermal_mirror_list):
out = compute_thermal_effect(self.kat, thermal_mirror_list, nScale=True)
mirrors = self.kat.IFO.mirrors
# Setting values to kat-object
for k,v in out.items():
# Setting new RoC (No RoC calculations yet)
# self.kat.components[k].Rc = v[1][0]
# Setting new lens
if k == mirrors['IX']:
self.kat.CPN_TL.f = float(v['f_CP_new'])
elif k == mirrors['IY']:
self.kat.CPW_TL.f = float(v['f_CP_new'])
def find_maxtem(self, tol=5e-3, verbose=False):
'''
Finding the minimum required maxtem for the power to converge to within the relative tolerance tol.
'''
kat1 = self.kat.deepcopy()
sigs = []
sigs.append(kat1.IFO.POW_BS.add_signal())
sigs.append(kat1.IFO.POW_X.add_signal())
sigs.append(kat1.IFO.POW_Y.add_signal())
kat1.parse('noxaxis\nyaxis abs')
run = True
P_old = np.zeros([2,3], dtype=float) + 1e-9
P = np.zeros(3, dtype=float) + 1e-9
rdiff = np.ones([2,3],dtype=float)
mxtm = 0
while run:
P_old[0,:] = P_old[1,:]
P_old[1,:] = P
rdiff_old = rdiff[1,:]
kat1.maxtem = mxtm
out = kat1.run()
# print(out.stdout)
for k,s in enumerate(sigs):
P[k] = out[s]
rdiff = np.abs((P-P_old)/P_old)
if rdiff.max()<tol:
run = False
# print(kat1.maxtem, rdiff, rdiff.max())
mxtm += 1
# Stepping back to the lowest acceptable maxtem
mxtm -= 2
# One more step back if the two previous iterations were within the tolerance
if rdiff_old.max() < tol:
mxtm -= 1
self.kat.maxtem = mxtm
vprint(verbose, "Maxtem set to {}".format(mxtm))
def find_warm_detector(self, mirror_list, DCoffset, verbose=False):
"""
Computes the thermal effects for the mirrors specified in mirror_list and sets the
warm interferometer values. For an input test masse, the thermal lens is computed
and is combined with the CP-lens into a new CP-lens.
mirror_list - List of mirror names to compute the thermal effect for
DCoffset - The DARM DC-offset used in this file [degrees]
verbose - If set to True, information is printed.
"""
kat1 = self.kat.deepcopy()
# Cold IFO RoCs and focal lengthts
new = copy.copy(kat1.IFO.cold_ifo)
tol = 1e-3
run = True
a = 0
while run:
a += 1
vprint(verbose, 'Iteration {}'.format(a))
old = copy.copy(new)
# Computing new paramaters
vprint(verbose, ' Finding maxtem...',end=" ")
kat2 = kat1.deepcopy()
kat2.IFO.remove_modulators()
kat2.IFO.find_maxtem(tol=1e-3)
vprint(verbose, '{}\n Re-tuning interferometer...'.format(kat2.maxtem), end=" ")
pretune(kat2, 1e-7, verbose=False)
# adv.pretune_status(kat2)
kat1.IFO.apply_tunings(kat2.IFO.get_tunings())
kat1.maxtem = kat2.maxtem
kat1.IFO.set_DC_offset(DCoffset=DCoffset, verbose=False)
vprint(verbose, 'Done!\n Computing thermal effect...', end=" ")
new, out = compute_thermal_effect(kat1, mirror_list, nScale=True)
vprint(verbose, 'Done!')
diff = np.zeros(len(new), dtype=float)
# Updating IFO parameters
for i,(k,v) in enumerate(new.items()):
if isinstance(kat1.components[k], pykat.components.lens):
kat1.components[k].f = v
vprint(verbose, ' {}.f: {:.5e} m --> {:.5e} m'.format(k,old[k],kat1.components[k].f.value), end=",")
elif (isinstance(kat1.components[k], pykat.components.mirror) or
isinstance(kat1.components[k], pykat.components.beamSplitter)):
kat1.components[k].Rc = v
vprint(verbose, ' {}.Rc: {:.5e} m --> {:.5e} m'.format(k,old[k],kat1.components[k].Rc.value), end=",")
# Comparing the new and previous parameters
diff[i] = np.abs((new[k] - old[k])/old[k])
vprint(verbose,"")
if diff.max() < tol:
run = False
vprint(verbose, 'Converged!')
# Setting new parameters to the kat-object
for i,(k,v) in enumerate(new.items()):
if isinstance(kat1.components[k], pykat.components.lens):
self.kat.components[k].f = v
elif (isinstance(kat1.components[k], pykat.components.mirror) or
isinstance(kat1.components[k], pykat.components.beamSplitter)):
self.kat.components[k].Rc = v
def assert_adv_ifo_kat(kat):
#print(ADV_IFO)
......@@ -893,7 +1015,7 @@ def make_kat(name="design_PR", katfile=None, verbose = False, debug=False, keepC
"""
# Pre-defined file-names
names = ['design_PR', 'design_PR_OMC']
names = ['design_PR', 'design_PR_OMC', "avirgo_PR_OMC", 'avirgo_PR_OMC_22012018']
# Mirror names. Mapping to IFO-specific names to faciliate creating new IFO-specific files.
# Change the values in the dictionary to the IFO-specific mirror names. Do not change the
......@@ -908,6 +1030,8 @@ def make_kat(name="design_PR", katfile=None, verbose = False, debug=False, keepC
'SR2': None, 'SR3': None,
'BS': 'BS', 'BSARX': 'BSAR1', 'BSARY': 'BSAR2'}
#signalNames = {'AS_DC': 'B1_DC', 'POP_f1': 'B2_f1', 'POP_f2': 'B2_f2', 'POP_f3': 'B2_f3', 'POP_f4':
# 'B2_f4', 'REFL_f1': 'B4_f1', 'REFL_f2': 'B4_f2'}
......@@ -1179,9 +1303,52 @@ def make_kat(name="design_PR", katfile=None, verbose = False, debug=False, keepC
kat.IFO.ASC_P_DOFs = kat.IFO.ASC_P_DOFs + (kat.IFO.PR3_P,)
kat.IFO.mirrors = mirrors
# Mirror properties for computing thermal effects
MPs = {}
# Common properties. Check if this is true.
common_properties = {}
common_properties['K'] = 1.380 # Thermal conductivity. Check value!
common_properties['T0'] = 295.0 # Temperature. Check value!
common_properties['emiss'] = 0.89 # Emissivity. Check value!
common_properties['alpha'] = 0.54e-6 # Thermal expansion coeff. Check value!
common_properties['sigma'] = 0.164 # Poisson ratio. Check value!
common_properties['dndT'] = 8.7e-6 # dn/dT. Check value!
# Setting common propertis
for k,v in mirrors.items():
if not ('AR' in k or v is None):
# print(k)
MPs[v] = copy.deepcopy(common_properties)
# Setting mirror specific properties
# HR coating absorptions. Values from Valeria Sequino.
MPs[mirrors['EX']]['aCoat'] = 0.24e-6
MPs[mirrors['EY']]['aCoat'] = 0.24e-6
MPs[mirrors['IX']]['aCoat'] = 0.19e-6
MPs[mirrors['IY']]['aCoat'] = 0.28e-6
# Substrate absorption [1/m]. Using upper limits from [TDR, table 2.6].
MPs[mirrors['EX']]['aSub'] = 3.0e-5
MPs[mirrors['EY']]['aSub'] = 3.0e-5
MPs[mirrors['IX']]['aSub'] = 3.0e-5
MPs[mirrors['IY']]['aSub'] = 3.0e-5
kat.IFO.mirror_properties = MPs
# Storing RoCs and focal lengths for the cold IFO. To be used when computing thermal effects
cold = {}
for k,v in kat.components.items():
if isinstance(v, pykat.components.mirror) or isinstance(v, pykat.components.beamSplitter):
cold[k] = v.Rc.value
elif isinstance(v, pykat.components.lens):
cold[k] = v.f.value
kat.IFO.cold_ifo = cold
kat.IFO.update()
kat.IFO.lockNames = None
return kat
......@@ -1204,9 +1371,9 @@ def pretune(_kat, pretune_precision=1.0e-4, verbose=False):
# kat and associated IFO object passed in
IFO = _kat.IFO
m = IFO.mirrors
print("-- pretuning interferometer to precision {0:2g} deg = {1:2g} m".format(pretune_precision,
pretune_precision*_kat.lambda0/360.0))
vprint(verbose,"-- pretuning interferometer to precision {0:2g} deg = {1:2g} m".format(pretune_precision,
pretune_precision*_kat.lambda0/360.0))
kat = _kat.deepcopy()
kat.removeBlock("locks", False)
......@@ -1273,7 +1440,7 @@ def pretune(_kat, pretune_precision=1.0e-4, verbose=False):
vprint(verbose, " found max/min at: {} (precision = {:2g})".format(phi, precision))
IFO.preSRCL.apply_tuning(phi)
print(" ... done")
vprint(verbose," ... done")
......@@ -1456,3 +1623,155 @@ def generate_locks(kat, gainsAdjustment = [0.1, 0.9, 0.9, 0.001, 0.02],
data['SRCL'] = {"accuracy": accuracies[4], "gain": gains[4]}
return data
def thermal_lensing(kat, mirror_list, nScale=False):
out = compute_thermal_effect(kat, mirror_list, nScale=nScale)
kat1 = kat.deepcopy()
mirrors = kat1.IFO.mirrors
# Setting values to kat-object
for k,v in out.items():
# Setting new RoC (No RoC calculations yet)
# kat1.components[k].Rc = v[1][0]
# Setting new lens
if k == mirrors['IX']:
kat1.CPN_TL.f = float(v['f_CP_new'])
elif k == mirrors['IY']:
kat1.CPW_TL.f = float(v['f_CP_new'])
return
def compute_thermal_effect(kat, mirror_list, nScale=False):
#################################
# Get powers and spot sizes
#################################
kat1 = kat.deepcopy()
mirrors = kat1.IFO.mirrors
code = ""
Ms = {}
for m in mirror_list:
# Getting HR-surface
hr = kat1.components[m]
# Getting AR-surface
arname = m+'AR'
if m == mirrors['BS']:
arname += '1'
ar = kat1.components[arname]
# Getting substrate
subname = 's'+m+'sub'
if m == mirrors['BS']:
subname += '1'
sub = kat1.components[subname]
# Storing compound mirrors in dictionary
Ms[m] = {'HR': hr, 'SUB': sub, 'AR': ar}
# Node definitions are different depending on mirrors
if m == mirrors['IY'] or m