detectors.py 35.6 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
updates  
Daniel Brown committed
34

35
id_____pykat_class = 0
Daniel Brown's avatar
updates  
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
updates  
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
updates  
Daniel Brown committed
53 54 55
        
        cnew = type(cnew_name, (cls,), {})
        
56 57
        o = object.__new__(cnew)
        return o
Daniel Brown's avatar
updates  
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
updates  
Daniel Brown committed
104 105 106 107 108 109 110 111 112 113
    
    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__)
114
        result._unfreeze()
115
        memo[id(self)] = result
Daniel Brown's avatar
updates  
Daniel Brown committed
116 117
        result.__dict__ = copy.deepcopy(self.__dict__, memo)
        
118
        result._freeze()
Daniel Brown's avatar
updates  
Daniel Brown committed
119
        return result
120
                
Daniel Brown's avatar
Daniel Brown committed
121 122 123 124
    def _register_param(self, param):
        self._params.append(param)
        
    def _on_kat_add(self, kat):
125 126
        self._kat = kat
        
127 128 129
        for rn in self._requested_nodes:
            if rn != None:
                self._nodes.append(kat.nodes.createNode(rn))
Daniel Brown's avatar
Daniel Brown committed
130
    
131 132 133
    def _on_kat_remove(self):
        self.__removed = True
        
134 135 136 137 138 139 140 141
    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
142 143 144 145 146 147
    @staticmethod
    def parseFinesseText(text):    
        raise NotImplementedError("This function is not implemented")
        
    def getFinesseText(self):
        """ Base class for individual finesse optical components """    
148
        #raise NotImplementedError("This function is not implemented")
Daniel Brown's avatar
Daniel Brown committed
149 150 151 152 153
        rtn = []
        
        for _ in self._mask:
            if _[0]+_[1] <= self._kat.maxtem:
                rtn.append("mask {} {} {} {}".format(self.name, _[0], _[1], self._mask[_]))
154 155
        
        if self.noplot:
Daniel Brown's avatar
Daniel Brown committed
156 157 158
            rtn.append("noplot " + self.name)
            
        return rtn
Daniel Brown's avatar
Daniel Brown committed
159 160
        
    def getQGraphicsItem(self):    
161 162 163
        if not USE_GUI:
            raise NoGUIException
            
Daniel Brown's avatar
Daniel Brown committed
164
        return None
165

166 167 168
    @property
    def removed(self): return self.__removed
    
169 170 171
    @property 
    def scale(self): return self.__scale
    @scale.setter
172
    def scale(self, value):
Daniel Brown's avatar
Daniel Brown committed
173 174
        if value not in self.__scale:
            self.__scale = value
Daniel Brown's avatar
Daniel Brown committed
175 176 177 178 179 180
    
    @property
    def name(self): return self.__name        

    def __str__(self): return self.name

181
    def mask(self, n, m, factor):
Daniel Brown's avatar
Daniel Brown committed
182 183 184 185 186 187 188
        """
        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
189
        _id = (n,m)
190 191 192
        
        # if the mask is 1 then remove this so it doesn't get 
        # printed as by default the value is 1.0
193 194 195 196 197
        if _id in self._mask and factor == 1.0:
            del self._mask[_id]
                
        self._mask[_id] = factor

198
    def _set_node(self, value, index):
199
        if self._kat is None:
200 201 202 203 204 205 206 207 208 209 210 211 212
            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
213 214
    def _getScaleCmds(self, rtn):
        if self.scale != None:
215
            if isinstance(self.scale, six.string_types):
Daniel Brown's avatar
Daniel Brown committed
216 217 218 219 220 221
                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
222 223 224 225 226 227 228

class Detector0(BaseDetector):
    """
    A detector that attaches to no nodes.
    """
    pass
                        
229
class Detector1(BaseDetector):
230 231 232
    """
    A detector that attaches to one node.
    """
233
    @property 
234
    def node(self): return self._nodes[0]
235 236 237 238 239
    @node.setter
    def node(self, value):
        self._set_node(value, 0)      
        
class Detector2(BaseDetector):
240 241 242
    """
    A detector that attaches to two node.
    """
243 244
    
    @property 
245
    def node1(self): return self._nodes[0]
246 247 248 249 250
    @node1.setter
    def node(self, value):
        self._set_node(value, 0)
        
    @property 
251
    def node2(self): return self._nodes[1]
252 253 254 255 256
    @node2.setter
    def node(self, value):
        self._set_node(value, 1)
                
                
Daniel Brown's avatar
Daniel Brown committed
257 258 259 260 261 262 263 264
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)        
    
265 266
        self._freeze()
        
Daniel Brown's avatar
Daniel Brown committed
267 268 269 270 271 272 273 274 275 276 277
    @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
278

Daniel Brown's avatar
Daniel Brown committed
279 280 281 282 283 284
        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
mend  
Daniel Brown committed
285
            return beam(values[1], node, alternate_beam=alt_beam, frequency=SIfloat(values[2]))
Daniel Brown's avatar
Daniel Brown committed
286 287 288 289
        else:
            raise pkex.BasePyKatException('Beam detector code "{0}" is not a valid FINESSE command'.format(text))
    
    def getFinesseText(self) :
290
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
291 292 293 294 295 296 297 298 299
        
        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:
300
            rtn.append("beam {name} {f} {node}{alt}".format(name=self.name, f=self.f, node=self.node.name, alt=alt))
301
        
Daniel Brown's avatar
Daniel Brown committed
302 303 304 305 306
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn
        
307 308 309 310 311 312 313 314
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
315 316
        
        self._freeze()
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

    @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) :
355
        rtn = BaseDetector.getFinesseText(self)
356 357 358 359 360 361 362 363
        
        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
364 365 366 367 368 369 370
class xd(Detector0):
    
    def __init__(self, name, component, motion):
        BaseDetector.__init__(self, name, None)
        
        self.component = component
        self.motion = motion
371 372
    
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
373 374 375 376 377 378 379 380 381 382 383

    @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) :
384
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
385
        
Daniel Brown's avatar
Daniel Brown committed
386 387 388 389 390 391
        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
392
        
393
class ad(Detector1):
Daniel Brown's avatar
Daniel Brown committed
394 395
    
    def __init__(self, name, frequency, node_name, mode=None, alternate_beam=False):
396
        BaseDetector.__init__(self, name, node_name)
Daniel Brown's avatar
Daniel Brown committed
397
        self.mode = mode
398
        
Daniel Brown's avatar
Daniel Brown committed
399 400
        self.alternate_beam = alternate_beam
        self.__f = Param("f", self, frequency)
Daniel Brown's avatar
Daniel Brown committed
401
    
402 403
        self._freeze()
    
404 405 406 407 408 409 410 411 412 413 414 415
    @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
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
    @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()
434 435 436

        node=values[-1]
        alt_beam = node[-1] == '*'
Daniel Brown's avatar
Daniel Brown committed
437 438 439
        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:
440
            return ad(values[1], values[2], values[3], alternate_beam=alt_beam)
Daniel Brown's avatar
Daniel Brown committed
441 442 443 444
        else:
            raise pkex.BasePyKatException('Amplitude detector code "{0}" is not a valid FINESSE command'.format(text))
            
    def getFinesseText(self) :
445
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
446 447 448 449 450 451
        
        if self.alternate_beam:
            alt = '*'
        else:
            alt = ''
        
452
        if self.mode is None:
453
            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
454
        else:
455
            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
456 457 458 459 460
            
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn
461

462
class gouy(Detector1):
463 464
    
    def __init__(self, name, direction, spaces):
465
        BaseDetector.__init__(self, name)
466 467 468
        self.spaces = copy.copy(spaces)
        self.direction = direction
        self.alternate_beam = False
469 470
    
        self._freeze()
471 472 473 474 475
        
    @property
    def direction(self): return self.__dir
    @direction.setter
    def direction(self, value):
476
        if value is None or (value != 'x' and value != 'y'):
477 478 479 480 481 482 483 484 485
            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):

486
        if value is None or len(value) < 1:
487 488 489 490 491 492 493 494 495 496 497 498 499 500
            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) :
501
        rtn = BaseDetector.getFinesseText(self)
502 503 504 505 506 507 508 509 510 511

        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



512
class bp(Detector1):
513
    acceptedParameters = ['w', 'w0', 'z', 'zr', 'g', 'r', 'q', 'Rc']
514 515
    
    def __init__(self, name, direction, parameter, node, alternate_beam=False):
516
        BaseDetector.__init__(self, name, node)
517 518 519
        self.parameter = parameter
        self.direction = direction
        self.alternate_beam = alternate_beam
520 521
    
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
522
        
523 524 525 526
    @property
    def direction(self): return self.__dir
    @direction.setter
    def direction(self, value):
527
        if value is None or (value != 'x' and value != 'y'):
528 529 530 531 532 533 534 535 536
            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):
        
537
        if value is None or (value not in self.acceptedParameters) :
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
            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) :
555
        rtn = BaseDetector.getFinesseText(self)
556 557 558 559 560 561 562 563 564 565 566 567 568

        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
569
id___2 = 0
570

571
class pd(Detector1):
572

573 574 575 576
    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
577 578 579 580 581 582
        global id___2
        id___2 += 1
        cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id___2))
    
        cnew = type(cnew_name, (cls,), {})
    
583
        return object.__new__(cnew)
Daniel Brown's avatar
Daniel Brown committed
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598

    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)
        
        result.__set_demod_attrs()
        
        return result
599

600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
    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
    
640
    def __init__(self, name=None, num_demods=1, node_name=None, senstype=None, alternate_beam=False, pdtype=None, **kwargs):
641
        BaseDetector.__init__(self, name, node_name)
Daniel Brown's avatar
Daniel Brown committed
642 643 644
        
        self.__num_demods = num_demods
        self.__senstype = senstype
Daniel Brown's avatar
Daniel Brown committed
645
        self.alternate_beam = alternate_beam
646 647
        self.__pdtype = pdtype

Daniel Brown's avatar
Daniel Brown committed
648 649 650 651
        # 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
        
652 653 654 655 656
        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
657
        
Daniel Brown's avatar
Daniel Brown committed
658 659 660 661 662
        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
663
        
664
        fs = [self.__f1, self.__f2, self.__f3, self.__f4, self.__f5]
Daniel Brown's avatar
Daniel Brown committed
665
        ps = [self.__phase1, self.__phase2, self.__phase3, self.__phase4, self.__phase5]
666

667 668
        for i in range(num_demods):
            f = 'f{0}'.format(i+1)
669
            #print("i {0} fs {1} f {2} keys {3}".format(i,len(fs),f, kwargs.keys()))
670 671 672 673 674 675
            
            if f in kwargs:
                fs[i].value = kwargs[f]
            else:
                raise pkex.BasePyKatException("Missing demodulation frequency {0} (f{0})".format(i+1))    
        
676
            p = 'phase{0}'.format(i+1)
677 678
            
            if p in kwargs:
679
                if kwargs[p] is None and i<num_demods-1:
680
                    raise pkex.BasePyKatException("Missing demodulation phase {0} (phase{0})".format(i+1))
681 682 683
                    
                ps[i].value = kwargs[p]
            elif i<num_demods-1:
684
                raise pkex.BasePyKatException("Missing demodulation phase {0} (phase{0})".format(i+1))
685
   
Daniel Brown's avatar
Daniel Brown committed
686
        self.__set_demod_attrs()
687 688
    
        self._freeze()
689
                
Daniel Brown's avatar
Daniel Brown committed
690 691 692 693 694 695 696 697 698 699 700
    @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
        
701
    @property
Daniel Brown's avatar
Daniel Brown committed
702 703 704 705 706 707 708 709
    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()
710 711 712 713 714

    @property
    def pdtype(self): return self.__pdtype
    @pdtype.setter
    def pdtype(self, value): self.__pdtype = value
Daniel Brown's avatar
Daniel Brown committed
715 716
    
    def __get_fphi(self, name):
Daniel Brown's avatar
Daniel Brown committed
717
        return getattr(self, '_pd__' + name)
Daniel Brown's avatar
Daniel Brown committed
718 719
    
    def __set_f(self, num, value):
Daniel Brown's avatar
Daniel Brown committed
720 721 722 723
        value = SIfloat(value)
        
        p = getattr(self, '_pd__f' + num)
        p.value = value
Daniel Brown's avatar
Daniel Brown committed
724 725
    
    def __set_phi(self, num, value):
Daniel Brown's avatar
Daniel Brown committed
726 727
        value = SIfloat(value)
        
728
        if value is None and num != self.num_demods:
Daniel Brown's avatar
Daniel Brown committed
729 730 731
            # 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")
732
        elif isinstance(value, six.string_types) and not isinstance(value,float) and value.lower() != "max":
Daniel Brown's avatar
Daniel Brown committed
733
            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
734
          
735
        p = getattr(self, '_pd__phase' + num)  
Daniel Brown's avatar
Daniel Brown committed
736
        p.value = value
Daniel Brown's avatar
Daniel Brown committed
737 738 739 740 741 742
        
    def __set_demod_attrs(self):
        """
        For the set number of demodulations the correct number of 
        Parameters are created.
        """
743
        self._unfreeze()
Daniel Brown's avatar
Daniel Brown committed
744 745 746 747 748 749 750
        
        # 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
751
                
Daniel Brown's avatar
Daniel Brown committed
752 753
                if i <= self.num_demods:
                    if not hasattr(self, "f"+name):
Daniel Brown's avatar
Daniel Brown committed
754 755 756 757
                        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
758
                    
759 760
                    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
761 762 763
                else:
                    if hasattr(self, "f"+name):
                        delattr(self.__class__, "f"+name)
Daniel Brown's avatar
Daniel Brown committed
764
                        
765 766
                    if hasattr(self, "phase"+name):
                        delattr(self.__class__, "phase"+name)
767 768
        
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
769
    
770 771
    @staticmethod
    def parseFinesseText(text): 
Daniel Brown's avatar
Daniel Brown committed
772
        values = text.split()
773 774 775 776 777 778 779 780
        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])
Daniel Brown's avatar
Daniel Brown committed
781
        elif len(values[0]) != 2:
782
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (1)".format(text))
783 784
        
        if len(values) <= 3 and demods > 0:
785
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (2)".format(text))
Daniel Brown's avatar
Daniel Brown committed
786
        elif len(values) > 3 and demods == 0:
787
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (3)".format(text))
788 789 790 791
            
        num_f_phs = len(values) - 3
        expected_f_phs = demods * 2
        
792 793
        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))
794 795 796
        
        f = values[2:len(values)-1:2]    
        phs = values[3:len(values)-1:2]
797 798
                
        dict = pd._parse_fphi(f, phs)
799 800 801 802 803 804 805 806 807 808
            
        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
809
    def getFinesseText(self) :
810
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
811 812 813 814
        
        if self.enabled:
            alt_str = ""
            
Daniel Brown's avatar
Daniel Brown committed
815
            if self.alternate_beam:
Daniel Brown's avatar
Daniel Brown committed
816
                alt_str = "*"
817 818
            
            fphi_str = self._get_fphi_str()
Daniel Brown's avatar
Daniel Brown committed
819 820 821
            
            senstype = self.senstype
            
822
            if senstype is None:
Daniel Brown's avatar
Daniel Brown committed
823
                senstype = ""
824
            
ssl's avatar
ssl committed
825
            rtn.append("pd{0}{1} {2}{3} {4}{5}".format(senstype, self.num_demods, self.name, fphi_str, self.node.name, alt_str))
826

Daniel Brown's avatar
Daniel Brown committed
827 828
            self._getScaleCmds(rtn)
            
829 830
            if self.pdtype != None:
                rtn.append("pdtype {0} {1}".format(self.name, self.pdtype))
831
                
832 833
            for p in self._params:
                rtn.extend(p.getFinesseText())
834
        
Daniel Brown's avatar
Daniel Brown committed
835
        return rtn
836
  
Daniel Brown's avatar
Daniel Brown committed
837
class qnoised(pd):
838 839
    
    def __init__(self, name, num_demods, node_name, alternate_beam=False, pdtype=None, **kwargs):
Daniel Brown's avatar
Daniel Brown committed
840
        super(qnoised, self).__init__(name, num_demods, node_name, alternate_beam=alternate_beam, pdtype=pdtype, **kwargs)
841
    
842 843
        self._unfreeze()
        
844
        self.__homangle = AttrParam("homangle", self, None)
845 846
        
        self._freeze()
847 848 849 850 851 852 853
    
    @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
854 855
    def pdtype(self, value):
        raise pkex.BasePyKatException("Setting pdtype is not possible with qnoised detectors")
856
    
Daniel Brown's avatar
Daniel Brown committed
857 858 859 860 861 862 863
    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))
864 865 866 867
    
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
Daniel Brown's avatar
Daniel Brown committed
868

869
        if len(values) <= 3:
Daniel Brown's avatar
Daniel Brown committed
870
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (2)".format(text))
Daniel Brown's avatar
Daniel Brown committed
871
            
Daniel Brown's avatar
Daniel Brown committed
872
        demods = int(values[2])
873
        
874
        if len(values) <= 4 and demods > 0:
Daniel Brown's avatar
Daniel Brown committed
875
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (2)".format(text))
876
        elif len(values) > 4 and demods == 0:
Daniel Brown's avatar
Daniel Brown committed
877
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (3)".format(text))
878 879 880 881
            
        num_f_phs = len(values) - 4
        expected_f_phs = demods * 2
        
Daniel Brown's avatar
Daniel Brown committed
882 883
        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))
884 885 886 887
        
        f = values[3:len(values)-1:2]    
        phs = values[4:len(values)-1:2]
        
888
        dict = pd._parse_fphi(f, phs)
Daniel Brown's avatar
Daniel Brown committed
889
            
890 891 892 893 894 895
        node = values[-1]
        alt_beam = node[-1] == '*'
        
        if alt_beam:
            node = node[0:-1]
        
896 897 898 899 900 901 902 903
        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)
904 905
    
    def getFinesseText(self) :
906
        rtn = BaseDetector.getFinesseText(self)
907 908 909
        
        if self.enabled:
            alt_str = ""
910
            fphi_str = fphi_str = self._get_fphi_str()
911 912 913
            
            if self.alternate_beam:
                alt_str = "*"
914
            
915 916
            senstype = self.senstype
            
917
            if senstype is None:
918 919
                senstype = ""
                
920
            rtn.append("qnoised{5} {0} {1} {2} {3}{4}".format(self.name, self.num_demods, fphi_str, self.node.name, alt_str, senstype))
Andreas Freise's avatar
Andreas Freise committed
921

Daniel Brown's avatar
Daniel Brown committed
922
            self._getScaleCmds(rtn)
923 924 925 926 927
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
        return rtn
Daniel Brown's avatar
Daniel Brown committed
928

929
class qshot(pd):
Daniel Brown's avatar
Daniel Brown committed
930 931
    
    def __init__(self, name, num_demods, node_name, alternate_beam=False, **kwargs):
932
        super(qshot, self).__init__(name, num_demods, node_name, alternate_beam=alternate_beam, pdtype=None, **kwargs)     
933

Daniel Brown's avatar
Daniel Brown committed
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964
    @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]
        
965
        dict = pd._parse_fphi(f, phs)
Daniel Brown's avatar
Daniel Brown committed
966 967 968 969 970 971
            
        node = values[-1]
        alt_beam = node[-1] == '*'
        
        if alt_beam:
            node = node[0:-1]
972 973 974 975 976 977 978
            
        if values[0].endswith('S'):
            sens='S'
        elif values[0].endswith('N'):
            sens='N'
        else:
            sens=None
Daniel Brown's avatar
Daniel Brown committed
979
        
980
        return qshot(values[1], demods, node, senstype=sens, alternate_beam=alt_beam, **dict)
981
    
Daniel Brown's avatar
Daniel Brown committed
982
    def getFinesseText(self) :
983
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
984 985 986
        
        if self.enabled:
            alt_str = ""
987
            fphi_str = fphi_str = self._get_fphi_str()
Daniel Brown's avatar
Daniel Brown committed
988 989 990
            
            if self.alternate_beam:
                alt_str = "*"
991
             
Daniel Brown's avatar
Daniel Brown committed
992 993
            senstype = self.senstype