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
68 if paraviewLink and not path.exists(self.controlDict()+".foam"):
69 symlink(path.basename(self.controlDict()),self.controlDict()+".foam")
70
71 emptyFoamFile=path.join(self.name,path.basename(self.name)+".foam")
72 if paraviewLink and not path.exists(emptyFoamFile):
73 dummy=open(emptyFoamFile,"w")
74
76 """Use the parallel times instead of the serial.
77
78 Used to reset the behaviour after it has been set by the constructor"""
79 if self.parallel:
80 warning(self.name,"is already in parallel mode")
81 else:
82 self.parallel=True
83 if self.processorDirs():
84 self.dirPrefix = self.processorDirs()[0]
85 self.reread(force=True)
86
88 """Add the local configuration file of the case to the configuration"""
89 fName=path.join(self.name,"LocalConfigPyFoam")
90 if path.exists(fName):
91 conf().addFile(fName)
92
96
98 self.reread()
99
100 if self.timeName(item)!=None:
101 return True
102 else:
103 return False
104
113
126
136
141
143 """Finds the name of a directory that corresponds with the given parameter
144 @param item: the time that should be found
145 @param minTime: search for the time with the minimal difference.
146 Otherwise an exact match will be searched"""
147
148 if type(item)==int:
149 return self.times[item]
150 else:
151 ind=self.timeIndex(item,minTime)
152 if ind==None:
153 return None
154 else:
155 return self.times[ind]
156
158 """Finds the index 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 self.reread()
163
164 time=float(item)
165 result=None
166
167 if minTime:
168 result=0
169 for i in range(1,len(self.times)):
170 if abs(float(self.times[result])-time)>abs(float(self.times[i])-time):
171 result=i
172 else:
173 for i in range(len(self.times)):
174 t=self.times[i]
175 if abs(float(t)-time)<1e-6:
176 if result==None:
177 result=i
178 elif abs(float(t)-time)<abs(float(self.times[result])-time):
179 result=i
180
181 return result
182
184 if self.dirPrefix:
185 return path.join(self.dirPrefix, time)
186 return time
187
189 """Checks whether this is a valid case directory by looking for
190 the system- and constant-directories and the controlDict-file"""
191
192 return len(self.missingFiles())==0
193
210
219
220 - def cloneCase(self,name,svnRemove=True,followSymlinks=False):
221 """create a clone of this case directory. Remove the target directory, if it already exists
222
223 @param name: Name of the new case directory
224 @param svnRemove: Look for .svn-directories and remove them
225 @param followSymlinks: Follow symbolic links instead of just copying them
226 @rtype: L{SolutionDirectory} or correct subclass
227 @return: The target directory"""
228
229 cpOptions="-R"
230 if followSymlinks:
231 cpOptions+=" -L"
232
233 if path.exists(name):
234 self.execute("rm -r "+name)
235 mkdir(name)
236 if self.parallel:
237 for i in range(self.nrProcs()):
238 mkdir(path.join(name,"processor%d" % i))
239
240 for d in self.essential:
241 if d!=None:
242 if self.parallel:
243 pth,fl=path.split(d)
244 if path.exists(path.join(pth,"processor0",fl)):
245 for i in range(self.nrProcs()):
246 self.execute("cp "+cpOptions+" "
247 + path.join(pth,"processor%d" % i,fl) +" "
248 + path.join(name,"processor%d" % i))
249
250 if path.exists(d):
251 self.execute("cp "+cpOptions+" "+d+" "+name)
252
253 if svnRemove:
254 self.execute("find "+name+" -name .svn -exec rm -rf {} \\; -prune")
255
256 return self.__class__(name,archive=self.archive)
257
258 - def packCase(self,tarname,last=False,exclude=[],additional=[],base=None):
259 """Packs all the important files into a compressed tarfile.
260 Uses the essential-list and excludes the .svn-directories.
261 Also excludes files ending with ~
262 @param tarname: the name of the tar-file
263 @param last: add the last directory to the list of directories to be added
264 @param exclude: List with additional glob filename-patterns to be excluded
265 @param additional: List with additional glob filename-patterns
266 that are to be added
267 @param base: Different name that is to be used as the baseName for the case inside the tar"""
268
269 ex=["*~",".svn"]+exclude
270 members=self.essential[:]
271 if last:
272 if self.getLast()!=self.first:
273 members.append(self.latestDir())
274 for p in additional:
275 for f in listdir(self.name):
276 if (f not in members) and fnmatch.fnmatch(f,p):
277 members.append(path.join(self.name,f))
278
279 tar=tarfile.open(tarname,"w:gz")
280
281 for m in members:
282 self.addToTar(tar,m,exclude=ex,base=base)
283
284 tar.close()
285
286 - def addToTar(self,tar,name,exclude=[],base=None):
287 """The workhorse for the packCase-method"""
288
289 if base==None:
290 base=path.basename(self.name)
291
292 for e in exclude:
293 if fnmatch.fnmatch(path.basename(name),e):
294 return
295
296 if path.isdir(name):
297 for m in listdir(name):
298 self.addToTar(tar,path.join(name,m),exclude=exclude,base=base)
299 else:
300 arcname=path.join(base,name[len(self.name)+1:])
301 tar.add(name,arcname=arcname)
302
304 """Get a list of the times in the processor0-directory"""
305 result=[]
306
307 proc0=path.join(self.name,"processor0")
308 if path.exists(proc0):
309 for f in listdir(proc0):
310 try:
311 val=float(f)
312 result.append(f)
313 except ValueError:
314 pass
315 result.sort(self.sorttimes)
316 return result
317
318 - def reread(self,force=False):
319 """Rescan the directory for the time directories"""
320
321 if not force and stat(self.name)[ST_CTIME]<=self.lastReread:
322 return
323
324 self.times=[]
325 self.first=None
326 self.last=None
327 procDirs = self.processorDirs()
328 self.procNr=len(procDirs)
329
330 if procDirs and self.parallel:
331 timesDir = path.join(self.name, procDirs[0])
332 else:
333 timesDir = self.name
334
335 for f in listdir(timesDir):
336 try:
337 val=float(f)
338 self.times.append(f)
339 except ValueError:
340 pass
341
342 self.lastReread=stat(self.name)[ST_CTIME]
343
344 self.times.sort(self.sorttimes)
345 if self.times:
346 self.first = self.times[0]
347 self.last = self.times[-1]
348
350 """List with the processor directories"""
351 try:
352 return self.procDirs
353 except:
354 pass
355 self.procDirs=[]
356 for f in listdir(self.name):
357 if re.compile("processor[0-9]+").match(f):
358 self.procDirs.append(f)
359
360 return self.procDirs
361
363 """The number of directories with processor-data"""
364 self.reread()
365 return self.procNr
366
368 """Sort function for the solution files"""
369 if(float(x)==float(y)):
370 return 0
371 elif float(x)<float(y):
372 return -1
373 else:
374 return 1
375
377 """ @return: List of all the available times"""
378 self.reread()
379 return self.times
380
382 """add file to list of files that are to be copied to the
383 archive"""
384 self.backups.append(path.join(self.name,pth))
385
387 """@return: the first time for which a solution exists
388 @rtype: str"""
389 self.reread()
390 return self.first
391
393 """@return: the last time for which a solution exists
394 @rtype: str"""
395 self.reread()
396 return self.last
397
399 """copy the last solution (plus the backup-files to the
400 archive)
401
402 @param name: name of the sub-directory in the archive"""
403 if self.archive==None:
404 print "Warning: nor Archive-directory"
405 return
406
407 self.reread()
408 fname=path.join(self.archive,name)
409 if path.exists(fname):
410 self.execute("rm -r "+fname)
411 mkdir(fname)
412 self.execute("cp -r "+path.join(self.name,self.last)+" "+fname)
413 for f in self.backups:
414 self.execute("cp -r "+f+" "+fname)
415
416 - def clearResults(self,
417 after=None,
418 removeProcs=False,
419 keepLast=False,
420 vtk=True,
421 keepRegular=False,
422 functionObjectData=False):
423 """remove all time-directories after a certain time. If not time ist
424 set the initial time is used
425 @param after: time after which directories ar to be removed
426 @param removeProcs: if True the processorX-directories are removed.
427 Otherwise the timesteps after last are removed from the
428 processor-directories
429 @param keepLast: Keep the data from the last timestep
430 @param vtk: Remove the VTK-directory if it exists
431 @param keepRegular: keep all the times (only remove processor and other stuff)
432 @param functionObjectData: tries do determine which data was written by function obejects and removes it"""
433
434 self.reread()
435
436 last=self.getLast()
437
438 if after==None:
439 try:
440 time=float(self.first)
441 except TypeError:
442 warning("The first timestep in",self.name," is ",self.first,"not a number. Doing nothing")
443 return
444 else:
445 time=float(after)
446
447 if not keepRegular:
448 for f in self.times:
449 if float(f)>time and not (keepLast and f==last):
450 self.execute("rm -r "+path.join(self.name,f))
451
452 if path.exists(path.join(self.name,"VTK")) and vtk:
453 self.execute("rm -r "+path.join(self.name,"VTK"))
454
455 if self.nrProcs():
456 for f in listdir(self.name):
457 if re.compile("processor[0-9]+").match(f):
458 if removeProcs:
459 self.execute("rm -r "+path.join(self.name,f))
460 else:
461 pDir=path.join(self.name,f)
462 for t in listdir(pDir):
463 try:
464 val=float(t)
465 if val>time:
466 self.execute("rm -r "+path.join(pDir,t))
467 except ValueError:
468 pass
469
470 if functionObjectData:
471 cd=ParsedParameterFile(self.controlDict())
472 if "functions" in cd:
473 for f in cd["functions"][0::2]:
474 pth=path.join(self.name,f)
475 if path.exists(pth):
476 shutil.rmtree(pth)
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 self.execute("rm -rf "+path.join(self.name,glob))
483
484 - def clearOther(self,
485 pyfoam=True,
486 clearHistory=False):
487 """Remove additional directories
488 @param pyfoam: rremove all directories typically created by PyFoam"""
489
490 if pyfoam:
491 self.clearPattern("PyFoam.?*")
492 self.clearPattern("*?.analyzed")
493 if clearHistory:
494 self.clearPattern("PyFoamHistory")
495
496 - def clear(self,
497 after=None,
498 processor=True,
499 pyfoam=True,
500 keepLast=False,
501 vtk=True,
502 keepRegular=False,
503 clearHistory=False,
504 functionObjectData=False):
505 """One-stop-shop to remove data
506 @param after: time after which directories ar to be removed
507 @param processor: remove the processorXX directories
508 @param pyfoam: rremove all directories typically created by PyFoam
509 @param keepLast: Keep the last time-step"""
510 self.clearResults(after=after,
511 removeProcs=processor,
512 keepLast=keepLast,
513 vtk=vtk,
514 keepRegular=keepRegular,
515 functionObjectData=functionObjectData)
516 self.clearOther(pyfoam=pyfoam,
517 clearHistory=clearHistory)
518
520 """@return: the name of the first time-directory (==initial
521 conditions
522 @rtype: str"""
523 self.reread()
524
525 if self.first:
526 return path.join(self.name,self.first)
527 else:
528 return None
529
531 """@return: the name of the first last-directory (==simulation
532 results)
533 @rtype: str"""
534 self.reread()
535
536 last=self.getLast()
537 if last:
538 return path.join(self.name,last)
539 else:
540 return None
541
543 """@param region: Specify the region for cases with more than 1 mesh
544 @param processor: name of the processor directory
545 @return: the name of the C{constant}-directory
546 @rtype: str"""
547 pre=self.name
548 if processor!=None:
549 if type(processor)==int:
550 processor="processor%d" % processor
551 pre=path.join(pre,processor)
552
553 if region==None and self.region!=None:
554 region=self.region
555 if region:
556 return path.join(pre,"constant",region)
557 else:
558 return path.join(pre,"constant")
559
561 """@param region: Specify the region for cases with more than 1 mesh
562 @return: the name of the C{system}-directory
563 @rtype: str"""
564 if region==None and self.region!=None:
565 region=self.region
566 if region:
567 return path.join(self.name,"system",region)
568 else:
569 return path.join(self.name,"system")
570
572 """@return: the name of the C{controlDict}
573 @rtype: str"""
574 return path.join(self.systemDir(),"controlDict")
575
576 - def polyMeshDir(self,region=None,time=None,processor=None):
577 """@param region: Specify the region for cases with more than 1 mesh
578 @return: the name of the C{polyMesh}
579 @param time: Time for which the mesh should be looked at
580 @param processor: Name of the processor directory for decomposed cases
581 @rtype: str"""
582 if region==None and self.region!=None:
583 region=self.region
584 if time==None:
585 return path.join(
586 self.constantDir(
587 region=region,
588 processor=processor),
589 "polyMesh")
590 else:
591 return path.join(
592 TimeDirectory(self.name,
593 time,
594 region=region,
595 processor=processor).name,
596 "polyMesh")
597
598 - def boundaryDict(self,region=None,time=None,processor=None):
599 """@param region: Specify the region for cases with more than 1 mesh
600 @return: name of the C{boundary}-file
601 @rtype: str"""
602 if region==None and self.region!=None:
603 region=self.region
604 return path.join(self.polyMeshDir(region=region,time=time,processor=processor),"boundary")
605
607 """@param region: Specify the region for cases with more than 1 mesh
608 @return: the name of the C{blockMeshDict} if it exists. Returns
609 an empty string if it doesn't
610 @rtype: str"""
611 if region==None and self.region!=None:
612 region=self.region
613 p=path.join(self.polyMeshDir(region=region),"blockMeshDict")
614 if path.exists(p):
615 return p
616 else:
617 return ""
618
620 """create a file in the solution directory and return a
621 corresponding BasicFile-object
622
623 @param name: Name of the file
624 @rtype: L{BasicFile}"""
625 return BasicFile(path.join(self.name,name))
626
628 """Gets a list of all the available mesh regions by checking all
629 directories in constant and using all those that have a polyMesh-subdirectory"""
630 lst=[]
631 for d in self.listDirectory(self.constantDir()):
632 if path.isdir(path.join(self.constantDir(),d)):
633 if path.exists(self.polyMeshDir(region=d)):
634 lst.append(d)
635 lst.sort()
636 return lst
637
638 - def addToHistory(self,*text):
639 """Adds a line with date and username to a file 'PyFoamHistory'
640 that resides in the local directory"""
641 hist=open(path.join(self.name,"PyFoamHistory"),"a")
642
643 try:
644
645 username=getlogin()
646 except OSError:
647 username=environ["USER"]
648
649 hist.write("%s by %s in %s :" % (asctime(),username,uname()[1]))
650
651 for t in text:
652 hist.write(str(t)+" ")
653
654 hist.write("\n")
655 hist.close()
656
658 """List all the plain files (not directories) in a subdirectory
659 of the case
660 @param directory: the subdirectory. If unspecified the
661 case-directory itself is used
662 @return: List with the plain filenames"""
663
664 result=[]
665 theDir=self.name
666 if directory:
667 theDir=path.join(theDir,directory)
668
669 for f in listdir(theDir):
670 if f[0]!='.' and f[-1]!='~':
671 if path.isfile(path.join(theDir,f)):
672 result.append(f)
673
674 return result
675
676 - def getDictionaryText(self,directory,name):
677 """@param directory: Sub-directory of the case
678 @param name: name of the dictionary file
679 @return: the contents of the file as a big string"""
680
681 result=None
682 theDir=self.name
683 if directory:
684 theDir=path.join(theDir,directory)
685
686 if path.exists(path.join(theDir,name)):
687 result=open(path.join(theDir,name)).read()
688 else:
689 warning("File",name,"does not exist in directory",directory,"of case",self.name)
690
691 return result
692
693 - def writeDictionaryContents(self,directory,name,contents):
694 """Writes the contents of a dictionary
695 @param directory: Sub-directory of the case
696 @param name: name of the dictionary file
697 @param contents: Python-dictionary with the dictionary contents"""
698
699 theDir=self.name
700 if directory:
701 theDir=path.join(theDir,directory)
702
703 result=WriteParameterFile(path.join(theDir,name))
704 result.content=contents
705 result.writeFile()
706
707 - def writeDictionaryText(self,directory,name,text):
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 text: String with the dictionary contents"""
712
713 theDir=self.name
714 if directory:
715 theDir=path.join(theDir,directory)
716
717 result=open(path.join(theDir,name),"w").write(text)
718
719 - def getDictionaryContents(self,directory,name):
720 """@param directory: Sub-directory of the case
721 @param name: name of the dictionary file
722 @return: the contents of the file as a python data-structure"""
723
724 result={}
725 theDir=self.name
726 if directory:
727 theDir=path.join(theDir,directory)
728
729 if path.exists(path.join(theDir,name)):
730 result=ParsedParameterFile(path.join(theDir,name)).content
731 else:
732 warning("File",name,"does not exist in directory",directory,"of case",self.name)
733
734 return result
735
737 """Find out whether this directory is controlled by a VCS and
738 return the abbreviation of that VCS"""
739
740 if path.isdir(path.join(self.name,".hg")):
741 return "hg"
742 elif path.isdir(path.join(self.name,".git")):
743 return "git"
744 elif path.isdir(path.join(self.name,".svn")):
745 return "svn"
746 else:
747 return None
748
750 """Solution directory with a directory for the Chemkin-files"""
751
752 chemkinName = "chemkin"
753
754 - def __init__(self,name,archive="ArchiveDir"):
758
760 """@rtype: str
761 @return: The directory with the Chemkin-Files"""
762
763 return path.join(self.name,self.chemkinName)
764
766 """Convenience class that makes sure that nothing new is created"""
767
768 - def __init__(self,
769 name,
770 region=None):
776