diff --git a/README.rst b/README.rst index 676b1e18a25d1777ab9f2d513d06baa899e754ca..157bc7a7c271d96f5fd0e952353acb55291995c6 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,8 @@ It aims to provide a Python toolset for automating more complex tasks as well as providing a GUI for manipulating and viewing simulation setups. +Source code is hosted at https://git.ligo.org/finesse/pykat + Installation ------------- diff --git a/pykat/__init__.py b/pykat/__init__.py index b256447fffe90bf87756efbd575e25d033fa4dad..daf240044eb61ebfdf08d216553f99a0a664659f 100644 --- a/pykat/__init__.py +++ b/pykat/__init__.py @@ -3,7 +3,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -__version__ = "1.0.14" +__version__ = "1.0.17" # This flag is used to switch on the gui features in pkat at import time USE_GUI = False @@ -20,7 +20,7 @@ except ImportError: import pykat.exceptions as pkex NoGUIException = pkex.BasePyKatException("No PyQt4 module was found so cannot open a GUI") - + import pykat.finesse as finesse import pykat.components as components import pykat.detectors as detectors @@ -30,4 +30,3 @@ from pykat.optics.gaussian_beams import BeamParam from pykat.plotting import init_pykat_plotting - diff --git a/pykat/commands.py b/pykat/commands.py index 76d803f71b0de709662925f48cc944e5de0dcced..aefa0930816b8ece6294afb91307ae92492a7b8c 100644 --- a/pykat/commands.py +++ b/pykat/commands.py @@ -10,6 +10,8 @@ from __future__ import print_function from __future__ import unicode_literals import numpy +import warnings +import pykat import pykat.external.six as six import pykat.exceptions as pkex @@ -22,35 +24,38 @@ from numpy import min, max from pykat.param import Param, putter from collections import namedtuple from pykat.optics.gaussian_beams import BeamParam +from pykat.freeze import canFreeze - - - +@canFreeze class Command(object): __metaclass__ = abc.ABCMeta def __init__(self, name, unique): + self.__dict__["____FROZEN____"] = False + self._kat = None self.__unique = unique self.tag = None self.__removed = False self.__name = name.strip("*") self._putters = [] + def __deepcopy__(self, memo): """ When deep copying a kat object we need to take into account the instance specific properties. """ - cls = self.__class__ result = cls.__new__(cls) + result._unfreeze() result.__dict__ = copy.deepcopy(self.__dict__, memo) for _ in result._putters: _._updateOwner(result) - + + result._freeze() return result - + def getFinesseText(self): """ Base class for individual finesse optical components """ raise NotImplementedError("This function is not implemented") @@ -103,7 +108,8 @@ class variable(Command): def __init__(self, name, value): Command.__init__(self, name, False) self.__value = value - + self._freeze() + def getFinesseText(self): return "variable {name} {value}".format(name=self.name, value=self.value) @@ -135,6 +141,8 @@ class func(Command): self.output = putter(name, self) self._putters.append(self.output) + + self._freeze() def getFinesseText(self): rtn = [] @@ -175,7 +183,8 @@ class lock(Command): self.output = putter(name, self) self._putters.append(self.output) - + self._freeze() + @staticmethod def parseFinesseText(line, kat): v = line.split() @@ -228,6 +237,8 @@ class cavity(Command): self.enabled = True + self._freeze() + def getFinesseText(self): if self.enabled: return 'cav {0} {1} {2} {3} {4}'.format(self.name, self.__c1.name, self.__n1.name, self.__c2.name, self.__n2.name); @@ -405,6 +416,8 @@ class tf(Command): self.poles = [] self.gain = 1 self.phase = 0 + + self._freeze() def addPole(self,f, Q): self.poles.append(tf.fQ(SIfloat(f), SIfloat(Q))) @@ -504,6 +517,8 @@ class xaxis(Command): self.__param = param self.__comp = param._owner() + self._freeze() + def _set_variables(self): self.x = putter("x1", self) self.mx = putter("mx1", self) diff --git a/pykat/components.py b/pykat/components.py index ac1126ab99766b86225f844773b51ca7bc97c0fd..fb78a5ed368dc5a31bc8b0d2102a417488b6ad81 100644 --- a/pykat/components.py +++ b/pykat/components.py @@ -16,6 +16,7 @@ import pykat.external.six as six if six.PY2: import exceptions +import warnings import pykat.exceptions as pkex import pykat from pykat.node_network import * @@ -36,6 +37,7 @@ from pykat.param import Param, AttrParam import weakref import pykat.exceptions as pkex from copy import deepcopy +from pykat.freeze import canFreeze next_component_id = 1 @@ -85,7 +87,8 @@ class NodeGaussSetter(object): self.__node().setGauss(self.__comp(), self.qx, complex(value)) id_____pykat_class = 0 - + +@canFreeze class Component(object): __metaclass__ = abc.ABCMeta @@ -99,9 +102,11 @@ class Component(object): cnew = type(cnew_name, (cls,), {}) - return object.__new__(cnew) + o = object.__new__(cnew) + return o def __init__(self, name=None): + self._unfreeze() self._optivis_component = None self.__name = name @@ -118,21 +123,22 @@ class Component(object): global next_component_id self.__id = next_component_id next_component_id += 1 - + def __deepcopy__(self, memo): """ When deep copying a kat object we need to take into account the instance specific properties. """ - # Here we create a copy of this object based of the base class # of this one, otherwise we're making a copy of a copy of a copy... result = self.__class__.__new__(self.__class__.__base__) + result._unfreeze() result.__dict__ = copy.deepcopy(self.__dict__, memo) for _ in result._params: _._updateOwner(result) - + + result._freeze() return result def _register_param(self, param): @@ -204,7 +210,8 @@ class Component(object): self.__add_node_setter(ns) def __add_node_setter(self, ns): - + self._unfreeze() + if not isinstance(ns, NodeGaussSetter): raise exceptions.ValueError("Argument is not of type NodeGaussSetter") @@ -214,6 +221,8 @@ class Component(object): setattr(self.__class__, name, property(fget)) setattr(self, '__nodesetter_' + name, ns) + self._freeze() + def __get_node_setter(self, name): return getattr(self, '__nodesetter_' + name) @@ -500,7 +509,8 @@ class mirror(AbstractMirrorComponent): self._requested_node_names.append(node1) self._requested_node_names.append(node2) - + self._freeze() + def parseAttributes(self, values): for key in values.keys(): @@ -592,6 +602,8 @@ class beamSplitter(AbstractMirrorComponent): self._requested_node_names.append(node3) self._requested_node_names.append(node4) self.__alpha = Param("alpha", self, SIfloat(alpha)) + + self._freeze() @property def alpha(self): return self.__alpha @@ -704,6 +716,8 @@ class space(Component): self.__gy = AttrParam("gy", self, gy) self._default_fsig_param = self.__L + + self._freeze() @property def L(self): return self.__L @@ -830,6 +844,8 @@ class grating(Component): self.__rho_0 = AttrParam("rho_0", self, SIfloat(rho_0)) self.__alpha = AttrParam("alpha", self, SIfloat(alpha)) self._svgItem = None + + self._freeze() @property def n(self): return Param('n', self.__n) @@ -959,6 +975,7 @@ class isolator(Component): self.__S = Param("S",self,SIfloat(S)) self.__L = Param("L",self,SIfloat(L)) + self._freeze() @property def S(self): return self.__S @@ -1064,6 +1081,8 @@ class isolator1(Component): self._requested_node_names.append(node4) self._svgItem = None + self._freeze() + @staticmethod def parseFinesseText(text): values = text.split() @@ -1104,6 +1123,8 @@ class lens(Component): self._svgItem = None self.__f = Param("f", self, SIfloat(f)) self.__p = Param("p", self, SIfloat(p)) + + self._freeze() @property def f(self): return self.__f @@ -1200,6 +1221,8 @@ class modulator(Component): self.type = modulation_type self._default_fsig_param = self.__phase + + self._freeze() @property def f(self): return self.__f @@ -1316,6 +1339,8 @@ class laser(Component): self._svgItem = None self._default_fsig_param = self.__f_offset + + self._freeze() @property def P(self): return self.__power @@ -1413,6 +1438,9 @@ class squeezer(Component): self.__angle = Param("angle", self, SIfloat(angle), canFsig=False, fsig_name="angle") self._svgItem = None self.entangled_carrier = entangled_carrier + + self._freeze() + @property def db(self): return self.__db diff --git a/pykat/detectors.py b/pykat/detectors.py index f3195d08a753f2eb8fd448da9a4192c4b96786ea..62e6938f3b7215cf4ddd7d65141c743013f0f084 100644 --- a/pykat/detectors.py +++ b/pykat/detectors.py @@ -26,6 +26,7 @@ import warnings import copy from pykat import USE_GUI, NoGUIException +from pykat.freeze import canFreeze if USE_GUI: import pykat.gui.resources @@ -33,6 +34,7 @@ if USE_GUI: id_____pykat_class = 0 +@canFreeze class BaseDetector(object) : """ This is a base class for all detectors. Classes Detector1 and Detector2 should be used directly. @@ -51,9 +53,12 @@ class BaseDetector(object) : cnew = type(cnew_name, (cls,), {}) - return object.__new__(cnew) + o = object.__new__(cnew) + return o def __init__(self, name, nodes=None, max_nodes=1): + + self._unfreeze() self.__name = name self._svgItem = None @@ -106,8 +111,10 @@ class BaseDetector(object) : # Here we create a copy of this object based of the base class # of this one, otherwise we're making a copy of a copy of a copy... result = self.__class__.__new__(self.__class__.__base__) + result._unfreeze() result.__dict__ = copy.deepcopy(self.__dict__, memo) + result._freeze() return result def _register_param(self, param): @@ -243,6 +250,8 @@ class beam(Detector1): self.alternate_beam = alternate_beam self.__f = Param("f", self, frequency) + self._freeze() + @property def f(self): return self.__f @@ -291,6 +300,8 @@ class cp(Detector0): self.cavity = str(cavity) self.direction = direction self.parameter = parameter + + self._freeze() @property def direction(self): return self.__direction @@ -345,6 +356,8 @@ class xd(Detector0): self.component = component self.motion = motion + + self._freeze() @staticmethod def parseFinesseText(text): @@ -373,6 +386,8 @@ class ad(Detector1): self.alternate_beam = alternate_beam self.__f = Param("f", self, frequency) + self._freeze() + @property def mode(self): return self.__mode @mode.setter @@ -426,6 +441,8 @@ class gouy(Detector1): self.spaces = copy.copy(spaces) self.direction = direction self.alternate_beam = False + + self._freeze() @property def direction(self): return self.__dir @@ -475,6 +492,8 @@ class bp(Detector1): self.parameter = parameter self.direction = direction self.alternate_beam = alternate_beam + + self._freeze() @property def direction(self): return self.__dir @@ -598,9 +617,10 @@ class pd(Detector1): ps[i].value = kwargs[p] elif i<num_demods-1: raise pkex.BasePyKatException("Missing demodulation phase {0} (phase{0})".format(i+1)) - self.__set_demod_attrs() + + self._freeze() @property def senstype(self): return self.__senstype @@ -655,6 +675,7 @@ class pd(Detector1): For the set number of demodulations the correct number of Parameters are created. """ + self._unfreeze() # if there are demodulations present then we want to add # the various parameters so they are available for users @@ -678,8 +699,8 @@ class pd(Detector1): if hasattr(self, "phase"+name): delattr(self.__class__, "phase"+name) - else: - return + + self._freeze() @staticmethod def parseFinesseText(text): @@ -776,7 +797,11 @@ class qnoised(pd): def __init__(self, name, num_demods, node_name, alternate_beam=False, pdtype=None, **kwargs): super(qnoised, self).__init__(name, num_demods, node_name, alternate_beam=alternate_beam, pdtype=pdtype, **kwargs) + self._unfreeze() + self.__homangle = AttrParam("homangle", self, None) + + self._freeze() @property def homangle(self): return self.__homangle @@ -985,7 +1010,9 @@ class hd(Detector2): BaseDetector.__init__(self, name, (node1_name, node2_name), max_nodes=2) self.__phase = Param("phase", self, phase) - + + self._freeze() + @property def phase(self): return self.__phase @phase.setter @@ -1026,6 +1053,8 @@ class qhd(Detector2): self.__phase = Param("phase", self, phase) self.sensitivity = sensitivity + + self._freeze() @property def phase(self): return self.__phase diff --git a/pykat/finesse.py b/pykat/finesse.py index f442a719cb8d644dfe9b7f2b11f312921359cb3a..f25155f43cc4a3bf5795eea590134d10a6392a4f 100644 --- a/pykat/finesse.py +++ b/pykat/finesse.py @@ -87,14 +87,13 @@ from pykat.commands import Command, xaxis from pykat.SIfloat import * from pykat.param import Param, AttrParam from pykat.external import progressbar - +from pykat.freeze import canFreeze import pykat.external.six as six import pykat.exceptions as pkex from pykat import USE_GUI, HAS_OPTIVIS, NoGUIException - if HAS_OPTIVIS: from optivis.bench.labels import Label as optivis_label from optivis.geometry import Coordinates as optivis_coord @@ -210,6 +209,16 @@ class BlockedKatFile(object): bkf.write("mytest.kat") """ + def __str__(self): + rtn = "" + + for block in self.ordering: + rtn += "\n%%% FTblock " + block + "\n" + rtn += self.blocks[block] + rtn += "%%% FTend " + block + "\n" + + return rtn + def __init__(self, NO_BLOCK="NO_BLOCK"): self.__NO_BLOCK = NO_BLOCK self.ordering = [self.__NO_BLOCK] @@ -265,12 +274,14 @@ class BlockedKatFile(object): if len(values) >= 3 and values[0] == "%%%": if values[1] == "FTblock": newTag = values[2] - + if self.__currentBlock != None and self.__currentBlock != self.__NO_BLOCK: warnings.warn("found block {0} before block {1} ended".format(newTag, self.__currentBlock)) if newTag in self.blocks: - raise pkex.BasePyKatException("Block `{0}` has already been read".format(newTag)) + #raise pkex.BasePyKatException("Block `{0}` has already been read".format(newTag)) + self.__currentBlock = newTag + continue self.blocks[newTag] = "" self.__currentBlock = newTag @@ -330,18 +341,24 @@ def GUILength(L): Should scale the lengths in some way to handle km and mm for time being """ return L # * ( 40 * erfc(L/400.0) + 0.01) - + +@canFreeze class KatRun(object): def __init__(self): + self._unfreeze() self.runtime = None self.StartDateTime = datetime.datetime.now() self.x = None + self.stdout = None + self.stderr = None + self.runDateTime = None self.y = None self.xlabel = None self.ylabels = None self.katScript = None self.katVersion = None self.yaxis = None + self._freeze() def info(self): @@ -623,9 +640,11 @@ class KatRun(object): return out.squeeze() else: raise pkex.BasePyKatException("No output by the name '{0}' found in the output".format(str(value))) - + +@canFreeze class KatRun2D(object): def __init__(self): + self._unfreeze() self.runtime = None self.startDateTime = datetime.datetime.now() self.x = None @@ -638,6 +657,7 @@ class KatRun2D(object): self.katVersion = None self.stderr = None self.stdout = None + self._freeze() def saveKatRun(self, filename): with open(filename,'w') as outfile: @@ -658,10 +678,13 @@ class KatRun2D(object): else: raise pkex.BasePyKatException("No output by the name {0} found".format(str(value))) - +@canFreeze class Signals(object): + + @canFreeze class fsig(object): def __init__(self, param, name, amplitude, phase, signal): + self._unfreeze() self._params = [] self.__target = param self.__name = name @@ -669,6 +692,7 @@ class Signals(object): self.__phase = Param("phase", self, SIfloat(phase)) self.__removed = False self.__signal = signal + self._freeze() # unfortunatenly the target names for fsig are not the same as the # various parameter names of the components, e.g. mirror xbeta is x @@ -691,7 +715,7 @@ class Signals(object): raise pkex.BasePyKatException("Signal {0} has already been marked as removed".format(self.name)) else: self.__signal.targets.remove(self) - self.__remove = True + self.__removed = True @property def name(self): return self.__name @@ -742,29 +766,36 @@ class Signals(object): del self.targets[:] + self.f = None + @property def f(self): return self.__f @f.setter def f(self,value): + if value is None: + self.__f.value = None + return + v = SIfloat(value) if v <= 0: raise pkex.BasePyKatException("Signal frequency must be greater than 0.") self.__f.value = SIfloat(value) - + def __init__(self, kat): + self._unfreeze() self._default_name = "fsignal" self.targets = [] self._params = [] self.__f = Param("f", self, None) self._kat = kat + self._freeze() def _register_param(self, param): self._params.append(param) def apply(self, target, amplitude, phase, name=None): - if target is None: raise pkex.BasePyKatException("No target was specified for signal to be applied") @@ -811,6 +842,7 @@ Constant = namedtuple('Constant', 'name, value, usedBy') id___ = 0 +@canFreeze class kat(object): def __new__(cls, *args, **kwargs): @@ -828,7 +860,7 @@ class kat(object): return object.__new__(cnew) def __init__(self, kat_file=None, kat_code=None, katdir="", katname="", tempdir=None, tempname=None): - + self._unfreeze() self.__looking = False self.scene = None # scene object for GUI self.verbose = True @@ -866,6 +898,7 @@ class kat(object): self.__time_code = None self.__yaxis = "abs" # default yaxis self.__lambda0 = 1064e-9 + self.__finesse_dir = None if kat_code != None and kat_file != None: raise pkex.BasePyKatException("Specify either a Kat file or some Kat code, not both.") @@ -875,7 +908,9 @@ class kat(object): if kat_file != None: self.loadKatFile(kat_file) - + + self._freeze() + def deepcopy(self): return copy.deepcopy(self) @@ -1137,6 +1172,9 @@ class kat(object): print("$" + key, "::::", "owner =", self.__variables[key].owner.name, ", use count =", self.__variables[key].putCount) def parseCommands(self, commands, blocks=None, addToBlock=None, preserve=False): + + commands = str(commands) + try: if addToBlock is not None and blocks is not None: raise pkex.BasePyKatException("When parsing commands you cannot set both blocks and addToBlock arguments") @@ -1172,7 +1210,9 @@ class kat(object): warnings.warn("found block {0} before block {1} ended".format(newTag, self.__currentTag)) if newTag in self.__blocks: - raise pkex.BasePyKatException("Block `{0}` has already been read".format(newTag)) + #raise pkex.BasePyKatException("Block `{0}` has already been read".format(newTag)) + self.__currentTag = newTag + continue # Just add to existing block data self.__blocks[newTag] = Block(newTag) # create new list to store all references to components in block self.__currentTag = newTag @@ -1791,6 +1831,10 @@ class kat(object): traceData[-1][node_name] = (pykat.BeamParam(q=complex(qx), wavelength=self.lambda0), pykat.BeamParam(q=complex(qy), wavelength=self.lambda0), component_name) + direc = a[1].split(";")[-1].strip().split(maxsplit=1)[-1] + + traceData[-1][node_name][0].direction = direc + traceData[-1][node_name][1].direction = direc finally: ifile.close() @@ -2016,6 +2060,7 @@ class kat(object): def add(self, obj, block=NO_BLOCK): try: + self._unfreeze() obj.tag = block self.__blocks[block].contents.append(obj) @@ -2048,9 +2093,11 @@ class kat(object): raise pkex.BasePyKatException("Object {0} could not be added".format(obj)) obj._on_kat_add(self) - + except pkex.BasePyKatException as ex: pkex.PrintError("Error on adding object:", ex) + finally: + self._freeze() def readOutFile(self, filename): diff --git a/pykat/freeze.py b/pykat/freeze.py new file mode 100644 index 0000000000000000000000000000000000000000..20a24a7c4152f1143ed5888220240afa04bda72a --- /dev/null +++ b/pykat/freeze.py @@ -0,0 +1,25 @@ +import warnings + +def canFreeze(cls): + + def _freeze(self): self.__dict__["____FROZEN____"] = True + def _unfreeze(self): self.__dict__["____FROZEN____"] = False + + def frozensetattr(self, name, value): + if "____FROZEN____" in self.__dict__ and self.__dict__["____FROZEN____"] and not hasattr(self, name): + if hasattr(self, "name"): + n = self.name + elif hasattr(self, "__name"): + n = self.__name + else: + n = self.__class__.__name__ + + warnings.warn("'%s' does not have attribute called '%s'" % (n, name), stacklevel=2) + + super(cls, self).__setattr__(name, value) + + cls.__setattr__ = frozensetattr + cls._freeze = _freeze + cls._unfreeze = _unfreeze + + return cls \ No newline at end of file diff --git a/pykat/gw_detectors/finesse_files/aLIGO.kat b/pykat/gw_detectors/finesse_files/aLIGO.kat index 601c6f7c641263d94fa9928755e1c8665bb996f4..93cd05395f6e998d14bad7320d6a2eddaa6ab3df 100644 --- a/pykat/gw_detectors/finesse_files/aLIGO.kat +++ b/pykat/gw_detectors/finesse_files/aLIGO.kat @@ -12,7 +12,7 @@ # http://iopscience.iop.org/article/10.1088/0264-9381/32/7/074001/meta#cqg507871s4-8 # - G. Billingsley. LIGO core optics reference page. https://galaxy.ligo. caltech.edu/optics/ # -# Anna Green, 28th November 2016 +# Anna Green, Andreas Freise, 28th November 2016 #-------------------------------------------------------------------------- # # References used for aLIGO parameters @@ -49,7 +49,7 @@ # Changes in file structure # - removed length calculation fucntions for PRC and SRC sub-length # and added length values into space components -# - +# - removed maxtem 4 command from HOMs block # # Naming changes # - changed naming for thermal lens components @@ -57,6 +57,8 @@ # - changed naming of SRMHR to SRM and node names in SRM substrate # - changed name of ITMHR to ITM and ETMHR to ETM # - changed names of arm spaces from LXarm, LYarm to LX, LY +# - changed name of HOMs block to cavities +# - changed PR2 node4 name from dump11 to nAPOP (auxilliary POP) # # #-------------------------------------------------------------------------- @@ -95,7 +97,7 @@ attr PRM Rc 11.009 # Distance between PRM and PR2 s lp1 16.6107 nPRM2 nPR2a # PR2 -bs1 PR2 250u $Mloss 0 -0.79 nPR2a nPR2b nPOP dump11 +bs1 PR2 250u $Mloss 0 -0.79 nPR2a nPR2b nPOP nAPOP attr PR2 Rc -4.545 # Distance from PR2 to PR3 s lp2 16.1647 nPR2b nPR3a @@ -231,9 +233,8 @@ attr SRM Rc -5.6938 ########################################################################### %%% FTend SRC -%%% FTblock HOMs +%%% FTblock cavities ########################################################################### -maxtem 4 #cav cavIMC MC2 nMC2in MC2 nMC2refl cav cavXARM ITMX nITMX2 ETMX nETMX1 cav cavYARM ITMY nITMY2 ETMY nETMY1 @@ -244,7 +245,7 @@ cav cavSRY SRM nSRM1 ITMY nITMYs2 # removed OMC ########################################################################### -%%% FTend HOMs +%%% FTend cavities %%% FTblock constants ########################################################################### diff --git a/pykat/gw_detectors/ifo.py b/pykat/gw_detectors/ifo.py index 6cd77428decd7076a12ce73ea4c6d025976ba4c3..b2e5c5d1e3db0ac0f6a8178e921b6c26f1ce6d2b 100644 --- a/pykat/gw_detectors/ifo.py +++ b/pykat/gw_detectors/ifo.py @@ -8,13 +8,18 @@ import math import copy import warnings import cmath +import inspect +import six + from pykat import finesse from pykat.finesse import BlockedKatFile + import pykat.components import pykat.exceptions as pkex import pykat.external.peakdetect as peak import matplotlib.pyplot as plt import pkg_resources + from scipy.optimize import fmin global nsilica, clight @@ -116,7 +121,16 @@ class aLIGO(object): self.CARM = DOF("CARM", self.REFL_f1, "I", ["ETMX", "ETMY"], [1, 1], 1.5) self.DARM = DOF("DARM", self.AS_DC, "", ["ETMX", "ETMY"], [1,-1], 1.0) self.SRCL = DOF("SRCL", self.REFL_f2, "I", "SRM", 1, 1e2) - + + self.__DOFs = {} + + for _ in inspect.getmembers(self, lambda x: isinstance(x, DOF)): + self.__DOFs[_[0]] = _[1] + + @property + def DOFs(self): + return copy.copy(self.__DOFs) + def adjust_PRC_length(self, kat, verbose=False): """ Adjust PRC length so that it fulfils the requirement @@ -159,7 +173,7 @@ put f1m f $mx1 ax.set_xlim([np.min(out.x-self.f1), np.max(out.x-self.f1)]) ax.set_xlabel("delta_f1 [Hz]") ax.set_ylabel('sqrt(W) ') - ax.grid() + ax.grid(True) ax.legend() plt.tight_layout() plt.show(block=0) @@ -395,28 +409,127 @@ put f1m f $mx1 ax.set_xlim([np.min(out.x), np.max(out.x)]) ax.set_xlabel("phi [deg] {}".format(d.optics[0])) ax.set_ylabel('{} [W] '.format(d.signal_name(kat))) - ax.grid() + ax.grid(True) plt.tight_layout() plt.show(block=0) - def plot_error_signals(self, _kat, xlimits=[-1,1]): + def _strToDOFs(self, DOFs): + dofs = [] + + for _ in DOFs: + if isinstance(_, six.string_types): + if _ in self.__DOFs: + dofs.append(self.__DOFs[_]) + else: + raise pkex.BasePyKatException("Could not find DOF called `%s`. Possible DOF options: %s" % (_, str(list(self.__DOFs.keys())))) + else: + raise pkex.BasePyKatException("'%s' not possible DOF options: %s" % (_, str(list(self.__DOFs.keys())))) + + return dofs + + def plot_error_signals(self, _kat, xlimits=[-1,1], DOFs=None, plotDOFs=None, + replaceDOFSignals=False, block=True, fig=None, legend=None): + """ + Displays error signals for a given kat file. Can also be used to plot multiple + DOF's error signals against each other for visualising any cross coupling. + + _kat: LIGO based kat object. + xlimits: Range of DOF to plot in degrees + DOFs: list, DOF names to compute. Default: DARM, CARM, PRCL, SRCL, MICH + plotDOFs: list, DOF names to plot against each DOF. If None the same DOF as in DOFs is plotted. + block: Boolean, for plot blocking terminal or not if being shown + replaceDOFSignals: Bool, replaces already present signals for any DOF if already defined in kat. Regardless of this value, it will add default signals if none found. + fig: figure, uses predefined figure, when defined it won't be shown automatically + legend: string, if no plotDOFs is defined this legend is shown + + Example: + import pykat + from pykat.gw_detectors import ifo + + ligo = ifo.aLIGO() + + # Plot default + ligo.plot_error_signals(ligo.kat, block=True) + # plot MICH and CARM against themselves + ligo.plot_error_signals(ligo.kat, DOFs=["MICH", "CARM"], block=True) + # plot DARM and CARM against MICH + ligo.plot_error_signals(ligo.kat, DOFs=["MICH"], plotDOFs=["DARM", "CARM"], block=True) + """ + kat = _kat.deepcopy() kat.verbose = False kat.noxaxis = True - dofs = [self.DARM, self.CARM, self.PRCL, self.SRCL, self.MICH] - idx=1 - fig = plt.figure() - for d in dofs: - ax = fig.add_subplot(2,3,idx) - idx+=1 - out = scan_DOF(kat, d, xlimits = np.multiply(d.scale,xlimits), relative = True) - ax.plot(out.x,out[d.signal_name(kat)]) + + if DOFs is None: + dofs = [self.DARM, self.CARM, self.PRCL, self.SRCL, self.MICH] + else: + dofs = self._strToDOFs(DOFs) + + # add in signals for those DOF to plot + for _ in dofs: + if not (not replaceDOFSignals and hasattr(kat, _.signal_name(kat))): + kat.parseCommands(_.signal(kat)) + + toShow = None + + if plotDOFs is not None: + toShow = self._strToDOFs(plotDOFs) + + # Check if other DOF signals we need to include for plotting + for _ in toShow: + if not (not replaceDOFSignals and hasattr(kat, _.signal_name(kat))): + kat.parseCommands(_.signal(kat)) + + if fig is not None: + _fig = fig + else: + _fig = plt.figure() + + nrows = 2 + ncols = 3 + + if DOFs is not None: + n = len(DOFs) + + if n < 3: + nrows = 1 + ncols = n + + for d, idx in zip(dofs, range(1, len(dofs)+1)): + ax = _fig.add_subplot(nrows, ncols, idx) + + scan_cmd = scan_optics_string(d.optics, d.factors, "scan", linlog="lin", + xlimits=np.multiply(d.scale, xlimits), steps=200, + axis=1, relative=True) + kat.parseCommands(scan_cmd) + out = kat.run() + + if toShow is None: + ax.plot(out.x, out[d.signal_name(kat)], label=legend) + else: + for _ in toShow: + if legend is None: + legend = _.name + + ax.plot(out.x, out[_.signal_name(kat)], label=legend) + ax.set_xlim([np.min(out.x), np.max(out.x)]) ax.set_xlabel("{} [deg]".format(d.name)) - ax.set_ylabel('{} [W] '.format(d.port.name)) - ax.grid() + + if plotDOFs is None: + ax.set_ylabel('{} [W] '.format(d.port.name)) + else: + ax.set_ylabel('Error signal [W]') + + ax.grid(True) + + if toShow is not None or legend is not None: + plt.legend(loc=0) + plt.tight_layout() - plt.show(block=0) + + if fig is None: + plt.show(block=block) def set_DC_offset(self, _kat, DCoffset=None, verbose=False): if DCoffset: @@ -799,19 +912,24 @@ class port(object): def scan_optics_string(_optics, _factors, _varName, linlog="lin", xlimits=[-100, 100], steps=200, axis=1,relative=False): optics=make_list_copy(_optics) factors=make_list_copy(_factors) + if len(optics) != len(factors): raise pkex.BasePyKatException("you must provide a factor for each optics") if linlog not in ["lin", "log"]: raise pkex.BasePyKatException("linlog must be 'lin' or 'log'") + _tuneStr = "var {} 0\n".format(_varName) + if axis==1: _tuneStr += "xaxis {} phi {} {} {} {}".format(_varName, linlog, xlimits[0], xlimits[1], steps) elif (axis==2 or axis==3): _tuneStr += "x{}axis {} phi {} {} {} {}".format(axis, _varName, linlog, xlimits[0], xlimits[1], steps) else: raise pkex.BasePyKatException("axis must be 1, 2 or 3") + _putStr = "" + for idx, o in enumerate(optics): if factors[idx] == 1: _xStr="$x" @@ -826,6 +944,7 @@ def scan_optics_string(_optics, _factors, _varName, linlog="lin", xlimits=[-100, _putStr = "\n".join([_putStr, "{} {} phi {}{}".format(_putCmd, o, _xStr, axis)]) _tuneStr += _putStr + return _tuneStr def make_transparent(kat, _components): @@ -915,12 +1034,14 @@ def BS_optical_path(thickness, n=nsilica, angle=45.0): def scan_DOF(_kat, DOF, xlimits=[-100, 100], steps=200, relative=False): kat = _kat.deepcopy() - scan_string = scan_optics_string(DOF.optics, DOF.factors, "scan", linlog="lin", xlimits=xlimits, steps=steps, axis=1,relative=relative) + + scan_string = scan_optics_string(DOF.optics, DOF.factors, "scan", linlog="lin", + xlimits=xlimits, steps=steps, axis=1, relative=relative) + kat.parseCommands(scan_string) - sigStr = DOF.signal(kat) - kat.parseCommands(sigStr) - out = kat.run() - return out + kat.parseCommands(DOF.signal(kat)) + + return kat.run() def scan_optics(_kat, _optics, _factors, xlimits=[-100, 100], steps=200,relative=False): """ @@ -935,12 +1056,16 @@ def scan_optics(_kat, _optics, _factors, xlimits=[-100, 100], steps=200,relative scan_optis(kat, ["ETMX", "ETMY", [1, -1]) """ kat = _kat.deepcopy() + optics=make_list_copy(_optics) factors=make_list_copy(_factors) - scan_string = scan_optics_string(optics, factors, "scan", linlog="lin", xlimits=xlimits, steps=steps, axis=1,relative=relative) + + scan_string = scan_optics_string(optics, factors, "scan", linlog="lin", xlimits=xlimits, + steps=steps, axis=1,relative=relative) + kat.parseCommands(scan_string) - out = kat.run() - return out + + return kat.run() def optical_gain(_kat, DOF_sig, DOF_det, f=10.0): kat = _kat.deepcopy() diff --git a/test/run_tests.py b/test/run_tests.py index ffa3705f552f43f2027e051d2acbafeadabed2fc..f89a89f26981f9c10ec3319f70bc413b77410d1a 100644 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -2,6 +2,9 @@ import pykat import traceback import os import sys +import warnings + +warnings.filterwarnings('error') class bcolors: HEADER = '\033[95m' diff --git a/test/test_scripts/structural/test_deepcopying_references.py b/test/test_scripts/structural/test_deepcopying_references.py index 2d3fc7f41ed55a947966d56388d9dbe9c3a8ccf4..99342fff049eadd6a48033b3b43169d0122ec2ba 100644 --- a/test/test_scripts/structural/test_deepcopying_references.py +++ b/test/test_scripts/structural/test_deepcopying_references.py @@ -44,7 +44,7 @@ kat1.o1.num_demods = 2 assert(hasattr(kat1.o1, "f2")) assert(not hasattr(kat0.o1, "f2")) -kat1.num_demods = 1 +kat1.o1.num_demods = 1 assert(hasattr(kat1.o1, "f1")) diff --git a/test/test_scripts/structural/test_lambda0_beam_param.py b/test/test_scripts/structural/test_lambda0_beam_param.py index ac78e97028cfb9309901452a88c2fa5f906d0de0..83010b824736b4ccc364879d32122b40fd745cc7 100644 --- a/test/test_scripts/structural/test_lambda0_beam_param.py +++ b/test/test_scripts/structural/test_lambda0_beam_param.py @@ -26,7 +26,7 @@ yaxis abs:deg # move detector frequency with xax kat = finesse.kat() kat.verbose = False kat.lambda0 = 1550e-9 -kat.parseKatCode(kat_code) +kat.parseCommands(kat_code) kat.maxtem = 0 kat.trace = 2 out, T = kat.run(getTraceData=True)