diff --git a/pykat/commands.py b/pykat/commands.py index 76d803f71b0de709662925f48cc944e5de0dcced..772c0039220a6707e99c98929ff3ecfac783bff8 100644 --- a/pykat/commands.py +++ b/pykat/commands.py @@ -10,6 +10,7 @@ from __future__ import print_function from __future__ import unicode_literals import numpy +import warnings import pykat.external.six as six import pykat.exceptions as pkex @@ -24,18 +25,19 @@ from collections import namedtuple from pykat.optics.gaussian_beams import BeamParam - - 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 @@ -50,7 +52,23 @@ class Command(object): _._updateOwner(result) return result + + def _freeze(self): self.__dict__["____FROZEN____"] = True + def _unfreeze(self): self.__dict__["____FROZEN____"] = False + + def __setattr__(self, name, value): + if self.__dict__["____FROZEN____"] and not hasattr(self, name): + warnings.warn("'%s' does not have attribute called '%s'" % (self.__name, name), stacklevel=2) + + if hasattr(self, name) and hasattr(self.__class__, name): + prop = getattr(self.__class__, name) + if isinstance(prop, property): + prop.fset(self, value) + return + + self.__dict__[name] = value + def getFinesseText(self): """ Base class for individual finesse optical components """ raise NotImplementedError("This function is not implemented") @@ -103,7 +121,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 +154,8 @@ class func(Command): self.output = putter(name, self) self._putters.append(self.output) + + self._freeze() def getFinesseText(self): rtn = [] @@ -175,7 +196,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 +250,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 +429,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 +530,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 07ecaa376ac62df5c22be95274960e7b743230e4..4c0f953e467f1b0b434c0b0f75e5ebeb16faebf4 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 * @@ -102,6 +103,7 @@ class Component(object): return object.__new__(cnew) def __init__(self, name=None): + self._unfreeze() self._optivis_component = None self.__name = name @@ -118,7 +120,23 @@ class Component(object): global next_component_id self.__id = next_component_id next_component_id += 1 - + + def _freeze(self): self.__dict__["____FROZEN____"] = True + def _unfreeze(self): self.__dict__["____FROZEN____"] = False + + def __setattr__(self, name, value): + if self.__dict__["____FROZEN____"] and not hasattr(self, name): + warnings.warn("'%s' does not have attribute called '%s'" % (self.__name, name), stacklevel=2) + + if hasattr(self, name) and hasattr(self.__class__, name): + prop = getattr(self.__class__, name) + + if isinstance(prop, property): + prop.fset(self, value) + return + + self.__dict__[name] = value + def __deepcopy__(self, memo): """ When deep copying a kat object we need to take into account @@ -204,7 +222,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 +233,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) @@ -480,7 +501,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(): @@ -572,6 +594,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 @@ -684,6 +708,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 @@ -810,6 +836,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) @@ -939,6 +967,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 @@ -1044,6 +1073,8 @@ class isolator1(Component): self._requested_node_names.append(node4) self._svgItem = None + self._freeze() + @staticmethod def parseFinesseText(text): values = text.split() @@ -1084,6 +1115,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 @@ -1180,6 +1213,8 @@ class modulator(Component): self.type = modulation_type self._default_fsig_param = self.__phase + + self._freeze() @property def f(self): return self.__f @@ -1296,6 +1331,8 @@ class laser(Component): self._svgItem = None self._default_fsig_param = self.__f_offset + + self._freeze() @property def P(self): return self.__power @@ -1393,6 +1430,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..a957cd6f14749e82d5d86e11a922401c300a62b8 100644 --- a/pykat/detectors.py +++ b/pykat/detectors.py @@ -54,6 +54,8 @@ class BaseDetector(object) : return object.__new__(cnew) def __init__(self, name, nodes=None, max_nodes=1): + + self.__dict__["____FROZEN____"] = False self.__name = name self._svgItem = None @@ -97,6 +99,22 @@ class BaseDetector(object) : else: raise pkex.BasePyKatException("Nodes should be a list or tuple of node names or a singular node name as a string.") + def _freeze(self): self.__dict__["____FROZEN____"] = True + def _unfreeze(self): self.__dict__["____FROZEN____"] = False + + def __setattr__(self, name, value): + if self.__dict__["____FROZEN____"] and not hasattr(self, name): + warnings.warn("'%s' does not have attribute called '%s'" % (self.__name, name), stacklevel=2) + + if hasattr(self, name) and hasattr(self.__class__, name): + prop = getattr(self.__class__, name) + + if isinstance(prop, property): + prop.fset(self, value) + return + + self.__dict__[name] = value + def __deepcopy__(self, memo): """ When deep copying a kat object we need to take into account @@ -243,6 +261,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 +311,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 +367,8 @@ class xd(Detector0): self.component = component self.motion = motion + + self._freeze() @staticmethod def parseFinesseText(text): @@ -373,6 +397,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 +452,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 +503,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 +628,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 +686,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 +710,8 @@ class pd(Detector1): if hasattr(self, "phase"+name): delattr(self.__class__, "phase"+name) - else: - return + + self._freeze() @staticmethod def parseFinesseText(text): @@ -776,7 +808,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 +1021,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 +1064,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 53bc695572ca162fef29343bc6e7150576bd44c1..93bf52fd9a143e37f84d1260880cede526ddb284 100644 --- a/pykat/finesse.py +++ b/pykat/finesse.py @@ -1149,6 +1149,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")