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