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
79
80 self.addToClone("PyFoamHistory")
81 self.addToClone("customRegexp")
82 self.addToClone("LocalConfigPyFoam")
83
84
85 self.addToClone("Allclean")
86 self.addToClone("Allrun")
87
88
89 emptyFoamFile=path.join(self.name,path.basename(self.name)+".foam")
90 if paraviewLink and not path.exists(emptyFoamFile):
91 dummy=open(emptyFoamFile,"w")
92
93 if addLocalConfig:
94 self.addLocalConfig()
95
97 """Use the parallel times instead of the serial.
98
99 Used to reset the behaviour after it has been set by the constructor"""
100 if self.parallel:
101 warning(self.name,"is already in parallel mode")
102 else:
103 self.parallel=True
104 if self.processorDirs():
105 self.dirPrefix = self.processorDirs()[0]
106 self.reread(force=True)
107
113
117
119 self.reread()
120
121 if self.timeName(item)!=None:
122 return True
123 else:
124 return False
125
134
147
157
162
164 """Finds the name of a directory that corresponds with the given parameter
165 @param item: the time that should be found
166 @param minTime: search for the time with the minimal difference.
167 Otherwise an exact match will be searched"""
168
169 if type(item)==int:
170 return self.times[item]
171 else:
172 ind=self.timeIndex(item,minTime)
173 if ind==None:
174 return None
175 else:
176 return self.times[ind]
177
179 """Finds the index of a directory that corresponds with the given parameter
180 @param item: the time that should be found
181 @param minTime: search for the time with the minimal difference.
182 Otherwise an exact match will be searched"""
183 self.reread()
184
185 time=float(item)
186 result=None
187
188 if minTime:
189 result=0
190 for i in range(1,len(self.times)):
191 if abs(float(self.times[result])-time)>abs(float(self.times[i])-time):
192 result=i
193 else:
194 for i in range(len(self.times)):
195 t=self.times[i]
196 if abs(float(t)-time)<1e-6:
197 if result==None:
198 result=i
199 elif abs(float(t)-time)<abs(float(self.times[result])-time):
200 result=i
201
202 return result
203
205 if self.dirPrefix:
206 return path.join(self.dirPrefix, time)
207 return time
208
210 """Checks whether this is a valid case directory by looking for
211 the system- and constant-directories and the controlDict-file"""
212
213 return len(self.missingFiles())==0
214
231
240
241 - def cloneCase(self,name,svnRemove=True,followSymlinks=False):
242 """create a clone of this case directory. Remove the target directory, if it already exists
243
244 @param name: Name of the new case directory
245 @param svnRemove: Look for .svn-directories and remove them
246 @param followSymlinks: Follow symbolic links instead of just copying them
247 @rtype: L{SolutionDirectory} or correct subclass
248 @return: The target directory"""
249
250 additional=eval(conf().get("Cloning","addItem"))
251 for a in additional:
252 self.addToClone(a)
253
254 cpOptions="-R"
255 if followSymlinks:
256 cpOptions+=" -L"
257
258 if path.exists(name):
259 self.rmtree(name)
260 mkdir(name)
261 if self.parallel:
262 for i in range(self.nrProcs()):
263 mkdir(path.join(name,"processor%d" % i))
264
265 for d in self.essential:
266 if d!=None:
267 if self.parallel:
268 pth,fl=path.split(d)
269 if path.exists(path.join(pth,"processor0",fl)):
270 for i in range(self.nrProcs()):
271 self.copytree(path.join(pth,"processor%d" % i,fl),
272 path.join(name,"processor%d" % i),
273 symlinks=not followSymlinks)
274
275 if path.exists(d):
276 self.copytree(d,name,symlinks=not followSymlinks)
277
278 if svnRemove:
279 self.execute("find "+name+" -name .svn -exec rm -rf {} \\; -prune")
280
281 return self.__class__(name,archive=self.archive)
282
283 - def symlinkCase(self,
284 name,
285 followSymlinks=False,
286 maxLevel=1,
287 relPath=False):
288 """create a clone of this case directory by creating a
289 directory with symbolic links
290
291 @param name: Name of the new case directory
292 @param maxLevel: Maximum level down to which directories are created instead of symbolically linked
293 @param followSymlinks: Follow symbolic links instead of just copying them
294 @param relPath: the created symbolic links are relative (instead of absolute)
295 @rtype: L{SolutionDirectory} or correct subclass
296 @return: The target directory
297 """
298 here=path.abspath(self.name)
299 polyDirs=[path.relpath(p,here) for p in self.find("polyMesh*",here)]
300
301 additional=eval(conf().get("Cloning","addItem"))
302 for a in additional:
303 self.addToClone(a)
304
305 if path.exists(name):
306 self.rmtree(name)
307 mkdir(name)
308 toProcess=[]
309 for d in self.essential:
310 if d!=None:
311 if self.parallel:
312 pth,fl=path.split(d)
313 if path.exists(path.join(pth,"processor0",fl)):
314 for i in range(self.nrProcs()):
315 toProcess.append("processor%d" % i)
316 if path.exists(d):
317 toProcess.append(path.relpath(d,here))
318
319 maxLevel=max(0,maxLevel)
320
321 self.__symlinkDir(src=here,
322 dest=path.abspath(name),
323 toProcess=toProcess,
324 maxLevel=maxLevel,
325 relPath=relPath,
326 polyDirs=polyDirs,
327 symlinks=not followSymlinks)
328
329 return self.__class__(name,archive=self.archive)
330
331 - def __symlinkDir(self,src,dest,toProcess,maxLevel,relPath,polyDirs,symlinks):
332 for f in toProcess:
333 there=path.join(src,f)
334 here=path.join(dest,f)
335 if path.islink(there) and not symlinks:
336 there=path.realpath(there)
337
338 doSymlink=False
339 done=False
340
341 if not path.isdir(there):
342 doSymlink=True
343 if path.basename(src)=="polyMesh":
344 if f not in ["blockMeshDict","blockMeshDict.gz"]:
345 doSymlink=False
346 else:
347 poly=[p for p in polyDirs if p.split(path.sep)[0]==f]
348 if maxLevel>0 or len(poly)>0:
349 done=True
350 mkdir(here)
351 self.__symlinkDir(src=there,dest=here,
352 toProcess=[p for p in os.listdir(there) if p[0]!='.'],
353 maxLevel=max(0,maxLevel-1),
354 relPath=relPath,
355 polyDirs=[path.join(*p.split(path.sep)[1:]) for p in poly if len(p.split(path.sep))>1],
356 symlinks=symlinks)
357 else:
358 doSymlink=True
359
360 if not done:
361 if doSymlink:
362 if relPath:
363 linkTo=path.relpath(there,dest)
364 else:
365 linkTo=path.abspath(there)
366 os.symlink(linkTo,here)
367 else:
368 self.copytree(there,here,symlinks=symlinks)
369
370 - def packCase(self,tarname,last=False,exclude=[],additional=[],base=None):
371 """Packs all the important files into a compressed tarfile.
372 Uses the essential-list and excludes the .svn-directories.
373 Also excludes files ending with ~
374 @param tarname: the name of the tar-file
375 @param last: add the last directory to the list of directories to be added
376 @param exclude: List with additional glob filename-patterns to be excluded
377 @param additional: List with additional glob filename-patterns
378 that are to be added
379 @param base: Different name that is to be used as the baseName for the case inside the tar"""
380
381 ex=["*~",".svn"]+exclude
382 members=self.essential[:]
383 if last:
384 if self.getLast()!=self.first:
385 members.append(self.latestDir())
386 for p in additional:
387 for f in listdir(self.name):
388 if (f not in members) and fnmatch.fnmatch(f,p):
389 members.append(path.join(self.name,f))
390
391 tar=tarfile.open(tarname,"w:gz")
392
393 for m in members:
394 self.addToTar(tar,m,exclude=ex,base=base)
395
396 additional=eval(conf().get("Cloning","addItem"))
397 for a in additional:
398 self.addToTar(tar,
399 path.join(self.name,a),
400 exclude=ex,
401 base=base)
402
403 tar.close()
404
405 - def addToTar(self,tar,name,exclude=[],base=None):
406 """The workhorse for the packCase-method"""
407
408 if base==None:
409 base=path.basename(self.name)
410
411 for e in exclude:
412 if fnmatch.fnmatch(path.basename(name),e):
413 return
414
415 if path.isdir(name):
416 for m in listdir(name):
417 self.addToTar(tar,path.join(name,m),exclude=exclude,base=base)
418 else:
419 arcname=path.join(base,name[len(self.name)+1:])
420 tar.add(name,arcname=arcname)
421
423 """Get a list of the times in the processor0-directory"""
424 result=[]
425
426 proc0=path.join(self.name,"processor0")
427 if path.exists(proc0):
428 for f in listdir(proc0):
429 try:
430 val=float(f)
431 result.append(f)
432 except ValueError:
433 pass
434 result.sort(key=float)
435 return result
436
437 - def reread(self,force=False):
438 """Rescan the directory for the time directories"""
439
440 if not force and stat(self.name)[ST_CTIME]<=self.lastReread:
441 return
442
443 self.times=[]
444 self.first=None
445 self.last=None
446 procDirs = self.processorDirs()
447 self.procNr=len(procDirs)
448
449 if procDirs and self.parallel:
450 timesDir = path.join(self.name, procDirs[0])
451 else:
452 timesDir = self.name
453
454 for f in listdir(timesDir):
455 try:
456 val=float(f)
457 self.times.append(f)
458 except ValueError:
459 pass
460
461 self.lastReread=stat(self.name)[ST_CTIME]
462
463 self.times.sort(key=float)
464 if self.times:
465 self.first = self.times[0]
466 self.last = self.times[-1]
467
469 """List with the processor directories"""
470 try:
471 return self.procDirs
472 except:
473 pass
474 self.procDirs=[]
475 for f in listdir(self.name):
476 if re.compile("processor[0-9]+").match(f):
477 self.procDirs.append(f)
478
479 return self.procDirs
480
482 """The number of directories with processor-data"""
483 self.reread()
484 return self.procNr
485
487 """ @return: List of all the available times"""
488 self.reread()
489 return self.times
490
492 """add file to list of files that are to be copied to the
493 archive"""
494 self.backups.append(path.join(self.name,pth))
495
497 """@return: the first time for which a solution exists
498 @rtype: str"""
499 self.reread()
500 return self.first
501
503 """@return: the last time for which a solution exists
504 @rtype: str"""
505 self.reread()
506 return self.last
507
509 """copy the last solution (plus the backup-files to the
510 archive)
511
512 @param name: name of the sub-directory in the archive"""
513 if self.archive==None:
514 print_("Warning: nor Archive-directory")
515 return
516
517 self.reread()
518 fname=path.join(self.archive,name)
519 if path.exists(fname):
520 self.rmtree(fname)
521 mkdir(fname)
522 self.copytree(path.join(self.name,self.last),fname)
523 for f in self.backups:
524 self.copytree(f,fname)
525
526 - def clearResults(self,
527 after=None,
528 removeProcs=False,
529 keepLast=False,
530 vtk=True,
531 keepRegular=False,
532 functionObjectData=False,
533 additional=[]):
534 """remove all time-directories after a certain time. If not time ist
535 set the initial time is used
536 @param after: time after which directories ar to be removed
537 @param removeProcs: if True the processorX-directories are removed.
538 Otherwise the timesteps after last are removed from the
539 processor-directories
540 @param keepLast: Keep the data from the last timestep
541 @param vtk: Remove the VTK-directory if it exists
542 @param keepRegular: keep all the times (only remove processor and other stuff)
543 @param functionObjectData: tries do determine which data was written by function obejects and removes it
544 @param additional: List with glob-patterns that are removed too"""
545
546 self.reread()
547
548 last=self.getLast()
549
550 if after==None:
551 try:
552 time=float(self.first)
553 except TypeError:
554 warning("The first timestep in",self.name," is ",self.first,"not a number. Doing nothing")
555 return
556 else:
557 time=float(after)
558
559 if not keepRegular:
560 for f in self.times:
561 if float(f)>time and not (keepLast and f==last):
562 self.rmtree(path.join(self.name,f))
563
564 if path.exists(path.join(self.name,"VTK")) and vtk:
565 self.rmtree(path.join(self.name,"VTK"))
566
567 if self.nrProcs():
568 for f in listdir(self.name):
569 if re.compile("processor[0-9]+").match(f):
570 if removeProcs:
571 self.rmtree(path.join(self.name,f))
572 else:
573 pDir=path.join(self.name,f)
574 for t in listdir(pDir):
575 try:
576 val=float(t)
577 if val>time:
578 self.rmtree(path.join(pDir,t))
579 except ValueError:
580 pass
581
582 if functionObjectData:
583 cd=ParsedParameterFile(self.controlDict())
584 if "functions" in cd:
585 if type(cd["functions"]) in [DictProxy,dict]:
586 for f in cd["functions"]:
587 pth=path.join(self.name,f)
588 if path.exists(pth):
589 self.rmtree(pth)
590 else:
591 for f in cd["functions"][0::2]:
592 pth=path.join(self.name,f)
593 if path.exists(pth):
594 self.rmtree(pth)
595
596 additional+=eval(conf().get("Clearing","additionalpatterns"))
597 for a in additional:
598 self.clearPattern(a)
599
601 """Clear all files that fit a certain shell (glob) pattern
602 @param glob: the pattern which the files are going to fit"""
603
604 for f in glob.glob(path.join(self.name,globPat)):
605 if path.isdir(f):
606 self.rmtree(f,ignore_errors=False)
607 else:
608 os.unlink(f)
609
610 - def clearOther(self,
611 pyfoam=True,
612 clearHistory=False):
613 """Remove additional directories
614 @param pyfoam: rremove all directories typically created by PyFoam"""
615
616 if pyfoam:
617 self.clearPattern("PyFoam.?*")
618 self.clearPattern("*?.analyzed")
619 if clearHistory:
620 self.clearPattern("PyFoamHistory")
621
622 - def clear(self,
623 after=None,
624 processor=True,
625 pyfoam=True,
626 keepLast=False,
627 vtk=True,
628 keepRegular=False,
629 clearHistory=False,
630 functionObjectData=False,
631 additional=[]):
632 """One-stop-shop to remove data
633 @param after: time after which directories ar to be removed
634 @param processor: remove the processorXX directories
635 @param pyfoam: rremove all directories typically created by PyFoam
636 @param keepLast: Keep the last time-step
637 @param additional: list with additional patterns to clear"""
638 self.clearResults(after=after,
639 removeProcs=processor,
640 keepLast=keepLast,
641 vtk=vtk,
642 keepRegular=keepRegular,
643 functionObjectData=functionObjectData,
644 additional=additional)
645 self.clearOther(pyfoam=pyfoam,
646 clearHistory=clearHistory)
647
649 """@return: the name of the first time-directory (==initial
650 conditions)
651 @rtype: str"""
652 self.reread()
653
654 if self.first:
655 return path.join(self.name,self.first)
656 else:
657 if path.exists(path.join(self.name,"0.org")):
658 return path.join(self.name,"0.org")
659 else:
660 return None
661
663 """@return: the name of the first last-directory (==simulation
664 results)
665 @rtype: str"""
666 self.reread()
667
668 last=self.getLast()
669 if last:
670 return path.join(self.name,last)
671 else:
672 return None
673
675 """@param region: Specify the region for cases with more than 1 mesh
676 @param processor: name of the processor directory
677 @return: the name of the C{constant}-directory
678 @rtype: str"""
679 pre=self.name
680 if processor!=None:
681 if type(processor)==int:
682 processor="processor%d" % processor
683 pre=path.join(pre,processor)
684
685 if region==None and self.region!=None:
686 region=self.region
687 if region:
688 return path.join(pre,"constant",region)
689 else:
690 return path.join(pre,"constant")
691
693 """@param region: Specify the region for cases with more than 1 mesh
694 @return: the name of the C{system}-directory
695 @rtype: str"""
696 if region==None and self.region!=None:
697 region=self.region
698 if region:
699 return path.join(self.name,"system",region)
700 else:
701 return path.join(self.name,"system")
702
704 """@return: the name of the C{controlDict}
705 @rtype: str"""
706 return path.join(self.systemDir(),"controlDict")
707
708 - def polyMeshDir(self,region=None,time=None,processor=None):
709 """@param region: Specify the region for cases with more than 1 mesh
710 @return: the name of the C{polyMesh}
711 @param time: Time for which the mesh should be looked at
712 @param processor: Name of the processor directory for decomposed cases
713 @rtype: str"""
714 if region==None and self.region!=None:
715 region=self.region
716 if time==None:
717 return path.join(
718 self.constantDir(
719 region=region,
720 processor=processor),
721 "polyMesh")
722 else:
723 return path.join(
724 TimeDirectory(self.name,
725 time,
726 region=region,
727 processor=processor).name,
728 "polyMesh")
729
730 - def boundaryDict(self,region=None,time=None,processor=None):
731 """@param region: Specify the region for cases with more than 1 mesh
732 @return: name of the C{boundary}-file
733 @rtype: str"""
734 if region==None and self.region!=None:
735 region=self.region
736 return path.join(self.polyMeshDir(region=region,time=time,processor=processor),"boundary")
737
739 """@param region: Specify the region for cases with more than 1 mesh
740 @return: the name of the C{blockMeshDict} if it exists. Returns
741 an empty string if it doesn't
742 @rtype: str"""
743 if region==None and self.region!=None:
744 region=self.region
745 p=path.join(self.polyMeshDir(region=region),"blockMeshDict")
746 if path.exists(p):
747 return p
748 else:
749 return ""
750
752 """create a file in the solution directory and return a
753 corresponding BasicFile-object
754
755 @param name: Name of the file
756 @rtype: L{BasicFile}"""
757 return BasicFile(path.join(self.name,name))
758
760 """Gets a list of all the available mesh regions by checking all
761 directories in constant and using all those that have a polyMesh-subdirectory
762 @param defaultRegion: should the default region also be added (as None)"""
763 lst=[]
764 for d in self.listDirectory(self.constantDir()):
765 if path.isdir(path.join(self.constantDir(),d)):
766 if path.exists(self.polyMeshDir(region=d)):
767 lst.append(d)
768
769 if defaultRegion:
770 if path.exists(self.polyMeshDir()):
771 lst.append(None)
772
773 lst.sort()
774 return lst
775
776 - def addToHistory(self,*text):
777 """Adds a line with date and username to a file 'PyFoamHistory'
778 that resides in the local directory"""
779 hist=open(path.join(self.name,"PyFoamHistory"),"a")
780
781 try:
782
783 username=getlogin()
784 except OSError:
785 username=environ["USER"]
786
787 hist.write("%s by %s in %s :" % (asctime(),username,uname()[1]))
788
789 for t in text:
790 hist.write(str(t)+" ")
791
792 hist.write("\n")
793 hist.close()
794
796 """List all the plain files (not directories) in a subdirectory
797 of the case
798 @param directory: the subdirectory. If unspecified the
799 case-directory itself is used
800 @return: List with the plain filenames"""
801
802 result=[]
803 theDir=self.name
804 if directory:
805 theDir=path.join(theDir,directory)
806
807 for f in listdir(theDir):
808 if f[0]!='.' and f[-1]!='~':
809 if path.isfile(path.join(theDir,f)):
810 result.append(f)
811
812 return result
813
814 - def getDictionaryText(self,directory,name):
815 """@param directory: Sub-directory of the case
816 @param name: name of the dictionary file
817 @return: the contents of the file as a big string"""
818
819 result=None
820 theDir=self.name
821 if directory:
822 theDir=path.join(theDir,directory)
823
824 if path.exists(path.join(theDir,name)):
825 result=open(path.join(theDir,name)).read()
826 else:
827 warning("File",name,"does not exist in directory",directory,"of case",self.name)
828
829 return result
830
831 - def writeDictionaryContents(self,directory,name,contents):
832 """Writes the contents of a dictionary
833 @param directory: Sub-directory of the case
834 @param name: name of the dictionary file
835 @param contents: Python-dictionary with the dictionary contents"""
836
837 theDir=self.name
838 if directory:
839 theDir=path.join(theDir,directory)
840
841 result=WriteParameterFile(path.join(theDir,name))
842 result.content=contents
843 result.writeFile()
844
845 - def writeDictionaryText(self,directory,name,text):
846 """Writes the contents of a dictionary
847 @param directory: Sub-directory of the case
848 @param name: name of the dictionary file
849 @param text: String with the dictionary contents"""
850
851 theDir=self.name
852 if directory:
853 theDir=path.join(theDir,directory)
854
855 result=open(path.join(theDir,name),"w").write(text)
856
857 - def getDictionaryContents(self,directory,name):
858 """@param directory: Sub-directory of the case
859 @param name: name of the dictionary file
860 @return: the contents of the file as a python data-structure"""
861
862 result={}
863 theDir=self.name
864 if directory:
865 theDir=path.join(theDir,directory)
866
867 if path.exists(path.join(theDir,name)):
868 result=ParsedParameterFile(path.join(theDir,name)).content
869 else:
870 warning("File",name,"does not exist in directory",directory,"of case",self.name)
871
872 return result
873
875 """Find out whether this directory is controlled by a VCS and
876 return the abbreviation of that VCS"""
877
878 if path.isdir(path.join(self.name,".hg")):
879 return "hg"
880 elif path.isdir(path.join(self.name,".git")):
881 return "git"
882 elif path.isdir(path.join(self.name,".svn")):
883 return "svn"
884 else:
885 return None
886
888 """Solution directory with a directory for the Chemkin-files"""
889
890 chemkinName = "chemkin"
891
892 - def __init__(self,name,archive="ArchiveDir"):
896
898 """@rtype: str
899 @return: The directory with the Chemkin-Files"""
900
901 return path.join(self.name,self.chemkinName)
902
904 """Convenience class that makes sure that nothing new is created"""
905
906 - def __init__(self,
907 name,
908 region=None):
914
915
916