detectors.py 36.4 KB
Newer Older
Daniel Brown's avatar
Daniel Brown committed
1 2
# -*- coding: utf-8 -*-
"""
Daniel Brown's avatar
Daniel Brown committed
3
Created on Fri Feb 01 0split()9:09:10 2013
Daniel Brown's avatar
Daniel Brown committed
4 5 6

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

import pykat.external.six as six
if six.PY2:
14
    import exceptions
15

Daniel Brown's avatar
Daniel Brown committed
16
import abc
Daniel Brown's avatar
Daniel Brown committed
17
from pykat.node_network import *
18
from pykat.param import Param, AttrParam
19
from pykat.SIfloat import SIfloat
20

21
import pykat.external.six as six
22
import pykat.exceptions as pkex
23 24

import collections
25
import warnings
26
import copy
Daniel Brown's avatar
Daniel Brown committed
27

28
from pykat import USE_GUI, NoGUIException
29
from pykat.freeze import canFreeze
30 31 32 33

if USE_GUI:
    import pykat.gui.resources
    from pykat.gui.graphics import *
Daniel Brown's avatar
Daniel Brown committed
34

35
id_____pykat_class = 0
Daniel Brown's avatar
Daniel Brown committed
36

37
@canFreeze
38
class BaseDetector(object) :
39 40 41 42
    """
    This is a base class for all detectors. Classes Detector1 and Detector2 should be used directly.
    This base class can handled detectors connected to multiple nodes.
    """
43
    
44 45
    __metaclass__ = abc.ABCMeta
    
Daniel Brown's avatar
Daniel Brown committed
46 47 48 49
    def __new__(cls, *args, **kwargs):
        # This creates an instance specific class for the component
        # this enables us to add properties to instances rather than
        # all classes
50 51 52
        global id_____pykat_class
        id_____pykat_class += 1
        cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id_____pykat_class))
Daniel Brown's avatar
Daniel Brown committed
53 54 55
        
        cnew = type(cnew_name, (cls,), {})
        
56 57
        o = object.__new__(cnew)
        return o
Daniel Brown's avatar
Daniel Brown committed
58
        
59
    def __init__(self, name, nodes=None, max_nodes=1):
60

61
        self._unfreeze()
62
        
Daniel Brown's avatar
Daniel Brown committed
63 64 65 66 67 68 69
        self.__name = name
        self._svgItem = None
        self._kat = None
        self.noplot = False
        self.enabled = True
        self.tag = None
        self._params = []
70
        self._mask = {}
Daniel Brown's avatar
Daniel Brown committed
71
        self.__scale = []
72
        self.__removed = False
73
        self.noplot = False
74
        
75 76 77 78 79
        self._alternate_beam = []
        self._nodes = []
        self._requested_nodes = []
        
        if nodes != None:
80 81
            if isinstance(nodes, (list, tuple)):
                
82 83 84 85 86 87 88 89 90 91 92
                if len(nodes) > max_nodes:
                    raise pkex.BasePyKatException("Tried to set too many nodes, %s, maximum number is %i." %(str(nodes),max_nodes))
                    
                for n in nodes:
                    if n[-1]=='*':
                        self._alternate_beam.append(True)
                        n = n[:-1]
                    else:
                        self._alternate_beam.append(False)
                        
                    self._requested_nodes.append(n)
93
            elif isinstance(nodes, six.string_types):
94 95 96 97 98 99 100 101
                # if we don't have a collection
                if nodes[-1]=='*':
                    self._alternate_beam.append(True)
                    nodes = nodes[:-1]
                else:
                    self._alternate_beam.append(False)
                    
                self._requested_nodes.append(nodes)
102 103
            else:
                raise pkex.BasePyKatException("Nodes should be a list or tuple of node names or a singular node name as a string.")
Daniel Brown's avatar
Daniel Brown committed
104
    
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
    # 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()
    #     memo[id(self)] = result
    #     result.__dict__ = copy.deepcopy(self.__dict__, memo)
    #
    #     result._freeze()
    #     return result
    
Daniel Brown's avatar
Daniel Brown committed
121 122 123 124 125 126 127 128 129 130 131
    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.__dict__ = copy.deepcopy(self.__dict__, memo)
        
132 133 134
        for _ in result._params:
            _._updateOwner(result)
            
Daniel Brown's avatar
Daniel Brown committed
135
        return result
136
                
Daniel Brown's avatar
Daniel Brown committed
137 138 139 140
    def _register_param(self, param):
        self._params.append(param)
        
    def _on_kat_add(self, kat):
141 142
        self._kat = kat
        
143 144 145
        for rn in self._requested_nodes:
            if rn != None:
                self._nodes.append(kat.nodes.createNode(rn))
Daniel Brown's avatar
Daniel Brown committed
146
    
147 148 149
    def _on_kat_remove(self):
        self.__removed = True
        
150 151 152 153 154 155 156 157
    def remove(self):
        if self.__removed:
            raise pkex.BasePyKatException("{0} has already been marked as removed".format(self.name))
        else:
            self._kat.remove(self)
    
        self.__removed = True
        
Daniel Brown's avatar
Daniel Brown committed
158 159 160 161 162 163
    @staticmethod
    def parseFinesseText(text):    
        raise NotImplementedError("This function is not implemented")
        
    def getFinesseText(self):
        """ Base class for individual finesse optical components """    
164
        #raise NotImplementedError("This function is not implemented")
Daniel Brown's avatar
Daniel Brown committed
165 166 167 168 169
        rtn = []
        
        for _ in self._mask:
            if _[0]+_[1] <= self._kat.maxtem:
                rtn.append("mask {} {} {} {}".format(self.name, _[0], _[1], self._mask[_]))
170 171
        
        if self.noplot:
Daniel Brown's avatar
Daniel Brown committed
172 173 174
            rtn.append("noplot " + self.name)
            
        return rtn
Daniel Brown's avatar
Daniel Brown committed
175 176
        
    def getQGraphicsItem(self):    
177 178 179
        if not USE_GUI:
            raise NoGUIException
            
Daniel Brown's avatar
Daniel Brown committed
180
        return None
181

182 183 184
    @property
    def removed(self): return self.__removed
    
185 186 187
    @property 
    def scale(self): return self.__scale
    @scale.setter
188
    def scale(self, value):
Daniel Brown's avatar
Daniel Brown committed
189 190
        if value not in self.__scale:
            self.__scale = value
Daniel Brown's avatar
Daniel Brown committed
191 192 193 194 195 196
    
    @property
    def name(self): return self.__name        

    def __str__(self): return self.name

197
    def mask(self, n, m, factor):
Daniel Brown's avatar
Daniel Brown committed
198 199 200 201 202 203 204
        """
        Sets the mode mask to apply to this detector for the mode TEMnm. By default
        all modes have a factor of 1.
        
        n,m - mode indicies
        factor - float between 0 and 1
        """
Daniel Brown's avatar
Daniel Brown committed
205
        _id = (n,m)
206 207 208
        
        # if the mask is 1 then remove this so it doesn't get 
        # printed as by default the value is 1.0
209 210 211 212 213
        if _id in self._mask and factor == 1.0:
            del self._mask[_id]
                
        self._mask[_id] = factor

214
    def _set_node(self, value, index):
215
        if self._kat is None:
216 217 218 219 220 221 222 223 224 225 226 227 228
            raise pkex.BasePyKatException("This detector has not been added to a kat object yet")
        else:
            if value[-1] == '*':
                self._alternate_beam[index] = True
                value = value[:-1]
            else:
                self._alternate_beam[index] = False
                
            if value in self._kat.nodes:
                self._nodes[index] = self._kat.nodes[value]
            else:
                raise pkex.BasePyKatException("There is no node called " + value + " in the kat object this detector is attached to.")
    
Daniel Brown's avatar
Daniel Brown committed
229 230
    def _getScaleCmds(self, rtn):
        if self.scale != None:
231
            if isinstance(self.scale, six.string_types):
Daniel Brown's avatar
Daniel Brown committed
232 233 234 235 236 237
                rtn.append("scale {1} {0}".format(self.name, self.scale))
            elif isinstance(self.scale, (list, tuple)):
                for s in self.scale:
                    rtn.append("scale {1} {0}".format(self.name, s))
            else:
                raise pkex.BasePyKatException("Scale command should either be a list of scales or a single string.")
Daniel Brown's avatar
Daniel Brown committed
238 239 240 241 242 243 244

class Detector0(BaseDetector):
    """
    A detector that attaches to no nodes.
    """
    pass
                        
245
class Detector1(BaseDetector):
246 247 248
    """
    A detector that attaches to one node.
    """
249
    @property 
250
    def node(self): return self._nodes[0]
251 252 253 254 255
    @node.setter
    def node(self, value):
        self._set_node(value, 0)      
        
class Detector2(BaseDetector):
256 257 258
    """
    A detector that attaches to two node.
    """
259 260
    
    @property 
261
    def node1(self): return self._nodes[0]
262 263 264 265 266
    @node1.setter
    def node(self, value):
        self._set_node(value, 0)
        
    @property 
267
    def node2(self): return self._nodes[1]
268 269 270 271 272
    @node2.setter
    def node(self, value):
        self._set_node(value, 1)
                
                
Daniel Brown's avatar
Daniel Brown committed
273 274 275 276 277 278 279 280
class beam(Detector1):
    
    def __init__(self, name, node_name, frequency=None, alternate_beam=False):
        BaseDetector.__init__(self, name, node_name)
        
        self.alternate_beam = alternate_beam
        self.__f = Param("f", self, frequency)        
    
281 282
        self._freeze()
        
Daniel Brown's avatar
Daniel Brown committed
283 284 285 286 287 288 289 290 291 292 293
    @property
    def f(self): return self.__f
    
    @f.setter
    def f(self, value):
        self.__f.value = value
        
    @staticmethod
    def parseFinesseText(text):
        
        values = text.split()
Daniel Brown's avatar
Daniel Brown committed
294

Daniel Brown's avatar
Daniel Brown committed
295 296 297 298 299 300
        node=values[-1]
        alt_beam = node[-1] == '*'
        
        if len(values) == 3:
            return beam(values[1], node, alternate_beam=alt_beam)
        elif len(values) == 4:
Daniel Brown's avatar
Daniel Brown committed
301
            return beam(values[1], node, alternate_beam=alt_beam, frequency=SIfloat(values[2]))
Daniel Brown's avatar
Daniel Brown committed
302 303 304 305
        else:
            raise pkex.BasePyKatException('Beam detector code "{0}" is not a valid FINESSE command'.format(text))
    
    def getFinesseText(self) :
306
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
307 308 309 310 311 312 313 314 315
        
        if self.alternate_beam:
            alt = '*'
        else:
            alt = ''
        
        if self.f.value is None:
            rtn.append("beam {name} {node}{alt}".format(name=self.name, node=self.node.name, alt=alt))
        else:
316
            rtn.append("beam {name} {f} {node}{alt}".format(name=self.name, f=self.f, node=self.node.name, alt=alt))
317
        
Daniel Brown's avatar
Daniel Brown committed
318 319 320 321 322
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn
        
323 324 325 326 327 328 329 330
class cp(Detector0):
    
    def __init__(self, name, cavity, direction, parameter):
        BaseDetector.__init__(self, name, None)
        
        self.cavity = str(cavity)
        self.direction = direction
        self.parameter = parameter
331 332
        
        self._freeze()
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

    @property
    def direction(self): return self.__direction
    @direction.setter
    def direction(self, value):
        if value.lower() not in ["x", "y"]:
            raise pkex.BasePyKatException('Cavity parameter detector direction must be x or y.')
    
        self.__direction = value
        
        
    @property
    def parameter(self): return self.__param
    @parameter.setter
    def parameter(self, value):
        
        params = ["w0","w","zr","z","r","q","finesse","m","stability","loss","length","fsr","fwhm","pole","gouy","fsep","A","B","C","D"]

        if value not in params:
            raise pkex.BasePyKatException('Cavity parameter detector direction must be one of: ' + ", ".join(params))
    
        self.__param = value


    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
        
        if len(values) == 4:
            # For FINESSE version < 2.1
            # Old format the name of the detector is a combination of the arguments
            return cp(values[1] + "_" + values[2] + "_" + values[3], values[1], values[2], values[3])
        elif len(values) == 5:
            return cp(values[1], values[2], values[3], values[4])
        else:
            raise pkex.BasePyKatException('Cavity parameter detector code "{0}" is not a valid FINESSE command'.format(text))
            
    def getFinesseText(self) :
371
        rtn = BaseDetector.getFinesseText(self)
372 373 374 375 376 377 378 379
        
        rtn.append("cp {name} {cavity} {direction} {parameter}".format(name=self.name,
                                                                   cavity=str(self.cavity),
                                                                   direction=self.direction,
                                                                   parameter=self.parameter))
        
        return rtn
        
Daniel Brown's avatar
Daniel Brown committed
380 381 382 383 384 385 386
class xd(Detector0):
    
    def __init__(self, name, component, motion):
        BaseDetector.__init__(self, name, None)
        
        self.component = component
        self.motion = motion
387 388
    
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
389 390 391 392 393 394 395 396 397 398 399

    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
        
        if len(values) == 4:
            return xd(values[1], values[2], values[3])
        else:
            raise pkex.BasePyKatException('Motion detector code "{0}" is not a valid FINESSE command'.format(text))
            
    def getFinesseText(self) :
400
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
401
        
Daniel Brown's avatar
Daniel Brown committed
402 403 404 405 406 407
        rtn.append("xd {name} {component} {motion}".format(name=self.name,
                                                           component=self.component,
                                                           motion=self.motion))
        
        return rtn
              
Daniel Brown's avatar
Daniel Brown committed
408
        
409
class ad(Detector1):
Daniel Brown's avatar
Daniel Brown committed
410 411
    
    def __init__(self, name, frequency, node_name, mode=None, alternate_beam=False):
412
        BaseDetector.__init__(self, name, node_name)
Daniel Brown's avatar
Daniel Brown committed
413
        self.mode = mode
414
        
Daniel Brown's avatar
Daniel Brown committed
415 416
        self.alternate_beam = alternate_beam
        self.__f = Param("f", self, frequency)
Daniel Brown's avatar
Daniel Brown committed
417
    
418 419
        self._freeze()
    
420 421 422 423 424 425 426 427 428 429 430 431
    @property
    def n(self): return self.__mode[0]
    @n.setter
    def n(self, value):
        self.__mode[0] = int(value)
        
    @property
    def m(self): return self.__mode[1] 
    @m.setter
    def m(self, value):
        self.__mode[1] = int(value)
        
Daniel Brown's avatar
Daniel Brown committed
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    @property
    def mode(self): return self.__mode
    @mode.setter
    def mode(self, value):
        if value != None and len(value) != 2:
            raise pkex.BasePyKatException('Mode must be a container of length 2, first element the x mode and second the y mode')
    
        self.__mode = value
        
    @property
    def f(self): return self.__f
    @f.setter
    def f(self, value): 
        self.__f.value = value
        
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
450 451 452

        node=values[-1]
        alt_beam = node[-1] == '*'
Daniel Brown's avatar
Daniel Brown committed
453 454 455
        if len(values) == 6:
            return ad(values[1], values[4], values[5], mode = [int(values[2]), int(values[3])], alternate_beam=alt_beam)
        elif len(values) == 4:
456
            return ad(values[1], values[2], values[3], alternate_beam=alt_beam)
Daniel Brown's avatar
Daniel Brown committed
457 458 459 460
        else:
            raise pkex.BasePyKatException('Amplitude detector code "{0}" is not a valid FINESSE command'.format(text))
            
    def getFinesseText(self) :
461
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
462 463 464 465 466 467
        
        if self.alternate_beam:
            alt = '*'
        else:
            alt = ''
        
468
        if self.mode is None:
469
            rtn.append("ad {name} {f} {node}{alt}".format(name=self.name, f=self.f, node=self.node.name, alt=alt))
Daniel Brown's avatar
Daniel Brown committed
470
        else:
471
            rtn.append("ad {name} {n} {m} {f} {node}{alt}".format(name=self.name, n=str(self.mode[0]), m=str(self.mode[1]), f=self.f, node=self.node.name, alt=alt))
Daniel Brown's avatar
Daniel Brown committed
472 473 474 475 476
            
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn
477

478
class gouy(Detector1):
479 480
    
    def __init__(self, name, direction, spaces):
481
        BaseDetector.__init__(self, name)
482 483 484
        self.spaces = copy.copy(spaces)
        self.direction = direction
        self.alternate_beam = False
485 486
    
        self._freeze()
487 488 489 490 491
        
    @property
    def direction(self): return self.__dir
    @direction.setter
    def direction(self, value):
492
        if value is None or (value != 'x' and value != 'y'):
493 494 495 496 497 498 499 500 501
            raise pkex.BasePyKatException('Direction must be either x or y')
    
        self.__dir = value

    @property
    def spaces(self): return self.__spaces
    @spaces.setter
    def spaces(self, value):

502
        if value is None or len(value) < 1:
503 504 505 506 507 508 509 510 511 512 513 514 515 516
            raise pkex.BasePyKatException('Must be a list of space names')
    
        self.__spaces = value
        
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()

        if len(values) > 3:
            return gouy(str(values[1]), str(values[2]), values[3:])
        else:
            raise pkex.BasePyKatException('Gouy detector code "{0}" is not a valid FINESSE command'.format(text))
            
    def getFinesseText(self) :
517
        rtn = BaseDetector.getFinesseText(self)
518 519 520 521 522 523 524 525 526 527

        rtn.append("gouy {name} {dir} {spaces}".format(name=self.name, dir=str(self.direction), spaces=" ".join(self.spaces)))
        
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn



528
class bp(Detector1):
529
    acceptedParameters = ['w', 'w0', 'z', 'zr', 'g', 'r', 'q', 'Rc']
530 531
    
    def __init__(self, name, direction, parameter, node, alternate_beam=False):
532
        BaseDetector.__init__(self, name, node)
533 534 535
        self.parameter = parameter
        self.direction = direction
        self.alternate_beam = alternate_beam
536 537
    
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
538
        
539 540 541 542
    @property
    def direction(self): return self.__dir
    @direction.setter
    def direction(self, value):
543
        if value is None or (value != 'x' and value != 'y'):
544 545 546 547 548 549 550 551 552
            raise pkex.BasePyKatException('Direction must be either x or y')
    
        self.__dir = value

    @property
    def parameter(self): return self.__param
    @parameter.setter
    def parameter(self, value):
        
553
        if value is None or (value not in self.acceptedParameters) :
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
            raise pkex.BasePyKatException('Parameter must be one of: %s'%(", ".join(self.acceptedParameters)))
    
        self.__param = value
        
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
        
        node=values[-1]
        alt_beam = node[-1] == '*'
        
        if len(values) > 3:
            return bp(str(values[1]), str(values[2]), str(values[3]), str(values[4]), alternate_beam=alt_beam)
        else:
            raise pkex.BasePyKatException('Gouy detector code "{0}" is not a valid FINESSE command'.format(text))
            
    def getFinesseText(self) :
571
        rtn = BaseDetector.getFinesseText(self)
572 573 574 575 576 577 578 579 580 581 582 583 584

        if self.alternate_beam:
            alt = "*"
        else:
            alt = ""
            
        rtn.append("bp {name} {dir} {param} {node}{alt}".format(name=self.name, dir=str(self.direction), param=self.parameter, node=self.node.name, alt=alt))
        
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn

Daniel Brown's avatar
Daniel Brown committed
585
id___2 = 0
586

587
class pd(Detector1):
588

589 590 591 592
    def __new__(cls, *args, **kwargs):
        # This creates an instance specific class for the component
        # this enables us to add properties to instances rather than
        # all classes
Daniel Brown's avatar
Daniel Brown committed
593 594 595 596 597 598
        global id___2
        id___2 += 1
        cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id___2))
    
        cnew = type(cnew_name, (cls,), {})
    
599
        return object.__new__(cnew)
Daniel Brown's avatar
Daniel Brown committed
600 601 602 603 604

    def __deepcopy__(self, memo):
        """
        When deep copying a kat object we need to take into account
        the instance specific properties.
605 606
        
        PD code needs to override this as it needs to call set_demod_attrs.
Daniel Brown's avatar
Daniel Brown committed
607 608 609 610 611 612 613
        """
        
        # 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.__dict__ = copy.deepcopy(self.__dict__, memo)
        
614
        # Need to update f/p attrs
Daniel Brown's avatar
Daniel Brown committed
615 616
        result.__set_demod_attrs()
        
617 618 619
        for _ in result._params:
            _._updateOwner(result)
            
Daniel Brown's avatar
Daniel Brown committed
620
        return result
621

622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
    def _get_fphi_str(self):
        """
        Will return the frequency phase pairs in a string for use
        in making the finesse commands for various detectors
        """
        fphi_str = ""
            
        for n in range(1, 1+self.num_demods):
            _f = self.__getattribute__("f"+str(n))
            
            if _f == "$fs":
                fphi_str += " $fs"
            else:
                fphi_str += " {0}".format(_f)
                
            phi_val = self.__getattribute__("phase"+str(n))
            
            if phi_val != None:
                if type(phi_val) == float:
                    fphi_str += " {}".format(phi_val)
                else:
                    fphi_str += " {0}".format(phi_val)
        
        return fphi_str
    
    @staticmethod
    def _parse_fphi(f, phs):
        dict = {}
        
        for i in range(len(f)):
            dict['f{0}'.format(i+1)] = SIfloat(f[i])
            
        for i in range(len(phs)):
            if phs[i] == "max":
                dict['phase{0}'.format(i+1)] = "max"
            else:
                dict['phase{0}'.format(i+1)] = SIfloat(phs[i])
        
        return dict
    
662
    def __init__(self, name=None, num_demods=1, node_name=None, senstype=None, alternate_beam=False, pdtype=None, **kwargs):
663
        BaseDetector.__init__(self, name, node_name)
Daniel Brown's avatar
Daniel Brown committed
664 665 666
        
        self.__num_demods = num_demods
        self.__senstype = senstype
Daniel Brown's avatar
Daniel Brown committed
667
        self.alternate_beam = alternate_beam
668 669
        self.__pdtype = pdtype

Daniel Brown's avatar
Daniel Brown committed
670 671 672 673
        # create the parameters for all 5 demodulations regardless
        # of how many the user specifies. Later we add properties to
        # those which correspond to the number of demodulations
        
674 675 676 677 678
        self.__f1 = Param("f1", self, None)
        self.__f2 = Param("f2", self, None)
        self.__f3 = Param("f3", self, None)
        self.__f4 = Param("f4", self, None)
        self.__f5 = Param("f5", self, None)
Daniel Brown's avatar
Daniel Brown committed
679
        
Daniel Brown's avatar
Daniel Brown committed
680 681 682 683 684
        self.__phase1 = Param("phase1", self, None)
        self.__phase2 = Param("phase2", self, None)
        self.__phase3 = Param("phase3", self, None)
        self.__phase4 = Param("phase4", self, None)
        self.__phase5 = Param("phase5", self, None)
Daniel Brown's avatar
Daniel Brown committed
685
        
686 687
        self.__fs = [self.__f1, self.__f2, self.__f3, self.__f4, self.__f5]
        self.__ps = [self.__phase1, self.__phase2, self.__phase3, self.__phase4, self.__phase5]
688

689 690
        for i in range(num_demods):
            f = 'f{0}'.format(i+1)
691
            #print("i {0} fs {1} f {2} keys {3}".format(i,len(fs),f, kwargs.keys()))
692 693
            
            if f in kwargs:
694
                self.__fs[i].value = kwargs[f]
695 696 697
            else:
                raise pkex.BasePyKatException("Missing demodulation frequency {0} (f{0})".format(i+1))    
        
698
            p = 'phase{0}'.format(i+1)
699 700
            
            if p in kwargs:
701
                if kwargs[p] is None and i<num_demods-1:
702
                    raise pkex.BasePyKatException("Missing demodulation phase {0} (phase{0})".format(i+1))
703
                    
704
                self.__ps[i].value = kwargs[p]
705
            elif i<num_demods-1:
706
                raise pkex.BasePyKatException("Missing demodulation phase {0} (phase{0})".format(i+1))
707
   
Daniel Brown's avatar
Daniel Brown committed
708
        self.__set_demod_attrs()
709 710
    
        self._freeze()
711
                
Daniel Brown's avatar
Daniel Brown committed
712 713 714 715 716 717 718 719 720 721 722
    @property
    def senstype(self): return self.__senstype
    @senstype.setter
    def senstype(self,value):
        if value == "": value = None
        
        if value != "S" and value != "N" and value != None: 
            raise pkex.BasePyKatException("Photodiode sensitivity type can either be 'N', 'S' or None.")
            
        self.__senstype = value
        
723
    @property
Daniel Brown's avatar
Daniel Brown committed
724 725 726 727 728 729 730 731
    def num_demods(self): return self.__num_demods
    @num_demods.setter
    def num_demods(self, value): 
        if value < 0 or value > 5:
            raise pkex.BasePyKatException("Number of demodulations must be between 0 and 5")
        
        self.__num_demods = value
        self.__set_demod_attrs()
732 733 734 735 736

    @property
    def pdtype(self): return self.__pdtype
    @pdtype.setter
    def pdtype(self, value): self.__pdtype = value
Daniel Brown's avatar
Daniel Brown committed
737 738
    
    def __get_fphi(self, name):
Daniel Brown's avatar
Daniel Brown committed
739
        return getattr(self, '_pd__' + name)
Daniel Brown's avatar
Daniel Brown committed
740 741
    
    def __set_f(self, num, value):
Daniel Brown's avatar
Daniel Brown committed
742 743 744 745
        value = SIfloat(value)
        
        p = getattr(self, '_pd__f' + num)
        p.value = value
Daniel Brown's avatar
Daniel Brown committed
746 747
    
    def __set_phi(self, num, value):
Daniel Brown's avatar
Daniel Brown committed
748 749
        value = SIfloat(value)
        
750
        if value is None and num != self.num_demods:
Daniel Brown's avatar
Daniel Brown committed
751 752 753
            # check if we are setting no phase that this is only on the last
            # demodulation phase.
            raise pkex.BasePyKatException("Only last demodulation phase can be set to None")
754
        elif isinstance(value, six.string_types) and not isinstance(value,float) and value.lower() != "max":
Daniel Brown's avatar
Daniel Brown committed
755
            raise pkex.BasePyKatException("Demodulation phase can only be set to a 'max' or a number (or None if the last demodulation phase)")
Daniel Brown's avatar
Daniel Brown committed
756
          
757
        p = getattr(self, '_pd__phase' + num)  
Daniel Brown's avatar
Daniel Brown committed
758
        p.value = value
Daniel Brown's avatar
Daniel Brown committed
759 760 761 762 763 764
        
    def __set_demod_attrs(self):
        """
        For the set number of demodulations the correct number of 
        Parameters are created.
        """
765
        self._unfreeze()
Daniel Brown's avatar
Daniel Brown committed
766 767 768 769 770 771 772
        
        # if there are demodulations present then we want to add
        # the various parameters so they are available for users
        # to play with.
        if self.__num_demods > 0:
            for i in range(1,6):
                name = str(i)
Daniel Brown's avatar
Daniel Brown committed
773
                
Daniel Brown's avatar
Daniel Brown committed
774 775
                if i <= self.num_demods:
                    if not hasattr(self, "f"+name):
Daniel Brown's avatar
Daniel Brown committed
776 777 778 779
                        fget = lambda self, i=i:        self.__get_fphi('f'+str(i))
                        fset = lambda self, value, i=i: self.__set_f(str(i), value)
                        
                        setattr(self.__class__, "f"+name, property(fget=fget, fset=fset))
Daniel Brown's avatar
Daniel Brown committed
780
                    
781 782
                    if not hasattr(self, "phase"+name):
                        setattr(self.__class__, "phase"+name, property(fget=lambda self, i=i: self.__get_fphi('phase'+str(i)), fset=lambda self, value, i=i: self.__set_phi(str(i), value)))
Daniel Brown's avatar
Daniel Brown committed
783 784 785
                else:
                    if hasattr(self, "f"+name):
                        delattr(self.__class__, "f"+name)
Daniel Brown's avatar
Daniel Brown committed
786
                        
787 788
                    if hasattr(self, "phase"+name):
                        delattr(self.__class__, "phase"+name)
789 790
        
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
791
    
792 793
    @staticmethod
    def parseFinesseText(text): 
Daniel Brown's avatar
Daniel Brown committed
794
        values = text.split()
795 796 797 798 799 800 801 802
        demods = 0
        senstype = None

        if len(values[0]) == 4:
            senstype = values[0][2]
            demods = int(values[0][3])
        elif len(values[0]) == 3:
            demods = int(values[0][2])
803
        elif len(values[0]) != 2:
804
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (1)".format(text))
805 806
        
        if len(values) <= 3 and demods > 0:
807
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (2)".format(text))
808
        elif len(values) > 3 and demods == 0:
809
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (3)".format(text))
810 811 812 813
            
        num_f_phs = len(values) - 3
        expected_f_phs = demods * 2
        
814 815
        if not (num_f_phs == expected_f_phs or num_f_phs == expected_f_phs-1):
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (4)".format(text))
816 817 818
        
        f = values[2:len(values)-1:2]    
        phs = values[3:len(values)-1:2]
819 820
                
        dict = pd._parse_fphi(f, phs)
821 822 823 824 825 826 827 828 829 830
            
        node = values[-1]
        alt_beam = node[-1] == '*'
        
        if alt_beam:
            node = node[0:-1]
        
        return pd(values[1], demods, node, senstype=senstype, alternate_beam=alt_beam, **dict)

        
Daniel Brown's avatar
Daniel Brown committed
831
    def getFinesseText(self) :
832
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
833 834 835 836
        
        if self.enabled:
            alt_str = ""
            
Daniel Brown's avatar
Daniel Brown committed
837
            if self.alternate_beam:
Daniel Brown's avatar
Daniel Brown committed
838
                alt_str = "*"
839 840
            
            fphi_str = self._get_fphi_str()
Daniel Brown's avatar
Daniel Brown committed
841 842 843
            
            senstype = self.senstype
            
844
            if senstype is None:
Daniel Brown's avatar
Daniel Brown committed
845
                senstype = ""
846
            
ssl's avatar
ssl committed
847
            rtn.append("pd{0}{1} {2}{3} {4}{5}".format(senstype, self.num_demods, self.name, fphi_str, self.node.name, alt_str))
848

Daniel Brown's avatar
Daniel Brown committed
849 850
            self._getScaleCmds(rtn)
            
851 852
            if self.pdtype != None:
                rtn.append("pdtype {0} {1}".format(self.name, self.pdtype))
853
                
854 855
            for p in self._params:
                rtn.extend(p.getFinesseText())
856
        
Daniel Brown's avatar
Daniel Brown committed
857
        return rtn
858
  
Daniel Brown's avatar
Daniel Brown committed
859
class qnoised(pd):
860 861
    
    def __init__(self, name, num_demods, node_name, alternate_beam=False, pdtype=None, **kwargs):
Daniel Brown's avatar
Daniel Brown committed
862
        super(qnoised, self).__init__(name, num_demods, node_name, alternate_beam=alternate_beam, pdtype=pdtype, **kwargs)
863
    
864 865
        self._unfreeze()
        
866
        self.__homangle = AttrParam("homangle", self, None)
867 868
        
        self._freeze()
869 870 871 872 873 874 875
    
    @property
    def homangle(self): return self.__homangle
    @homangle.setter
    def homangle(self, value): self.__homangle.value = value
    
    @pd.pdtype.setter
Daniel Brown's avatar
Daniel Brown committed
876 877
    def pdtype(self, value):
        raise pkex.BasePyKatException("Setting pdtype is not possible with qnoised detectors")
878
    
Daniel Brown's avatar
Daniel Brown committed
879 880 881 882 883 884 885
    def parseAttributes(self, values):
        
        for key in values.keys():
            if key in ["homangle"]:
                self.__homangle.value = values[key]
            else:
                raise pkex.BasePyKatException("No attribute {0} for qnoised".format(key))
886 887 888 889
    
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
Daniel Brown's avatar
Daniel Brown committed
890

891
        if len(values) <= 3:
Daniel Brown's avatar
Daniel Brown committed
892
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (2)".format(text))
Daniel Brown's avatar
Daniel Brown committed
893
            
Daniel Brown's avatar
Daniel Brown committed
894
        demods = int(values[2])
895
        
896
        if len(values) <= 4 and demods > 0:
Daniel Brown's avatar
Daniel Brown committed
897
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (2)".format(text))
898
        elif len(values) > 4 and demods == 0:
Daniel Brown's avatar
Daniel Brown committed
899
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (3)".format(text))
900 901 902 903
            
        num_f_phs = len(values) - 4
        expected_f_phs = demods * 2
        
Daniel Brown's avatar
Daniel Brown committed
904 905
        if not (num_f_phs == expected_f_phs or num_f_phs == (expected_f_phs-1)):
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (4)".format(text))
906 907 908 909
        
        f = values[3:len(values)-1:2]    
        phs = values[4:len(values)-1:2]
        
910
        dict = pd._parse_fphi(f, phs)
Daniel Brown's avatar
Daniel Brown committed
911
            
912 913 914 915 916 917
        node = values[-1]
        alt_beam = node[-1] == '*'
        
        if alt_beam:
            node = node[0:-1]
        
918 919 920 921 922 923 924 925
        if values[0].endswith('S'):
            sens='S'
        elif values[0].endswith('N'):
            sens='N'
        else:
            sens=None
            
        return qnoised(values[1], demods, node, senstype=sens, alternate_beam=alt_beam, **dict)
926 927
    
    def getFinesseText(self) :
928
        rtn = BaseDetector.getFinesseText(self)
929 930 931
        
        if self.enabled:
            alt_str = ""
932
            fphi_str = fphi_str = self._get_fphi_str()
933 934 935
            
            if self.alternate_beam:
                alt_str = "*"
936
            
937 938
            senstype = self.senstype
            
939
            if senstype is None:
940 941
                senstype = ""
                
942
            rtn.append("qnoised{5} {0} {1} {2} {3}{4}".format(self.name, self.num_demods, fphi_str, self.node.name, alt_str, senstype))
943

Daniel Brown's avatar
Daniel Brown committed
944
            self._getScaleCmds(rtn)
945 946 947 948 949
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
        return rtn
Daniel Brown's avatar
Daniel Brown committed
950

951
class qshot(pd):
Daniel Brown's avatar
Daniel Brown committed
952 953
    
    def __init__(self, name, num_demods, node_name, alternate_beam=False, **kwargs):
954
        super(qshot, self).__init__(name, num_demods, node_name, alternate_beam=alternate_beam, pdtype=None, **kwargs)     
955

Daniel Brown's avatar
Daniel Brown committed
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
    @pd.pdtype.setter
    def pdtype(self, value):
        raise pkex.BasePyKatException("Setting pdtype is not possible with qshot detectors")
    
    @pd.senstype.setter
    def senstype(self,value):
        raise pkex.BasePyKatException("qshot detector has no sensitvity type")
    
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()

        if len(values) <= 3:
            raise pkex.BasePyKatException("qshot code format incorrect '{0}' (2)".format(text))
            
        demods = int(values[2])
        
        if len(values) <= 4 and demods > 0:
            raise pkex.BasePyKatException("qshot code format incorrect '{0}' (2)".format(text))
        elif len(values) > 4 and demods == 0:
            raise pkex.BasePyKatException("qshot code format incorrect '{0}' (3)".format(text))
            
        num_f_phs = len(values) - 4
        expected_f_phs = demods * 2
        
        if not (num_f_phs == expected_f_phs or num_f_phs == (expected_f_phs-1)):
            raise pkex.BasePyKatException("qshot code format incorrect '{0}' (4)".format(text))
        
        f = values[3:len(values)-1:2]    
        phs = values[4:len(values)-1:2]
        
987
        dict = pd._parse_fphi(f, phs)
Daniel Brown's avatar
Daniel Brown committed
988 989 990 991 992 993
            
        node = values[-1]
        alt_beam = node[-1] == '*'
        
        if alt_beam:
            node = node[0:-1]
994 995 996 997 998 999 1000
            
        if values[0].endswith('S'):
            sens='S'
        elif values[0].endswith('N'):
            sens='N'
        else:
            sens=None
Daniel Brown's avatar
Daniel Brown committed
1001
        
1002
        return qshot(values[1], demods, node, senstype=sens, alternate_beam=alt_beam, **dict)
1003
    
Daniel Brown's avatar
Daniel Brown committed
1004
    def getFinesseText(self) :
1005
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
1006 1007 1008
        
        if self.enabled:
            alt_str = ""
1009
            fphi_str = fphi_str = self._get_fphi_str()
Daniel Brown's avatar
Daniel Brown committed
1010 1011 1012
            
            if self.alternate_beam:
                alt_str = "*"
1013
             
Daniel Brown's avatar
Daniel Brown committed
1014 1015
            senstype = self.senstype
            
1016
            if senstype is None:
Daniel Brown's avatar
Daniel Brown committed
1017 1018
                senstype = ""
                
1019
            rtn.append("qshot{5} {0} {1} {2} {3}{4}".format(self.name, self.num_demods, fphi_str, self.node.name, alt_str,senstype))
Daniel Brown's avatar
Daniel Brown committed
1020

Daniel Brown's avatar
Daniel Brown committed
1021
            self._getScaleCmds(rtn)
Daniel Brown's avatar
Daniel Brown committed
1022 1023 1024 1025 1026
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
        return rtn
Daniel Brown's avatar
Daniel Brown committed
1027
    
1028 1029 1030 1031 1032 1033
    
class hd(Detector2):
    
    def __init__(self, name, phase, node1_name, node2_name):
        BaseDetector.__init__(self, name, (node1_name, node2_name), max_nodes=2)
    
1034
        self.__phase = Param("phase", self, phase)
1035 1036 1037

        self._freeze()
        
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052
    @property
    def phase(self): return self.__phase
    @phase.setter
    def phase(self, value): self.__phase.value = value
    
    def parseAttributes(self, values):
        raise pkex.BasePyKatException("hd detector %s has no attributes to set" % self.name)
    
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
        
        return hd(values[1], float(values[2]), str(values[3]), str(values[4]))
    
    def getFinesseText(self):
1053
        rtn = BaseDetector.getFinesseText(self)
1054 1055
        
        if self.enabled:   
1056 1057
            n1 = self.node1.name
            n2 = self.node2.name
1058
            
1059 1060
            if self._alternate_beam[0]: n1 += "*"
            if self._alternate_beam[1]: n2 += "*"
1061 1062 1063
            
            rtn.append("hd {0} {1} {2} {3}".format(self.name, self.phase, n1, n2))

Daniel Brown's avatar
Daniel Brown committed
1064
            self._getScaleCmds(rtn)
1065 1066 1067 1068
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
1069 1070 1071 1072 1073 1074 1075 1076 1077
        return rtn
        
class qhd(Detector2):
    
    def __init__(self, name, phase, node1_name, node2_name, sensitivity=""):
        BaseDetector.__init__(self, name, (node1_name, node2_name), max_nodes=2)
    
        self.__phase = Param("phase", self, phase)
        self.sensitivity = sensitivity
1078 1079
    
        self._freeze()
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
        
    @property
    def phase(self): return self.__phase
    @phase.setter
    def phase(self, value): self.__phase.value = value
    
    @property
    def sensitivity(self): 
        return self.__sensitivity
    @sensitivity.setter
    def sensitivity(self, value):
        if value == 'S' or value == 'N':
            self.__sensitivity = value
1093
        elif value is None or value == '':
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
            self.__sensitivity = ""
        else:
            raise pkex.BasePyKatException("qhd (%s) sensitivity option '%s' is not available, use either 'S' or 'N'." % (self.name, value))
        
    
    def parseAttributes(self, values):
        raise pkex.BasePyKatException("hd detector %s has no attributes to set" % self.name)
    
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
        
Daniel Brown's avatar
Daniel Brown committed
1106 1107 1108 1109 1110 1111
        sens = values[0][-1]
        
        if sens != 'S' and sens != 'N':
            sens = None
            
        return qhd(values[1], float(values[2]), str(values[3]), str(values[4]), sensitivity = sens)
1112 1113
    
    def getFinesseText(self):
1114
        rtn = BaseDetector.getFinesseText(self)
1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
        
        if self.enabled:   
            n1 = self.node1.name
            n2 = self.node2.name
            
            if self._alternate_beam[0]: n1 += "*"
            if self._alternate_beam[1]: n2 += "*"
            
            rtn.append("qhd{4} {0} {1} {2} {3}".format(self.name, self.phase, n1, n2, self.sensitivity))

Daniel Brown's avatar
Daniel Brown committed
1125
            self._getScaleCmds(rtn)
1126 1127 1128 1129
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
1130
        return rtn