1
2 """
3 Application class that implements pyFoamCasedReport.py
4 """
5
6 import sys,string
7 from optparse import OptionGroup
8
9 from fnmatch import fnmatch
10
11 from .PyFoamApplication import PyFoamApplication
12 from PyFoam.RunDictionary.SolutionDirectory import SolutionDirectory
13 from PyFoam.RunDictionary.BoundaryDict import BoundaryDict
14 from PyFoam.RunDictionary.MeshInformation import MeshInformation
15 from PyFoam.RunDictionary.ParsedParameterFile import PyFoamParserError,ParsedBoundaryDict,ParsedParameterFile
16 from PyFoam.Basics.RestructuredTextHelper import RestructuredTextHelper
17 from PyFoam.Basics.DataStructures import DictProxy,Field
18
19 from PyFoam.Error import error,warning
20
21 from PyFoam.ThirdParty.six import print_,iteritems
22
23 from math import log10,ceil
24 from os import path
25
26 import sys
27
30 description="""\
31 Produces human-readable reports about a case. Attention: the amount of
32 information in the reports is limited. The truth is always in the
33 dictionary-files.
34
35 The format of the output is restructured-text so it can be run through
36 a postprocessor like rst2tex or rst2html to produce PDF or HTML
37 respectivly
38 """
39
40 PyFoamApplication.__init__(self,
41 args=args,
42 description=description,
43 usage="%prog [options] <casedir>",
44 nr=1,
45 changeVersion=False,
46 interspersed=True)
47
49 report=OptionGroup(self.parser,
50 "Reports",
51 "What kind of reports should be produced")
52 self.parser.add_option_group(report)
53 select=OptionGroup(self.parser,
54 "Selection",
55 "Which data should be used for the reports")
56 self.parser.add_option_group(select)
57 internal=OptionGroup(self.parser,
58 "Internal",
59 "Details of the parser")
60 self.parser.add_option_group(internal)
61
62 format=OptionGroup(self.parser,
63 "Formatting",
64 "Restructured Text formatting")
65 self.parser.add_option_group(format)
66
67 format.add_option("--heading-level",
68 action="store",
69 type="int",
70 default=2,
71 dest="headingLevel",
72 help="Default level of the headings. Valid values from 0 to 5. Default: %default")
73
74 output=OptionGroup(self.parser,
75 "Output",
76 "How Output should be generated")
77 self.parser.add_option_group(output)
78
79 output.add_option("--file",
80 action="store",
81 default=None,
82 dest="file",
83 help="Write the output to a file instead of the console")
84
85 report.add_option("--full-report",
86 action="store_true",
87 default=False,
88 dest="all",
89 help="Print all available reports at once")
90
91 report.add_option("--short-bc-report",
92 action="store_true",
93 default=False,
94 dest="shortBCreport",
95 help="Gives a short overview of the boundary-conditions in the case")
96
97 report.add_option("--long-bc-report",
98 action="store_true",
99 default=False,
100 dest="longBCreport",
101 help="Gives a full overview of the boundary-conditions in the case")
102
103 report.add_option("--dimensions",
104 action="store_true",
105 default=False,
106 dest="dimensions",
107 help="Show the dimensions of the fields")
108
109 report.add_option("--internal-field",
110 action="store_true",
111 default=False,
112 dest="internal",
113 help="Show the internal value of the fields (the initial conditions)")
114
115 report.add_option("--linear-solvers",
116 action="store_true",
117 default=False,
118 dest="linearSolvers",
119 help="Print the linear solvers and their tolerance")
120
121 report.add_option("--relaxation-factors",
122 action="store_true",
123 default=False,
124 dest="relaxationFactors",
125 help="Print the relaxation factors (if there are any)")
126
127 select.add_option("--time",
128 action="store",
129 type="float",
130 default=None,
131 dest="time",
132 help="Time to use as the basis for the reports")
133
134 select.add_option("--region",
135 dest="region",
136 default=None,
137 help="Do the report for a special region for multi-region cases")
138
139 select.add_option("--all-regions",
140 dest="allRegions",
141 action="store_true",
142 default=False,
143 help="Do the report for all regions for multi-region cases")
144
145 select.add_option("--parallel",
146 action="store_true",
147 default=False,
148 dest="parallel",
149 help="Get times from the processor-directories")
150
151 internal.add_option("--long-field-threshold",
152 action="store",
153 type="int",
154 default=100,
155 dest="longlist",
156 help="Fields that are longer than this won't be parsed, but read into memory (and compared as strings). Default: %default")
157 internal.add_option("--no-do-macro-expansion",
158 action="store_false",
159 default=True,
160 dest="doMacros",
161 help="Don't expand macros with $ and # in the field-files")
162
163 internal.add_option("--treat-binary-as-ascii",
164 action="store_true",
165 default=False,
166 dest="treatBinaryAsASCII",
167 help="Try to treat binary dictionaries as ASCII anyway")
168
169 select.add_option("--patches",
170 action="append",
171 default=None,
172 dest="patches",
173 help="Patches which should be processed (pattern, can be used more than once)")
174
175 select.add_option("--exclude-patches",
176 action="append",
177 default=None,
178 dest="expatches",
179 help="Patches which should not be processed (pattern, can be used more than once)")
180
181 report.add_option("--processor-matrix",
182 action="store_true",
183 default=False,
184 dest="processorMatrix",
185 help="Prints the matrix how many faces from one processor interact with another")
186
187 report.add_option("--case-size",
188 action="store_true",
189 default=False,
190 dest="caseSize",
191 help="Report the number of cells, points and faces in the case")
192
193 report.add_option("--decomposition",
194 action="store_true",
195 default=False,
196 dest="decomposition",
197 help="Reports the size of the parallel decomposition")
198
212
214 ReST=RestructuredTextHelper(defaultHeading=self.opts.headingLevel)
215
216 if self.opts.allRegions:
217 print_(ReST.buildHeading("Region: ",theRegion,level=self.opts.headingLevel-1))
218
219 sol=SolutionDirectory(self.parser.getArgs()[0],
220 archive=None,
221 parallel=self.opts.parallel,
222 paraviewLink=False,
223 region=theRegion)
224
225 if self.opts.all:
226 self.opts.caseSize=True
227 self.opts.shortBCreport=True
228 self.opts.longBCreport=True
229 self.opts.dimensions=True
230 self.opts.internal=True
231 self.opts.linearSolvers=True
232 self.opts.relaxationFactors=True
233 self.opts.processorMatrix=True
234 self.opts.decomposition=True
235
236 if self.opts.time:
237 try:
238 self.opts.time=sol.timeName(sol.timeIndex(self.opts.time,minTime=True))
239 except IndexError:
240 error("The specified time",self.opts.time,"doesn't exist in the case")
241 print_("Using time t="+self.opts.time+"\n")
242
243 needsPolyBoundaries=False
244 needsInitialTime=False
245
246 if self.opts.longBCreport:
247 needsPolyBoundaries=True
248 needsInitialTime=True
249 if self.opts.shortBCreport:
250 needsPolyBoundaries=True
251 needsInitialTime=True
252 if self.opts.dimensions:
253 needsInitialTime=True
254 if self.opts.internal:
255 needsInitialTime=True
256 if self.opts.decomposition:
257 needsPolyBoundaries=True
258
259 defaultProc=None
260 if self.opts.parallel:
261 defaultProc=0
262
263 if needsPolyBoundaries:
264 proc=None
265 boundary=BoundaryDict(sol.name,
266 region=theRegion,
267 time=self.opts.time,
268 treatBinaryAsASCII=self.opts.treatBinaryAsASCII,
269 processor=defaultProc)
270
271 boundMaxLen=0
272 boundaryNames=[]
273 for b in boundary:
274 if b.find("procBoundary")!=0:
275 boundaryNames.append(b)
276 if self.opts.patches!=None:
277 tmp=boundaryNames
278 boundaryNames=[]
279 for b in tmp:
280 for p in self.opts.patches:
281 if fnmatch(b,p):
282 boundaryNames.append(b)
283 break
284
285 if self.opts.expatches!=None:
286 tmp=boundaryNames
287 boundaryNames=[]
288 for b in tmp:
289 keep=True
290 for p in self.opts.expatches:
291 if fnmatch(b,p):
292 keep=False
293 break
294 if keep:
295 boundaryNames.append(b)
296
297 for b in boundaryNames:
298 boundMaxLen=max(boundMaxLen,len(b))
299 boundaryNames.sort()
300
301 if self.opts.time==None:
302 procTime="constant"
303 else:
304 procTime=self.opts.time
305
306 if needsInitialTime:
307 fields={}
308
309 if self.opts.time==None:
310 try:
311 time=sol.timeName(0)
312 except IndexError:
313 error("There is no timestep in the case")
314 else:
315 time=self.opts.time
316
317 tDir=sol[time]
318
319 nameMaxLen=0
320
321 for f in tDir:
322 try:
323 fields[f.baseName()]=f.getContent(listLengthUnparsed=self.opts.longlist,
324 treatBinaryAsASCII=self.opts.treatBinaryAsASCII,
325 doMacroExpansion=self.opts.doMacros)
326 nameMaxLen=max(nameMaxLen,len(f.baseName()))
327 except PyFoamParserError:
328 e = sys.exc_info()[1]
329 warning("Couldn't parse",f.name,"because of an error:",e," -> skipping")
330
331 fieldNames=list(fields.keys())
332 fieldNames.sort()
333
334 if self.opts.caseSize:
335 print_(ReST.heading("Size of the case"))
336
337 nFaces=0
338 nPoints=0
339 nCells=0
340 if self.opts.parallel:
341 procs=list(range(sol.nrProcs()))
342 print_("Accumulated from",sol.nrProcs(),"processors")
343 else:
344 procs=[None]
345
346 for p in procs:
347 info=MeshInformation(sol.name,
348 processor=p,
349 region=theRegion,
350 time=self.opts.time)
351 nFaces+=info.nrOfFaces()
352 nPoints+=info.nrOfPoints()
353 try:
354 nCells+=info.nrOfCells()
355 except:
356 nCells="Not available"
357 tab=ReST.table()
358 tab[0]=("Faces",nFaces)
359 tab[1]=("Points",nPoints)
360 tab[2]=("Cells",nCells)
361 print_(tab)
362
363 if self.opts.decomposition:
364 print_(ReST.heading("Decomposition"))
365
366 if sol.nrProcs()<2:
367 print_("This case is not decomposed")
368 else:
369 print_("Case is decomposed for",sol.nrProcs(),"processors")
370 print_()
371
372 nCells=[]
373 nFaces=[]
374 nPoints=[]
375 for p in sol.processorDirs():
376 info=MeshInformation(sol.name,
377 processor=p,
378 region=theRegion,
379 time=self.opts.time)
380 nPoints.append(info.nrOfPoints())
381 nFaces.append(info.nrOfFaces())
382 nCells.append(info.nrOfCells())
383
384 digits=int(ceil(log10(max(sol.nrProcs(),
385 max(nCells),
386 max(nFaces),
387 max(nPoints)
388 ))))+2
389 nameLen=max(len("Points"),boundMaxLen)
390
391 tab=ReST.table()
392 tab[0]=["CPU"]+list(range(sol.nrProcs()))
393
394 tab.addLine()
395
396 tab[1]=["Points"]+nPoints
397 tab[2]=["Faces"]+nFaces
398 tab[3]=["Cells"]+nCells
399 tab.addLine(head=True)
400
401 nr=3
402 for b in boundaryNames:
403 nr+=1
404 tab[(nr,0)]=b
405 for i,p in enumerate(sol.processorDirs()):
406 try:
407 nFaces= ParsedBoundaryDict(sol.boundaryDict(processor=p,
408 region=theRegion,
409 time=self.opts.time),
410 treatBinaryAsASCII=self.opts.treatBinaryAsASCII
411 )[b]["nFaces"]
412 except IOError:
413 nFaces= ParsedBoundaryDict(sol.boundaryDict(processor=p,
414 region=theRegion),
415 treatBinaryAsASCII=self.opts.treatBinaryAsASCII
416 )[b]["nFaces"]
417 except KeyError:
418 nFaces=0
419
420 tab[(nr,i+1)]=nFaces
421
422 print_(tab)
423
424 if self.opts.longBCreport:
425 print_(ReST.heading("The boundary conditions for t =",time))
426
427 for b in boundaryNames:
428 print_(ReST.buildHeading("Boundary: ",b,level=self.opts.headingLevel+1))
429 bound=boundary[b]
430 print_(":Type:\t",bound["type"])
431 if "physicalType" in bound:
432 print_(":Physical:\t",bound["physicalType"])
433 print_(":Faces:\t",bound["nFaces"])
434 print_()
435 heads=["Field","type"]
436 tab=ReST.table()
437 tab[0]=heads
438 tab.addLine(head=True)
439 for row,fName in enumerate(fieldNames):
440 tab[(row+1,0)]=fName
441 f=fields[fName]
442 if "boundaryField" not in f:
443 tab[(row+1,1)]="Not a field file"
444 elif b not in f["boundaryField"]:
445 tab[(row+1,1)]="MISSING !!!"
446 else:
447 bf=f["boundaryField"][b]
448
449 for k in bf:
450 try:
451 col=heads.index(k)
452 except ValueError:
453 col=len(heads)
454 tab[(0,col)]=k
455 heads.append(k)
456 cont=str(bf[k])
457 if type(bf[k])==Field:
458 if bf[k].isBinary():
459 cont= bf[k].binaryString()
460
461 if cont.find("\n")>=0:
462 tab[(row+1,col)]=cont[:cont.find("\n")]+"..."
463 else:
464 tab[(row+1,col)]=cont
465 print_(tab)
466
467 if self.opts.shortBCreport:
468 print_(ReST.heading("Table of boundary conditions for t =",time))
469
470 types={}
471 hasPhysical=False
472 for b in boundary:
473 if "physicalType" in boundary[b]:
474 hasPhysical=True
475
476 types[b]={}
477
478 for fName in fields:
479 f=fields[fName]
480 try:
481 if b not in f["boundaryField"]:
482 types[b][fName]="MISSING"
483 else:
484 types[b][fName]=f["boundaryField"][b]["type"]
485 except KeyError:
486 types[b][fName]="Not a field"
487
488 tab=ReST.table()
489 tab[0]=[""]+boundaryNames
490 tab.addLine()
491 tab[(1,0)]="Patch Type"
492 for i,b in enumerate(boundaryNames):
493 tab[(1,i+1)]=boundary[b]["type"]
494
495 nr=2
496 if hasPhysical:
497 tab[(nr,0)]="Physical Type"
498 for i,b in enumerate(boundaryNames):
499 if "physicalType" in boundary[b]:
500 tab[(nr,i+1)]=boundary[b]["physicalType"]
501 nr+=1
502
503 tab[(nr,0)]="Length"
504 for i,b in enumerate(boundaryNames):
505 tab[(nr,i+1)]=boundary[b]["nFaces"]
506 nr+=1
507 tab.addLine(head=True)
508
509 for fName in fieldNames:
510 tab[(nr,0)]=fName
511 for i,b in enumerate(boundaryNames):
512 tab[(nr,i+1)]=types[b][fName]
513 nr+=1
514
515 print_(tab)
516
517 if self.opts.dimensions:
518 print_(ReST.heading("Dimensions of fields for t =",time))
519
520 tab=ReST.table()
521 tab[0]=["Name"]+"[ kg m s K mol A cd ]".split()[1:-1]
522 tab.addLine(head=True)
523 for i,fName in enumerate(fieldNames):
524 f=fields[fName]
525 try:
526 dim=str(f["dimensions"]).split()[1:-1]
527 except KeyError:
528 dim=["-"]*7
529 tab[i+1]=[fName]+dim
530 print_(tab)
531
532 if self.opts.internal:
533 print_(ReST.heading("Internal value of fields for t =",time))
534
535 tab=ReST.table()
536 tab[0]=["Name","Value"]
537 tab.addLine(head=True)
538 for i,fName in enumerate(fieldNames):
539 f=fields[fName]
540
541 try:
542 if f["internalField"].isBinary():
543 val=f["internalField"].binaryString()
544 else:
545 cont=str(f["internalField"])
546 if cont.find("\n")>=0:
547 val=cont[:cont.find("\n")]+"..."
548 else:
549 val=cont
550 except KeyError:
551 val="Not a field file"
552 tab[i+1]=[fName,val]
553 print_(tab)
554
555 if self.opts.processorMatrix:
556 print_(ReST.heading("Processor matrix"))
557
558 if sol.nrProcs()<2:
559 print_("This case is not decomposed")
560 else:
561 matrix=[ [0,]*sol.nrProcs() for i in range(sol.nrProcs())]
562
563 for i,p in enumerate(sol.processorDirs()):
564 try:
565 bound=ParsedBoundaryDict(sol.boundaryDict(processor=p,
566 region=theRegion,
567 time=self.opts.time)
568 ,treatBinaryAsASCII=self.opts.treatBinaryAsASCII)
569 except IOError:
570 bound=ParsedBoundaryDict(sol.boundaryDict(processor=p,
571 treatBinaryAsASCII=self.opts.treatBinaryAsASCII,
572 region=theRegion)
573 ,treatBinaryAsASCII=self.opts.treatBinaryAsASCII)
574
575 for j in range(sol.nrProcs()):
576 name="procBoundary%dto%d" %(j,i)
577 name2="procBoundary%dto%d" %(i,j)
578 if name in bound:
579 matrix[i][j]=bound[name]["nFaces"]
580 if name2 in bound:
581 matrix[i][j]=bound[name2]["nFaces"]
582
583 print_("Matrix of processor interactions (faces)")
584 print_()
585
586 tab=ReST.table()
587 tab[0]=["CPU"]+list(range(sol.nrProcs()))
588 tab.addLine(head=True)
589
590 for i,col in enumerate(matrix):
591 tab[i+1]=[i]+matrix[i]
592
593 print_(tab)
594
595 if self.opts.linearSolvers:
596 print_(ReST.heading("Linear Solvers"))
597
598 linTable=ReST.table()
599
600 fvSol=ParsedParameterFile(path.join(sol.systemDir(),"fvSolution"),
601 treatBinaryAsASCII=self.opts.treatBinaryAsASCII)
602 allInfo={}
603 for sName in fvSol["solvers"]:
604 raw=fvSol["solvers"][sName]
605 info={}
606 if type(raw) in [dict,DictProxy]:
607
608 info["solver"]=raw["solver"]
609 solverData=raw
610 else:
611 info["solver"]=raw[0]
612 solverData=raw[1]
613
614 if type(solverData) in [dict,DictProxy]:
615 try:
616 info["tolerance"]=solverData["tolerance"]
617 except KeyError:
618 info["tolerance"]=1.
619 try:
620 info["relTol"]=solverData["relTol"]
621 except KeyError:
622 info["relTol"]=0.
623 else:
624
625 info["tolerance"]=solverData
626 info["relTol"]=raw[2]
627
628 allInfo[sName]=info
629
630 linTable[0]=["Name","Solver","Abs. Tolerance","Relative Tol."]
631 linTable.addLine(head=True)
632
633 nr=0
634 for n,i in iteritems(allInfo):
635 nr+=1
636 linTable[nr]=(n,i["solver"],i["tolerance"],i["relTol"])
637 print_(linTable)
638
639 if self.opts.relaxationFactors:
640 print_(ReST.heading("Relaxation"))
641
642 fvSol=ParsedParameterFile(path.join(sol.systemDir(),"fvSolution"),
643 treatBinaryAsASCII=self.opts.treatBinaryAsASCII)
644 if "relaxationFactors" in fvSol:
645 tab=ReST.table()
646 tab[0]=["Name","Factor"]
647 tab.addLine(head=True)
648 nr=0
649 for n,f in iteritems(fvSol["relaxationFactors"]):
650 nr+=1
651 tab[nr]=[n,f]
652 print_(tab)
653 else:
654 print_("No relaxation factors defined for this case")
655
656
657