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
212 """add directory to the list that is needed to clone this case
213 @param name: name of the subdirectory (the case directory is prepended)"""
214 if path.exists(path.join(self.name,name)):
215 self.essential.append(path.join(self.name,name))
216
217 - def cloneCase(self,name,svnRemove=True,followSymlinks=False):
218 """create a clone of this case directory. Remove the target directory, if it already exists
219
220 @param name: Name of the new case directory
221 @param svnRemove: Look for .svn-directories and remove them
222 @param followSymlinks: Follow symbolic links instead of just copying them
223 @rtype: L{SolutionDirectory} or correct subclass
224 @return: The target directory"""
225
226 cpOptions="-R"
227 if followSymlinks:
228 cpOptions+=" -L"
229
230 if path.exists(name):
231 self.execute("rm -r "+name)
232 mkdir(name)
233 for d in self.essential:
234 if d!=None:
235 self.execute("cp "+cpOptions+" "+d+" "+name)
236
237 if svnRemove:
238 self.execute("find "+name+" -name .svn -exec rm -rf {} \\; -prune")
239
240 return self.__class__(name,archive=self.archive)
241
242 - def packCase(self,tarname,last=False,exclude=[],additional=[],base=None):
243 """Packs all the important files into a compressed tarfile.
244 Uses the essential-list and excludes the .svn-directories.
245 Also excludes files ending with ~
246 @param tarname: the name of the tar-file
247 @param last: add the last directory to the list of directories to be added
248 @param exclude: List with additional glob filename-patterns to be excluded
249 @param additional: List with additional glob filename-patterns
250 that are to be added
251 @param base: Different name that is to be used as the baseName for the case inside the tar"""
252
253 ex=["*~",".svn"]+exclude
254 members=self.essential[:]
255 if last:
256 if self.getLast()!=self.first:
257 members.append(self.latestDir())
258 for p in additional:
259 for f in listdir(self.name):
260 if (f not in members) and fnmatch.fnmatch(f,p):
261 members.append(path.join(self.name,f))
262
263 tar=tarfile.open(tarname,"w:gz")
264
265 for m in members:
266 self.addToTar(tar,m,exclude=ex,base=base)
267
268 tar.close()
269
270 - def addToTar(self,tar,name,exclude=[],base=None):
271 """The workhorse for the packCase-method"""
272
273 if base==None:
274 base=path.basename(self.name)
275
276 for e in exclude:
277 if fnmatch.fnmatch(path.basename(name),e):
278 return
279
280 if path.isdir(name):
281 for m in listdir(name):
282 self.addToTar(tar,path.join(name,m),exclude=exclude,base=base)
283 else:
284 arcname=path.join(base,name[len(self.name)+1:])
285 tar.add(name,arcname=arcname)
286
288 """Get a list of the times in the processor0-directory"""
289 result=[]
290
291 proc0=path.join(self.name,"processor0")
292 if path.exists(proc0):
293 for f in listdir(proc0):
294 try:
295 val=float(f)
296 result.append(f)
297 except ValueError:
298 pass
299 result.sort(self.sorttimes)
300 return result
301
302 - def reread(self,force=False):
303 """Rescan the directory for the time directories"""
304
305 if not force and stat(self.name)[ST_CTIME]<=self.lastReread:
306 return
307
308 self.times=[]
309 self.first=None
310 self.last=None
311 procDirs = self.processorDirs()
312 self.procNr=len(procDirs)
313
314 if procDirs and self.parallel:
315 timesDir = path.join(self.name, procDirs[0])
316 else:
317 timesDir = self.name
318
319 for f in listdir(timesDir):
320 try:
321 val=float(f)
322 self.times.append(f)
323 except ValueError:
324 pass
325
326 self.lastReread=stat(self.name)[ST_CTIME]
327
328 self.times.sort(self.sorttimes)
329 if self.times:
330 self.first = self.times[0]
331 self.last = self.times[-1]
332
334 """List with the processor directories"""
335 try:
336 return self.procDirs
337 except:
338 pass
339 self.procDirs=[]
340 for f in listdir(self.name):
341 if re.compile("processor[0-9]+").match(f):
342 self.procDirs.append(f)
343
344 return self.procDirs
345
347 """The number of directories with processor-data"""
348 self.reread()
349 return self.procNr
350
352 """Sort function for the solution files"""
353 if(float(x)==float(y)):
354 return 0
355 elif float(x)<float(y):
356 return -1
357 else:
358 return 1
359
361 """ @return: List of all the available times"""
362 self.reread()
363 return self.times
364
366 """add file to list of files that are to be copied to the
367 archive"""
368 self.backups.append(path.join(self.name,pth))
369
371 """@return: the first time for which a solution exists
372 @rtype: str"""
373 self.reread()
374 return self.first
375
377 """@return: the last time for which a solution exists
378 @rtype: str"""
379 self.reread()
380 return self.last
381
383 """copy the last solution (plus the backup-files to the
384 archive)
385
386 @param name: name of the sub-directory in the archive"""
387 if self.archive==None:
388 print "Warning: nor Archive-directory"
389 return
390
391 self.reread()
392 fname=path.join(self.archive,name)
393 if path.exists(fname):
394 self.execute("rm -r "+fname)
395 mkdir(fname)
396 self.execute("cp -r "+path.join(self.name,self.last)+" "+fname)
397 for f in self.backups:
398 self.execute("cp -r "+f+" "+fname)
399
400 - def clearResults(self,
401 after=None,
402 removeProcs=False,
403 keepLast=False,
404 vtk=True,
405 keepRegular=False,
406 functionObjectData=False):
407 """remove all time-directories after a certain time. If not time ist
408 set the initial time is used
409 @param after: time after which directories ar to be removed
410 @param removeProcs: if True the processorX-directories are removed.
411 Otherwise the timesteps after last are removed from the
412 processor-directories
413 @param keepLast: Keep the data from the last timestep
414 @param vtk: Remove the VTK-directory if it exists
415 @param keepRegular: keep all the times (only remove processor and other stuff)
416 @param functionObjectData: tries do determine which data was written by function obejects and removes it"""
417
418 self.reread()
419
420 last=self.getLast()
421
422 if after==None:
423 try:
424 time=float(self.first)
425 except TypeError:
426 warning("The first timestep in",self.name," is ",self.first,"not a number. Doing nothing")
427 return
428 else:
429 time=float(after)
430
431 if not keepRegular:
432 for f in self.times:
433 if float(f)>time and not (keepLast and f==last):
434 self.execute("rm -r "+path.join(self.name,f))
435
436 if path.exists(path.join(self.name,"VTK")) and vtk:
437 self.execute("rm -r "+path.join(self.name,"VTK"))
438
439 if self.nrProcs():
440 for f in listdir(self.name):
441 if re.compile("processor[0-9]+").match(f):
442 if removeProcs:
443 self.execute("rm -r "+path.join(self.name,f))
444 else:
445 pDir=path.join(self.name,f)
446 for t in listdir(pDir):
447 try:
448 val=float(t)
449 if val>time:
450 self.execute("rm -r "+path.join(pDir,t))
451 except ValueError:
452 pass
453
454 if functionObjectData:
455 cd=ParsedParameterFile(self.controlDict())
456 if "functions" in cd:
457 for f in cd["functions"][0::2]:
458 pth=path.join(self.name,f)
459 if path.exists(pth):
460 shutil.rmtree(pth)
461
463 """Clear all files that fit a certain shell (glob) pattern
464 @param glob: the pattern which the files are going to fit"""
465
466 self.execute("rm -rf "+path.join(self.name,glob))
467
468 - def clearOther(self,
469 pyfoam=True,
470 clearHistory=False):
471 """Remove additional directories
472 @param pyfoam: rremove all directories typically created by PyFoam"""
473
474 if pyfoam:
475 self.clearPattern("PyFoam.?*")
476 self.clearPattern("*?.analyzed")
477 if clearHistory:
478 self.clearPattern("PyFoamHistory")
479
480 - def clear(self,
481 after=None,
482 processor=True,
483 pyfoam=True,
484 keepLast=False,
485 vtk=True,
486 keepRegular=False,
487 clearHistory=False,
488 functionObjectData=False):
489 """One-stop-shop to remove data
490 @param after: time after which directories ar to be removed
491 @param processor: remove the processorXX directories
492 @param pyfoam: rremove all directories typically created by PyFoam
493 @param keepLast: Keep the last time-step"""
494 self.clearResults(after=after,
495 removeProcs=processor,
496 keepLast=keepLast,
497 vtk=vtk,
498 keepRegular=keepRegular,
499 functionObjectData=functionObjectData)
500 self.clearOther(pyfoam=pyfoam,
501 clearHistory=clearHistory)
502
504 """@return: the name of the first time-directory (==initial
505 conditions
506 @rtype: str"""
507 self.reread()
508
509 if self.first:
510 return path.join(self.name,self.first)
511 else:
512 return None
513
515 """@return: the name of the first last-directory (==simulation
516 results)
517 @rtype: str"""
518 self.reread()
519
520 last=self.getLast()
521 if last:
522 return path.join(self.name,last)
523 else:
524 return None
525
527 """@param region: Specify the region for cases with more than 1 mesh
528 @param processor: name of the processor directory
529 @return: the name of the C{constant}-directory
530 @rtype: str"""
531 pre=self.name
532 if processor!=None:
533 if type(processor)==int:
534 processor="processor%d" % processor
535 pre=path.join(pre,processor)
536
537 if region==None and self.region!=None:
538 region=self.region
539 if region:
540 return path.join(pre,"constant",region)
541 else:
542 return path.join(pre,"constant")
543
545 """@param region: Specify the region for cases with more than 1 mesh
546 @return: the name of the C{system}-directory
547 @rtype: str"""
548 if region==None and self.region!=None:
549 region=self.region
550 if region:
551 return path.join(self.name,"system",region)
552 else:
553 return path.join(self.name,"system")
554
556 """@return: the name of the C{controlDict}
557 @rtype: str"""
558 return path.join(self.systemDir(),"controlDict")
559
560 - def polyMeshDir(self,region=None,time=None,processor=None):
561 """@param region: Specify the region for cases with more than 1 mesh
562 @return: the name of the C{polyMesh}
563 @param time: Time for which the mesh should be looked at
564 @param processor: Name of the processor directory for decomposed cases
565 @rtype: str"""
566 if region==None and self.region!=None:
567 region=self.region
568 if time==None:
569 return path.join(
570 self.constantDir(
571 region=region,
572 processor=processor),
573 "polyMesh")
574 else:
575 return path.join(
576 TimeDirectory(self.name,
577 time,
578 region=region,
579 processor=processor).name,
580 "polyMesh")
581
582 - def boundaryDict(self,region=None,time=None,processor=None):
583 """@param region: Specify the region for cases with more than 1 mesh
584 @return: name of the C{boundary}-file
585 @rtype: str"""
586 if region==None and self.region!=None:
587 region=self.region
588 return path.join(self.polyMeshDir(region=region,time=time,processor=processor),"boundary")
589
591 """@param region: Specify the region for cases with more than 1 mesh
592 @return: the name of the C{blockMeshDict} if it exists. Returns
593 an empty string if it doesn't
594 @rtype: str"""
595 if region==None and self.region!=None:
596 region=self.region
597 p=path.join(self.polyMeshDir(region=region),"blockMeshDict")
598 if path.exists(p):
599 return p
600 else:
601 return ""
602
604 """create a file in the solution directory and return a
605 corresponding BasicFile-object
606
607 @param name: Name of the file
608 @rtype: L{BasicFile}"""
609 return BasicFile(path.join(self.name,name))
610
612 """Gets a list of all the available mesh regions by checking all
613 directories in constant and using all those that have a polyMesh-subdirectory"""
614 lst=[]
615 for d in self.listDirectory(self.constantDir()):
616 if path.isdir(path.join(self.constantDir(),d)):
617 if path.exists(self.polyMeshDir(region=d)):
618 lst.append(d)
619 lst.sort()
620 return lst
621
622 - def addToHistory(self,*text):
623 """Adds a line with date and username to a file 'PyFoamHistory'
624 that resides in the local directory"""
625 hist=open(path.join(self.name,"PyFoamHistory"),"a")
626
627 try:
628
629 username=getlogin()
630 except OSError:
631 username=environ["USER"]
632
633 hist.write("%s by %s in %s :" % (asctime(),username,uname()[1]))
634
635 for t in text:
636 hist.write(str(t)+" ")
637
638 hist.write("\n")
639 hist.close()
640
642 """List all the plain files (not directories) in a subdirectory
643 of the case
644 @param directory: the subdirectory. If unspecified the
645 case-directory itself is used
646 @return: List with the plain filenames"""
647
648 result=[]
649 theDir=self.name
650 if directory:
651 theDir=path.join(theDir,directory)
652
653 for f in listdir(theDir):
654 if f[0]!='.' and f[-1]!='~':
655 if path.isfile(path.join(theDir,f)):
656 result.append(f)
657
658 return result
659
660 - def getDictionaryText(self,directory,name):
661 """@param directory: Sub-directory of the case
662 @param name: name of the dictionary file
663 @return: the contents of the file as a big string"""
664
665 result=None
666 theDir=self.name
667 if directory:
668 theDir=path.join(theDir,directory)
669
670 if path.exists(path.join(theDir,name)):
671 result=open(path.join(theDir,name)).read()
672 else:
673 warning("File",name,"does not exist in directory",directory,"of case",self.name)
674
675 return result
676
677 - def writeDictionaryContents(self,directory,name,contents):
678 """Writes the contents of a dictionary
679 @param directory: Sub-directory of the case
680 @param name: name of the dictionary file
681 @param contents: Python-dictionary with the dictionary contents"""
682
683 theDir=self.name
684 if directory:
685 theDir=path.join(theDir,directory)
686
687 result=WriteParameterFile(path.join(theDir,name))
688 result.content=contents
689 result.writeFile()
690
691 - def writeDictionaryText(self,directory,name,text):
692 """Writes the contents of a dictionary
693 @param directory: Sub-directory of the case
694 @param name: name of the dictionary file
695 @param text: String with the dictionary contents"""
696
697 theDir=self.name
698 if directory:
699 theDir=path.join(theDir,directory)
700
701 result=open(path.join(theDir,name),"w").write(text)
702
703 - def getDictionaryContents(self,directory,name):
704 """@param directory: Sub-directory of the case
705 @param name: name of the dictionary file
706 @return: the contents of the file as a python data-structure"""
707
708 result={}
709 theDir=self.name
710 if directory:
711 theDir=path.join(theDir,directory)
712
713 if path.exists(path.join(theDir,name)):
714 result=ParsedParameterFile(path.join(theDir,name)).content
715 else:
716 warning("File",name,"does not exist in directory",directory,"of case",self.name)
717
718 return result
719
721 """Solution directory with a directory for the Chemkin-files"""
722
723 chemkinName = "chemkin"
724
725 - def __init__(self,name,archive="ArchiveDir"):
729
731 """@rtype: str
732 @return: The directory with the Chemkin-Files"""
733
734 return path.join(self.name,self.chemkinName)
735
737 """Convenience class that makes sure that nothing new is created"""
738
739 - def __init__(self,
740 name,
741 region=None):
747