1
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,glob
16 import re,os
17
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")
70
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
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
92
94 self.reread()
95
96 if self.timeName(item)!=None:
97 return True
98 else:
99 return False
100
109
122
132
137
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
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
180 if self.dirPrefix:
181 return path.join(self.dirPrefix, time)
182 return time
183
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
206
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
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
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
359 """The number of directories with processor-data"""
360 self.reread()
361 return self.procNr
362
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
373 """ @return: List of all the available times"""
374 self.reread()
375 return self.times
376
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
383 """@return: the first time for which a solution exists
384 @rtype: str"""
385 self.reread()
386 return self.first
387
389 """@return: the last time for which a solution exists
390 @rtype: str"""
391 self.reread()
392 return self.last
393
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 self.rmtree(pth)
473
474 additional=eval(conf().get("Clearing","additionalpatterns"))
475 for a in additional:
476 self.clearPattern(a)
477
479 """Clear all files that fit a certain shell (glob) pattern
480 @param glob: the pattern which the files are going to fit"""
481
482 for f in glob.glob(path.join(self.name,globPat)):
483 if path.isdir(f):
484 self.rmtree(f,ignore_errors=False)
485 else:
486 os.unlink(f)
487
488 - def clearOther(self,
489 pyfoam=True,
490 clearHistory=False):
491 """Remove additional directories
492 @param pyfoam: rremove all directories typically created by PyFoam"""
493
494 if pyfoam:
495 self.clearPattern("PyFoam.?*")
496 self.clearPattern("*?.analyzed")
497 if clearHistory:
498 self.clearPattern("PyFoamHistory")
499
500 - def clear(self,
501 after=None,
502 processor=True,
503 pyfoam=True,
504 keepLast=False,
505 vtk=True,
506 keepRegular=False,
507 clearHistory=False,
508 functionObjectData=False):
509 """One-stop-shop to remove data
510 @param after: time after which directories ar to be removed
511 @param processor: remove the processorXX directories
512 @param pyfoam: rremove all directories typically created by PyFoam
513 @param keepLast: Keep the last time-step
514 @param additional: list with additional patterns to clear"""
515 self.clearResults(after=after,
516 removeProcs=processor,
517 keepLast=keepLast,
518 vtk=vtk,
519 keepRegular=keepRegular,
520 functionObjectData=functionObjectData)
521 self.clearOther(pyfoam=pyfoam,
522 clearHistory=clearHistory)
523
525 """@return: the name of the first time-directory (==initial
526 conditions)
527 @rtype: str"""
528 self.reread()
529
530 if self.first:
531 return path.join(self.name,self.first)
532 else:
533 if path.exists(path.join(self.name,"0.org")):
534 return path.join(self.name,"0.org")
535 else:
536 return None
537
539 """@return: the name of the first last-directory (==simulation
540 results)
541 @rtype: str"""
542 self.reread()
543
544 last=self.getLast()
545 if last:
546 return path.join(self.name,last)
547 else:
548 return None
549
551 """@param region: Specify the region for cases with more than 1 mesh
552 @param processor: name of the processor directory
553 @return: the name of the C{constant}-directory
554 @rtype: str"""
555 pre=self.name
556 if processor!=None:
557 if type(processor)==int:
558 processor="processor%d" % processor
559 pre=path.join(pre,processor)
560
561 if region==None and self.region!=None:
562 region=self.region
563 if region:
564 return path.join(pre,"constant",region)
565 else:
566 return path.join(pre,"constant")
567
569 """@param region: Specify the region for cases with more than 1 mesh
570 @return: the name of the C{system}-directory
571 @rtype: str"""
572 if region==None and self.region!=None:
573 region=self.region
574 if region:
575 return path.join(self.name,"system",region)
576 else:
577 return path.join(self.name,"system")
578
580 """@return: the name of the C{controlDict}
581 @rtype: str"""
582 return path.join(self.systemDir(),"controlDict")
583
584 - def polyMeshDir(self,region=None,time=None,processor=None):
585 """@param region: Specify the region for cases with more than 1 mesh
586 @return: the name of the C{polyMesh}
587 @param time: Time for which the mesh should be looked at
588 @param processor: Name of the processor directory for decomposed cases
589 @rtype: str"""
590 if region==None and self.region!=None:
591 region=self.region
592 if time==None:
593 return path.join(
594 self.constantDir(
595 region=region,
596 processor=processor),
597 "polyMesh")
598 else:
599 return path.join(
600 TimeDirectory(self.name,
601 time,
602 region=region,
603 processor=processor).name,
604 "polyMesh")
605
606 - def boundaryDict(self,region=None,time=None,processor=None):
607 """@param region: Specify the region for cases with more than 1 mesh
608 @return: name of the C{boundary}-file
609 @rtype: str"""
610 if region==None and self.region!=None:
611 region=self.region
612 return path.join(self.polyMeshDir(region=region,time=time,processor=processor),"boundary")
613
615 """@param region: Specify the region for cases with more than 1 mesh
616 @return: the name of the C{blockMeshDict} if it exists. Returns
617 an empty string if it doesn't
618 @rtype: str"""
619 if region==None and self.region!=None:
620 region=self.region
621 p=path.join(self.polyMeshDir(region=region),"blockMeshDict")
622 if path.exists(p):
623 return p
624 else:
625 return ""
626
628 """create a file in the solution directory and return a
629 corresponding BasicFile-object
630
631 @param name: Name of the file
632 @rtype: L{BasicFile}"""
633 return BasicFile(path.join(self.name,name))
634
636 """Gets a list of all the available mesh regions by checking all
637 directories in constant and using all those that have a polyMesh-subdirectory
638 @param defaultRegion: should the default region also be added (as None)"""
639 lst=[]
640 for d in self.listDirectory(self.constantDir()):
641 if path.isdir(path.join(self.constantDir(),d)):
642 if path.exists(self.polyMeshDir(region=d)):
643 lst.append(d)
644
645 if defaultRegion:
646 if path.exists(self.polyMeshDir()):
647 lst.append(None)
648
649 lst.sort()
650 return lst
651
652 - def addToHistory(self,*text):
653 """Adds a line with date and username to a file 'PyFoamHistory'
654 that resides in the local directory"""
655 hist=open(path.join(self.name,"PyFoamHistory"),"a")
656
657 try:
658
659 username=getlogin()
660 except OSError:
661 username=environ["USER"]
662
663 hist.write("%s by %s in %s :" % (asctime(),username,uname()[1]))
664
665 for t in text:
666 hist.write(str(t)+" ")
667
668 hist.write("\n")
669 hist.close()
670
672 """List all the plain files (not directories) in a subdirectory
673 of the case
674 @param directory: the subdirectory. If unspecified the
675 case-directory itself is used
676 @return: List with the plain filenames"""
677
678 result=[]
679 theDir=self.name
680 if directory:
681 theDir=path.join(theDir,directory)
682
683 for f in listdir(theDir):
684 if f[0]!='.' and f[-1]!='~':
685 if path.isfile(path.join(theDir,f)):
686 result.append(f)
687
688 return result
689
690 - def getDictionaryText(self,directory,name):
691 """@param directory: Sub-directory of the case
692 @param name: name of the dictionary file
693 @return: the contents of the file as a big string"""
694
695 result=None
696 theDir=self.name
697 if directory:
698 theDir=path.join(theDir,directory)
699
700 if path.exists(path.join(theDir,name)):
701 result=open(path.join(theDir,name)).read()
702 else:
703 warning("File",name,"does not exist in directory",directory,"of case",self.name)
704
705 return result
706
707 - def writeDictionaryContents(self,directory,name,contents):
708 """Writes the contents of a dictionary
709 @param directory: Sub-directory of the case
710 @param name: name of the dictionary file
711 @param contents: Python-dictionary with the dictionary contents"""
712
713 theDir=self.name
714 if directory:
715 theDir=path.join(theDir,directory)
716
717 result=WriteParameterFile(path.join(theDir,name))
718 result.content=contents
719 result.writeFile()
720
721 - def writeDictionaryText(self,directory,name,text):
722 """Writes the contents of a dictionary
723 @param directory: Sub-directory of the case
724 @param name: name of the dictionary file
725 @param text: String with the dictionary contents"""
726
727 theDir=self.name
728 if directory:
729 theDir=path.join(theDir,directory)
730
731 result=open(path.join(theDir,name),"w").write(text)
732
733 - def getDictionaryContents(self,directory,name):
734 """@param directory: Sub-directory of the case
735 @param name: name of the dictionary file
736 @return: the contents of the file as a python data-structure"""
737
738 result={}
739 theDir=self.name
740 if directory:
741 theDir=path.join(theDir,directory)
742
743 if path.exists(path.join(theDir,name)):
744 result=ParsedParameterFile(path.join(theDir,name)).content
745 else:
746 warning("File",name,"does not exist in directory",directory,"of case",self.name)
747
748 return result
749
751 """Find out whether this directory is controlled by a VCS and
752 return the abbreviation of that VCS"""
753
754 if path.isdir(path.join(self.name,".hg")):
755 return "hg"
756 elif path.isdir(path.join(self.name,".git")):
757 return "git"
758 elif path.isdir(path.join(self.name,".svn")):
759 return "svn"
760 else:
761 return None
762
764 """Solution directory with a directory for the Chemkin-files"""
765
766 chemkinName = "chemkin"
767
768 - def __init__(self,name,archive="ArchiveDir"):
772
774 """@rtype: str
775 @return: The directory with the Chemkin-Files"""
776
777 return path.join(self.name,self.chemkinName)
778
780 """Convenience class that makes sure that nothing new is created"""
781
782 - def __init__(self,
783 name,
784 region=None):
790