Trisurf Monte Carlo simulator
Samo Penic
2016-04-07 47d80d88e986fc039866c3a627ffe1140538d4b2
commit | author | age
bc14fb 1 #!/usr/bin/python3
SP 2
3 import configobj
bd6993 4 import xml.etree.ElementTree as ET
SP 5 import base64
6 import zlib
7 import io
c14bd6 8 import os
SP 9 from itertools import islice
10 import mmap
7c45b1 11 import shlex
34600b 12 import psutil
f81585 13 import time
SP 14 import datetime
47d80d 15 from subprocess import call, Popen
bc14fb 16
055489 17 # Process status
SP 18 TS_NOLOCK=0 # lock file does not exist
19 TS_NONEXISTANT=0 # process is not in the list of processes
20 TS_STOPPED=1 # the process is listed, but is in stopped state
21 TS_RUNNING=2 # process is running
bc14fb 22
9154b3 23 class FileContent:
055489 24     '''
SP 25     Class is helpful for reading and writting the specific files.
26     '''
9154b3 27     def __init__(self,filename):
055489 28         ''' The instance is done by calling constructor FileContent(filename)
SP 29
30         The object then reads filename if it exists, otherwise the data is empty string.
31         User may force to reread file by calling the readline() method of the class.
32
33         Filename is stored in local variable for future file operations.
34         '''
35
9154b3 36         self.filename=filename
SP 37         self.readfile()
bc14fb 38
9154b3 39     def readfile(self):
055489 40         '''Force reread of the file and setting the data'''
SP 41         self.data=""
9154b3 42         try:
SP 43             with open (self.filename, "r") as myfile:
055489 44                 self.data=myfile.read().replace('\n', '') #read the file and remove newline from the text
9154b3 45         except:
055489 46             pass # does nothing if error occurs
9154b3 47
SP 48
49     def writefile(self, data, mode='w'):
055489 50         '''File may be updated by completely rewritting the file contents or appending the data to the end of the file.
SP 51         this is achieved by calling writefile(data, mode) method, where data is the string data to be written and
52         mode can be 'a' for append and 'w' for writting the file anew.
53         '''
9154b3 54         with open (self.filename, mode) as myfile:
SP 55             myfile.write(data)
56
57     def getText(self):
055489 58         '''
SP 59         Method getText() or calling the object itself returns string of data
60         '''
9154b3 61         return self.data
SP 62
63     def __str__(self):
055489 64         '''
SP 65         Method getText() or calling the object itself returns string of data
66         '''
9154b3 67         return self.getText()
bc14fb 68
SP 69 class Tape:
055489 70     '''
SP 71     Special class that manages configuration of trisurf (commonly named tape). It can read and parse configuration from disk or parse it from string.
72     '''
bc14fb 73
SP 74     def __init__(self):
055489 75         '''The object is instatiated by calling Tape() constructor without parameters'''
bc14fb 76         return
SP 77
78     def readTape(self, tape='tape'):
055489 79         '''
SP 80         Tape is read and parsed by calling the readTape() method with optional tape parameter, which is full path to filename where the configuration is stored.
81         If the tape cannot be read, it prints error and exits.
82         '''
bc14fb 83         try:
SP 84             self.config=configobj.ConfigObj(tape)
85         except:
86             print("Error reading or parsing tape file!\n")
87             exit(1)
88
89     def setTape(self, string):
055489 90         '''Method setTape(string) parses the string in memory that hold the tape contents.'''
bd6993 91         self.config=configobj.ConfigObj(io.StringIO(string))
bc14fb 92         return
SP 93
94     def getValue(self,key):
055489 95         '''Method getValue(key) returns value of a single parsed setting named "key".'''
SP 96
bc14fb 97         return self.config[key]
SP 98
c14bd6 99     def __str__(self):
055489 100         '''Calling the object itself, it recreates the tape contents from parsed values in form of key=value.'''
c14bd6 101         retval=""
SP 102         for key,val in self.config.iteritems():
103             retval=retval+str(key)+" = "+str(val)+"\n"
104         return retval
bc14fb 105
c14bd6 106
SP 107
108 class Directory:
055489 109     '''
SP 110     Class deals with the paths where the simulation is run and data is stored.
111     '''
c14bd6 112     def __init__(self, maindir=".", simdir=""):
055489 113         '''Initialization Directory() takes two optional parameters, namely maindir and simdir. Defaults to current directory. It sets local variables maindir and simdir accordingly.'''
c14bd6 114         self.maindir=maindir
SP 115         self.simdir=simdir
116         return
117
118     def fullpath(self):
055489 119         '''
SP 120         Method returns string of path where the data is stored. It combines values of maindir and simdir as maindir/simdir on Unix.
121         '''
c14bd6 122         return os.path.join(self.maindir,self.simdir)
SP 123
124     def exists(self):
055489 125         ''' Method checks whether the directory  specified by fullpath() exists. It return True/False on completion.'''
c14bd6 126         path=self.fullpath()
SP 127         if(os.path.exists(path)):
055489 128             return True
c14bd6 129         else:
055489 130             return False
c14bd6 131
SP 132     def make(self):
055489 133         ''' Method make() creates directory. If it fails it exits the program with error message.'''
c14bd6 134         try:
SP 135             os.makedirs(self.fullpath())
136         except:
137             print("Cannot make directory "+self.fullpath()+"\n")
138             exit(1)
139         return
140
141     def makeifnotexist(self):
055489 142         '''Method makeifnotexist() creates directory if it does not exist.'''
c14bd6 143         if(self.exists()==0):
SP 144             self.make()
145         return
146
147     def remove(self):
055489 148         '''Method remove() removes directory recursively. WARNING! No questions asked.'''
c14bd6 149         if(self.exists()):
SP 150             try:
151                 os.rmdir(self.fullpath())
152             except:
153                 print("Cannot remove directory "+self.fullpath()+ "\n")
154                 exit(1)
155         return
156
157     def goto(self):
055489 158         '''
SP 159         Method goto() moves current directory to the one specified by fullpath(). WARNING: when using the relative paths, do not call this function multiple times.
160         '''
c14bd6 161         try:
SP 162             os.chdir(self.fullpath())
163         except:
164             print("Cannot go to directory "+self.fullpath()+"\n")
165         return
166
167
168 class Statistics:
055489 169     '''
SP 170     Class that deals with the statistics file from the simulations.
171     File is generally large and not all data is needed, so it is dealt with in a specific way.
172     '''
173
c14bd6 174     def __init__(self,path,filename="statistics.csv"):
055489 175         '''
SP 176         At the initialization call it receives optional filename parameter specifying the path and filename of the statistics file.
177
178         The local variables path, filename, fullname (joined path and filename) and private check if the file exists are stored.
179         '''
c14bd6 180         self.path=path
SP 181         self.filename=filename
182         self.fullname=os.path.join(path,filename)
f81585 183         self.fileOK=self.read()
c14bd6 184         return
SP 185
186     def exists(self):
055489 187         '''Method check if the statistics file exists.'''
c14bd6 188         if(os.path.isfile(self.fullname)):
SP 189             return True
190         else:
191             return False
192
193     def mapcount(self):
055489 194         '''
SP 195         Internal method for determining the number of the lines in the most efficient way. Is it really the most efficient?
196         '''
c14bd6 197         f = open(self.fullname, "r+")
SP 198         buf = mmap.mmap(f.fileno(), 0)
199         lines = 0
200         readline = buf.readline
201         while readline():
202             lines += 1
203         return lines
204
205     def read(self):
055489 206         '''
SP 207         Method read() reads the statistics if it exists. It sets local variable dT storing the time differential between two intervals of simulation (outer loops). It also stores last simulation loop and the start of the run.
208         '''
c14bd6 209         if(self.exists()):
SP 210             nlines=self.mapcount()
211             try:
212                 with open(self.fullname, "r+") as fin:
213                     i=0;
214                     for line in fin:
215                         if(i==1):
7c45b1 216                             #print (line)
SP 217                             fields=shlex.split(line)
218                             epoch1=fields[0]
219                             n1=fields[1]
c14bd6 220                         if(i==nlines-1):
7c45b1 221                             fields=shlex.split(line)
SP 222                             epoch2=fields[0]
223                             n2=fields[1]
c14bd6 224                         i=i+1
SP 225             except:
f81585 226                 #print("Cannot read statistics file in "+self.fullname+"\n")
7c45b1 227                 return(False)
c14bd6 228         else:
f81585 229             #print("File "+self.fullname+" does not exists.\n")
7c45b1 230             return(False)
SP 231
232         self.dT=(int(epoch2)-int(epoch1))/(int(n2)-int(n1))
f81585 233         self.last=n2
SP 234         self.startDate=epoch1
7c45b1 235         return(True)
f81585 236
SP 237     def __str__(self):
055489 238         '''
SP 239         Prints the full path with filename of the statistics.csv file
240         '''
f81585 241         return(str(self.fullname))
SP 242
7c45b1 243
bc14fb 244
SP 245 class Runner:
246     '''
055489 247     Class Runner consists of a single running or terminated instance of the trisurf. It manages starting, stopping, verifying the running process and printing the reports of the configured instances.
bc14fb 248     '''
c14bd6 249     def __init__(self, subdir='run0', tape='', snapshot=''):
SP 250         self.subdir=subdir
47d80d 251         self.fromSnapshot=False
c14bd6 252         if(tape!=''):
SP 253             self.initFromTape(tape)
254         if(snapshot!=''):
255             self.initFromSnapshot(snapshot)
256         return
257
258
a99f2b 259     def initFromTape(self, tape):
bc14fb 260         self.tape=Tape()
SP 261         self.tape.readTape(tape)
262
bd6993 263     def initFromSnapshot(self, snapshotfile):
SP 264         try:
265             tree = ET.parse(snapshotfile)
266         except:
267             print("Error reading snapshot file")
268             exit(1)
47d80d 269         self.fromSnapshot=True
SP 270         self.snapshotFile=snapshotfile
bd6993 271         root = tree.getroot()
SP 272         tapetxt=root.find('tape')
273         version=root.find('trisurfversion')
274         self.tape=Tape()
275         self.tape.setTape(tapetxt.text)
bc14fb 276
9154b3 277     def getPID(self):
34600b 278         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
SP 279         self.Dir.makeifnotexist()
280         try:
281             fp = open(os.path.join(self.Dir.fullpath(),'.lock'))
282         except IOError as e:
283             return 0 #file probably does not exist. e==2??
9154b3 284         pid=fp.readline()
SP 285         fp.close()
055489 286         return int(pid)
9154b3 287
SP 288     def getStatus(self):
289         pid=self.getPID()
290         if(pid==0):
055489 291             return TS_NOLOCK
34600b 292         if(psutil.pid_exists(int(pid))):
055489 293             proc= psutil.Process(int(pid))
SP 294             if proc.name=="trisurf":
295                 if proc.status=="stopped":
296                     return TS_STOPPED
297                 else:
298                     return TS_RUNNING
9154b3 299             else:
055489 300                 return TS_NONEXISTANT
34600b 301         else:
055489 302             return TS_NONEXISTANT
bc14fb 303
SP 304     def start(self):
c14bd6 305         if(self.getStatus()==0):
SP 306             self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
307             self.Dir.makeifnotexist()
47d80d 308             cwd=Directory(maindir=os.getcwd())
SP 309             self.Dir.goto()
c14bd6 310             print("Starting trisurf-ng executable at "+self.Dir.fullpath()+"\n")
47d80d 311             if(self.fromSnapshot==True):
SP 312                 params=["trisurf", "--restore-from-vtk",self.snapshotFile]
313             else:
314                 params="trisurf"
315             Popen (params, stdout=False)
316             cwd.goto()
c14bd6 317         else:
SP 318             print("Process already running. Not starting\n")
319         return
bc14fb 320
SP 321     def stop(self):
322         pass
323
c14bd6 324     def setMaindir(self,prefix,variables):
055489 325         maindir=""
c14bd6 326         for p,v in zip(prefix,variables):
SP 327             if(v=="xk0"):
328                 tv=str(round(float(self.tape.config[v])))
329             else:
330                 tv=self.tape.config[v]
331             maindir=maindir+p+tv
332         self.maindir=maindir
333         return
334
335     def setSubdir(self, subdir="run0"):
336         self.subdir=subdir
337         return
338
339     def getStatistics(self, statfile="statistics.csv"):
7ca5ae 340         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
34600b 341         self.statistics=Statistics(self.Dir.fullpath(), statfile)
9154b3 342         self.Comment=FileContent(os.path.join(self.Dir.fullpath(),".comment"))
SP 343         pid=self.getPID();
055489 344         status=self.getStatus()
SP 345         if(status==TS_NONEXISTANT or status==TS_NOLOCK):
346             statustxt="Not running"
9154b3 347             pid=""
055489 348         elif status==TS_STOPPED:
SP 349             statustxt="Stopped"
350         else:
351             statustxt="Running"
9154b3 352
SP 353         if(self.statistics.fileOK):
354             report=[time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(self.statistics.startDate))),str(datetime.timedelta(microseconds=(int(self.tape.config['iterations'])-int(self.statistics.last))*self.statistics.dT)*1000), statustxt, pid, str(self.Dir.fullpath()), self.Comment.getText()]
355         else:
356             report=["N/A","N/A\t",statustxt, pid, str(self.Dir.fullpath()), self.Comment.getText()]
f81585 357         return report
c14bd6 358
055489 359     def writeComment(self, data, mode='w'):
9154b3 360         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
SP 361         self.Comment=FileContent(os.path.join(self.Dir.fullpath(),".comment"))
055489 362         self.Comment.writefile(data,mode=mode)
9154b3 363
bc14fb 364     def __str__(self):
c14bd6 365         if(self.getStatus()==0):
SP 366             str=" not running."
367         else:
368             str=" running."
369         return(self.Dir.fullpath()+str)
370
bc14fb 371