diff --git a/pykat/commands.py b/pykat/commands.py index 241f7b7d971efec41b6e3f0d382845bb70c8613a..f146667019f1d6142fa7cf597341c5708c0d61ca 100644 --- a/pykat/commands.py +++ b/pykat/commands.py @@ -34,7 +34,23 @@ class Command(object): 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.__dict__ = copy.deepcopy(self.__dict__, memo) + for _ in result._putters: + _._updateOwner(result) + + return result + def getFinesseText(self): """ Base class for individual finesse optical components """ raise NotImplementedError("This function is not implemented") @@ -48,10 +64,30 @@ class Command(object): Called when this component has been added to a kat object """ self._kat = kat + + for _ in self._putters: + kat.registerVariable(_.name, _) - def remove(self): - self._kat.remove(self) + def _on_kat_remove(self): self.__removed = True + + for i in range(len(self._putters)): + _ = self._putters[i] + + self._kat.unregisterVariable(_.name) + _.clearPuts() + + for i in range(len(self._putters)): + del self._putters[0] + + del self._putters[:] + + + def remove(self): + if self.__removed: + raise pkex.BasePyKatException("{0} has already been marked as removed".format(self.name)) + else: + self._kat.remove(self) @property def name(self): return self.__name @@ -95,14 +131,19 @@ class func(Command): self.value = value self.noplot = False + self.enabled = True + + self.output = putter(name, self) + self._putters.append(self.output) def getFinesseText(self): rtn = [] - if self.noplot: - rtn.append("noplot " + self.name) + if self.enabled: + if self.noplot: + rtn.append("noplot " + self.name) - rtn.append("func {name} = {value}".format(name=self.name, value=str(self.value))) + rtn.append("func {name} = {value}".format(name=self.name, value=str(self.value))) return rtn @@ -118,10 +159,7 @@ class func(Command): return func(v2[0].split()[1], v2[1]) else: raise pkex.BasePyKatException("'{0}' not a valid Finesse func command".format(line)) - - - - + class lock(Command): def __init__(self, name, variable, gain, accuracy, singleLock=False): @@ -132,6 +170,10 @@ class lock(Command): self.__accuracy = accuracy self.singleLock = singleLock self.enabled = True + + + self.output = putter(name, self) + self._putters.append(self.output) @staticmethod @@ -278,7 +320,78 @@ class gauss(object): kat.nodes[node].setGauss(kat.components[component], gp) else: kat.nodes[node].setGauss(kat.components[component], gpx, gpy) - + +# class tf(Command): +# +# class fQ(object): +# def __init__(self, f, Q, tf): +# assert(tf is not None) +# self._tf = tf +# self.__f = Param("f", self, None, canFsig=False, isPutable=True, isPutter=False, isTunable=True) +# self.__Q = Param("Q", self, None, canFsig=False, isPutable=True, isPutter=False, isTunable=True) +# +# def _register_param(self, param): +# self._tf._params.append(param) +# +# @property +# def f(self): return self.__f +# @f.setter +# def f(self,value): self.__f.value = SIfloat(value) +# +# @property +# def Q(self): return self.__Q +# @Q.setter +# def Q(self,value): self.__Q.value = SIfloat(value) +# +# def __init__(self, name): +# Command.__init__(self, name, False) +# self.zeros = [] +# self.poles = [] +# self.gain = 1 +# self.phase = 0 +# self._params = [] +# +# def addPole(self,f, Q): +# self.poles.append(tf.fQ(SIfloat(f), SIfloat(Q), self)) +# +# def addZero(self,f, Q): +# self.zeros.append(tf.fQ(SIfloat(f), SIfloat(Q), self)) +# +# @staticmethod +# def parseFinesseText(text): +# values = text.split() +# +# if ((len(values)-4) % 3) != 0: +# raise pkex.BasePyKatException("Transfer function Finesse code format incorrect '{0}'".format(text)) +# +# _tf = tf(values[1]) +# +# _tf.gain = SIfloat(values[2]) +# _tf.phase = SIfloat(values[3]) +# +# N = int((len(values)-4) / 3) +# +# for i in range(1,N+1): +# if values[i*3+1] == 'p': +# _tf.addPole(SIfloat(values[i*3+2]), SIfloat(values[i*3+3])) +# elif values[i*3+1] == 'z': +# _tf.addZero(SIfloat(values[i*3+2]), SIfloat(values[i*3+3])) +# else: +# raise pkex.BasePyKatException("Transfer function pole/zero Finesse code format incorrect '{0}'".format(text)) +# +# return _tf +# +# def getFinesseText(self): +# rtn = "tf {name} {gain} {phase} ".format(name=self.name,gain=self.gain,phase=self.phase) +# +# for p in self.poles: +# rtn += "p {f} {Q} ".format(f=p.f, Q=p.Q) +# +# for z in self.zeros: +# rtn += "p {f} {Q} ".format(f=z.f, Q=z.Q) +# +# return rtn + class tf(Command): class fQ(object): @@ -298,7 +411,7 @@ class tf(Command): def addZero(self,f, Q): self.zeros.append(tf.fQ(SIfloat(f), SIfloat(Q))) - + @staticmethod def parseFinesseText(text): values = text.split() @@ -354,9 +467,12 @@ class xaxis(Command): self._axis_type = axis_type - self.x = putter("x1") - self.mx = putter("mx1") + self.x = putter("x1", self) + self.mx = putter("mx1", self) + self._putters.append(self.x) + self._putters.append(self.mx) + if scale == "lin": scale = Scale.linear elif scale == "log": @@ -433,8 +549,11 @@ class xaxis(Command): class x2axis(xaxis): def __init__(self, scale, limits, param, steps, comp=None, axis_type="x2axis"): xaxis.__init__(self, scale, limits, param, steps, comp=comp, axis_type=axis_type) - self.x = putter("x2") - self.mx = putter("mx2") + self.x = putter("x2", self) + self.mx = putter("mx2", self) + + self._putters.append(self.x) + self._putters.append(self.mx) @staticmethod def parseFinesseText(text): diff --git a/pykat/components.py b/pykat/components.py index da2aca3589843796d08e75acbc291754c4de4c42..7c5a6920553638384eb44a89362ff627f5844a1f 100644 --- a/pykat/components.py +++ b/pykat/components.py @@ -84,7 +84,7 @@ class NodeGaussSetter(object): def qy(self, value): self.__node().setGauss(self.__comp(), self.qx, complex(value)) -id___ = 0 +id_____pykat_class = 0 class Component(object): __metaclass__ = abc.ABCMeta @@ -93,9 +93,9 @@ class Component(object): # This creates an instance specific class for the component # this enables us to add properties to instances rather than # all classes - global id___ - id___ += 1 - cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id___)) + global id_____pykat_class + id_____pykat_class += 1 + cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id_____pykat_class)) cnew = type(cnew_name, (cls,), {}) @@ -130,6 +130,9 @@ class Component(object): result = self.__class__.__new__(self.__class__.__base__) result.__dict__ = copy.deepcopy(self.__dict__, memo) + for _ in result._params: + _._updateOwner(result) + return result def _register_param(self, param): @@ -162,7 +165,17 @@ class Component(object): self._kat = kat kat.nodes.registerComponentNodes(self, self._requested_node_names, self.__on_node_change) + + def _on_kat_remove(self): + # inform all parameters that we have removed its owner + # so that it can then warn about any puts/vars/xaxis + for p in self._params: + p._onOwnerRemoved() + del self._params[:] + + self.__removed = True + def __on_node_change(self): # need to update the node gauss parameter setter members self.__update_node_setters() @@ -237,16 +250,10 @@ class Component(object): def __str__(self): return self.name def remove(self): - self._kat.remove(self) - - # inform all parameters that we have removed its owner - # so that it can then warn about any puts/vars/xaxis - for p in self._params: - p._onOwnerRemoved() - - del self._params[:] - - self.__removed = True + if self.__removed: + raise pkex.BasePyKatException("{0} has already been marked as removed".format(self.name)) + else: + self._kat.remove(self) def getOptivisParameterDict(self): if len(self._params) == 0: diff --git a/pykat/detectors.py b/pykat/detectors.py index 4b1b7db9da74b87992b12a4593debfd3955ee040..e652372326ff8e4cf885f5fcd9e365330de935f0 100644 --- a/pykat/detectors.py +++ b/pykat/detectors.py @@ -31,7 +31,7 @@ if USE_GUI: import pykat.gui.resources from pykat.gui.graphics import * -id___ = 0 +id_____pykat_class = 0 class BaseDetector(object) : """ @@ -45,9 +45,9 @@ class BaseDetector(object) : # This creates an instance specific class for the component # this enables us to add properties to instances rather than # all classes - global id___ - id___ += 1 - cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id___)) + global id_____pykat_class + id_____pykat_class += 1 + cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id_____pykat_class)) cnew = type(cnew_name, (cls,), {}) @@ -120,6 +120,9 @@ class BaseDetector(object) : if rn != None: self._nodes.append(kat.nodes.createNode(rn)) + def _on_kat_remove(self): + self.__removed = True + def remove(self): if self.__removed: raise pkex.BasePyKatException("{0} has already been marked as removed".format(self.name)) diff --git a/pykat/finesse.py b/pykat/finesse.py index c1ea4e70b7e82d794bc5d5b519e541e8f2337c79..83bf3b32babb2efc1cb914d148581ee6e8763c67 100644 --- a/pykat/finesse.py +++ b/pykat/finesse.py @@ -753,6 +753,7 @@ class kat(object): self.vacuum = [] self.__prevrunfilename = None self.printmatrix = None + self.__variables = {} # initialise default block self.__currentTag= NO_BLOCK @@ -1004,8 +1005,8 @@ class kat(object): sys.exit(1) else: return - - for o in self.__blocks[name].contents: + + for o in self.__blocks[name].contents.copy(): self.remove(o) del self.__blocks[name] @@ -1013,6 +1014,31 @@ class kat(object): def __str__(self): return "".join(self.generateKatScript()) + def getVariable(self, name): + if name not in self.__variables: + raise pkex.BasePyKatException("Finesse variable `$%s` does not exist." % name) + + return self.__variables[name] + + def registerVariable(self, name, putter): + if '$' in name: + raise pkex.BasePyKatException("Finesse variable name `%s` should not include the `$` symbol as it is added internally." % name) + + assert(putter is not None) + assert(name == putter.name) + + if name in self.__variables: + raise pkex.BasePyKatException("Finesse variable name `%s` already exists." % name) + + self.__variables[name] = putter + + def unregisterVariable(self, name): + del self.__variables[name] + + def printVariables(self): + for key in self.__variables: + print("$" + key, "::::", "owner =", self.__variables[key].owner.name, ", use count =", self.__variables[key].putCount) + def parseCommands(self, commands, blocks=None, addToBlock=None): try: if addToBlock is not None and blocks is not None: @@ -1190,6 +1216,8 @@ class kat(object): after_process.append((line, self.__currentTag)) elif(first == "noplot"): after_process.append((line, self.__currentTag)) + elif(first == "put" or first == "put*"): + after_process.append((line, self.__currentTag)) else: if self.verbose: print ("Parsing `{0}` into pykat object not implemented yet, added as extra line.".format(line)) @@ -1239,7 +1267,41 @@ class kat(object): raise pkex.BasePyKatException("noplot command `{0}` refers to non-existing detector".format(line)) getattr(self, rest).noplot = True + + elif (first == "put" or first =="put*"): + alt = first == "put*" + values = line.split() + obj = values[1] + target = values[2] + variable = values[3] + + try: + if not hasattr(self, obj): + raise pkex.BasePyKatException("put command `{0}` refers to non-existing component".format(line)) + + obj = getattr(self, obj) + + if not hasattr(obj, target): + raise pkex.BasePyKatException("put command component `{0}` does not have a parameter `{1}`".format(line, target)) + + target = getattr(obj, target) + + if not target.isPutable: + raise pkex.BasePyKatException("put command `{0}` parameter `{1}` cannot be put to".format(line, target)) + + target.put(self.getVariable(variable.replace('$', '')), alt) + + except pkex.BasePyKatException as ex: + if self.verbose: + print("Warning: ", ex.msg) + print ("Parsing `{0}` into pykat object not implemented yet, added as extra line.".format(line)) + + obj = line + # manually add the line to the block contents + self.__blocks[block].contents.append(line) + + elif (first == "scale"): v = line.split() accepted = ["psd","psd_hf","asd","asd_hf","meter", "ampere", "deg", "rad", "1/deg", "1/rad",] @@ -1376,7 +1438,7 @@ class kat(object): except pkex.BasePyKatException as ex: - pkex.PrintError("Error parsing line: '%s':"% line, ex) + pkex.PrintError("Pykat error parsing line: '%s':"% line, ex) sys.exit(1) def saveScript(self, filename=None): @@ -1649,14 +1711,14 @@ class kat(object): #if len(self.detectors.keys()) > 0: if hasattr(self, "x2axis") and self.noxaxis == False: - [r.x,r.y,r.z,hdr] = self.readOutFile(outfile) + [r.x, r.y, r.z, hdr] = self.readOutFile(outfile) r.xlabel = hdr[0] r.ylabel = hdr[1] r.zlabels = [s.strip() for s in hdr[2:]] #r.zlabels = map(str.strip, hdr[2:]) else: - [r.x,r.y,hdr] = self.readOutFile(outfile) + [r.x, r.y, hdr] = self.readOutFile(outfile) r.xlabel = hdr[0] r.ylabels = [s.strip() for s in hdr[1:]] @@ -1767,6 +1829,9 @@ class kat(object): del nodes + if hasattr(obj, "_on_kat_remove"): + obj._on_kat_remove() + #import gc #print (gc.get_referrers(obj)) diff --git a/pykat/param.py b/pykat/param.py index 264de77b1e3e13d2f01da656a3fb3b04b7ab474d..6644407a2a00d859ef9b6e9d9ad52257b88b2cc3 100644 --- a/pykat/param.py +++ b/pykat/param.py @@ -12,7 +12,8 @@ class putable(object): Objects that inherit this should be able to have something `put` to it. Essentially this means you could write Finesse commands like - put this parameter value + param.put(kat.xaxis.x) + """ __metaclass__ = abc.ABCMeta @@ -20,45 +21,96 @@ class putable(object): self._parameter_name = parameter_name self._component_name = component_name self._putter = None + self._alt = False self._isPutable = isPutable @property def isPutable(self): return self._isPutable - def put(self, var): - - if not isinstance(var, putter): - raise pkex.BasePyKatException("var was not something that can be `put` as a value") + def put(self, var, alt=False): + if not self._isPutable: + raise pkex.BasePyKatException("Can't put to this object") + + if var is not None and not isinstance(var, putter): + raise pkex.BasePyKatException("`%s` was not something that can be `put` to a parameter" % str(var)) - if self._putter != None: - self._putter.put_count -= 1 - self._putter.putees.remove(self) + # Remove existing puts + if self._putter is not None: + self._putter.unregister(self) self._putter = var + + self._alt = alt - if var != None: - self._putter.put_count += 1 - self._putter.putees.append(self) + if var is not None: + self._putter.register(self) def _getPutFinesseText(self): rtn = [] - # if something is being put to this - if self._putter != None: - rtn.append("put {comp} {param} ${value}".format(comp=self._component_name, param=self._parameter_name, value=self._putter.put_name())) + + if self._isPutable and self._putter is not None: + putter_enabled = True + + if hasattr(self._putter.owner, 'enabled'): + putter_enabled = self._putter.owner.enabled + + if putter_enabled: + if self._alt: + alt = '*' + else: + alt = '' + + # if something is being put to this + rtn.append("put{alt} {comp} {param} ${value}".format(alt=alt, comp=self._component_name, param=self._parameter_name, value=self._putter.put_name())) return rtn - + + class putter(object): """ If an object can be put to something that is putable it should inherit this object. """ - def __init__(self, put_name, isPutter=True): + def __init__(self, put_name, owner, isPutter=True): self._put_name = put_name self.put_count = 0 self._isPutter = isPutter self.putees = [] # list of params that this puts to + + assert(owner is not None) + self.__owner = weakref.ref(owner) + + def _updateOwner(self, newOwner): + del self.__owner + self.__owner = weakref.ref(newOwner) + + def clearPuts(self): + for _ in self.putees.copy(): + _.put(None) + + def register(self, toput): + if not self._isPutter: + raise pkex.BasePyKatException("This object can't put") + + self.put_count += 1 + self.putees.append(toput) + + def unregister(self, item): + if not self._isPutter: + raise pkex.BasePyKatException("This object can't put") + + self.put_count -= 1 + self.putees.remove(item) + + @property + def owner(self): return self.__owner() + + @property + def name(self): return self._put_name + + @property + def putCount(self): return self.put_count @property def isPutter(self): return self._isPutter @@ -68,39 +120,33 @@ class putter(object): class Param(putable, putter): - def __init__(self, name, owner, value, canFsig=False, fsig_name=None, isPutable=True, isPutter=True, isTunable=True, var_name=None): + def __init__(self, name, owner, value, canFsig=False, fsig_name=None, isPutable=True, isPutter=True, isTunable=True, var_name=None, register=True): self._name = name + self._registered = register self._owner = weakref.ref(owner) self._value = value self._isPutter = isPutter self._isTunable = isTunable - self._owner()._register_param(self) self._canFsig = False + if self._registered: + self._owner()._register_param(self) + if canFsig: self._canFsig = True - + if fsig_name is None: raise pkex.BasePyKatException("If parameter is a possible fsig target the fsig_name argument must be set") - + self.__fsig_name = fsig_name if isPutter: if var_name is None: var_name = "var_{0}_{1}".format(owner.name, name) - putter.__init__(self, var_name, isPutter) + putter.__init__(self, var_name, owner, isPutter) putable.__init__(self, owner.name, name, isPutable) - - def _updateOwner(self, newOwner): - """ - This updates the internal weak reference to link a parameter to who owns it. - Should only be called by the __deepcopy__ component method to ensure things - are kept up to date. - """ - del self._owner - self._owner = weakref.ref(newOwner) @property def canFsig(self): return self._canFsig @@ -162,7 +208,16 @@ class Param(putable, putter): rtn.append("set {put_name} {comp} {param}".format(put_name=self.put_name(), comp=self._owner().name, param=self.name)) return rtn - + + def _updateOwner(self, newOwner): + """ + This updates the internal weak reference to link a parameter to who owns it. + Should only be called by the __deepcopy__ component method to ensure things + are kept up to date. + """ + del self._owner + self._owner = weakref.ref(newOwner) + def _onOwnerRemoved(self): #if this param can be put somewhere we need to check if it is if self.isPutable: diff --git a/test/test_scripts/random/test_unicode_printing.py b/test/test_scripts/random/test_unicode_printing.py index 2afb8c3fc2c3720af478ac8d67d7859aad9fb364..42b544852bb341ddbca14611c2b7c1288f7a5773 100644 --- a/test/test_scripts/random/test_unicode_printing.py +++ b/test/test_scripts/random/test_unicode_printing.py @@ -13,9 +13,11 @@ kat.loadKatFile("LHO_IFO_maxtem2.kat") # load the conf kat.parseKatCode( "fsig sig1 ETMXHR 10 180") kat.parseKatCode( "fsig sig1 ETMYHR 10 0") kat.parseKatCode( "pd1 myomc 10 nOMC_HROC_trans") -kat.parseKatCode( "put myomc f1 $x1") # to follow kat.parseKatCode( "xaxis sig1 f log 10 1k 10") +kat.parseKatCode( "put myomc f1 $x1") # to follow kat.parseKatCode( "yaxis abs:deg") kat.verbose = True out = kat.run() # do the computation -result.append(out['myomc']) # append the result \ No newline at end of file +result.append(out['myomc']) # append the result + +print("PASSED") \ No newline at end of file diff --git a/test/test_scripts/structural/test_deepcopying_references.py b/test/test_scripts/structural/test_deepcopying_references.py index a82b0613cc47eac50f8ba3897899a513286e5d6c..2d3fc7f41ed55a947966d56388d9dbe9c3a8ccf4 100644 --- a/test/test_scripts/structural/test_deepcopying_references.py +++ b/test/test_scripts/structural/test_deepcopying_references.py @@ -25,6 +25,9 @@ assert(kat0.nodes.n0 != kat1.nodes.n0) assert(kat0.o1 != kat1.o1) assert(kat0.o1.__class__ != kat1.o1.__class__) +assert(kat0.m1.phi.owner == kat0.m1) +assert(kat1.m1.phi.owner == kat1.m1) + # use is to compare if two params are the same object as equals is override to compare the value assert(kat0.o1.f1 is not kat1.o1.f1) assert(kat0.o1.f1 == kat1.o1.f1) diff --git a/work_in_progress/optivis_ex.py b/work_in_progress/optivis_ex.py index a0806ee1153766503aa12d129b3f553aa12730e3..d291127588a872dc2af9783077db9b3bd383836e 100644 --- a/work_in_progress/optivis_ex.py +++ b/work_in_progress/optivis_ex.py @@ -22,6 +22,7 @@ attr m1 Rc -500 attr m2 Rc 500 cav c1 m1 n4b m2 n7a +pd P n5b maxtem 0 noxaxis """)