Trisurf Monte Carlo simulator
Samo Penic
2016-05-25 aafd67f2d592799167864698ea07eb8aadcc86a7
commit | author | age
bc14fb 1 import configobj
bd6993 2 import xml.etree.ElementTree as ET
SP 3 import base64
4 import zlib
5 import io
c14bd6 6 import os
SP 7 from itertools import islice
8 import mmap
7c45b1 9 import shlex
34600b 10 import psutil
f81585 11 import time
SP 12 import datetime
fca8d6 13 import subprocess
38cb4a 14 import shutil
bc14fb 15
055489 16 # Process status
SP 17 TS_NOLOCK=0 # lock file does not exist
18 TS_NONEXISTANT=0 # process is not in the list of processes
19 TS_STOPPED=1 # the process is listed, but is in stopped state
20 TS_RUNNING=2 # process is running
3d0247 21 TS_COMPLETED=3 #simulation is completed
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)
cc95bd 85             with open (tape, "r") as myfile:
SP 86                 self.rawText=myfile.read() #read the file
87
bc14fb 88         except:
SP 89             print("Error reading or parsing tape file!\n")
90             exit(1)
91
92     def setTape(self, string):
055489 93         '''Method setTape(string) parses the string in memory that hold the tape contents.'''
bd6993 94         self.config=configobj.ConfigObj(io.StringIO(string))
cc95bd 95         self.rawText=string
bc14fb 96         return
SP 97
98     def getValue(self,key):
055489 99         '''Method getValue(key) returns value of a single parsed setting named "key".'''
SP 100
bc14fb 101         return self.config[key]
SP 102
c14bd6 103     def __str__(self):
055489 104         '''Calling the object itself, it recreates the tape contents from parsed values in form of key=value.'''
c14bd6 105         retval=""
SP 106         for key,val in self.config.iteritems():
107             retval=retval+str(key)+" = "+str(val)+"\n"
108         return retval
bc14fb 109
c14bd6 110
SP 111
112 class Directory:
055489 113     '''
SP 114     Class deals with the paths where the simulation is run and data is stored.
115     '''
c14bd6 116     def __init__(self, maindir=".", simdir=""):
055489 117         '''Initialization Directory() takes two optional parameters, namely maindir and simdir. Defaults to current directory. It sets local variables maindir and simdir accordingly.'''
c14bd6 118         self.maindir=maindir
SP 119         self.simdir=simdir
120         return
121
122     def fullpath(self):
055489 123         '''
SP 124         Method returns string of path where the data is stored. It combines values of maindir and simdir as maindir/simdir on Unix.
125         '''
c14bd6 126         return os.path.join(self.maindir,self.simdir)
SP 127
128     def exists(self):
055489 129         ''' Method checks whether the directory  specified by fullpath() exists. It return True/False on completion.'''
c14bd6 130         path=self.fullpath()
SP 131         if(os.path.exists(path)):
055489 132             return True
c14bd6 133         else:
055489 134             return False
c14bd6 135
SP 136     def make(self):
055489 137         ''' Method make() creates directory. If it fails it exits the program with error message.'''
c14bd6 138         try:
SP 139             os.makedirs(self.fullpath())
140         except:
141             print("Cannot make directory "+self.fullpath()+"\n")
142             exit(1)
143         return
144
145     def makeifnotexist(self):
055489 146         '''Method makeifnotexist() creates directory if it does not exist.'''
c14bd6 147         if(self.exists()==0):
SP 148             self.make()
bd826d 149             return True
SP 150         else:
151             return False
c14bd6 152
SP 153     def remove(self):
055489 154         '''Method remove() removes directory recursively. WARNING! No questions asked.'''
c14bd6 155         if(self.exists()):
SP 156             try:
157                 os.rmdir(self.fullpath())
158             except:
159                 print("Cannot remove directory "+self.fullpath()+ "\n")
160                 exit(1)
161         return
162
163     def goto(self):
055489 164         '''
SP 165         Method goto() moves current directory to the one specified by fullpath(). WARNING: when using the relative paths, do not call this function multiple times.
166         '''
c14bd6 167         try:
SP 168             os.chdir(self.fullpath())
169         except:
170             print("Cannot go to directory "+self.fullpath()+"\n")
171         return
172
173
174 class Statistics:
055489 175     '''
SP 176     Class that deals with the statistics file from the simulations.
177     File is generally large and not all data is needed, so it is dealt with in a specific way.
178     '''
179
c14bd6 180     def __init__(self,path,filename="statistics.csv"):
055489 181         '''
SP 182         At the initialization call it receives optional filename parameter specifying the path and filename of the statistics file.
183
184         The local variables path, filename, fullname (joined path and filename) and private check if the file exists are stored.
185         '''
c14bd6 186         self.path=path
SP 187         self.filename=filename
188         self.fullname=os.path.join(path,filename)
f81585 189         self.fileOK=self.read()
c14bd6 190         return
SP 191
192     def exists(self):
055489 193         '''Method check if the statistics file exists.'''
c14bd6 194         if(os.path.isfile(self.fullname)):
SP 195             return True
196         else:
197             return False
198
199     def mapcount(self):
055489 200         '''
SP 201         Internal method for determining the number of the lines in the most efficient way. Is it really the most efficient?
202         '''
c14bd6 203         f = open(self.fullname, "r+")
d665c0 204         try:
SP 205             buf = mmap.mmap(f.fileno(), 0)
206             lines = 0
207             readline = buf.readline
208             while readline():
209                 lines += 1
210             f.close()
211         except:
212             lines=0
213             f.close()
c14bd6 214         return lines
d665c0 215         
c14bd6 216     def read(self):
055489 217         '''
SP 218         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.
219         '''
c14bd6 220         if(self.exists()):
3d0247 221         #    epoch1=0
SP 222         #    epoch2=0
223         #    n1=0
224         #    n2=0
c14bd6 225             nlines=self.mapcount()
3d0247 226             if nlines<2:
SP 227                 return(False)
c14bd6 228             try:
SP 229                 with open(self.fullname, "r+") as fin:
230                     i=0;
231                     for line in fin:
232                         if(i==1):
7c45b1 233                             #print (line)
SP 234                             fields=shlex.split(line)
235                             epoch1=fields[0]
236                             n1=fields[1]
c14bd6 237                         if(i==nlines-1):
7c45b1 238                             fields=shlex.split(line)
SP 239                             epoch2=fields[0]
240                             n2=fields[1]
c14bd6 241                         i=i+1
SP 242             except:
f81585 243                 #print("Cannot read statistics file in "+self.fullname+"\n")
7c45b1 244                 return(False)
c14bd6 245         else:
f81585 246             #print("File "+self.fullname+" does not exists.\n")
7c45b1 247             return(False)
3d0247 248         try:
SP 249             self.dT=(int(epoch2)-int(epoch1))/(int(n2)-int(n1))
250         except:
251             self.dT=0
f81585 252         self.last=n2
SP 253         self.startDate=epoch1
7c45b1 254         return(True)
f81585 255
SP 256     def __str__(self):
055489 257         '''
SP 258         Prints the full path with filename of the statistics.csv file
259         '''
f81585 260         return(str(self.fullname))
SP 261
7c45b1 262
bc14fb 263
SP 264 class Runner:
265     '''
055489 266     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 267     '''
bd826d 268     def __init__(self, subdir='run0', tape=None, snapshot=None, runArgs=[]):
c14bd6 269         self.subdir=subdir
b122c4 270         self.runArgs=runArgs
47d80d 271         self.fromSnapshot=False
bd826d 272         if(tape!=None):
c14bd6 273             self.initFromTape(tape)
bd826d 274         if(snapshot!=None):
c14bd6 275             self.initFromSnapshot(snapshot)
SP 276         return
277
278
a99f2b 279     def initFromTape(self, tape):
bc14fb 280         self.tape=Tape()
SP 281         self.tape.readTape(tape)
bd826d 282         self.tapeFile=tape
bc14fb 283
bd6993 284     def initFromSnapshot(self, snapshotfile):
SP 285         try:
286             tree = ET.parse(snapshotfile)
287         except:
288             print("Error reading snapshot file")
289             exit(1)
47d80d 290         self.fromSnapshot=True
SP 291         self.snapshotFile=snapshotfile
bd6993 292         root = tree.getroot()
SP 293         tapetxt=root.find('tape')
294         version=root.find('trisurfversion')
295         self.tape=Tape()
296         self.tape.setTape(tapetxt.text)
2ded03 297
9154b3 298     def getPID(self):
34600b 299         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
bd826d 300         #self.Dir.makeifnotexist()
34600b 301         try:
SP 302             fp = open(os.path.join(self.Dir.fullpath(),'.lock'))
303         except IOError as e:
304             return 0 #file probably does not exist. e==2??
9154b3 305         pid=fp.readline()
SP 306         fp.close()
055489 307         return int(pid)
9154b3 308
3d0247 309     def getLastIteration(self):
SP 310         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
311         #self.Dir.makeifnotexist()
312         try:
313             fp = open(os.path.join(self.Dir.fullpath(),'.status'))
314         except IOError as e:
315             return -1 #file probably does not exist. e==2??
316         status=fp.readline()
317         fp.close()
318         return int(status)
319
320     def isCompleted(self):
321         if (int(self.tape.getValue("iterations"))==self.getLastIteration()+1):
322             return True
323         else:
324             return False
325
9154b3 326     def getStatus(self):
SP 327         pid=self.getPID()
3d0247 328         if(self.isCompleted()):
SP 329             return TS_COMPLETED
9154b3 330         if(pid==0):
055489 331             return TS_NOLOCK
34600b 332         if(psutil.pid_exists(int(pid))):
055489 333             proc= psutil.Process(int(pid))
56ba2f 334             #psutil.__version__ == '3.4.2' requires name() and status(), some older versions reguire name, status
9aa037 335             if(psutil.__version__>='2.0.0'):
SP 336                 procname=proc.name()
337                 procstat=proc.status()
338             else:
339                 procname=proc.name
340                 procstat=proc.status
341             if procname=="trisurf":
342                 if procstat=="stopped":
055489 343                     return TS_STOPPED
SP 344                 else:
345                     return TS_RUNNING
9154b3 346             else:
055489 347                 return TS_NONEXISTANT
34600b 348         else:
055489 349             return TS_NONEXISTANT
bc14fb 350
SP 351     def start(self):
3d0247 352         if(self.getStatus()==0 or self.getStatus()==TS_COMPLETED):
38cb4a 353             #check if executable exists
SP 354             if(shutil.which('trisurf')==None):
355                 print("Error. Trisurf executable not found in PATH. Please install trisurf prior to running trisurf manager.")
356                 exit(1)
c14bd6 357             self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
bd826d 358 #Symlinks tape file to the directory or create tape file from snapshot in the direcory...
SP 359             if(self.Dir.makeifnotexist()):
360                 if(self.fromSnapshot==False):
361                     try:
362                         os.symlink(os.path.abspath(self.tapeFile), self.Dir.fullpath()+"/tape")
363                     except:
364                         print("Error while symlinking "+os.path.abspath(self.tapeFile)+" to "+self.Dir.fullpath()+"/tape")
365                         exit(1)
366                 else:
367                     try:
368                         with open (os.path.join(self.Dir.fullpath(),"tape"), "w") as myfile:
cc95bd 369                             #myfile.write("#This is automatically generated tape file from snapshot")
SP 370                             myfile.write(str(self.tape.rawText))
bd826d 371                     except:
SP 372                         print("Error -- cannot make tapefile  "+ os.path.join(self.Dir.fullpath(),"tape")+" from the snapshot in the running directory")
373                         exit(1)
374                     try:
354145 375                         os.symlink(os.path.abspath(self.snapshotFile), os.path.join(self.Dir.fullpath(),"initial_snapshot.vtu"))
bd826d 376                     except:
SP 377                         print("Error while symlinking "+os.path.abspath(self.snapshotFile)+" to "+os.path.join(self.Dir.fullpath(),self.snapshotFile))
3d0247 378         
SP 379             #check if the simulation has been completed. in this case notify user and stop executing.
380             if(self.isCompleted() and ("--force-from-tape" not in self.runArgs) and ("--reset-iteration-count" not in self.runArgs)):
3b83db 381                 print("The simulation was completed. Not starting executable in "+self.Dir.fullpath())
3d0247 382                 return
SP 383
47d80d 384             cwd=Directory(maindir=os.getcwd())
SP 385             self.Dir.goto()
3b83db 386             print("Starting trisurf-ng executable in "+self.Dir.fullpath())
47d80d 387             if(self.fromSnapshot==True):
354145 388                 params=["trisurf", "--restore-from-vtk","initial_snapshot.vtu"]+self.runArgs
47d80d 389             else:
3d0247 390                 #veify if dump exists. If not it is a first run and shoud be run with --force-from-tape
SP 391                 if(os.path.isfile("dump.bin")==False):
392                     self.runArgs.append("--force-from-tape")
b122c4 393                 params=["trisurf"]+self.runArgs
fca8d6 394             subprocess.Popen (params, stdout=subprocess.DEVNULL)
47d80d 395             cwd.goto()
c14bd6 396         else:
3b83db 397             print("Process in "+self.Dir.fullpath()+" already running. Not starting.")
c14bd6 398         return
bc14fb 399
SP 400
c14bd6 401     def setMaindir(self,prefix,variables):
055489 402         maindir=""
c14bd6 403         for p,v in zip(prefix,variables):
SP 404             if(v=="xk0"):
405                 tv=str(round(float(self.tape.config[v])))
406             else:
407                 tv=self.tape.config[v]
408             maindir=maindir+p+tv
409         self.maindir=maindir
410         return
411
412     def setSubdir(self, subdir="run0"):
413         self.subdir=subdir
414         return
415
416     def getStatistics(self, statfile="statistics.csv"):
7ca5ae 417         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
34600b 418         self.statistics=Statistics(self.Dir.fullpath(), statfile)
9154b3 419         self.Comment=FileContent(os.path.join(self.Dir.fullpath(),".comment"))
38cb4a 420         pid=self.getPID()
055489 421         status=self.getStatus()
25296b 422         if(self.statistics.fileOK):
SP 423             ETA=str(datetime.timedelta(microseconds=(int(self.tape.config['iterations'])-int(self.statistics.last))*self.statistics.dT)*1000000)
055489 424         if(status==TS_NONEXISTANT or status==TS_NOLOCK):
SP 425             statustxt="Not running"
9154b3 426             pid=""
38cb4a 427             ETA=""
055489 428         elif status==TS_STOPPED:
SP 429             statustxt="Stopped"
38cb4a 430             ETA="N/A"
3d0247 431         elif status==TS_COMPLETED:
SP 432             statustxt="Completed"
38cb4a 433             pid=""
SP 434             ETA=""
055489 435         else:
SP 436             statustxt="Running"
9154b3 437
SP 438         if(self.statistics.fileOK):
38cb4a 439             report=[time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(self.statistics.startDate))),ETA, statustxt, pid, str(self.Dir.fullpath()), self.Comment.getText()]
9154b3 440         else:
25296b 441             report=["N/A","N/A",statustxt, pid, str(self.Dir.fullpath()), self.Comment.getText()]
f81585 442         return report
c14bd6 443
f06659 444
SP 445     def stop(self):
446         p=psutil.Process(self.getPID())
447         p.kill()
448
055489 449     def writeComment(self, data, mode='w'):
9154b3 450         self.Dir=Directory(maindir=self.maindir,simdir=self.subdir)
SP 451         self.Comment=FileContent(os.path.join(self.Dir.fullpath(),".comment"))
055489 452         self.Comment.writefile(data,mode=mode)
9154b3 453
bc14fb 454     def __str__(self):
c14bd6 455         if(self.getStatus()==0):
SP 456             str=" not running."
457         else:
458             str=" running."
459         return(self.Dir.fullpath()+str)
460
bc14fb 461