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
8 from TimeDirectory import TimeDirectory
9 from ParsedParameterFile import ParsedParameterFile,WriteParameterFile
10
11 from os import listdir,path,mkdir,symlink,stat,getlogin,uname,environ
12 from time import asctime
13 from stat import ST_CTIME
14 import tarfile,fnmatch
15 import re
16
18 """Represents a solution directory
19
20 In the solution directory subdirectories whose names are numbers
21 are assumed to be solutions for a specific time-step
22
23 A sub-directory (called the Archive) is created to which solution
24 data is copied"""
25
26 - def __init__(self,
27 name,
28 archive="ArchiveDir",
29 paraviewLink=True,
30 parallel=False,
31 region=None):
32 """@param name: Name of the solution directory
33 @param archive: name of the directory where the lastToArchive-method
34 should copy files, if None no archive is created
35 @param paraviewLink: Create a symbolic link controlDict.foam for paraview
36 @param parallel: use the first processor-subdirectory for the authorative information
37 @param region: Mesh region for multi-region cases"""
38
39 self.name=path.abspath(name)
40 self.archive=None
41 if archive!=None:
42 self.archive=path.join(name,archive)
43 if not path.exists(self.archive):
44 mkdir(self.archive)
45
46 self.region=region
47 self.backups=[]
48
49 self.parallel=parallel
50
51 self.lastReread=0L
52 self.reread()
53
54 self.dirPrefix=''
55 if self.processorDirs() and parallel:
56 self.dirPrefix = self.processorDirs()[0]
57
58 self.essential=[self.systemDir(),
59 self.constantDir(),
60 self.initialDir()]
61 self.addToClone("PyFoamHistory")
62
63 if paraviewLink and not path.exists(self.controlDict()+".foam"):
64 symlink(path.basename(self.controlDict()),self.controlDict()+".foam")
65
69
71 self.reread()
72
73 if self.timeName(item)!=None:
74 return True
75 else:
76 return False
77
86
99
109
114
116 """Finds the name of a directory that corresponds with the given parameter
117 @param item: the time that should be found
118 @param minTime: search for the time with the minimal difference.
119 Otherwise an exact match will be searched"""
120
121 if type(item)==int:
122 return self.times[item]
123 else:
124 ind=self.timeIndex(item,minTime)
125 if ind==None:
126 return None
127 else:
128 return self.times[ind]
129
131 """Finds the index of a directory that corresponds with the given parameter
132 @param item: the time that should be found
133 @param minTime: search for the time with the minimal difference.
134 Otherwise an exact match will be searched"""
135 self.reread()
136
137 time=float(item)
138 result=None
139
140 if minTime:
141 result=0
142 for i in range(1,len(self.times)):
143 if abs(float(self.times[result])-time)>abs(float(self.times[i])-time):
144 result=i
145 else:
146 for i in range(len(self.times)):
147 t=self.times[i]
148 if abs(float(t)-time)<1e-6:
149 if result==None:
150 result=i
151 elif abs(float(t)-time)<abs(float(self.times[result])-time):
152 result=i
153
154 return result
155
157 if self.dirPrefix:
158 return path.join(self.dirPrefix, time)
159 return time
160
162 """Checks whether this is a valid case directory by looking for
163 the system- and constant-directories and the controlDict-file"""
164
165 return len(self.missingFiles())==0
166
183
185 """add directory to the list that is needed to clone this case
186 @param name: name of the subdirectory (the case directory is prepended)"""
187 if path.exists(path.join(self.name,name)):
188 self.essential.append(path.join(self.name,name))
189
190 - def cloneCase(self,name,svnRemove=True,followSymlinks=False):
191 """create a clone of this case directory. Remove the target directory, if it already exists
192
193 @param name: Name of the new case directory
194 @param svnRemove: Look for .svn-directories and remove them
195 @param followSymlinks: Follow symbolic links instead of just copying them
196 @rtype: L{SolutionDirectory} or correct subclass
197 @return: The target directory"""
198
199 cpOptions="-R"
200 if followSymlinks:
201 cpOptions+=" -L"
202
203 if path.exists(name):
204 self.execute("rm -r "+name)
205 mkdir(name)
206 for d in self.essential:
207 if d!=None:
208 self.execute("cp "+cpOptions+" "+d+" "+name)
209
210 if svnRemove:
211 self.execute("find "+name+" -name .svn -exec rm -rf {} \\; -prune")
212
213 return self.__class__(name,archive=self.archive)
214
215 - def packCase(self,tarname,last=False,exclude=[],additional=[],base=None):
216 """Packs all the important files into a compressed tarfile.
217 Uses the essential-list and excludes the .svn-directories.
218 Also excludes files ending with ~
219 @param tarname: the name of the tar-file
220 @param last: add the last directory to the list of directories to be added
221 @param exclude: List with additional glob filename-patterns to be excluded
222 @param additional: List with additional glob filename-patterns
223 that are to be added
224 @param base: Different name that is to be used as the baseName for the case inside the tar"""
225
226 ex=["*~",".svn"]+exclude
227 members=self.essential[:]
228 if last:
229 if self.getLast()!=self.first:
230 members.append(self.latestDir())
231 for p in additional:
232 for f in listdir(self.name):
233 if (f not in members) and fnmatch.fnmatch(f,p):
234 members.append(path.join(self.name,f))
235
236 tar=tarfile.open(tarname,"w:gz")
237
238 for m in members:
239 self.addToTar(tar,m,exclude=ex,base=base)
240
241 tar.close()
242
243 - def addToTar(self,tar,name,exclude=[],base=None):
244 """The workhorse for the packCase-method"""
245
246 if base==None:
247 base=path.basename(self.name)
248
249 for e in exclude:
250 if fnmatch.fnmatch(path.basename(name),e):
251 return
252
253 if path.isdir(name):
254 for m in listdir(name):
255 self.addToTar(tar,path.join(name,m),exclude=exclude,base=base)
256 else:
257 arcname=path.join(base,name[len(self.name)+1:])
258 tar.add(name,arcname=arcname)
259
261 """Get a list of the times in the processor0-directory"""
262 result=[]
263
264 proc0=path.join(self.name,"processor0")
265 if path.exists(proc0):
266 for f in listdir(proc0):
267 try:
268 val=float(f)
269 result.append(f)
270 except ValueError:
271 pass
272 result.sort(self.sorttimes)
273 return result
274
275 - def reread(self,force=False):
276 """Rescan the directory for the time directories"""
277
278 if not force and stat(self.name)[ST_CTIME]<=self.lastReread:
279 return
280
281 self.times=[]
282 self.first=None
283 self.last=None
284 procDirs = self.processorDirs()
285 self.procNr=len(procDirs)
286
287 if procDirs and self.parallel:
288 timesDir = path.join(self.name, procDirs[0])
289 else:
290 timesDir = self.name
291
292 for f in listdir(timesDir):
293 try:
294 val=float(f)
295 self.times.append(f)
296 except ValueError:
297 pass
298
299 self.lastReread=stat(self.name)[ST_CTIME]
300
301 self.times.sort(self.sorttimes)
302 if self.times:
303 self.first = self.times[0]
304 self.last = self.times[-1]
305
307 """List with the processor directories"""
308 try:
309 return self.procDirs
310 except:
311 pass
312 self.procDirs=[]
313 for f in listdir(self.name):
314 if re.compile("processor[0-9]+").match(f):
315 self.procDirs.append(f)
316
317 return self.procDirs
318
320 """The number of directories with processor-data"""
321 self.reread()
322 return self.procNr
323
325 """Sort function for the solution files"""
326 if(float(x)==float(y)):
327 return 0
328 elif float(x)<float(y):
329 return -1
330 else:
331 return 1
332
334 """ @return: List of all the available times"""
335 self.reread()
336 return self.times
337
339 """add file to list of files that are to be copied to the
340 archive"""
341 self.backups.append(path.join(self.name,pth))
342
344 """@return: the first time for which a solution exists
345 @rtype: str"""
346 self.reread()
347 return self.first
348
350 """@return: the last time for which a solution exists
351 @rtype: str"""
352 self.reread()
353 return self.last
354
356 """copy the last solution (plus the backup-files to the
357 archive)
358
359 @param name: name of the sub-directory in the archive"""
360 if self.archive==None:
361 print "Warning: nor Archive-directory"
362 return
363
364 self.reread()
365 fname=path.join(self.archive,name)
366 if path.exists(fname):
367 self.execute("rm -r "+fname)
368 mkdir(fname)
369 self.execute("cp -r "+path.join(self.name,self.last)+" "+fname)
370 for f in self.backups:
371 self.execute("cp -r "+f+" "+fname)
372
373 - def clearResults(self,after=None,removeProcs=False,keepLast=False,vtk=True,keepRegular=False):
374 """remove all time-directories after a certain time. If not time ist
375 set the initial time is used
376 @param after: time after which directories ar to be removed
377 @param removeProcs: if True the processorX-directories are removed.
378 Otherwise the timesteps after last are removed from the
379 processor-directories
380 @param keepLast: Keep the data from the last timestep
381 @param vtk: Remove the VTK-directory if it exists
382 @param keepRegular: keep all the times (only remove processor and other stuff)"""
383
384 self.reread()
385
386 last=self.getLast()
387
388 if after==None:
389 try:
390 time=float(self.first)
391 except TypeError:
392 warning("The first timestep in",self.name," is ",self.first,"not a number. Doing nothing")
393 return
394 else:
395 time=float(after)
396
397 if not keepRegular:
398 for f in self.times:
399 if float(f)>time and not (keepLast and f==last):
400 self.execute("rm -r "+path.join(self.name,f))
401
402 if path.exists(path.join(self.name,"VTK")) and vtk:
403 self.execute("rm -r "+path.join(self.name,"VTK"))
404
405 if self.nrProcs():
406 for f in listdir(self.name):
407 if re.compile("processor[0-9]+").match(f):
408 if removeProcs:
409 self.execute("rm -r "+path.join(self.name,f))
410 else:
411 pDir=path.join(self.name,f)
412 for t in listdir(pDir):
413 try:
414 val=float(t)
415 if val>time:
416 self.execute("rm -r "+path.join(pDir,t))
417 except ValueError:
418 pass
419
421 """Clear all files that fit a certain shell (glob) pattern
422 @param glob: the pattern which the files are going to fit"""
423
424 self.execute("rm -rf "+path.join(self.name,glob))
425
426 - def clearOther(self,
427 pyfoam=True,
428 clearHistory=False):
429 """Remove additional directories
430 @param pyfoam: rremove all directories typically created by PyFoam"""
431
432 if pyfoam:
433 self.clearPattern("PyFoam.?*")
434 self.clearPattern("*?.analyzed")
435 if clearHistory:
436 self.clearPattern("PyFoamHistory")
437
438 - def clear(self,
439 after=None,
440 processor=True,
441 pyfoam=True,
442 keepLast=False,
443 vtk=True,
444 keepRegular=False,
445 clearHistory=False):
446 """One-stop-shop to remove data
447 @param after: time after which directories ar to be removed
448 @param processor: remove the processorXX directories
449 @param pyfoam: rremove all directories typically created by PyFoam
450 @param keepLast: Keep the last time-step"""
451 self.clearResults(after=after,
452 removeProcs=processor,
453 keepLast=keepLast,
454 vtk=vtk,
455 keepRegular=keepRegular)
456 self.clearOther(pyfoam=pyfoam,
457 clearHistory=clearHistory)
458
460 """@return: the name of the first time-directory (==initial
461 conditions
462 @rtype: str"""
463 self.reread()
464
465 if self.first:
466 return path.join(self.name,self.first)
467 else:
468 return None
469
471 """@return: the name of the first last-directory (==simulation
472 results)
473 @rtype: str"""
474 self.reread()
475
476 last=self.getLast()
477 if last:
478 return path.join(self.name,last)
479 else:
480 return None
481
483 """@param region: Specify the region for cases with more than 1 mesh
484 @param processor: name of the processor directory
485 @return: the name of the C{constant}-directory
486 @rtype: str"""
487 pre=self.name
488 if processor!=None:
489 pre=path.join(pre,processor)
490
491 if region==None and self.region!=None:
492 region=self.region
493 if region:
494 return path.join(pre,"constant",region)
495 else:
496 return path.join(pre,"constant")
497
499 """@param region: Specify the region for cases with more than 1 mesh
500 @return: the name of the C{system}-directory
501 @rtype: str"""
502 if region==None and self.region!=None:
503 region=self.region
504 if region:
505 return path.join(self.name,"system",region)
506 else:
507 return path.join(self.name,"system")
508
510 """@return: the name of the C{controlDict}
511 @rtype: str"""
512 return path.join(self.systemDir(),"controlDict")
513
514 - def polyMeshDir(self,region=None,time="constant",processor=None):
515 """@param region: Specify the region for cases with more than 1 mesh
516 @return: the name of the C{polyMesh}
517 @param time: Time for which the mesh should be looked at
518 @param processor: Name of the processor directory for decomposed cases
519 @rtype: str"""
520 if region==None and self.region!=None:
521 region=self.region
522 return path.join(self.constantDir(region=region,processor=processor),"polyMesh")
523
524 - def boundaryDict(self,region=None,time="constant",processor=None):
525 """@param region: Specify the region for cases with more than 1 mesh
526 @return: name of the C{boundary}-file
527 @rtype: str"""
528 if region==None and self.region!=None:
529 region=self.region
530 return path.join(self.polyMeshDir(region=region,time=time,processor=processor),"boundary")
531
533 """@param region: Specify the region for cases with more than 1 mesh
534 @return: the name of the C{blockMeshDict} if it exists. Returns
535 an empty string if it doesn't
536 @rtype: str"""
537 if region==None and self.region!=None:
538 region=self.region
539 p=path.join(self.polyMeshDir(region=region),"blockMeshDict")
540 if path.exists(p):
541 return p
542 else:
543 return ""
544
546 """create a file in the solution directory and return a
547 corresponding BasicFile-object
548
549 @param name: Name of the file
550 @rtype: L{BasicFile}"""
551 return BasicFile(path.join(self.name,name))
552
554 """Gets a list of all the available mesh regions by checking all
555 directories in constant and using all those that have a polyMesh-subdirectory"""
556 lst=[]
557 for d in self.listDirectory(self.constantDir()):
558 if path.isdir(path.join(self.constantDir(),d)):
559 if path.exists(self.polyMeshDir(region=d)):
560 lst.append(d)
561 lst.sort()
562 return lst
563
564 - def addToHistory(self,*text):
565 """Adds a line with date and username to a file 'PyFoamHistory'
566 that resides in the local directory"""
567 hist=open(path.join(self.name,"PyFoamHistory"),"a")
568
569 try:
570
571 username=getlogin()
572 except OSError:
573 username=environ["USER"]
574
575 hist.write("%s by %s in %s :" % (asctime(),username,uname()[1]))
576
577 for t in text:
578 hist.write(str(t)+" ")
579
580 hist.write("\n")
581 hist.close()
582
584 """List all the plain files (not directories) in a subdirectory
585 of the case
586 @param directory: the subdirectory. If unspecified the
587 case-directory itself is used
588 @return: List with the plain filenames"""
589
590 result=[]
591 theDir=self.name
592 if directory:
593 theDir=path.join(theDir,directory)
594
595 for f in listdir(theDir):
596 if f[0]!='.' and f[-1]!='~':
597 if path.isfile(path.join(theDir,f)):
598 result.append(f)
599
600 return result
601
602 - def getDictionaryText(self,directory,name):
603 """@param directory: Sub-directory of the case
604 @param name: name of the dictionary file
605 @return: the contents of the file as a big string"""
606
607 result=None
608 theDir=self.name
609 if directory:
610 theDir=path.join(theDir,directory)
611
612 if path.exists(path.join(theDir,name)):
613 result=open(path.join(theDir,name)).read()
614 else:
615 warning("File",name,"does not exist in directory",directory,"of case",self.name)
616
617 return result
618
619 - def writeDictionaryContents(self,directory,name,contents):
620 """Writes the contents of a dictionary
621 @param directory: Sub-directory of the case
622 @param name: name of the dictionary file
623 @param contents: Python-dictionary with the dictionary contents"""
624
625 theDir=self.name
626 if directory:
627 theDir=path.join(theDir,directory)
628
629 result=WriteParameterFile(path.join(theDir,name))
630 result.content=contents
631 result.writeFile()
632
633 - def writeDictionaryText(self,directory,name,text):
634 """Writes the contents of a dictionary
635 @param directory: Sub-directory of the case
636 @param name: name of the dictionary file
637 @param text: String with the dictionary contents"""
638
639 theDir=self.name
640 if directory:
641 theDir=path.join(theDir,directory)
642
643 result=open(path.join(theDir,name),"w").write(text)
644
645 - def getDictionaryContents(self,directory,name):
646 """@param directory: Sub-directory of the case
647 @param name: name of the dictionary file
648 @return: the contents of the file as a python data-structure"""
649
650 result={}
651 theDir=self.name
652 if directory:
653 theDir=path.join(theDir,directory)
654
655 if path.exists(path.join(theDir,name)):
656 result=ParsedParameterFile(path.join(theDir,name)).content
657 else:
658 warning("File",name,"does not exist in directory",directory,"of case",self.name)
659
660 return result
661
663 """Solution directory with a directory for the Chemkin-files"""
664
665 chemkinName = "chemkin"
666
667 - def __init__(self,name,archive="ArchiveDir"):
671
673 """@rtype: str
674 @return: The directory with the Chemkin-Files"""
675
676 return path.join(self.name,self.chemkinName)
677
679 """Convenience class that makes sure that nothing new is created"""
680
681 - def __init__(self,
682 name,
683 region=None):
689