detectors.py 36.1 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()
Daniel Brown's avatar
updates    
Daniel Brown committed
115
116
        result.__dict__ = copy.deepcopy(self.__dict__, memo)
        
117
        result._freeze()
Daniel Brown's avatar
updates    
Daniel Brown committed
118
        return result
119
                
Daniel Brown's avatar
Daniel Brown committed
120
121
122
123
    def _register_param(self, param):
        self._params.append(param)
        
    def _on_kat_add(self, kat):
124
125
        self._kat = kat
        
126
127
128
        for rn in self._requested_nodes:
            if rn != None:
                self._nodes.append(kat.nodes.createNode(rn))
Daniel Brown's avatar
Daniel Brown committed
129
    
130
131
132
    def _on_kat_remove(self):
        self.__removed = True
        
133
134
135
136
137
138
139
140
    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
141
142
143
144
145
146
    @staticmethod
    def parseFinesseText(text):    
        raise NotImplementedError("This function is not implemented")
        
    def getFinesseText(self):
        """ Base class for individual finesse optical components """    
147
148
149
150
151
152
        #raise NotImplementedError("This function is not implemented")
        
        if self.noplot:
            return ["noplot " + self.name]
        else:
            return []
Daniel Brown's avatar
Daniel Brown committed
153
154
        
    def getQGraphicsItem(self):    
155
156
157
        if not USE_GUI:
            raise NoGUIException
            
Daniel Brown's avatar
Daniel Brown committed
158
        return None
159

160
161
162
    @property
    def removed(self): return self.__removed
    
163
164
165
    @property 
    def scale(self): return self.__scale
    @scale.setter
166
    def scale(self, value):
Daniel Brown's avatar
Daniel Brown committed
167
168
        if value not in self.__scale:
            self.__scale = value
Daniel Brown's avatar
Daniel Brown committed
169
170
171
172
173
174
    
    @property
    def name(self): return self.__name        

    def __str__(self): return self.name

175
    def mask(self, n, m, factor):
176
        _id = str(n)+"_"+str(m)
177
178
179
        
        # if the mask is 1 then remove this so it doesn't get 
        # printed as by default the value is 1.0
180
181
182
183
184
185
        if _id in self._mask and factor == 1.0:
            del self._mask[_id]
                
        self._mask[_id] = factor

    def _set_node(value, index):
186
        if self._kat is None:
187
188
189
190
191
192
193
194
195
196
197
198
199
            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
200
201
    def _getScaleCmds(self, rtn):
        if self.scale != None:
202
            if isinstance(self.scale, six.string_types):
Daniel Brown's avatar
Daniel Brown committed
203
204
205
206
207
208
                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
209
210
211
212
213
214
215

class Detector0(BaseDetector):
    """
    A detector that attaches to no nodes.
    """
    pass
                        
216
class Detector1(BaseDetector):
217
218
219
    """
    A detector that attaches to one node.
    """
220
    @property 
221
    def node(self): return self._nodes[0]
222
223
224
225
226
227
    @node.setter
    def node(self, value):
        self._set_node(value, 0)      
        
                
class Detector2(BaseDetector):
228
229
230
    """
    A detector that attaches to two node.
    """
231
232
    
    @property 
233
    def node1(self): return self._nodes[0]
234
235
236
237
238
    @node1.setter
    def node(self, value):
        self._set_node(value, 0)
        
    @property 
239
    def node2(self): return self._nodes[1]
240
241
242
243
244
    @node2.setter
    def node(self, value):
        self._set_node(value, 1)
                
                
Daniel Brown's avatar
Daniel Brown committed
245
246
247
248
249
250
251
252
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)        
    
253
254
        self._freeze()
        
Daniel Brown's avatar
Daniel Brown committed
255
256
257
258
259
260
261
262
263
264
265
    @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
266

Daniel Brown's avatar
Daniel Brown committed
267
268
269
270
271
272
        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
273
            return beam(values[1], node, alternate_beam=alt_beam, frequency=SIfloat(values[2]))
Daniel Brown's avatar
Daniel Brown committed
274
275
276
277
        else:
            raise pkex.BasePyKatException('Beam detector code "{0}" is not a valid FINESSE command'.format(text))
    
    def getFinesseText(self) :
278
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
279
280
281
282
283
284
285
286
287
288
        
        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:
            rtn.append("beam {name} {f} {node}{alt}".format(name=self.name, f=str(self.f.value), node=self.node.name, alt=alt))
289
        
Daniel Brown's avatar
Daniel Brown committed
290
291
292
293
294
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn
        
295
296
297
298
299
300
301
302
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
303
304
        
        self._freeze()
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
335
336
337
338
339
340
341
342

    @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) :
343
        rtn = BaseDetector.getFinesseText(self)
344
345
346
347
348
349
350
351
        
        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
352
353
354
355
356
357
358
class xd(Detector0):
    
    def __init__(self, name, component, motion):
        BaseDetector.__init__(self, name, None)
        
        self.component = component
        self.motion = motion
359
360
    
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
361
362
363
364
365
366
367
368
369
370
371

    @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) :
372
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
373
        
Daniel Brown's avatar
Daniel Brown committed
374
375
376
377
378
379
        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
380
        
381
class ad(Detector1):
Daniel Brown's avatar
Daniel Brown committed
382
383
    
    def __init__(self, name, frequency, node_name, mode=None, alternate_beam=False):
384
        BaseDetector.__init__(self, name, node_name)
Daniel Brown's avatar
Daniel Brown committed
385
386
387
        self.mode = mode
        self.alternate_beam = alternate_beam
        self.__f = Param("f", self, frequency)
Daniel Brown's avatar
Daniel Brown committed
388
    
389
390
        self._freeze()
    
Daniel Brown's avatar
Daniel Brown committed
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
    @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()
409
410
411

        node=values[-1]
        alt_beam = node[-1] == '*'
Daniel Brown's avatar
Daniel Brown committed
412
413
414
        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:
415
            return ad(values[1], values[2], values[3], alternate_beam=alt_beam)
Daniel Brown's avatar
Daniel Brown committed
416
417
418
419
        else:
            raise pkex.BasePyKatException('Amplitude detector code "{0}" is not a valid FINESSE command'.format(text))
            
    def getFinesseText(self) :
420
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
421
422
423
424
425
426
        
        if self.alternate_beam:
            alt = '*'
        else:
            alt = ''
        
427
        if self.mode is None:
Daniel Brown's avatar
Daniel Brown committed
428
429
            rtn.append("ad {name} {f} {node}{alt}".format(name=self.name, f=str(self.f.value), node=self.node.name, alt=alt))
        else:
430
            rtn.append("ad {name} {n} {m} {f} {node}{alt}".format(name=self.name, n=str(self.mode[0]), m=str(self.mode[1]), f=str(self.f.value), node=self.node.name, alt=alt))
Daniel Brown's avatar
Daniel Brown committed
431
432
433
434
435
            
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn
436

437
class gouy(Detector1):
438
439
    
    def __init__(self, name, direction, spaces):
440
        BaseDetector.__init__(self, name)
441
442
443
        self.spaces = copy.copy(spaces)
        self.direction = direction
        self.alternate_beam = False
444
445
    
        self._freeze()
446
447
448
449
450
        
    @property
    def direction(self): return self.__dir
    @direction.setter
    def direction(self, value):
451
        if value is None or (value != 'x' and value != 'y'):
452
453
454
455
456
457
458
459
460
            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):

461
        if value is None or len(value) < 1:
462
463
464
465
466
467
468
469
470
471
472
473
474
475
            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) :
476
        rtn = BaseDetector.getFinesseText(self)
477
478
479
480
481
482
483
484
485
486

        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



487
class bp(Detector1):
488
    acceptedParameters = ['w', 'w0', 'z', 'zr', 'g', 'r', 'q', 'Rc']
489
490
    
    def __init__(self, name, direction, parameter, node, alternate_beam=False):
491
        BaseDetector.__init__(self, name, node)
492
493
494
        self.parameter = parameter
        self.direction = direction
        self.alternate_beam = alternate_beam
495
496
    
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
497
        
498
499
500
501
    @property
    def direction(self): return self.__dir
    @direction.setter
    def direction(self, value):
502
        if value is None or (value != 'x' and value != 'y'):
503
504
505
506
507
508
509
510
511
            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):
        
512
        if value is None or (value not in self.acceptedParameters) :
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
            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) :
530
        rtn = BaseDetector.getFinesseText(self)
531
532
533
534
535
536
537
538
539
540
541
542
543

        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
544
id___2 = 0
545

546
class pd(Detector1):
547

548
549
550
551
    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
552
553
554
555
556
557
        global id___2
        id___2 += 1
        cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id___2))
    
        cnew = type(cnew_name, (cls,), {})
    
558
        return object.__new__(cnew)
Daniel Brown's avatar
Daniel Brown committed
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573

    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
574
575

    def __init__(self, name=None, num_demods=1, node_name=None, senstype=None, alternate_beam=False, pdtype=None, **kwargs):
576
        BaseDetector.__init__(self, name, node_name)
Daniel Brown's avatar
Daniel Brown committed
577
578
579
        
        self.__num_demods = num_demods
        self.__senstype = senstype
Daniel Brown's avatar
Daniel Brown committed
580
        self.alternate_beam = alternate_beam
581
582
        self.__pdtype = pdtype

Daniel Brown's avatar
Daniel Brown committed
583
584
585
586
        # 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
        
587
588
589
590
591
        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
592
        
Daniel Brown's avatar
Daniel Brown committed
593
594
595
596
597
        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
598
        
599
        fs = [self.__f1, self.__f2, self.__f3, self.__f4, self.__f5]
Daniel Brown's avatar
Daniel Brown committed
600
        ps = [self.__phase1, self.__phase2, self.__phase3, self.__phase4, self.__phase5]
601

602
603
        for i in range(num_demods):
            f = 'f{0}'.format(i+1)
604
            #print("i {0} fs {1} f {2} keys {3}".format(i,len(fs),f, kwargs.keys()))
605
606
607
608
609
610
            
            if f in kwargs:
                fs[i].value = kwargs[f]
            else:
                raise pkex.BasePyKatException("Missing demodulation frequency {0} (f{0})".format(i+1))    
        
611
            p = 'phase{0}'.format(i+1)
612
613
            
            if p in kwargs:
614
                if kwargs[p] is None and i<num_demods-1:
615
                    raise pkex.BasePyKatException("Missing demodulation phase {0} (phase{0})".format(i+1))
616
617
618
                    
                ps[i].value = kwargs[p]
            elif i<num_demods-1:
619
                raise pkex.BasePyKatException("Missing demodulation phase {0} (phase{0})".format(i+1))
620
   
Daniel Brown's avatar
Daniel Brown committed
621
        self.__set_demod_attrs()
622
623
    
        self._freeze()
624
                
Daniel Brown's avatar
Daniel Brown committed
625
626
627
628
629
630
631
632
633
634
635
    @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
        
636
    @property
Daniel Brown's avatar
Daniel Brown committed
637
638
639
640
641
642
643
644
    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()
645
646
647
648
649

    @property
    def pdtype(self): return self.__pdtype
    @pdtype.setter
    def pdtype(self, value): self.__pdtype = value
Daniel Brown's avatar
Daniel Brown committed
650
651
    
    def __get_fphi(self, name):
Daniel Brown's avatar
Daniel Brown committed
652
        return getattr(self, '_pd__' + name)
Daniel Brown's avatar
Daniel Brown committed
653
654
    
    def __set_f(self, num, value):
Daniel Brown's avatar
Daniel Brown committed
655
656
657
658
        value = SIfloat(value)
        
        p = getattr(self, '_pd__f' + num)
        p.value = value
Daniel Brown's avatar
Daniel Brown committed
659
660
    
    def __set_phi(self, num, value):
Daniel Brown's avatar
Daniel Brown committed
661
662
        value = SIfloat(value)
        
663
        if value is None and num != self.num_demods:
Daniel Brown's avatar
Daniel Brown committed
664
665
666
            # 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")
667
        elif isinstance(value, six.string_types) and not isinstance(value,float) and value.lower() != "max":
Daniel Brown's avatar
Daniel Brown committed
668
            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
669
          
670
        p = getattr(self, '_pd__phase' + num)  
Daniel Brown's avatar
Daniel Brown committed
671
        p.value = value
Daniel Brown's avatar
Daniel Brown committed
672
673
674
675
676
677
        
    def __set_demod_attrs(self):
        """
        For the set number of demodulations the correct number of 
        Parameters are created.
        """
678
        self._unfreeze()
Daniel Brown's avatar
Daniel Brown committed
679
680
681
682
683
684
685
        
        # 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
686
                
Daniel Brown's avatar
Daniel Brown committed
687
688
                if i <= self.num_demods:
                    if not hasattr(self, "f"+name):
Daniel Brown's avatar
Daniel Brown committed
689
690
691
692
                        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
693
                    
694
695
                    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
696
697
698
                else:
                    if hasattr(self, "f"+name):
                        delattr(self.__class__, "f"+name)
Daniel Brown's avatar
Daniel Brown committed
699
                        
700
701
                    if hasattr(self, "phase"+name):
                        delattr(self.__class__, "phase"+name)
702
703
        
        self._freeze()
Daniel Brown's avatar
Daniel Brown committed
704
    
705
706
    @staticmethod
    def parseFinesseText(text): 
Daniel Brown's avatar
Daniel Brown committed
707
        values = text.split()
708
709
710
711
712
713
714
715
        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
716
        elif len(values[0]) != 2:
717
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (1)".format(text))
718
719
        
        if len(values) <= 3 and demods > 0:
720
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (2)".format(text))
Daniel Brown's avatar
Daniel Brown committed
721
        elif len(values) > 3 and demods == 0:
722
            raise pkex.BasePyKatException("Photodiode code format incorrect '{0}' (3)".format(text))
723
724
725
726
            
        num_f_phs = len(values) - 3
        expected_f_phs = demods * 2
        
727
728
        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))
729
730
731
732
733
734
735
        
        f = values[2:len(values)-1:2]    
        phs = values[3:len(values)-1:2]
        
        dict = {}
        
        for i in range(len(f)):
Daniel Brown's avatar
Daniel Brown committed
736
            dict['f{0}'.format(i+1)] = SIfloat(f[i])
737
        for i in range(len(phs)):
Daniel Brown's avatar
Daniel Brown committed
738
            if phs[i] == "max":
739
                dict['phase{0}'.format(i+1)] = "max"
Daniel Brown's avatar
Daniel Brown committed
740
            else:
741
                dict['phase{0}'.format(i+1)] = SIfloat(phs[i])
742
743
744
745
746
747
748
749
750
751
            
        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
752
    def getFinesseText(self) :
753
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
754
755
756
757
758
        
        if self.enabled:
            alt_str = ""
            fphi_str = ""
            
Daniel Brown's avatar
Daniel Brown committed
759
            if self.alternate_beam:
Daniel Brown's avatar
Daniel Brown committed
760
761
762
                alt_str = "*"
                
            for n in range(1, 1+self.num_demods):
763
764
765
766
767
768
769
                _f = self.__getattribute__("f"+str(n))
                
                if _f == "$fs":
                    fphi_str += " $fs"
                else:
                    fphi_str += " {0:.16g}".format(float(_f))
                    
770
                phi_val = self.__getattribute__("phase"+str(n))
Daniel Brown's avatar
Daniel Brown committed
771
772
                
                if phi_val != None:
773
774
775
776
                    if type(phi_val) == float:
                        fphi_str += " {0:.16g}".format(float(phi_val))
                    else:
                        fphi_str += " {0}".format(phi_val)
Daniel Brown's avatar
Daniel Brown committed
777
778
779
            
            senstype = self.senstype
            
780
            if senstype is None:
Daniel Brown's avatar
Daniel Brown committed
781
                senstype = ""
782
            
Sean Leavey's avatar
Sean Leavey committed
783
            rtn.append("pd{0}{1} {2}{3} {4}{5}".format(senstype, self.num_demods, self.name, fphi_str, self.node.name, alt_str))
784

Daniel Brown's avatar
Daniel Brown committed
785
786
            self._getScaleCmds(rtn)
            
787
788
            if self.pdtype != None:
                rtn.append("pdtype {0} {1}".format(self.name, self.pdtype))
789
                
790
791
            for p in self._params:
                rtn.extend(p.getFinesseText())
Daniel Brown's avatar
Daniel Brown committed
792
793
            
        return rtn
794
  
Daniel Brown's avatar
Daniel Brown committed
795
class qnoised(pd):
796
797
    
    def __init__(self, name, num_demods, node_name, alternate_beam=False, pdtype=None, **kwargs):
Daniel Brown's avatar
Daniel Brown committed
798
        super(qnoised, self).__init__(name, num_demods, node_name, alternate_beam=alternate_beam, pdtype=pdtype, **kwargs)
799
    
800
801
        self._unfreeze()
        
802
        self.__homangle = AttrParam("homangle", self, None)
803
804
        
        self._freeze()
805
806
807
808
809
810
811
    
    @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
812
813
    def pdtype(self, value):
        raise pkex.BasePyKatException("Setting pdtype is not possible with qnoised detectors")
814
    
Daniel Brown's avatar
Daniel Brown committed
815
816
817
818
819
820
821
    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))
822
823
824
825
    
    @staticmethod
    def parseFinesseText(text): 
        values = text.split()
Daniel Brown's avatar
Daniel Brown committed
826

827
        if len(values) <= 3:
Daniel Brown's avatar
Daniel Brown committed
828
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (2)".format(text))
Daniel Brown's avatar
Daniel Brown committed
829
            
Daniel Brown's avatar
Daniel Brown committed
830
        demods = int(values[2])
831
        
832
        if len(values) <= 4 and demods > 0:
Daniel Brown's avatar
Daniel Brown committed
833
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (2)".format(text))
834
        elif len(values) > 4 and demods == 0:
Daniel Brown's avatar
Daniel Brown committed
835
            raise pkex.BasePyKatException("qnoised code format incorrect '{0}' (3)".format(text))
836
837
838
839
            
        num_f_phs = len(values) - 4
        expected_f_phs = demods * 2
        
Daniel Brown's avatar
Daniel Brown committed
840
841
        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))
842
843
844
845
846
        
        f = values[3:len(values)-1:2]    
        phs = values[4:len(values)-1:2]
        
        dict = {}
Daniel Brown's avatar
Daniel Brown committed
847
        
848
849
850
        for i in range(len(f)):
            dict['f{0}'.format(i+1)] = f[i]
        for i in range(len(phs)):
851
            dict['phase{0}'.format(i+1)] = phs[i]
Daniel Brown's avatar
Daniel Brown committed
852
            
853
854
855
856
857
858
        node = values[-1]
        alt_beam = node[-1] == '*'
        
        if alt_beam:
            node = node[0:-1]
        
859
860
861
862
863
864
865
866
        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)
867
868
    
    def getFinesseText(self) :
869
        rtn = BaseDetector.getFinesseText(self)
870
871
872
873
874
875
876
        
        if self.enabled:
            alt_str = ""
            fphi_str = ""
            
            if self.alternate_beam:
                alt_str = "*"
877
            
878
            for n in range(1, 1+self.num_demods):
879
880
881
882
883
884
885
                _f = self.__getattribute__("f"+str(n))
                
                if _f == "$fs":
                    fphi_str += " $fs"
                else:
                    fphi_str += " {0:.16g}".format(float(_f))
                    
886
                phi_val = self.__getattribute__("phase"+str(n))
887
888
                
                if phi_val != None:
889
890
891
892
                    if type(phi_val) == float:
                        fphi_str += " {0:.16g}".format(float(phi_val))
                    else:
                        fphi_str += " " + str(phi_val)
893
894
895
            
            senstype = self.senstype
            
896
            if senstype is None:
897
898
                senstype = ""
                
899
            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
900

Daniel Brown's avatar
Daniel Brown committed
901
            self._getScaleCmds(rtn)
902
903
904
905
906
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
        return rtn
Daniel Brown's avatar
Daniel Brown committed
907

908
class qshot(pd):
Daniel Brown's avatar
Daniel Brown committed
909
910
    
    def __init__(self, name, num_demods, node_name, alternate_beam=False, **kwargs):
911
        super(qshot, self).__init__(name, num_demods, node_name, alternate_beam=alternate_beam, pdtype=None, **kwargs)     
912

Daniel Brown's avatar
Daniel Brown committed
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
    @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]
        
        dict = {}
        
        for i in range(len(f)):
            dict['f{0}'.format(i+1)] = f[i]
        for i in range(len(phs)):
949
            dict['phase{0}'.format(i+1)] = phs[i]
Daniel Brown's avatar
Daniel Brown committed
950
951
952
953
954
955
            
        node = values[-1]
        alt_beam = node[-1] == '*'
        
        if alt_beam:
            node = node[0:-1]
956
957
958
959
960
961
962
            
        if values[0].endswith('S'):
            sens='S'
        elif values[0].endswith('N'):
            sens='N'
        else:
            sens=None
Daniel Brown's avatar
Daniel Brown committed
963
        
964
        return qshot(values[1], demods, node, senstype=sens, alternate_beam=alt_beam, **dict)
965
    
Daniel Brown's avatar
Daniel Brown committed
966
    def getFinesseText(self) :
967
        rtn = BaseDetector.getFinesseText(self)
Daniel Brown's avatar
Daniel Brown committed
968
969
970
971
972
973
974
975
976
        
        if self.enabled:
            alt_str = ""
            fphi_str = ""
            
            if self.alternate_beam:
                alt_str = "*"
                
            for n in range(1, 1+self.num_demods):
977
978
979
980
981
982
983
                _f = self.__getattribute__("f"+str(n))
                
                if _f == "$fs":
                    fphi_str += " $fs"
                else:
                    fphi_str += " {0:.16g}".format(float(_f))
                    
984
                phi_val = self.__getattribute__("phase"+str(n))
Daniel Brown's avatar
Daniel Brown committed
985
986
                
                if phi_val != None:
987
988
989
990
                    if type(phi_val) == float:
                        fphi_str += " {0:.16g}".format(float(phi_val))
                    else:
                        fphi_str += " " + str(phi_val)
Daniel Brown's avatar
Daniel Brown committed
991
992
993
            
            senstype = self.senstype
            
994
            if senstype is None:
Daniel Brown's avatar
Daniel Brown committed
995
996
                senstype = ""
                
997
            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
998

Daniel Brown's avatar
Daniel Brown committed
999
            self._getScaleCmds(rtn)
Daniel Brown's avatar
Daniel Brown committed
1000
1001
1002
1003
1004
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
        return rtn
Daniel Brown's avatar
Daniel Brown committed
1005
    
1006
1007
1008
1009
1010
1011
    
class hd(Detector2):
    
    def __init__(self, name, phase, node1_name, node2_name):
        BaseDetector.__init__(self, name, (node1_name, node2_name), max_nodes=2)
    
1012
        self.__phase = Param("phase", self, phase)
1013
1014
1015

        self._freeze()
        
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
    @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):
1031
        rtn = BaseDetector.getFinesseText(self)
1032
1033
        
        if self.enabled:   
1034
1035
            n1 = self.node1.name
            n2 = self.node2.name
1036
            
1037
1038
            if self._alternate_beam[0]: n1 += "*"
            if self._alternate_beam[1]: n2 += "*"
1039
1040
1041
            
            rtn.append("hd {0} {1} {2} {3}".format(self.name, self.phase, n1, n2))

Daniel Brown's avatar
Daniel Brown committed
1042
            self._getScaleCmds(rtn)
1043
1044
1045
1046
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
1047
1048
1049
1050
1051
1052
1053
1054
1055
        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
1056
1057
    
        self._freeze()
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
        
    @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
1071
        elif value is None or value == '':
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
            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
1084
1085
1086
1087
1088
1089
        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)
1090
1091
    
    def getFinesseText(self):
1092
        rtn = BaseDetector.getFinesseText(self)
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
        
        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
1103
            self._getScaleCmds(rtn)
1104
1105
1106
1107
                
            for p in self._params:
                rtn.extend(p.getFinesseText())
            
1108
        return rtn