commands.py 20.1 KB
Newer Older
Daniel Brown's avatar
Daniel Brown committed
1 2 3 4 5 6
# -*- coding: utf-8 -*-
"""
Created on Mon Jan 28 11:58:09 2013

@author: Daniel
"""
7 8 9 10 11
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

Daniel Brown's avatar
Daniel Brown committed
12
import numpy
13
import warnings
14
import pykat
15
import pykat.external.six as six
16 17
import pykat.exceptions as pkex

18 19
if six.PY2:
	import exceptions
20
    
21 22
from pykat.components import *
from pykat.structs import *
23
from numpy import min, max
Daniel Brown's avatar
Daniel Brown committed
24
from pykat.param import Param, putter
Daniel Brown's avatar
Daniel Brown committed
25
from collections import namedtuple
26
from pykat.optics.gaussian_beams import BeamParam
27
from pykat.freeze import canFreeze
Daniel Brown's avatar
Daniel Brown committed
28

29
@canFreeze
Daniel Brown's avatar
Daniel Brown committed
30
class Command(object):
31 32
    __metaclass__ = abc.ABCMeta
    
33
    def __init__(self, name, unique):
34 35
        self.__dict__["____FROZEN____"] = False
        self._kat = None
36
        self.__unique = unique
37 38
        self.tag = None
        self.__removed = False
39
        self.__name = name.strip("*")
40 41
        self._putters = []
    
42
        
43 44 45 46 47 48 49
    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)
50
        result._unfreeze()
51
        memo[id(self)] = result
52 53 54 55
        result.__dict__ = copy.deepcopy(self.__dict__, memo)
        
        for _ in result._putters:
            _._updateOwner(result)
56 57

        result._freeze()
58
        return result
59
    
Daniel Brown's avatar
Daniel Brown committed
60 61 62 63 64
    def getFinesseText(self):
        """ Base class for individual finesse optical components """
        raise NotImplementedError("This function is not implemented")

    @staticmethod
65
    def parseFinesseText(line, kat):
Daniel Brown's avatar
Daniel Brown committed
66 67 68 69 70 71 72
        raise NotImplementedError("This function is not implemented")

    def _on_kat_add(self, kat):
        """
        Called when this component has been added to a kat object
        """
        self._kat = kat
73
        
74
        for _ in self._putters:
75
            kat.registerVariable(_.name, _)
Daniel Brown's avatar
Daniel Brown committed
76

77
    def _on_kat_remove(self):
78
        self.__removed = True
79
        
80 81
        for i in range(len(self._putters)):
            _ = self._putters[i]
82 83 84
            
            self._kat.unregisterVariable(_.name)
            _.clearPuts()
Daniel Brown's avatar
Daniel Brown committed
85 86 87
        
        for i in range(len(self._putters)):  
            del self._putters[0]
88
            
89
        del self._putters[:]
90 91 92 93 94 95 96
        
        
    def remove(self):
        if self.__removed:
            raise pkex.BasePyKatException("{0} has already been marked as removed".format(self.name))
        else:
            self._kat.remove(self)
97
    
98 99 100
    @property
    def name(self): return self.__name
    
101 102
    @property
    def removed(self): return self.__removed
103 104
    
    def __str__(self): return self.name
105

106 107 108 109 110 111
class Constant(Command):
    def __init__(self, name, value):
        Command.__init__(self, name, False)
        self.value = value
        self.usedBy = []
        self._freeze()
112

113 114 115 116
    def _on_kat_remove(self):
        Command._on_kat_remove(self)
        self._kat.constants.pop(self.name)

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
    def getFinesseText(self):
        return "const {name} {value}".format(name=self.name, value=self.value)
    
    @staticmethod
    def parseFinesseText(line, kat):
        v = line.split()
        
        if len(v) != 3:
            raise pkex.BasePyKatException("'{0}' not a valid Finesse const command".format(line))
        
        return Constant(v[1], SIfloat(v[2]))
    
    @property
    def value(self): return self.__value
    @value.setter
    def value(self, Value): self.__value = SIfloat(Value)
    
134 135
    def __str__(self): return "$"+self.name
    
136 137 138
class variable(Command):
    def __init__(self, name, value):
        Command.__init__(self, name, False)
139
        self.value = value
140
        
141 142
        self._freeze()
        
143
    def getFinesseText(self):
144
        return "var {name} {value}".format(name=self.name, value=self.value)
145
    
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    @staticmethod
    def parseFinesseText(line, kat):
        v = line.split()
        
        if len(v) != 3:
            raise pkex.BasePyKatException("'{0}' not a valid Finesse variable command".format(line))
        
        return variable(v[1], SIfloat(v[2]))
    
    @property
    def value(self): return self.__value
    @value.setter
    def value(self, Value): self.__value = SIfloat(Value)





class func(Command):
    def __init__(self, name, value):
        Command.__init__(self, name, False)
        
        self.value = value
169
        self.noplot = False
170 171 172
        self.enabled = True
        
        self.output = putter(name, self)
173
        self._putters.append(self.output)
174 175

        self._freeze()
176
        
177
    def getFinesseText(self):
178 179
        rtn = []

180 181 182
        if self.enabled:
            if self.noplot:
                rtn.append("noplot " + self.name)
183
        
184
            rtn.append("func {name} = {value}".format(name=self.name, value=str(self.value)))
185
            rtn.extend(self.output._getPutFinesseText())
186
        return rtn
187 188 189 190

    @staticmethod
    def parseFinesseText(line, kat):
        v = line.split(None, 3)
191
        v2 = line.split("=", 2)
192 193 194
        
        if "=" in v and len(v) == 4:
            v.remove("=")
195 196 197
            return func(v[1], v[2]) 
        if len(v2) == 2:
            return func(v2[0].split()[1], v2[1]) 
198 199
        else:
            raise pkex.BasePyKatException("'{0}' not a valid Finesse func command".format(line))
200
            
201 202

class lock(Command):
203
    def __init__(self, name, variable, gain, accuracy, offset=None, singleLock=False):
204
        Command.__init__(self, name, False)
205 206

        self.__params = []
207 208 209
        self.__variable = variable
        self.__gain = gain
        self.__accuracy = accuracy
210 211
        self.__offset = Param("offset", self, SIfloat(offset))
        
212 213
        self.singleLock = singleLock
        self.enabled = True
Daniel Brown's avatar
Daniel Brown committed
214
        self.noplot = False
215 216
        
        self.output = putter(name, self)
217
        self._putters.append(self.output)
218

219
        self._freeze()
220 221 222

    def _register_param(self, param):
        self.__params.append(param)
223
        
224 225 226
    @property
    def offset(self): return self.__offset
    
227 228 229 230
    @staticmethod
    def parseFinesseText(line, kat):
        v = line.split()
        
231
        if len(v) not in (5,6):
232
            raise pkex.BasePyKatException("'{0}' not a valid Finesse lock command".format(line))
233 234 235 236 237
        
        if len(v) == 5:
            return lock(v[1], v[2], SIfloat(v[3]), SIfloat(v[4]), singleLock="*" in v[0])
        else:
            return lock(v[1], v[2], SIfloat(v[3]), SIfloat(v[4]), SIfloat(v[5]), singleLock="*" in v[0])
238 239 240 241


    def getFinesseText(self):
        if self.enabled:
242
            rtn = []
243 244 245 246 247
            cmds = "{name} {var} {gain} {accuracy}".format( name=self.name,
                                                            var=str(self.variable),
                                                            gain=str(self.gain),
                                                            accuracy=str(self.accuracy))
            
248 249 250
            if self.offset.value is not None:
                cmds += " %s" % str(self.offset)
                
251
            if self.singleLock:
252
                rtn.append("lock* %s" % cmds)
253
            else:
254 255 256 257
                rtn.append("lock %s" % cmds)
            
            rtn.extend(self.output._getPutFinesseText())
            
258 259 260
            for p in self.__params:
                rtn.extend(p.getFinesseText())
            
261
            return rtn
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
        else:
            return None

    @property
    def variable(self): return self.__variable
    @variable.setter
    def variable(self, value): self.__variable = value

    @property
    def gain(self): return self.__gain
    @gain.setter
    def gain(self, value): self.__gain = SIfloat(value)

    @property
    def accuracy(self): return self.__accuracy
    @accuracy.setter
    def accuracy(self, value): self.__accuracy = SIfloat(value)



Daniel Brown's avatar
Daniel Brown committed
282 283
class cavity(Command):
    def __init__(self, name, c1, n1, c2, n2):
284
        Command.__init__(self, name, False)
285
        
Daniel Brown's avatar
Daniel Brown committed
286 287 288 289
        self.__c1 = c1
        self.__c2 = c2
        self.__n1 = n1
        self.__n2 = n2
290 291
        
        self.enabled = True
Daniel Brown's avatar
Daniel Brown committed
292

293
        self._freeze()
294
    
Daniel Brown's avatar
Daniel Brown committed
295
    def getFinesseText(self):
296 297 298 299
        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);
        else:
            return None
Daniel Brown's avatar
Daniel Brown committed
300

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
    @staticmethod
    def parseFinesseText(line, kat):
        v = line.split()
        
        if len(v) != 6:
            raise pkex.BasePyKatException("cav command format `{0}` is incorrect".format(line))
        
        if v[2] not in kat.components:
            raise pkex.BasePyKatException("cav command `{0}` refers to component `{1}` which does not exist".format(line, v[2]))
        
        if v[4] not in kat.components:
            raise pkex.BasePyKatException("cav command `{0}` refers to component `{1}` which does not exist".format(line, v[4]))
        
        if v[3] not in kat.nodes.getNodes():
            raise pkex.BasePyKatException("cav command `{0}` refers to node `{1}` which does not exist".format(line, v[3]))
        
        if v[5] not in kat.nodes.getNodes():
            raise pkex.BasePyKatException("cav command `{0}` refers to node `{1}` which does not exist".format(line, v[5]))
        
        c1 = getattr(kat, v[2])
        c2 = getattr(kat, v[4])
        
        n1 = getattr(kat.nodes, v[3])
        n2 = getattr(kat.nodes, v[5])
        
        if not hasattr(c1, n1.name):
            raise pkex.BasePyKatException("cav command `{0}`: node `{1}` is not attached to `{2}`".format(line, n1.name, c1.name))
        
        if not hasattr(c2, n2.name):
            raise pkex.BasePyKatException("cav command `{0}`: node `{1}` is not attached to `{2}`".format(line, n2.name, c2.name))
            
        return pykat.commands.cavity(v[1], c1, n1, c2, n2)
        
        
Daniel Brown's avatar
Daniel Brown committed
335 336 337
class gauss(object):
    @staticmethod
    def parseFinesseText(text, kat):
338
        
339
        values = text.split()
340
        if not values[0].startswith("gauss") or (len(values) != 6 and len(values) != 8):
341
            raise pkex.BasePyKatException("'{0}' not a valid Finesse gauss command".format(text))        
342
        
343 344 345 346
        name = values[1]
        component = values[2]
        node = values[3]
        
Daniel Brown's avatar
Daniel Brown committed
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
        # setting the name of the gauss parameter is slightly convoluted
        # as we don't explicitly store gauss paramters as an object, they
        # are simply just complex numbers stored at each node. To fix this
        # the name is stored in the NodeGaussSetter object for each component
        
        if component in kat.components:
            c = kat.components[component]
            if hasattr(c, node):
                ns = getattr(c, node)
                ns.name = name
            else:
                raise pkex.BasePyKatException("Component '{0}' is not attached to node {1}".format(component, node))        
        else:
            raise pkex.BasePyKatException("Component '{0}' was not found".format(component))        
        
362
        if not values[0].endswith("*"):
363
            if len(values) == 6:
364
                gp = BeamParam(kat.lambda0, w0=values[-2], z=values[-1])
365
            elif len(values) == 8:
366 367
                gpx = BeamParam(kat.lambda0, w0=values[-4], z=values[-3])
                gpy = BeamParam(kat.lambda0, w0=values[-2], z=values[-1])
368 369
        elif values[0].endswith("*"):
            if len(values) == 6:
370
                gp = BeamParam(kat.lambda0, z=values[-2], zr=values[-1])
371
            elif len(values) == 8:
372 373
                gpx = BeamParam(kat.lambda0, z=values[-4], zr=values[-3])
                gpy = BeamParam(kat.lambda0, z=values[-2], zr=values[-1])
Daniel Brown's avatar
Daniel Brown committed
374
        elif values[0].endswith("**"):
375
            if len(values) == 6:
376
                gp = BeamParam(kat.lambda0, w=values[-2], rc=values[-1])
377
            elif len(values) == 8:
378 379
                gpx = BeamParam(kat.lambda0, w=values[-4], rc=values[-3])
                gpy = BeamParam(kat.lambda0, w=values[-2], rc=values[-1])
Daniel Brown's avatar
Daniel Brown committed
380 381 382
        else:
            raise pkex.BasePyKatException("Unexpected ending to gauss command '{0}'".format(text))
            
Daniel Brown's avatar
Daniel Brown committed
383 384 385 386
        if len(values) == 6:
            kat.nodes[node].setGauss(kat.components[component], gp)
        else:
            kat.nodes[node].setGauss(kat.components[component], gpx, gpy)
387

388
                   
Daniel Brown's avatar
Daniel Brown committed
389 390
class tf(Command):
    
Daniel Brown's avatar
Daniel Brown committed
391 392 393 394 395 396
    class fQ(object):
        def __init__(self, f, Q):
            self.f = f
            self.Q = Q
            
    def __init__(self, name):
397
        Command.__init__(self, name, False)
Daniel Brown's avatar
Daniel Brown committed
398 399 400 401
        self.zeros = []
        self.poles = []
        self.gain = 1
        self.phase = 0
402 403
        
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
404 405 406 407 408 409
    
    def addPole(self,f, Q):
        self.poles.append(tf.fQ(SIfloat(f), SIfloat(Q)))
    
    def addZero(self,f, Q):
        self.zeros.append(tf.fQ(SIfloat(f), SIfloat(Q)))
410
    
Daniel Brown's avatar
Daniel Brown committed
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
    @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
        
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
class tf2(Command):
            
    def __init__(self, name):
        Command.__init__(self, name, False)
        self.zeros = []
        self.poles = []
        self.gain = 1
        self.phase = 0
        self.precision = 4
        self._freeze()
    
    def addPole(self, z):
        self.poles.append(z)
    
    def addZero(self, z):
        self.zeros.append(z)
    
    
    @staticmethod
    def parseFinesseText(text):
        values = text.split()
        
        if len(values) != 6:
Daniel Brown's avatar
Daniel Brown committed
469
            print(values)
470 471 472 473 474 475 476 477
            raise pkex.BasePyKatException("Transfer function Finesse code format incorrect '{0}'".format(text))

        _tf = tf2(values[1])
        
        _tf.gain = SIfloat(values[2])
        _tf.phase = SIfloat(values[3])
        
        for _ in values[4].strip("{}").split(','):
Daniel Brown's avatar
Daniel Brown committed
478 479 480
            _ = _.strip()
            if len(_) > 0:
                _tf.poles.append(complex(_.replace('i','j')))
481 482
        
        for _ in values[5].strip("{}").split(','):
Daniel Brown's avatar
Daniel Brown committed
483 484 485
            _ = _.strip()
            if len(_) > 0:
                _tf.zeros.append(complex(_.replace('i','j')))
486 487 488 489 490 491 492 493 494 495 496 497 498 499
        
        return _tf
        
    def getFinesseText(self):
        rtn = "tf2 {name} {gain} {phase} ".format(name=self.name,gain=self.gain,phase=self.phase)
        
        fmt = '{0:%i.%if}{1}{2:%i.%if}i'%((self.precision,)*4)
        c2str = lambda n: fmt.format(n.real, '+' if n.imag>0 else '-', abs(n.imag   ))
        
        rtn += "{%s}" % ",".join([c2str(_) for _ in self.poles])
        rtn += " {%s}" % ",".join([c2str(_) for _ in self.zeros])
        
        return rtn
        
Daniel Brown's avatar
Daniel Brown committed
500
class xaxis(Command):
501 502 503 504 505
    """
    The xaxis object is a unique object to each pykat.finesse.kat instance. It provides
    and interface to the xaxis command in FINESSE.
    """
    
506
    def __init__(self, scale, limits, param, steps, comp=None, axis_type="xaxis"):
507 508 509 510 511 512 513 514 515
        """
        Typical usage:
            xaxis(["lin" or "log"], [upper, lower], param, steps)
            
        param must be an object of the type pykat.param.Param whose
        isPutable() member returns true.
        
        steps is the number of points to compute between upper and lower limits.
        """
516
        Command.__init__(self, axis_type, True)
517
        
Daniel Brown's avatar
Daniel Brown committed
518
        self._axis_type = axis_type
519 520
        
        self._set_variables()
521
        
Daniel Brown's avatar
Daniel Brown committed
522
        if scale == "lin":
523
            scale = Scale.linear
Daniel Brown's avatar
Daniel Brown committed
524 525
        elif scale == "log":
            scale = Scale.logarithmic
526
        elif isinstance(scale, six.string_types):
Daniel Brown's avatar
Daniel Brown committed
527
            # else we have a string but not a recognisable one
528
            raise pkex.BasePyKatException("scale argument '{0}' is not valid, must be 'lin' or 'log'".format(scale))
Daniel Brown's avatar
Daniel Brown committed
529 530

        if scale != Scale.linear and scale != Scale.logarithmic:
531
            raise pkex.BasePyKatException("scale is not Scale.linear or Scale.logarithmic")
Daniel Brown's avatar
Daniel Brown committed
532 533 534 535

        self.scale = scale

        if numpy.size(limits) != 2 :
536
            raise pkex.BasePyKatException("limits input should be a 2x1 vector of limits for the xaxis")
Daniel Brown's avatar
Daniel Brown committed
537 538 539

        self.limits = numpy.array(SIfloat(limits)).astype(float)

540
        if int(steps) <= 0 :
541
            raise pkex.BasePyKatException("steps value should be > 0")
Daniel Brown's avatar
Daniel Brown committed
542 543 544

        self.steps = int(steps)

545
        if isinstance(param, six.string_types):
Daniel Brown's avatar
Daniel Brown committed
546
            self.__param = param
547 548 549 550
            if comp == None:
                raise pkex.BasePyKatException("If parameter is set with a string, the comp argument must set the component name")
                
            self.__comp = comp
Daniel Brown's avatar
Daniel Brown committed
551
        elif not isinstance(param, Param) :
552
            raise pkex.BasePyKatException("param argument is not of type Param")
Daniel Brown's avatar
Daniel Brown committed
553 554
        else:
            self.__param = param
555
            self.__comp = param._owner()
Daniel Brown's avatar
Daniel Brown committed
556

557 558
        self._freeze()
        
559 560 561 562 563 564 565
    def _set_variables(self):
        self.x = putter("x1", self)
        self.mx = putter("mx1", self)

        self._putters.append(self.x)
        self._putters.append(self.mx)
        
ssl's avatar
ssl committed
566 567 568 569
    @property
    def param(self): return self.__param
    @param.setter
    def param(self, value):
570 571 572 573 574 575
        if not isinstance(value, Param):
            raise pkex.BasePyKatException("param argument is not of type Param")
        else:
            self.__param = value
            self.__comp = value._owner()
            
Daniel Brown's avatar
Daniel Brown committed
576 577
    @staticmethod
    def parseFinesseText(text):
578
        values = text.split()
Daniel Brown's avatar
Daniel Brown committed
579 580

        if values[0] != "xaxis" and values[0] != "xaxis*":
581
            raise pkex.BasePyKatException("'{0}' not a valid Finesse xaxis command".format(text))
Daniel Brown's avatar
Daniel Brown committed
582 583 584 585 586 587

        axis_type = values[0]

        values.pop(0) # remove initial value

        if len(values) != 6:
588
            raise pkex.BasePyKatException("xaxis Finesse code format incorrect '{0}'".format(text))
Daniel Brown's avatar
Daniel Brown committed
589

590
        return xaxis(values[2], [values[3], values[4]], values[1], values[5], comp=values[0], axis_type=axis_type)
Daniel Brown's avatar
Daniel Brown committed
591 592

    def getFinesseText(self):
593 594
        if(self._kat.noxaxis):
            return '# noaxis is true, switching xaxis off'
Daniel Brown's avatar
Daniel Brown committed
595
        
Daniel Brown's avatar
Daniel Brown committed
596
        # store either the component name of the string provided
597
        comp_name = self.__comp.name if hasattr(self.__comp, "name") else self.__comp
Daniel Brown's avatar
Daniel Brown committed
598
        param_name = self.__param.name if isinstance(self.__param, Param) else self.__param
599
        
600
        rtn =   ['{axis_type} {0} {1} {2} {3:.16g} {4:.16g} {5}'.format(
Daniel Brown's avatar
Daniel Brown committed
601
                comp_name, param_name, self.scale,
602 603 604 605 606 607
                self.limits[0], self.limits[1], self.steps, axis_type=self._axis_type)]
            
        rtn.extend(self.x._getPutFinesseText())
        rtn.extend(self.mx._getPutFinesseText())
        
        return rtn
Daniel Brown's avatar
Daniel Brown committed
608 609

class x2axis(xaxis):
610 611
    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)
612 613

    def _set_variables(self):
614 615 616
        self.x = putter("x2", self)
        self.mx = putter("mx2", self)

617 618
        self._putters.append(self.x)
        self._putters.append(self.mx)
619
        
Daniel Brown's avatar
Daniel Brown committed
620 621
    @staticmethod
    def parseFinesseText(text):
622
        values = text.split()
Daniel Brown's avatar
Daniel Brown committed
623 624

        if values[0] != "x2axis" and values[0] != "x2axis*":
625
            raise pkex.BasePyKatException("'{0}' not a valid Finesse xaxis command".format(text))
Daniel Brown's avatar
Daniel Brown committed
626 627 628 629 630 631

        axis_type = values[0]

        values.pop(0) # remove initial value

        if len(values) != 6:
632
            raise pkex.BasePyKatException("xaxis Finesse code format incorrect '{0}'".format(text))
Daniel Brown's avatar
Daniel Brown committed
633

634
        return x2axis(values[2], [values[3], values[4]], values[1], values[5], comp=values[0],axis_type=axis_type)
635