finesse.py 43.8 KB
Newer Older
Daniel Brown's avatar
Daniel Brown committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# -*- coding: utf-8 -*-
"""
Created on Sun Jan 27 09:56:53 2013

PyKat - Python interface and wrapper for FINESSE
Copyright (C) 2013 Daniel David Brown

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Contact at ddb@star.sr.bham.ac.uk

@author: Daniel Brown
"""
import sys
import os
import subprocess
import tempfile
import numpy as np
import datetime
import pickle
import pykat
import warnings
import re

Daniel Brown's avatar
Daniel Brown committed
37
import itertools
Daniel Brown's avatar
Daniel Brown committed
38
import ctypes
Daniel Brown's avatar
Daniel Brown committed
39
import collections
40
from collections import namedtuple, OrderedDict
41

Daniel Brown's avatar
Daniel Brown committed
42
43
44
45
46
from pykat.node_network import NodeNetwork
from pykat.detectors import Detector
from pykat.components import Component
from pykat.commands import Command, xaxis
from pykat.gui.gui import pyKatGUI
47
from pykat.SIfloat import *
48
from pykat.param import Param, AttrParam
Daniel Brown's avatar
Daniel Brown committed
49
50
51
52
53
54

import pykat.exceptions as pkex

from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QApplication

Daniel Brown's avatar
Daniel Brown committed
55
56
from multiprocessing import Process, Manager

Daniel Brown's avatar
Daniel Brown committed
57
58
NO_GUI = False
NO_BLOCK = "NO_BLOCK"
59
60
pykat_web = "www.gwoptics.org/pykat"

Daniel Brown's avatar
Daniel Brown committed
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def f__lkat_process(callback, cmd, kwargs):
    """
    """
    lkat = ctypes.PyDLL("libkat.so.0")

    try:
        lkat._pykat_preInit() # must always be called, sets up
                        # exception handling and such no simulation
                        # specifc code here

        # reads in the kat.ini and setups up other parts
        lkat._pykat_init()
        lkat._pykat_setup(cmd)
    
        callback(lkat, **kwargs)
    
    except Exception as ex: 
        print "Exception caught in python: ", ex.message
    finally:
        # This should always be called no matter what
        lkat._pykat_finish(0)
        
Daniel Brown's avatar
Daniel Brown committed
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
class katRun(object):
    def __init__(self):
        self.runDateTime = datetime.datetime.now()
        self.x = None
        self.y = None
        self.xlabel = None
        self.ylabels = None
        self.katScript = None
        self.katVersion = None
        
    def saveKatRun(self, filename):
        with open(filename,'w') as outfile:
            pickle.dump(self, outfile)
    
    @staticmethod
    def loadKatRun(filename):
        with open(filename,'r') as infile:
            return pickle.load(infile)
    
    def get(self, value): return self[value]
    
    def __getitem__(self, value):
105
        idx = [i for i in range(len(self.ylabels)) if self.ylabels[i].split()[0] == str(value)]
Daniel Brown's avatar
Daniel Brown committed
106
107
        
        if len(idx) > 0 and self.y.shape == ():
108
109
            # In the case we have a noxaxis and just one output...
            return float(self.y)
Daniel Brown's avatar
Daniel Brown committed
110
        elif len(idx) == 1 and len(self.y.shape) == 1:
111
112
            
            # only selecting a single output from a 1D array
Daniel Brown's avatar
Daniel Brown committed
113
            if self.y.size == 1:
114
115
116
117
118
                return self.y
            else:
                return self.y[idx[0]]
                
	
119
120
        elif len(idx) > 0 and len(self.y.shape) == 1:
            return self.y[idx]
121
122
        elif len(idx) > 0:
            return self.y[:,idx].squeeze()
123
        elif len(idx) >= 1:
124
            return self.y[:,idx].squeeze()
Daniel Brown's avatar
Daniel Brown committed
125
        else:
126
            raise  pkex.BasePyKatException("No output by the name '{0}' found in the output".format(str(value)))
Daniel Brown's avatar
Daniel Brown committed
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
      
class katRun2D(object):
    def __init__(self):
        self.runDateTime = datetime.datetime.now()
        self.x = None
        self.y = None
        self.z = None
        self.xlabel = None
        self.ylabel = None
        self.zlabels = None
        self.katScript = None
        self.katVersion = None
        
    def saveKatRun(self, filename):
        with open(filename,'w') as outfile:
            pickle.dump(self, outfile)
    
    @staticmethod
    def loadKatRun(filename):
        with open(filename,'r') as infile:
            return pickle.load(infile)
    
    def get(self, value): return self[value]
    
    def __getitem__(self, value):
152
        idx = [i for i in range(len(self.zlabels)) if self.zlabels[i].split()[0] == str(value)]
Daniel Brown's avatar
Daniel Brown committed
153
154
155
156
157
        
        if len(idx) > 0:
            return self.z[idx].squeeze()
        else:
            raise  pkex.BasePyKatException("No output by the name {0} found".format(str(value)))
158
    
159
160
class Signals(object):
    class fsig(object):
161
162
163
164
165
166
        def __init__(self, param, name, amplitude, phase):
            self._params = []
            self.__target = param
            self.__name = name
            self.__amplitude = Param("amp", self, SIfloat(amplitude))
            self.__phase = Param("phase", self, SIfloat(phase))
167
            self.__removed = False
168
169
170
171
172
173
            
            # unfortunatenly the target names for fsig are not the same as the
            # various parameter names of the components, e.g. mirror xbeta is x 
            # for fsig. So we need to check here what type of component we are targetting
            # and then based on the parameter specfied get the name
            if not param.canFsig:
174
                raise  pkex.BasePyKatException("Cannot fsig parameter {1} on component {0}".format(str(param._owner().name), param.name))
175
176
177
            
        def _register_param(self, param):
            self._params.append(param)
178
179
180
        
        @property
        def removed(self): return self.__removed
181
  
182
183
184
185
186
187
        def remove(self):
            if self.__removed:
                raise pkex.BasePyKatException("Signal {0} has already been marked as removed".format(self.name))
            else:
                self._kat.remove(self)
        
188
189
190
191
192
        @property
        def name(self): return self.__name

        @property
        def amplitude(self): return self.__amplitude
193
194
195
        @amplitude.setter
        def amplitude(self,value): self.__amplitude.value = SIfloat(value)

196
197
198

        @property
        def phase(self): return self.__phase
199
200
        @phase.setter
        def phase(self,value): self.__phase.value = SIfloat(value)
201
202
203
204
205

        @property
        def target(self): return self.__target.fsig_name

        @property
206
        def owner(self): return self.__target._owner().name
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    
        def getFinesseText(self):
            rtn = []
    
            for p in self._params:
                rtn.extend(p.getFinesseText())
        
            return rtn
    
    @property
    def name(self):
        # if we don't have any signals yet then use a dummy name
        # however we need to always tune a real fsig command
        # so need to get the name of at least one of them
        # as if you tune one you tune them all
        if len(self.targets) == 0:
223
            return "fsignal"
224
225
        else:
            return self.targets[0].name
226
227
228
229
            
    @property
    def removed(self): return False # we can never remove the Signal object altogethr just the
                                    # individual fsig targets
230

231
232
233
234
    def remove(self):
        for t in self.targets:
            self._kat.remove(self)
            
235
236
    @property
    def f(self): return self.__f
237
238
    @f.setter
    def f(self,value): self.__f.value = SIfloat(value)
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
    
    def __init__(self):

        self.targets = []
        self._params = []
        
        self.__f = Param("f", self, 1)
    
    def _register_param(self, param):
        self._params.append(param)
        
    def apply(self, target, amplitude, phase, name=None):
        
        if target == None:
            raise  pkex.BasePyKatException("No target was specified for signal to be applied")
        
        if name == None:
256
            name = "sig_" + target._owner().name + "_" + target.name
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
        
        self.targets.append(Signals.fsig(target, name, amplitude, phase))
        
    def getFinesseText(self):
        rtn = []
        
        for t in self.targets:
            rtn.extend(t.getFinesseText())
            rtn.append("fsig {name} {comp} {target} {frequency} {phase} {amplitude}".format(name = t.name, comp=t.owner, target=t.target, frequency=str(self.f), phase=str(t.phase), amplitude=str(t.amplitude)))
        
        for p in self._params:
            rtn.extend(p.getFinesseText())
        
        return rtn
        
        
Daniel Brown's avatar
Daniel Brown committed
273
274
275
276
277
278
279
280
281
class Block:
    def __init__(self, name):
        self.__name = name
        self.contents = [] # List of objects and strings of finesse code
        self.enabled = True 
        
    @property
    def name(self): return self.__name
    
282
283
284
Constant = namedtuple('Constant', 'name, value, usedBy')
    
class kat(object):  
Daniel Brown's avatar
Daniel Brown committed
285
286
287
288
289
        
    def __init__(self, kat_file=None, kat_code=None, katdir="", katname="", tempdir=None, tempname=None):
        
        self.scene = None # scene object for GUI
        self.verbose = True
290
        self.__blocks = OrderedDict() # dictionary of blocks that are used
Daniel Brown's avatar
Daniel Brown committed
291
292
293
294
295
296
297
298
299
300
        self.__components = {}  # dictionary of optical components      
        self.__detectors = {}   # dictionary of detectors
        self.__commands = {}    # dictionary of commands
        self.__gui = None
        self.nodes = NodeNetwork(self)  
        self.__katdir = katdir
        self.__katname = katname
        self.__tempdir = tempdir
        self.__tempname = tempname
        self.pykatgui = None
301
        self.__signals = Signals()
302
        self.constants = {}
303
        self.vacuum = []
304
305
306
307
        
        # initialise default block
        self.__currentTag= NO_BLOCK
        self.__blocks[NO_BLOCK] = Block(NO_BLOCK)
Daniel Brown's avatar
Daniel Brown committed
308
309
310
        
        # Various options for running finesse, typicaly the commands with just 1 input
        # and have no name attached to them.
311
312
        self.retrace = None
        self.deriv_h = None
313
        self.scale = None
Daniel Brown's avatar
Daniel Brown committed
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
        self.__phase = None
        self.__maxtem = None
        self.__noxaxis = None
        self.__time_code = None
        
        if kat_code != None and kat_file != None:
            raise pkex.BasePyKatException("Specify either a Kat file or some Kat code, not both.")
        
        if kat_code != None:
            self.parseCommands(kat_code)
        
        if kat_file != None:
            self.loadKatFile(kat_file)
        
        cls = type(self)
        self.__class__ = type(cls.__name__, (cls,), {})
        
331
332
333
    @property
    def signals(self): return self.__signals

Daniel Brown's avatar
Daniel Brown committed
334
335
336
    @property
    def maxtem(self): return self.__maxtem
    @maxtem.setter
337
338
339
340
341
    def maxtem(self,value):
        if value == "off":
            self.__maxtem = -1
        else:
            self.__maxtem = int(value)
Daniel Brown's avatar
Daniel Brown committed
342
343
344
345
346
    
    @property
    def phase(self): return self.__phase
    @phase.setter
    def phase(self,value): self.__phase = int(value)
347
        
Daniel Brown's avatar
Daniel Brown committed
348
349
350
351
352
    @property
    def getPerformanceData(self): return self.__time_code
    @getPerformanceData.setter
    def getPerformanceData(self,value): self.__time_code = bool(value)
    
Daniel Brown's avatar
Daniel Brown committed
353
354
355
356
357
358
359
360
    @property
    def components(self):
        return self.__components.copy()
    
    @property
    def detectors(self):
        return self.__detectors.copy()
        
Daniel Brown's avatar
Daniel Brown committed
361
362
363
    @property
    def noxaxis(self): return self.__noxaxis
    @noxaxis.setter
364
    def noxaxis(self,value): self.__noxaxis = bool(value) 
Daniel Brown's avatar
Daniel Brown committed
365

366
367
    @staticmethod
    def logo():
Daniel Brown's avatar
Daniel Brown committed
368
        print """                                              ..-
369
    PyKat {0:7}         _                  '(
Daniel Brown's avatar
Daniel Brown committed
370
371
372
373
374
375
                          \\`.|\\.__...-\"\"""-_." )
       ..+-----.._        /  ' `            .-'
   . '            `:      7/* _/._\\    \\   (
  (        '::;;+;;:      `-"' =" /,`"" `) /
  L.        \\`:::a:f            c_/     n_'
  ..`--...___`.  .    ,  
Daniel Brown's avatar
Daniel Brown committed
376
   `^-....____:   +.      {1}""".format(pykat.__version__, pykat_web)
Daniel Brown's avatar
Daniel Brown committed
377
378
379
380
381
382
383
384
    
    def loadKatFile(self, katfile):
        commands=open(katfile).read()
        self.parseCommands(commands)
    
    def parseKatCode(self, code):
        #commands = code.split("\n")
        self.parseCommands(code)
385
386
387
388
389
390
391
392
393

    def processConstants(self, commands):
        """
        Before fully parsing a bunch of commands firstly any constants or variables
        to be recorded and replaced.
        """
        constants = self.constants
        
        for line in commands:
394
            values = line.split()
395
396
397
            
            if len(values)>0 and values[0] == 'const':
                
Daniel Brown's avatar
Daniel Brown committed
398
                if len(values) >= 3:
399
400
401
                    if values[1] in constants:
                        raise pkex.BasePyKatException('const command with the name "{0}" already used'.format(values[1]))
                    else:
Daniel Brown's avatar
Daniel Brown committed
402
                        constants[str(values[1])] = Constant(values[1], values[2], [])
403
404
405
406
407
408
                else:
                    raise pkex.BasePyKatException('const command "{0}" was not the correct format'.format(line))
        
        commands_new = []
        
        for line in commands:
409
            values = line.split()
410
            
411
            if len(values) > 0 and values[0] != 'const':
412
413
414
415
416
417
418
419
420
421
422
423
424
                # check if we have a var/constant in this line
                if line.find('$') >= 0:
                    for key in constants.keys():
                        if line.find('$'+key) > -1:
                            constants[key].usedBy.append(line)
                            line = line.replace('$'+key, str(constants[key].value))
                        
                        
                commands_new.append(line)
    
        self.constants = constants
        
        return commands_new
Daniel Brown's avatar
Daniel Brown committed
425
426
427
428
429
        
    def parseCommands(self, commands):
        blockComment = False
        
        commands=self.remove_comments(commands)
430
431
        
        commands=self.processConstants(commands)
Daniel Brown's avatar
Daniel Brown committed
432
433
434
435
        
        after_process = [] # list of commands that should be processed after 
                           # objects have been set and created
        
436
        for line in commands:
Daniel Brown's avatar
Daniel Brown committed
437
438
439
440
441
            #for line in commands:
            if len(line.strip()) >= 2:
                line = line.strip()

                # Looking for block start or end
442
                values = line.split()
Daniel Brown's avatar
Daniel Brown committed
443
444
445
446
                if values[0] == "%%%":
                    if values[1] == "FTblock":
                        newTag = values[2]
                        
447
                        if self.__currentTag != None and self.__currentTag != NO_BLOCK: 
Daniel Brown's avatar
Daniel Brown committed
448
449
450
                            warnings.warn("found block {0} before block {1} ended".format(newTag, self.__currentTag))    
                            
                        if newTag in self.__blocks:
Daniel Brown's avatar
Daniel Brown committed
451
                            raise pkex.BasePyKatException("Block `{0}` has already been read".format(newTag))
Daniel Brown's avatar
Daniel Brown committed
452
453
                            
                        self.__blocks[newTag] = Block(newTag) # create new list to store all references to components in block
454
                        self.__currentTag = newTag
Daniel Brown's avatar
Daniel Brown committed
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
                        
                    if values[1] == "FTend":
                        self.__currentTag = NO_BLOCK
                        
                    continue
                #warnings.warn("current tag {0}".format(self.__currentTag))    

                # don't read comment lines
                if line[0] == "#" or line[0] == "%":
                    continue
                
                # check if block comment is being used
                if not blockComment and line[0:2] == "/*":
                    blockComment = True
                    continue
                elif blockComment and line[0:2] == "*/":
                    blockComment = False
                    continue
                
                first = line.split(" ",1)[0]
                obj = None
                if(first == "m" or first == "m1" or first == "m2"):
                    obj = pykat.components.mirror.parseFinesseText(line)
                elif(first == "s"):
                    obj = pykat.components.space.parseFinesseText(line)
                elif(first == "l"):
                    obj = pykat.components.laser.parseFinesseText(line)
                elif(first[0:2] == "bs"):
                    obj = pykat.components.beamSplitter.parseFinesseText(line)
                elif(first[0:2] == "gr"):
                    obj = pykat.components.grating.parseFinesseText(line)
                elif(first[0:4] == "isol"):
                    obj = pykat.components.isolator.parseFinesseText(line)
                elif(first[0:4] == "lens"):
                    obj = pykat.components.lens.parseFinesseText(line)
                elif(first[0:3] == "mod"):
                    obj = pykat.components.modulator.parseFinesseText(line)
Daniel Brown's avatar
Daniel Brown committed
492
493
                elif(first[0:2] == "ad"):
                    obj = pykat.detectors.ad.parseFinesseText(line)
494
                elif(first[0:2] == "pd" and first != "pdtype"):
495
                    obj = pykat.detectors.pd.parseFinesseText(line)
Daniel Brown's avatar
Daniel Brown committed
496
497
498
499
                elif(first == "qshot"):
                    obj = pykat.detectors.qshot.parseFinesseText(line)
                elif(first == "qnoised"):
                    obj = pykat.detectors.qnoised.parseFinesseText(line)
Daniel Brown's avatar
Daniel Brown committed
500
501
502
503
504
505
                elif(first == "xaxis" or first == "xaxis*"):
                    obj = pykat.commands.xaxis.parseFinesseText(line)
                elif(first == "x2axis" or first == "x2axis*"):
                    obj = pykat.commands.x2axis.parseFinesseText(line)
                elif(first == "gauss" or first == "gauss*" or first == "gauss**"):
                    after_process.append(line)
506
507
                elif(first == "scale"):
                    after_process.append(line)
508
509
                elif(first == "pdtype"):
                    after_process.append(line)
Daniel Brown's avatar
Daniel Brown committed
510
511
                elif(first == "attr"):
                    after_process.append(line)
Daniel Brown's avatar
Daniel Brown committed
512
513
                elif(first == "noxaxis"):
                    self.noxaxis = True
514
                elif(first == "phase"):
515
                    v = line.split()
516
                    if len(v) != 2:
517
                        raise pkex.BasePyKatException("phase command `{0}` is incorrect.".format(line))
518
519
                    else:
                        self.phase = int(v[1])
520
                elif(first == "maxtem"):
521
                    v = line.split()
522
523
524
                    if len(v) != 2:
                        raise pkex.BasePyKatException("maxtem command `{0}` is incorrect.".format(line))
                    else:
525
526
527
528
			if v[1] == "off":
				self.maxtem = -1
			else:
	                        self.maxtem = int(v[1])
529
                elif(first == "retrace"):
530
                    v = line.split()
Daniel Brown's avatar
Daniel Brown committed
531
                    if len(v) > 2:
532
                        raise pkex.BasePyKatException("Retrace command `{0}` is incorrect.".format(line))
Daniel Brown's avatar
Daniel Brown committed
533
                    elif len(v) == 2:
534
                        self.retrace = v[1]                        
535
                elif(first == "deriv_h"):
536
                    v = line.split()
537
                    if len(v) != 2:
538
                        raise pkex.BasePyKatException("deriv_h command `{0}` is incorrect.".format(line))
539
540
                    else:
                        self.deriv_h = float(v[1])
541
542
543
                elif(first == "gnuterm" or first == "pyterm"):
                    if self.verbose:
                        print "Ignoring Gnuplot/Python terminal command '{0}'".format(line)
Daniel Brown's avatar
Daniel Brown committed
544
545
546
547
548
549
550
551
                else:
                    if self.verbose:
                        print "Parsing `{0}` into pykat object not implemented yet, added as extra line.".format(line)
                    obj = line
                    # manually add the line to the block contents
                    self.__blocks[self.__currentTag].contents.append(line) 
                
                if obj != None and not isinstance(obj, str):
552
553
554
555
                    if self.hasNamedObject(obj.name):
                        getattr(self, obj.name).remove()
                        print "Removed existing object '{0}' of type {1} to add new object".format(obj.name, obj.__class__)
                        
Daniel Brown's avatar
Daniel Brown committed
556
557
                    self.add(obj)
                    
558
                    
Daniel Brown's avatar
Daniel Brown committed
559
560
561
        # now process all the varous gauss/attr etc. commands which require
        # components to exist first before they can be processed
        for line in after_process:
562
            first = line.split(" ",1)[0]            
Daniel Brown's avatar
Daniel Brown committed
563
            if first == "gauss" or first == "gauss*" or first == "gauss**":
564
                pykat.commands.gauss.parseFinesseText(line, self)
565
            elif (first == "scale"):
566
                v = line.split()
567
568
569
570
571
                if len(v) == 3:
                    component_name = v[2]
                    if component_name in self.__detectors :
                        self.__detectors[component_name].scale = SIfloat(v[1])
                    else:
572
                        raise pkex.BasePyKatException("scale command `{0}` refers to non-existing output".format(component_name))
573
574
575
                elif len(values) == 2:
                    self.scale = SIfloat(v[1])
                else:
576
                    raise pkex.BasePyKatException("scale command `{0}` is incorrect.".format(line))
577
            elif (first == "pdtype"):
578
                v = line.split()
579
580
581
                if len(v) == 3:
                    component_name = v[1]
                    if component_name in self.__detectors :
Andreas Freise's avatar
Andreas Freise committed
582
                        self.__detectors[component_name].pdtype = v[2]
583
                    else:
584
                        raise pkex.BasePyKatException("pdtype command `{0}` refers to non-existing detector".format(component_name))
585
                else:
586
                    raise pkex.BasePyKatException("pdtype command `{0}` is incorrect.".format(line))
Daniel Brown's avatar
Daniel Brown committed
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
            elif(first == "attr"):
                v = line.split()
                                
                if len(v) < 4:
                    raise pkex.BasePyKatException("attr command `{0}` is incorrect.".format(line))
                else:
                    # get the component/detector in question
                    if v[1] in self.__components:
                        comp = self.__components[v[1]]
                    elif v[1] in self.__detectors:
                        comp = self.__detectors[v[1]]
                    else:
                        raise pkex.BasePyKatException("Could not find the component '{0}' for attr command in line '{1}'".format(v[1], line))
                
                    if len(v[2:]) % 2 == 1:
                        raise pkex.BasePyKatException("Attr command '{0}' must specify both parameter and value pairs".format(line))
                                                
                    # convert split list to key value pairs
                    kv = dict(itertools.izip_longest(*[iter(v[2:])] * 2, fillvalue=None))

                    comp.parseAttributes(kv)
608
                    
Daniel Brown's avatar
Daniel Brown committed
609
        self.__currentTag = NO_BLOCK 
610
        
Daniel Brown's avatar
Daniel Brown committed
611
612
613
614
615
616
617
618
619
620
621
622
623
    def saveScript(self, filename=None):
        """
        Saves the current kat object to a Finesse input file
        """
        try:
            katScript = "".join(self.generateKatScript())       
            katfile = open(filename,'w')
            katfile.writelines(katScript)
            katfile.flush()
            katfile.close()

        except pkex.BasePyKatException as ex:
            print ex
Daniel Brown's avatar
Daniel Brown committed
624

Daniel Brown's avatar
Daniel Brown committed
625
            
Daniel Brown's avatar
Daniel Brown committed
626
627
628
629
630
631
632
633
    def getProcess(self, callback, **kwargs):
        """
        """
        
        cmd = "\n".join(self.generateKatScript())
        
        return Process(target=f__lkat_process, args=(callback, cmd, kwargs))
           
Daniel Brown's avatar
Daniel Brown committed
634
635
636
637
638
    def run(self, printout=0, printerr=0, save_output=False, save_kat=False,kat_name=None) :
        """ 
        Runs the current simulation setup that has been built thus far.
        It returns a katRun or katRun2D object which is populated with the various
        data from the simulation run.
639
640
        printoutput=1 prints the Finesse banner
        printerr shows the Finesse progress (set kat.verbose=1 to see warnings and errors)
Daniel Brown's avatar
Daniel Brown committed
641
642
643
644
645
646
647
648
649
650
651
652
        """
        start = datetime.datetime.now()
        
        try:        
            if not hasattr(self, "xaxis") and self.noxaxis != None and self.noxaxis == False:
                raise pkex.BasePyKatException("No xaxis was defined")
            
            if len(self.__katdir) == 0:
                # Get the environment variable for where Finesse is stored
                self.__finesse_dir = os.environ.get('FINESSE_DIR')
                
                if self.__finesse_dir == None :
653
                    raise pkex.MissingFinesseEnvVar()
Daniel Brown's avatar
Daniel Brown committed
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
            else:
                self.__finesse_dir = self.__katdir
                
            if len(self.__katname) == 0:
                katexe = "kat"
                
                if os.sys.platform == "win32":
                    katexe += ".exe"
            else:
                katexe = self.__katname
            
            kat_exec = os.path.join(self.__finesse_dir, katexe) 
            
            # check if kat file exists and it is executable by user        
            if not (os.path.isfile(kat_exec) and os.access(kat_exec, os.X_OK)):
669
                raise pkex.MissingFinesse()
Daniel Brown's avatar
Daniel Brown committed
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
                
            if self.verbose: print "--------------------------------------------------------------"
            if self.verbose: print "Running kat - Started at " + str(start)
            
            if hasattr(self, "x2axis"):
                r = katRun2D()
            else:
                r = katRun()
                
            r.katScript = "".join(self.generateKatScript())   
            
            # create a kat file which we will write the script into
            if self.__tempname == None:
                katfile = tempfile.NamedTemporaryFile(suffix=".kat", dir=self.__tempdir)
            else:
                filepath =os.path.join(self.__tempdir, self.__tempname+".kat" )
                katfile = open( filepath, 'w' ) 
                
            katfile.writelines(r.katScript)
            katfile.flush()
690
691
692
693
694

            if printout == 1:
                cmd=[kat_exec]
            else:
                cmd=[kat_exec, '--perl1']
Daniel Brown's avatar
Daniel Brown committed
695
696
697
698
699
700
701
702
703
704
            
            if self.__time_code:
                cmd.append('--perf-timing')

            cmd.append('--no-backspace')
            # set default format so that less repeated numbers are printed to the
            # output file, should speed up running and parsing of output files
            cmd.append('-format=%.15g')

            cmd.append(katfile.name)
705
                            
Daniel Brown's avatar
Daniel Brown committed
706
707
708
            p=subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            err = ""
            
Andreas Freise's avatar
Andreas Freise committed
709
            #if self.verbose: print "Finesse output:"
710

Daniel Brown's avatar
Daniel Brown committed
711
712
            for line in iter(p.stderr.readline, ""):
                
713
                if len(line) > 0:
Daniel Brown's avatar
Daniel Brown committed
714
715
                    
                    if line.rstrip().endswith('s'):
Daniel Brown's avatar
Daniel Brown committed
716
717
                        vals = line.split("-")
                        action = vals[0].strip()
Daniel Brown's avatar
Daniel Brown committed
718
719
                        prc = vals[1].strip()[:]
                        
720
                        if printerr == 1:
Daniel Brown's avatar
Daniel Brown committed
721
722
723
724
725
726
727
728
729
                            sys.stdout.write("\r{0} {1}".format(action, prc))
                    elif line.rstrip().endswith('%'):
                        vals = line.split("-")
                        action = vals[0].strip()
                        prc = vals[1].strip()[:]
                        
                        if printerr == 1:
                            sys.stdout.write("\r{0} {1}".format(action, prc))
                            
730
731
                    elif line[0:3] == '** ':
                        if self.verbose: sys.stdout.write(line)
732
733
                    else:
                        err += line
734

Daniel Brown's avatar
Daniel Brown committed
735
            
Daniel Brown's avatar
Daniel Brown committed
736
            [out,errpipe] = p.communicate()
737
738
            if printout == 1: 
                print out
Andreas Freise's avatar
Andreas Freise committed
739
740
            else:
                if printerr == 1: print ""
741

Daniel Brown's avatar
Daniel Brown committed
742
743
744
745
746
747
            # get the version number
            ix = out.find('build ') + 6
            ix2 = out.find(')',ix)
            r.katVersion = out[ix:ix2]
            
            r.runDateTime = datetime.datetime.now()
748
749

            # If Finesse returned an error, just print that and exit!
Daniel Brown's avatar
Daniel Brown committed
750
            if p.returncode != 0:
751
752
                print err
                sys.exit(1) 
Daniel Brown's avatar
Daniel Brown committed
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
            
            root = os.path.splitext(katfile.name)
            base = os.path.basename(root[0])            
            outfile = root[0] + ".out"
            
            if save_output:        
                newoutfile = "{0}.out".format(base)
                
                cwd = os.path.os.getcwd()
                newoutfile = os.path.join(cwd,newoutfile)
                
                if os.path.isfile(newoutfile):
                    os.remove(newoutfile)
                    
                os.rename(outfile, newoutfile)

                if self.verbose: print "\nOutput data saved to '{0}'".format(newoutfile)
            
            if hasattr(self, "x2axis"):
                [r.x,r.y,r.z,hdr] = self.readOutFile(outfile)
                
                r.xlabel = hdr[0]
                r.ylabel = hdr[1]
                r.zlabels = map(str.strip, hdr[2:])
            else:
                [r.x,r.y,hdr] = self.readOutFile(outfile)
            
                r.xlabel = hdr[0]
                r.ylabels = map(str.strip, hdr[1:])
                            
            if save_kat:
                if kat_name == None:
                    kat_name = "pykat_output"                
                
                cwd = os.path.os.getcwd()
                newkatfile = os.path.join(cwd, kat_name + ".kat")
                
                if os.path.isfile(newkatfile):
                    os.remove(newkatfile)
                  
                os.rename(katfile.name, newkatfile)         
                
                if self.verbose: print "Kat file saved to '{0}'".format(newkatfile)
                

            katfile.close()
            perfData = []
            
            if self.__time_code:
                perffile = open(root[0] + ".perf",'r')
                
                for l in perffile.readlines():
805
                    vals = l.strip().split()
Daniel Brown's avatar
Daniel Brown committed
806
807
808
809
810
                    perfData.append((vals[0], float(vals[1]), float(vals[2]), float(vals[3])))
                    
                return [r, perfData]
            else:
                return r
811
812
            
        except pkex.FinesseRunError as fe:
Daniel Brown's avatar
Daniel Brown committed
813
814
815
816
817
            print fe
        finally:
            if self.verbose: print ""
            if self.verbose: print "Finished in " + str(datetime.datetime.now()-start)
            
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
    def remove(self, obj):
        if not (obj.name in self.__components or obj.name in self.__detectors or obj.name in self.__commands):
            raise pkex.BasePyKatException("{0} is not currently in the simulation".format(obj.name))
        
        if obj.removed:
            raise pkex.BasePyKatException("{0} has already been removed".format(obj.name))        
        
        if isinstance(obj, Component):    
            del self.__components[obj.name]
            self.__del_component(obj)
            self.nodes.removeComponent(obj)
        elif isinstance(obj, Command):    
            del self.__commands[obj.name]
            self.__del_command(obj)
        elif isinstance(obj, Detector):    
            del self.__detectors[obj.name]
            self.__del_detector(obj)
        
        for b in self.__blocks:
            if obj in self.__blocks[b].contents:
                self.__blocks[b].contents.remove(obj)
        
        import gc
        print gc.get_referrers(obj)
842
843
844
    
    def hasNamedObject(self, name):
        return name in self.__components or name in self.__detectors or name in self.__commands
Daniel Brown's avatar
Daniel Brown committed
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
        
    def add(self, obj):
        try:
            obj.tag = self.__currentTag
            self.__blocks[self.__currentTag].contents.append(obj)
            
            if isinstance(obj, Component):
                
                if obj.name in self.__components :
                    raise pkex.BasePyKatException("A component with name '{0}' has already been added".format([obj.name]))            
                            
                self.__components[obj.name] = obj
                self.__add_component(obj)
                
            elif isinstance(obj, Detector):
                
                if obj.name in self.__detectors :
                        raise pkex.BasePyKatException("A detector '{0}' has already been added".format(obj.name))
                        
                self.__detectors[obj.name] = obj
                self.__add_detector(obj)
                
            elif isinstance(obj, Command):
                
                self.__commands[obj.__class__.__name__] = obj
                self.__add_command(obj)
                
872
            else:
Daniel Brown's avatar
Daniel Brown committed
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
                raise pkex.BasePyKatException("Object {0} could not be added".format(obj))
                
            obj._on_kat_add(self)
            
        except pkex.BasePyKatException as ex:
            print ex

    def readOutFile(self, filename):
        
        with open(filename,'r') as outfile:
            # read first to lines to get to header line
            outfile.readline()
            outfile.readline()
            
            hdr = outfile.readline().replace('%','').replace('\n','').split(',')
        
        data = np.loadtxt(filename,comments='%',skiprows=4)
        
        if hasattr(self, "x2axis"):
            # need to parse 2D outputs slightly different as they are effectively 2D matrices
            # written in linear form
            x = data[0::(1+self.x2axis.steps),0]
            y = data[0:(1+self.x2axis.steps),1]
            # get rows and columns lined up so that we can reshape a single column of all x/y data
            # into a matrix
            z = data[:,2:].transpose().reshape(data.shape[1]-2, 1+self.xaxis.steps, 1+self.x2axis.steps)
            # once you do this the data for y and x axes need swapping
            z = z.swapaxes(1,2)
            return [x, y, z, hdr]
        else:
            shape_len = len(data.shape)
            
            if shape_len > 1:
                rows,cols = data.shape
                x = data[:,0]
                y = data[:,1:cols].squeeze()
            else:
                rows = 1
                cols = data.shape[0]
                
                x = data[0]
                y = data[1:cols].squeeze()
            
            return [x, y, hdr]
917
918
919
920
921
922
923
924
925

    def removeLine(self, fragment) :
        for key in self.__blocks:
            objs = self.__blocks[key].contents
            for obj in objs:
                if isinstance(obj, str):
                    if fragment in obj:
                        print "  ** removing line '{0}'".format(obj)
                        objs.remove(obj)
926
927

                    
Daniel Brown's avatar
Daniel Brown committed
928
929
    def generateKatScript(self) :
        """ Generates the kat file which can then be run """
930

931
        def writeBlock():
Daniel Brown's avatar
Daniel Brown committed
932
933
934
935
936
937
938
939
940
941
942
943
944
            for obj in objs:
                if isinstance(obj, str):
                    out.append(obj + '\n')
                    
                elif isinstance(obj, Component) or isinstance(obj, Detector) or isinstance(obj, Command):
                    txt = obj.getFinesseText() 
                    
                    if txt != None:
                        if isinstance(txt,list):
                            for t in txt:
                                out.append(t + "\n")
                        else:
                            out.append(txt + "\n")
945

946
947
948
949
950
951
952
953
954
955
        
        out = []    
        import datetime
        strtoday = datetime.datetime.now()
        out.append(strtoday.strftime("%% Generated by PyKat %d.%m.%Y %H:%M:%S\n") )

        # write the FTblocks
        for key in self.__blocks:
            objs = self.__blocks[key].contents

956
            if key != NO_BLOCK:
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
                if np.size(objs)>0:
                    out.append("\n")
                    out.append("%%% FTblock " + key + "\n")
                    writeBlock()
                    out.append("%%% FTend " + key + "\n")

        # write the NO_BLOCK blocks
        for key in self.__blocks:
            objs = self.__blocks[key].contents


            if key == NO_BLOCK:
                if np.size(objs)>0:
                    out.append("\n")
                    writeBlock()
                
Daniel Brown's avatar
Daniel Brown committed
973
974
975
976
977
978
979
980
981
982
        # now loop through all the nodes and get any gauss commands
        for key in self.nodes.getNodes():
            txt = self.nodes.getNodes()[key].getFinesseText()
            
            if txt != None:
                if isinstance(txt,list):
                    for t in txt: out.append(t+ "\n")
                else:
                    out.append(txt + "\n")
        
983

984
985
986
987
988
989
990
991
992
        # now get any signal commands
        txt = self.signals.getFinesseText()
        
        if txt != None:
            if isinstance(txt,list):
                for t in txt: out.append(t+ "\n")
            else:
                out.append(txt + "\n")

993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
        if self.vacuum != None:
            
            if isinstance(self.vacuum, collections.Container):
                objs = []
                
                if len(self.vacuum) > 0:
                    for a in self.vacuum:
                        if hasattr(a, 'name'):
                            objs.append(a.name)
                        else:
                            objs.append(str(a))

                    out.append("vacuum {0}\n".format(" ".join(objs)))
                                        
            elif isinstance(self.vacuum, str):
                out.append("vacuum {0}\n".format(self.vacuum))
            else:
                pkex.BasePyKatException("Couldn't understand vacuum input list")

1012
        if self.scale != None and self.scale !='': out.append("scale {0}\n".format(self.scale))
Daniel Brown's avatar
Daniel Brown committed
1013
        if self.phase != None: out.append("phase {0}\n".format(self.phase))
1014
1015
1016
1017
1018
        if self.maxtem != None:
                if self.maxtem == -1:
                        out.append("maxtem off\n")
                else:
                        out.append("maxtem {0}\n".format(self.maxtem))
Daniel Brown's avatar
Daniel Brown committed
1019

1020
1021
1022
        if self.noxaxis == True:
            out.append("noxaxis\n")

Daniel Brown's avatar
Daniel Brown committed
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
        # ensure we don't do any plotting. That should be handled
        # by user themselves
        out.append("gnuterm no\n")
        out.append("pyterm no\n")
        
        return out
        
    def openGUI(self):
        if NO_GUI:
            print  "No PyQt4 module was installed so cannot open a GUI"
        else:
            self.app = QCoreApplication.instance() 
            created = False
            
            if self.app == None:
                created = True
                self.app = QApplication([""])
                
            if self.pykatgui == None:
                self.pykatgui = pyKatGUI(self)
                self.pykatgui.main()
            else:
                self.pykatgui.show()
                
            if created: self.app.exec_()
    
    def getComponents(self):
        return self.__components.values()
    
    def hasComponent(self, name):
        return (name in self.__components)
    
    def _newName(self, container, prefix):
        n = 1
        name = "{0}{1}".format(prefix, n)
        
        while name in container:
            n += 1
            name = "{0}{1}".format(prefix,n)
        
        return name
    
    def getNewComponentName(self,prefix):
        '''
        Returns a name for a component which hasn't already been added.
        Returns [prefix] + number, where number is greater than 1. e.g.
        if m1 exists getNewName('m') will return 'm2'
        '''
        return self._newName(self.__components, prefix)
    
    def getNewDetectorName(self,prefix):
        '''
        Returns a name for a component which hasn't already been added.
        Returns [prefix] + number, where number is greater than 1. e.g.
        if m1 exists getNewName('m') will return 'm2'
        '''
        return self._newName(self.__detectors, prefix)
        
    def getNewNodeNames(self,prefix,N=1):
        '''
        Returns a list of names for N number of nodes which haven't already been added.
        Returns [prefix] + number, where number is greater than 1. e.g.
        if m1 exists getNewName('m') will return 'm2'
        '''
        rtn = []
        n = 1
        
        for M in range(1,N+1):
            name = "{0}{1}".format(prefix, n)
            
            while name in self.nodes.getNodes() or (name in rtn):
                n += 1
                name = "{0}{1}".format(prefix,n)
        
            rtn.append(name)
            
        return rtn
        
    
    def __add_detector(self, det):

        if not isinstance(det, Detector):
            raise exceptions.ValueError("Argument is not of type Detector")
        
        name = det.name
        fget = lambda self: self.__get_detector(name)
        
        setattr(self.__class__, name, property(fget))
1111
1112
1113
        setattr(self, '__det_' + name, det)                

    def __del_detector(self, det):
Daniel Brown's avatar
Daniel Brown committed
1114

1115
1116
1117
1118
1119
1120
1121
1122
        if not isinstance(det, Detector):
            raise exceptions.ValueError("Argument is not of type Detector")
        
        name = det.name
        
        delattr(self.__class__, name)
        delattr(self, '__det_' + name) 
        
Daniel Brown's avatar
Daniel Brown committed
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
    def __get_detector(self, name):
        return getattr(self, '__det_' + name) 
        
    def __add_command(self, com):

        if not isinstance(com, Command):
            raise exceptions.ValueError("Argument is not of type Command")
        
        name = com.__class__.__name__
        fget = lambda self: self.__get_command(name)
        
        setattr(self.__class__, name, property(fget))
        setattr(self, '__com_' + name, com)                   

1137
1138
1139
1140
1141
1142
1143
1144
1145
    def __del_command(self, com):

        if not isinstance(com, Command):
            raise exceptions.ValueError("Argument is not of type Command")
        
        name = com.__class__.__name__
        delattr(self.__class__, name)
        delattr(self, '__com_' + name)
        
Daniel Brown's avatar
Daniel Brown committed
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
    def __get_command(self, name):
        return getattr(self, '__com_' + name)            
    
    def __add_component(self, comp):

        if not isinstance(comp, Component):
            raise exceptions.ValueError("Argument is not of type Component")
            
        fget = lambda self: self.__get_component(comp.name)
        
        setattr(self.__class__, comp.name, property(fget))
        setattr(self, '__comp_' + comp.name, comp)                   
1158
1159
        
    def __del_component(self, comp):
Daniel Brown's avatar
Daniel Brown committed
1160

1161
1162
1163
1164
1165
1166
        if not isinstance(comp, Component):
            raise exceptions.ValueError("Argument is not of type Component")
        
        delattr(self.__class__, comp.name)
        delattr(self, '__comp_' + comp.name)
        
Daniel Brown's avatar
Daniel Brown committed
1167
1168
1169
1170
    def __get_component(self, name):
        return getattr(self, '__comp_' + name)        

    def remove_comments(self, string):
1171
1172
1173
        """
        This takes a raw Finesse code string and removes any comments
        It returns a list of lines however, not a multiline string.
1174
        Also removes any extrawhite space in command lines.
1175
        """
Daniel Brown's avatar
Daniel Brown committed
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
        pattern = r"(\".*?\"|\'.*?\'|%{3}[^\r\n]*$)|(/\*.*?\*/|%[^\r\n]*$|#[^\r\n]*$|//[^\r\n]*$)"
        # first group captures quoted strings (double or single)
        # second group captures comments (//single-line or /* multi-line */)
        regex = re.compile(pattern, re.MULTILINE|re.DOTALL)
        def _replacer(match):
            # if the 2nd group (capturing comments) is not None,
            # it means we have captured a non-quoted (real) comment string.
            if match.group(2) is not None:
                return "" # so we will return empty to remove the comment
            else: # otherwise, we will return the 1st group
                return match.group(1) # captured quoted-string
1187
1188
1189
1190
1191
1192
1193
        
        # remove any inline comments
        string = regex.sub(_replacer, string)
        
        commands = []
        
        for line in string.split('\n'):
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
            line = line.replace('\r','')
            if len(line) > 0:
                # remove any mutliple whitespace
                line = " ".join(line.split())
                # add to a list all the positions of any inline comment markers
                i = [line.find('#'), line.find('\\')]
                i = filter(lambda a: a != -1, i)
        
                if len(i) == 0:
                    commands.append(line)
                else:
                    line = line[0:min(i)]
                    if len(line):
                        commands.append(line)
        
        
1210
        return commands
1211
1212
1213

# printing pykat logo on first input
kat.logo()