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