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