Trisurf Monte Carlo simulator
Samo Penic
2016-03-16 05548956c5a43a83542a88f6437a247f5a95146d
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
15
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
251         if(tape!=''):
252             self.initFromTape(tape)
253         if(snapshot!=''):
254             self.initFromSnapshot(snapshot)
255         return
256
257
a99f2b 258     def initFromTape(self, tape):
bc14fb 259         self.tape=Tape()
SP 260         self.tape.readTape(tape)
261
bd6993 262     def initFromSnapshot(self, snapshotfile):
SP 263         try:
264             tree = ET.parse(snapshotfile)
265         except:
266             print("Error reading snapshot file")
267             exit(1)
268
269         root = tree.getroot()
270         tapetxt=root.find('tape')
271         version=root.find('trisurfversion')
272         self.tape=Tape()
273         self.tape.setTape(tapetxt.text)
bc14fb 274
9154b3 275     def getPID(self):
34600b 276         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
SP 277         self.Dir.makeifnotexist()
278         try:
279             fp = open(os.path.join(self.Dir.fullpath(),'.lock'))
280         except IOError as e:
281             return 0 #file probably does not exist. e==2??
9154b3 282         pid=fp.readline()
SP 283         fp.close()
055489 284         return int(pid)
9154b3 285
SP 286     def getStatus(self):
287         pid=self.getPID()
288         if(pid==0):
055489 289             return TS_NOLOCK
34600b 290         if(psutil.pid_exists(int(pid))):
055489 291             proc= psutil.Process(int(pid))
SP 292             if proc.name=="trisurf":
293                 if proc.status=="stopped":
294                     return TS_STOPPED
295                 else:
296                     return TS_RUNNING
9154b3 297             else:
055489 298                 return TS_NONEXISTANT
34600b 299         else:
055489 300             return TS_NONEXISTANT
bc14fb 301
SP 302     def start(self):
c14bd6 303         if(self.getStatus()==0):
SP 304             self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
305             self.Dir.makeifnotexist()
34600b 306 #            self.Dir.goto()
c14bd6 307             print("Starting trisurf-ng executable at "+self.Dir.fullpath()+"\n")
SP 308         else:
309             print("Process already running. Not starting\n")
310         return
bc14fb 311
SP 312     def stop(self):
313         pass
314
c14bd6 315     def setMaindir(self,prefix,variables):
055489 316         maindir=""
c14bd6 317         for p,v in zip(prefix,variables):
SP 318             if(v=="xk0"):
319                 tv=str(round(float(self.tape.config[v])))
320             else:
321                 tv=self.tape.config[v]
322             maindir=maindir+p+tv
323         self.maindir=maindir
324         return
325
326     def setSubdir(self, subdir="run0"):
327         self.subdir=subdir
328         return
329
330     def getStatistics(self, statfile="statistics.csv"):
7ca5ae 331         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
34600b 332         self.statistics=Statistics(self.Dir.fullpath(), statfile)
9154b3 333         self.Comment=FileContent(os.path.join(self.Dir.fullpath(),".comment"))
SP 334         pid=self.getPID();
055489 335         status=self.getStatus()
SP 336         if(status==TS_NONEXISTANT or status==TS_NOLOCK):
337             statustxt="Not running"
9154b3 338             pid=""
055489 339         elif status==TS_STOPPED:
SP 340             statustxt="Stopped"
341         else:
342             statustxt="Running"
9154b3 343
SP 344         if(self.statistics.fileOK):
345             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()]
346         else:
347             report=["N/A","N/A\t",statustxt, pid, str(self.Dir.fullpath()), self.Comment.getText()]
f81585 348         return report
c14bd6 349
055489 350     def writeComment(self, data, mode='w'):
9154b3 351         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
SP 352         self.Comment=FileContent(os.path.join(self.Dir.fullpath(),".comment"))
055489 353         self.Comment.writefile(data,mode=mode)
9154b3 354
bc14fb 355     def __str__(self):
c14bd6 356         if(self.getStatus()==0):
SP 357             str=" not running."
358         else:
359             str=" running."
360         return(self.Dir.fullpath()+str)
361
bc14fb 362