Package PyFoam :: Package RunDictionary :: Module SolutionDirectory
[hide private]
[frames] | no frames]

Source Code for Module PyFoam.RunDictionary.SolutionDirectory

  1  #  ICE Revision: $Id: /local/openfoam/Python/PyFoam/PyFoam/RunDictionary/SolutionDirectory.py 7524 2011-07-15T17:08:53.797429Z bgschaid  $  
  2  """Working with a solution directory""" 
  3   
  4  from PyFoam.Basics.Utilities import Utilities 
  5  from PyFoam.Basics.BasicFile import BasicFile 
  6  from PyFoam.Error import warning 
  7  from PyFoam import configuration as conf 
  8   
  9  from TimeDirectory import TimeDirectory 
 10  from ParsedParameterFile import ParsedParameterFile,WriteParameterFile 
 11   
 12  from os import listdir,path,mkdir,symlink,stat,getlogin,uname,environ 
 13  from time import asctime 
 14  from stat import ST_CTIME 
 15  import tarfile,fnmatch 
 16  import re,shutil 
 17   
18 -class SolutionDirectory(Utilities):
19 """Represents a solution directory 20 21 In the solution directory subdirectories whose names are numbers 22 are assumed to be solutions for a specific time-step 23 24 A sub-directory (called the Archive) is created to which solution 25 data is copied""" 26
27 - def __init__(self, 28 name, 29 archive="ArchiveDir", 30 paraviewLink=True, 31 parallel=False, 32 region=None):
33 """@param name: Name of the solution directory 34 @param archive: name of the directory where the lastToArchive-method 35 should copy files, if None no archive is created 36 @param paraviewLink: Create a symbolic link controlDict.foam for paraview 37 @param parallel: use the first processor-subdirectory for the authorative information 38 @param region: Mesh region for multi-region cases""" 39 40 self.name=path.abspath(name) 41 self.archive=None 42 if archive!=None: 43 self.archive=path.join(name,archive) 44 if not path.exists(self.archive): 45 mkdir(self.archive) 46 47 self.region=region 48 self.backups=[] 49 50 self.parallel=parallel 51 52 self.lastReread=0L 53 self.reread() 54 55 self.dirPrefix='' 56 if self.processorDirs() and parallel: 57 self.dirPrefix = self.processorDirs()[0] 58 59 self.essential=[self.systemDir(), 60 self.constantDir(), 61 self.initialDir()] 62 self.addToClone("PyFoamHistory") 63 64 self.addToClone("customRegexp") 65 self.addToClone("LocalConfigPyFoam") 66 67 emptyFoamFile=path.join(self.name,path.basename(self.name)+".foam") 68 if paraviewLink and not path.exists(emptyFoamFile): 69 dummy=open(emptyFoamFile,"w") # equivalent to touch
70
71 - def setToParallel(self):
72 """Use the parallel times instead of the serial. 73 74 Used to reset the behaviour after it has been set by the constructor""" 75 if self.parallel: 76 warning(self.name,"is already in parallel mode") 77 else: 78 self.parallel=True 79 if self.processorDirs(): 80 self.dirPrefix = self.processorDirs()[0] 81 self.reread(force=True)
82
83 - def addLocalConfig(self):
84 """Add the local configuration file of the case to the configuration""" 85 fName=path.join(self.name,"LocalConfigPyFoam") 86 if path.exists(fName): 87 conf().addFile(fName)
88
89 - def __len__(self):
90 self.reread() 91 return len(self.times)
92
93 - def __contains__(self,item):
94 self.reread() 95 96 if self.timeName(item)!=None: 97 return True 98 else: 99 return False
100
101 - def __getitem__(self,key):
102 self.reread() 103 104 ind=self.timeName(key) 105 if ind==None: 106 raise KeyError(key) 107 else: 108 return TimeDirectory(self.name, self.fullPath(ind), region=self.region)
109
110 - def __setitem__(self,key,value):
111 self.reread() 112 if type(key)!=str: 113 raise TypeError(type(key),"of",key,"is not 'str'") 114 115 if type(value)!=TimeDirectory: 116 raise TypeError(type(value),"is not TimeDirectory") 117 118 dest=TimeDirectory(self.name, self.fullPath(key), create=True,region=self.region) 119 dest.copy(value) 120 121 self.reread(force=True)
122
123 - def __delitem__(self,key):
124 self.reread() 125 nm=self.timeName(key) 126 if nm==None: 127 raise KeyError(key) 128 129 self.rmtree(path.join(self.name, self.fullPath(nm)),ignore_errors=True) 130 131 self.reread(force=True)
132
133 - def __iter__(self):
134 self.reread() 135 for key in self.times: 136 yield TimeDirectory(self.name, self.fullPath(key), region=self.region)
137
138 - def timeName(self,item,minTime=False):
139 """Finds the name of a directory that corresponds with the given parameter 140 @param item: the time that should be found 141 @param minTime: search for the time with the minimal difference. 142 Otherwise an exact match will be searched""" 143 144 if type(item)==int: 145 return self.times[item] 146 else: 147 ind=self.timeIndex(item,minTime) 148 if ind==None: 149 return None 150 else: 151 return self.times[ind]
152
153 - def timeIndex(self,item,minTime=False):
154 """Finds the index of a directory that corresponds with the given parameter 155 @param item: the time that should be found 156 @param minTime: search for the time with the minimal difference. 157 Otherwise an exact match will be searched""" 158 self.reread() 159 160 time=float(item) 161 result=None 162 163 if minTime: 164 result=0 165 for i in range(1,len(self.times)): 166 if abs(float(self.times[result])-time)>abs(float(self.times[i])-time): 167 result=i 168 else: 169 for i in range(len(self.times)): 170 t=self.times[i] 171 if abs(float(t)-time)<1e-6: 172 if result==None: 173 result=i 174 elif abs(float(t)-time)<abs(float(self.times[result])-time): 175 result=i 176 177 return result
178
179 - def fullPath(self,time):
180 if self.dirPrefix: 181 return path.join(self.dirPrefix, time) 182 return time
183
184 - def isValid(self):
185 """Checks whether this is a valid case directory by looking for 186 the system- and constant-directories and the controlDict-file""" 187 188 return len(self.missingFiles())==0
189
190 - def missingFiles(self):
191 """Return a list of all the missing files and directories that 192 are needed for a valid case""" 193 missing=[] 194 if not path.exists(self.systemDir()): 195 missing.append(self.systemDir()) 196 elif not path.isdir(self.systemDir()): 197 missing.append(self.systemDir()) 198 if not path.exists(self.constantDir()): 199 missing.append(self.constantDir()) 200 elif not path.isdir(self.constantDir()): 201 missing.append(self.constantDir()) 202 if not path.exists(self.controlDict()): 203 missing.append(self.controlDict()) 204 205 return missing
206
207 - def addToClone(self,name):
208 """add directory to the list that is needed to clone this case 209 @param name: name of the subdirectory (the case directory is prepended)""" 210 if path.exists(path.join(self.name,name)): 211 self.essential.append(path.join(self.name,name)) 212 elif self.parallel: 213 if path.exists(path.join(self.name,"processor0",name)): 214 self.essential.append(path.join(self.name,name))
215
216 - def cloneCase(self,name,svnRemove=True,followSymlinks=False):
217 """create a clone of this case directory. Remove the target directory, if it already exists 218 219 @param name: Name of the new case directory 220 @param svnRemove: Look for .svn-directories and remove them 221 @param followSymlinks: Follow symbolic links instead of just copying them 222 @rtype: L{SolutionDirectory} or correct subclass 223 @return: The target directory""" 224 225 cpOptions="-R" 226 if followSymlinks: 227 cpOptions+=" -L" 228 229 if path.exists(name): 230 self.rmtree(name) 231 mkdir(name) 232 if self.parallel: 233 for i in range(self.nrProcs()): 234 mkdir(path.join(name,"processor%d" % i)) 235 236 for d in self.essential: 237 if d!=None: 238 if self.parallel: 239 pth,fl=path.split(d) 240 if path.exists(path.join(pth,"processor0",fl)): 241 for i in range(self.nrProcs()): 242 self.copytree(path.join(pth,"processor%d" % i,fl), 243 path.join(name,"processor%d" % i), 244 symlinks=not followSymlinks) 245 246 if path.exists(d): 247 self.copytree(d,name,symlinks=not followSymlinks) 248 249 if svnRemove: 250 self.execute("find "+name+" -name .svn -exec rm -rf {} \\; -prune") 251 252 return self.__class__(name,archive=self.archive)
253
254 - def packCase(self,tarname,last=False,exclude=[],additional=[],base=None):
255 """Packs all the important files into a compressed tarfile. 256 Uses the essential-list and excludes the .svn-directories. 257 Also excludes files ending with ~ 258 @param tarname: the name of the tar-file 259 @param last: add the last directory to the list of directories to be added 260 @param exclude: List with additional glob filename-patterns to be excluded 261 @param additional: List with additional glob filename-patterns 262 that are to be added 263 @param base: Different name that is to be used as the baseName for the case inside the tar""" 264 265 ex=["*~",".svn"]+exclude 266 members=self.essential[:] 267 if last: 268 if self.getLast()!=self.first: 269 members.append(self.latestDir()) 270 for p in additional: 271 for f in listdir(self.name): 272 if (f not in members) and fnmatch.fnmatch(f,p): 273 members.append(path.join(self.name,f)) 274 275 tar=tarfile.open(tarname,"w:gz") 276 277 for m in members: 278 self.addToTar(tar,m,exclude=ex,base=base) 279 280 tar.close()
281
282 - def addToTar(self,tar,name,exclude=[],base=None):
283 """The workhorse for the packCase-method""" 284 285 if base==None: 286 base=path.basename(self.name) 287 288 for e in exclude: 289 if fnmatch.fnmatch(path.basename(name),e): 290 return 291 292 if path.isdir(name): 293 for m in listdir(name): 294 self.addToTar(tar,path.join(name,m),exclude=exclude,base=base) 295 else: 296 arcname=path.join(base,name[len(self.name)+1:]) 297 tar.add(name,arcname=arcname)
298
299 - def getParallelTimes(self):
300 """Get a list of the times in the processor0-directory""" 301 result=[] 302 303 proc0=path.join(self.name,"processor0") 304 if path.exists(proc0): 305 for f in listdir(proc0): 306 try: 307 val=float(f) 308 result.append(f) 309 except ValueError: 310 pass 311 result.sort(self.sorttimes) 312 return result
313
314 - def reread(self,force=False):
315 """Rescan the directory for the time directories""" 316 317 if not force and stat(self.name)[ST_CTIME]<=self.lastReread: 318 return 319 320 self.times=[] 321 self.first=None 322 self.last=None 323 procDirs = self.processorDirs() 324 self.procNr=len(procDirs) 325 326 if procDirs and self.parallel: 327 timesDir = path.join(self.name, procDirs[0]) 328 else: 329 timesDir = self.name 330 331 for f in listdir(timesDir): 332 try: 333 val=float(f) 334 self.times.append(f) 335 except ValueError: 336 pass 337 338 self.lastReread=stat(self.name)[ST_CTIME] 339 340 self.times.sort(self.sorttimes) 341 if self.times: 342 self.first = self.times[0] 343 self.last = self.times[-1]
344
345 - def processorDirs(self):
346 """List with the processor directories""" 347 try: 348 return self.procDirs 349 except: 350 pass 351 self.procDirs=[] 352 for f in listdir(self.name): 353 if re.compile("processor[0-9]+").match(f): 354 self.procDirs.append(f) 355 356 return self.procDirs
357
358 - def nrProcs(self):
359 """The number of directories with processor-data""" 360 self.reread() 361 return self.procNr
362
363 - def sorttimes(self,x,y):
364 """Sort function for the solution files""" 365 if(float(x)==float(y)): 366 return 0 367 elif float(x)<float(y): 368 return -1 369 else: 370 return 1
371
372 - def getTimes(self):
373 """ @return: List of all the available times""" 374 self.reread() 375 return self.times
376
377 - def addBackup(self,pth):
378 """add file to list of files that are to be copied to the 379 archive""" 380 self.backups.append(path.join(self.name,pth))
381
382 - def getFirst(self):
383 """@return: the first time for which a solution exists 384 @rtype: str""" 385 self.reread() 386 return self.first
387
388 - def getLast(self):
389 """@return: the last time for which a solution exists 390 @rtype: str""" 391 self.reread() 392 return self.last
393
394 - def lastToArchive(self,name):
395 """copy the last solution (plus the backup-files to the 396 archive) 397 398 @param name: name of the sub-directory in the archive""" 399 if self.archive==None: 400 print "Warning: nor Archive-directory" 401 return 402 403 self.reread() 404 fname=path.join(self.archive,name) 405 if path.exists(fname): 406 self.rmtree(fname) 407 mkdir(fname) 408 self.copytree(path.join(self.name,self.last),fname) 409 for f in self.backups: 410 self.copytree(f,fname)
411
412 - def clearResults(self, 413 after=None, 414 removeProcs=False, 415 keepLast=False, 416 vtk=True, 417 keepRegular=False, 418 functionObjectData=False):
419 """remove all time-directories after a certain time. If not time ist 420 set the initial time is used 421 @param after: time after which directories ar to be removed 422 @param removeProcs: if True the processorX-directories are removed. 423 Otherwise the timesteps after last are removed from the 424 processor-directories 425 @param keepLast: Keep the data from the last timestep 426 @param vtk: Remove the VTK-directory if it exists 427 @param keepRegular: keep all the times (only remove processor and other stuff) 428 @param functionObjectData: tries do determine which data was written by function obejects and removes it""" 429 430 self.reread() 431 432 last=self.getLast() 433 434 if after==None: 435 try: 436 time=float(self.first) 437 except TypeError: 438 warning("The first timestep in",self.name," is ",self.first,"not a number. Doing nothing") 439 return 440 else: 441 time=float(after) 442 443 if not keepRegular: 444 for f in self.times: 445 if float(f)>time and not (keepLast and f==last): 446 self.rmtree(path.join(self.name,f)) 447 448 if path.exists(path.join(self.name,"VTK")) and vtk: 449 self.rmtree(path.join(self.name,"VTK")) 450 451 if self.nrProcs(): 452 for f in listdir(self.name): 453 if re.compile("processor[0-9]+").match(f): 454 if removeProcs: 455 self.rmtree(path.join(self.name,f)) 456 else: 457 pDir=path.join(self.name,f) 458 for t in listdir(pDir): 459 try: 460 val=float(t) 461 if val>time: 462 self.rmtree(path.join(pDir,t)) 463 except ValueError: 464 pass 465 466 if functionObjectData: 467 cd=ParsedParameterFile(self.controlDict()) 468 if "functions" in cd: 469 for f in cd["functions"][0::2]: 470 pth=path.join(self.name,f) 471 if path.exists(pth): 472 shutil.rmtree(pth)
473
474 - def clearPattern(self,glob):
475 """Clear all files that fit a certain shell (glob) pattern 476 @param glob: the pattern which the files are going to fit""" 477 478 self.rmtree(path.join(self.name,glob),ignore_errors=True)
479
480 - def clearOther(self, 481 pyfoam=True, 482 clearHistory=False):
483 """Remove additional directories 484 @param pyfoam: rremove all directories typically created by PyFoam""" 485 486 if pyfoam: 487 self.clearPattern("PyFoam.?*") 488 self.clearPattern("*?.analyzed") 489 if clearHistory: 490 self.clearPattern("PyFoamHistory")
491
492 - def clear(self, 493 after=None, 494 processor=True, 495 pyfoam=True, 496 keepLast=False, 497 vtk=True, 498 keepRegular=False, 499 clearHistory=False, 500 functionObjectData=False):
501 """One-stop-shop to remove data 502 @param after: time after which directories ar to be removed 503 @param processor: remove the processorXX directories 504 @param pyfoam: rremove all directories typically created by PyFoam 505 @param keepLast: Keep the last time-step""" 506 self.clearResults(after=after, 507 removeProcs=processor, 508 keepLast=keepLast, 509 vtk=vtk, 510 keepRegular=keepRegular, 511 functionObjectData=functionObjectData) 512 self.clearOther(pyfoam=pyfoam, 513 clearHistory=clearHistory)
514
515 - def initialDir(self):
516 """@return: the name of the first time-directory (==initial 517 conditions 518 @rtype: str""" 519 self.reread() 520 521 if self.first: 522 return path.join(self.name,self.first) 523 else: 524 return None
525
526 - def latestDir(self):
527 """@return: the name of the first last-directory (==simulation 528 results) 529 @rtype: str""" 530 self.reread() 531 532 last=self.getLast() 533 if last: 534 return path.join(self.name,last) 535 else: 536 return None
537
538 - def constantDir(self,region=None,processor=None):
539 """@param region: Specify the region for cases with more than 1 mesh 540 @param processor: name of the processor directory 541 @return: the name of the C{constant}-directory 542 @rtype: str""" 543 pre=self.name 544 if processor!=None: 545 if type(processor)==int: 546 processor="processor%d" % processor 547 pre=path.join(pre,processor) 548 549 if region==None and self.region!=None: 550 region=self.region 551 if region: 552 return path.join(pre,"constant",region) 553 else: 554 return path.join(pre,"constant")
555
556 - def systemDir(self,region=None):
557 """@param region: Specify the region for cases with more than 1 mesh 558 @return: the name of the C{system}-directory 559 @rtype: str""" 560 if region==None and self.region!=None: 561 region=self.region 562 if region: 563 return path.join(self.name,"system",region) 564 else: 565 return path.join(self.name,"system")
566
567 - def controlDict(self):
568 """@return: the name of the C{controlDict} 569 @rtype: str""" 570 return path.join(self.systemDir(),"controlDict")
571
572 - def polyMeshDir(self,region=None,time=None,processor=None):
573 """@param region: Specify the region for cases with more than 1 mesh 574 @return: the name of the C{polyMesh} 575 @param time: Time for which the mesh should be looked at 576 @param processor: Name of the processor directory for decomposed cases 577 @rtype: str""" 578 if region==None and self.region!=None: 579 region=self.region 580 if time==None: 581 return path.join( 582 self.constantDir( 583 region=region, 584 processor=processor), 585 "polyMesh") 586 else: 587 return path.join( 588 TimeDirectory(self.name, 589 time, 590 region=region, 591 processor=processor).name, 592 "polyMesh")
593
594 - def boundaryDict(self,region=None,time=None,processor=None):
595 """@param region: Specify the region for cases with more than 1 mesh 596 @return: name of the C{boundary}-file 597 @rtype: str""" 598 if region==None and self.region!=None: 599 region=self.region 600 return path.join(self.polyMeshDir(region=region,time=time,processor=processor),"boundary")
601
602 - def blockMesh(self,region=None):
603 """@param region: Specify the region for cases with more than 1 mesh 604 @return: the name of the C{blockMeshDict} if it exists. Returns 605 an empty string if it doesn't 606 @rtype: str""" 607 if region==None and self.region!=None: 608 region=self.region 609 p=path.join(self.polyMeshDir(region=region),"blockMeshDict") 610 if path.exists(p): 611 return p 612 else: 613 return ""
614
615 - def makeFile(self,name):
616 """create a file in the solution directory and return a 617 corresponding BasicFile-object 618 619 @param name: Name of the file 620 @rtype: L{BasicFile}""" 621 return BasicFile(path.join(self.name,name))
622
623 - def getRegions(self):
624 """Gets a list of all the available mesh regions by checking all 625 directories in constant and using all those that have a polyMesh-subdirectory""" 626 lst=[] 627 for d in self.listDirectory(self.constantDir()): 628 if path.isdir(path.join(self.constantDir(),d)): 629 if path.exists(self.polyMeshDir(region=d)): 630 lst.append(d) 631 lst.sort() 632 return lst
633
634 - def addToHistory(self,*text):
635 """Adds a line with date and username to a file 'PyFoamHistory' 636 that resides in the local directory""" 637 hist=open(path.join(self.name,"PyFoamHistory"),"a") 638 639 try: 640 # this seems to fail when no stdin is available 641 username=getlogin() 642 except OSError: 643 username=environ["USER"] 644 645 hist.write("%s by %s in %s :" % (asctime(),username,uname()[1])) 646 647 for t in text: 648 hist.write(str(t)+" ") 649 650 hist.write("\n") 651 hist.close()
652
653 - def listFiles(self,directory=None):
654 """List all the plain files (not directories) in a subdirectory 655 of the case 656 @param directory: the subdirectory. If unspecified the 657 case-directory itself is used 658 @return: List with the plain filenames""" 659 660 result=[] 661 theDir=self.name 662 if directory: 663 theDir=path.join(theDir,directory) 664 665 for f in listdir(theDir): 666 if f[0]!='.' and f[-1]!='~': 667 if path.isfile(path.join(theDir,f)): 668 result.append(f) 669 670 return result
671
672 - def getDictionaryText(self,directory,name):
673 """@param directory: Sub-directory of the case 674 @param name: name of the dictionary file 675 @return: the contents of the file as a big string""" 676 677 result=None 678 theDir=self.name 679 if directory: 680 theDir=path.join(theDir,directory) 681 682 if path.exists(path.join(theDir,name)): 683 result=open(path.join(theDir,name)).read() 684 else: 685 warning("File",name,"does not exist in directory",directory,"of case",self.name) 686 687 return result
688
689 - def writeDictionaryContents(self,directory,name,contents):
690 """Writes the contents of a dictionary 691 @param directory: Sub-directory of the case 692 @param name: name of the dictionary file 693 @param contents: Python-dictionary with the dictionary contents""" 694 695 theDir=self.name 696 if directory: 697 theDir=path.join(theDir,directory) 698 699 result=WriteParameterFile(path.join(theDir,name)) 700 result.content=contents 701 result.writeFile()
702
703 - def writeDictionaryText(self,directory,name,text):
704 """Writes the contents of a dictionary 705 @param directory: Sub-directory of the case 706 @param name: name of the dictionary file 707 @param text: String with the dictionary contents""" 708 709 theDir=self.name 710 if directory: 711 theDir=path.join(theDir,directory) 712 713 result=open(path.join(theDir,name),"w").write(text)
714
715 - def getDictionaryContents(self,directory,name):
716 """@param directory: Sub-directory of the case 717 @param name: name of the dictionary file 718 @return: the contents of the file as a python data-structure""" 719 720 result={} 721 theDir=self.name 722 if directory: 723 theDir=path.join(theDir,directory) 724 725 if path.exists(path.join(theDir,name)): 726 result=ParsedParameterFile(path.join(theDir,name)).content 727 else: 728 warning("File",name,"does not exist in directory",directory,"of case",self.name) 729 730 return result
731
732 - def determineVCS(self):
733 """Find out whether this directory is controlled by a VCS and 734 return the abbreviation of that VCS""" 735 736 if path.isdir(path.join(self.name,".hg")): 737 return "hg" 738 elif path.isdir(path.join(self.name,".git")): 739 return "git" 740 elif path.isdir(path.join(self.name,".svn")): 741 return "svn" 742 else: 743 return None
744
745 -class ChemkinSolutionDirectory(SolutionDirectory):
746 """Solution directory with a directory for the Chemkin-files""" 747 748 chemkinName = "chemkin" 749
750 - def __init__(self,name,archive="ArchiveDir"):
751 SolutionDirectory.__init__(self,name,archive=archive) 752 753 self.addToClone(self.chemkinName)
754
755 - def chemkinDir(self):
756 """@rtype: str 757 @return: The directory with the Chemkin-Files""" 758 759 return path.join(self.name,self.chemkinName)
760
761 -class NoTouchSolutionDirectory(SolutionDirectory):
762 """Convenience class that makes sure that nothing new is created""" 763
764 - def __init__(self, 765 name, 766 region=None):
767 SolutionDirectory.__init__(self, 768 name, 769 archive=None, 770 paraviewLink=False, 771 region=region)
772