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
16 import re,shutil
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 shutil.rmtree(pth)
473
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
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
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
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
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
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
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
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
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
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
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
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
746 """Solution directory with a directory for the Chemkin-files"""
747
748 chemkinName = "chemkin"
749
750 - def __init__(self,name,archive="ArchiveDir"):
754
756 """@rtype: str
757 @return: The directory with the Chemkin-Files"""
758
759 return path.join(self.name,self.chemkinName)
760
762 """Convenience class that makes sure that nothing new is created"""
763
764 - def __init__(self,
765 name,
766 region=None):
772