1
2 """
3 Application class that implements pyFoamTimelinePlot.py
4 """
5
6 import sys
7 from os import path
8 from optparse import OptionGroup
9
10 from .PyFoamApplication import PyFoamApplication
11 from PyFoam.RunDictionary.TimelineDirectory import TimelineDirectory
12 from PyFoam.Basics.SpreadsheetData import WrongDataSize
13 from PyFoam.ThirdParty.six import print_
14
15 from .PlotHelpers import cleanFilename
16
18 - def __init__(self,
19 args=None,
20 **kwargs):
21 description="""\
22 Searches a directory for timelines that were generated by some
23 functionObject and generates the commands to gnuplot it. As an option
24 the data can be written to a CSV-file.
25 """
26
27 PyFoamApplication.__init__(self,
28 args=args,
29 description=description,
30 usage="%prog [options] <casedir>",
31 nr=1,
32 changeVersion=False,
33 interspersed=True,
34 **kwargs)
35
37 data=OptionGroup(self.parser,
38 "Data",
39 "Select the data to plot")
40 self.parser.add_option_group(data)
41
42 data.add_option("--fields",
43 action="append",
44 default=None,
45 dest="fields",
46 help="The fields for which timelines should be plotted. All if unset")
47 data.add_option("--positions",
48 action="append",
49 default=None,
50 dest="positions",
51 help="The positions for which timelines should be plotted. Either strings or integers (then the corresponding column number will be used). All if unset")
52 data.add_option("--write-time",
53 default=None,
54 dest="writeTime",
55 help="If more than one time-subdirectory is stored select which one is used")
56 data.add_option("--directory-name",
57 action="store",
58 default="probes",
59 dest="dirName",
60 help="Alternate name for the directory with the samples (Default: %default)")
61 data.add_option("--reference-directory",
62 action="store",
63 default=None,
64 dest="reference",
65 help="A reference directory. If fitting timeline data is found there it is plotted alongside the regular data")
66 data.add_option("--reference-case",
67 action="store",
68 default=None,
69 dest="referenceCase",
70 help="A reference case where a directory with the same name is looked for. Mutual exclusive with --reference-directory")
71
72 time=OptionGroup(self.parser,
73 "Time",
74 "Select the times to plot")
75 self.parser.add_option_group(time)
76
77 time.add_option("--time",
78 action="append",
79 type="float",
80 default=None,
81 dest="time",
82 help="The times that are plotted (can be used more than once). Has to be specified for bars")
83 time.add_option("--min-time",
84 action="store",
85 type="float",
86 default=None,
87 dest="minTime",
88 help="The smallest time that should be used for lines")
89 time.add_option("--max-time",
90 action="store",
91 type="float",
92 default=None,
93 dest="maxTime",
94 help="The biggest time that should be used for lines")
95 time.add_option("--reference-time",
96 action="store_true",
97 default=False,
98 dest="referenceTime",
99 help="Use the time of the reference data for scaling instead of the regular data")
100
101
102 plot=OptionGroup(self.parser,
103 "Plot",
104 "How data should be plotted")
105 self.parser.add_option_group(plot)
106
107 plot.add_option("--basic-mode",
108 type="choice",
109 dest="basicMode",
110 default=None,
111 choices=["bars","lines"],
112 help="Whether 'bars' of the values at selected times or 'lines' over the whole timelines should be plotted")
113 vModes=["mag","x","y","z"]
114 plot.add_option("--vector-mode",
115 type="choice",
116 dest="vectorMode",
117 default="mag",
118 choices=vModes,
119 help="How vectors should be plotted. By magnitude or as a component. Possible values are "+str(vModes)+" Default: %default")
120 plot.add_option("--collect-lines-by",
121 type="choice",
122 dest="collectLines",
123 default="fields",
124 choices=["fields","positions"],
125 help="Collect lines for lineplotting either by 'fields' or 'positions'. Default: %default")
126
127 output=OptionGroup(self.parser,
128 "Output",
129 "Where data should be plotted to")
130 self.parser.add_option_group(output)
131
132 output.add_option("--gnuplot-file",
133 action="store",
134 dest="gnuplotFile",
135 default=None,
136 help="Write the necessary gnuplot commands to this file. Else they are written to the standard output")
137 output.add_option("--picture-destination",
138 action="store",
139 dest="pictureDest",
140 default=None,
141 help="Directory the pictures should be stored to")
142 output.add_option("--name-prefix",
143 action="store",
144 dest="namePrefix",
145 default=None,
146 help="Prefix to the picture-name")
147 output.add_option("--clean-filename",
148 action="store_true",
149 dest="cleanFilename",
150 default=False,
151 help="Clean filenames so that they can be used in HTML or Latex-documents")
152 output.add_option("--csv-file",
153 action="store",
154 dest="csvFile",
155 default=None,
156 help="Write the data to a CSV-file instead of the gnuplot-commands")
157 output.add_option("--excel-file",
158 action="store",
159 dest="excelFile",
160 default=None,
161 help="Write the data to a Excel-file instead of the gnuplot-commands")
162 output.add_option("--pandas-data",
163 action="store_true",
164 dest="pandasData",
165 default=False,
166 help="Pass the raw data in pandas-format")
167 output.add_option("--numpy-data",
168 action="store_true",
169 dest="numpyData",
170 default=False,
171 help="Pass the raw data in numpy-format")
172 output.add_option("--reference-prefix",
173 action="store",
174 dest="refprefix",
175 default="Reference",
176 help="Prefix that gets added to the reference lines. Default: %default")
177
178 data.add_option("--info",
179 action="store_true",
180 dest="info",
181 default=False,
182 help="Print info about the sampled data and exit")
183 output.add_option("--resample",
184 action="store_true",
185 dest="resample",
186 default=False,
187 help="Resample the reference value to the current x-axis (for CSV and Excel-output)")
188 output.add_option("--extend-data",
189 action="store_true",
190 dest="extendData",
191 default=False,
192 help="Extend the data range if it differs (for CSV and Excel-files)")
193 output.add_option("--silent",
194 action="store_true",
195 dest="silent",
196 default=False,
197 help="Don't write to screen (with the silent and the compare-options)")
198
199 numerics=OptionGroup(self.parser,
200 "Quantify",
201 "Metrics of the data and numerical comparisons")
202 self.parser.add_option_group(numerics)
203 numerics.add_option("--compare",
204 action="store_true",
205 dest="compare",
206 default=None,
207 help="Compare all data sets that are also in the reference data")
208 numerics.add_option("--metrics",
209 action="store_true",
210 dest="metrics",
211 default=None,
212 help="Print the metrics of the data sets")
213 numerics.add_option("--use-reference-for-comparison",
214 action="store_false",
215 dest="compareOnOriginal",
216 default=True,
217 help="Use the reference-data as the basis for the numerical comparison. Otherwise the original data will be used")
218
229
231
232 if self.opts.dirName[-1]==path.sep:
233 self.opts.dirName=self.opts.dirName[:-1]
234
235 usedDirName=self.opts.dirName.replace("/","_")
236
237 timelines=TimelineDirectory(self.parser.getArgs()[0],
238 dirName=self.opts.dirName,
239 writeTime=self.opts.writeTime)
240 reference=None
241 if self.opts.reference and self.opts.referenceCase:
242 self.error("Options --reference-directory and --reference-case are mutual exclusive")
243 if (self.opts.csvFile or self.opts.excelFile or self.opts.pandasData or self.opts.numpyData) and (self.opts.compare or self.opts.metrics):
244 self.error("Options --csv-file/excel-file/--pandas-data/--numpy-data and --compare/--metrics are mutual exclusive")
245
246 if self.opts.reference:
247 reference=TimelineDirectory(self.parser.getArgs()[0],
248 dirName=self.opts.reference,
249 writeTime=self.opts.writeTime)
250 elif self.opts.referenceCase:
251 reference=TimelineDirectory(self.opts.referenceCase,
252 dirName=self.opts.dirName,
253 writeTime=self.opts.writeTime)
254
255 if self.opts.info:
256 self.setData({'writeTimes' : timelines.writeTimes,
257 'usedTimes' : timelines.usedTime,
258 'fields' : timelines.values,
259 'positions' : timelines.positions(),
260 'timeRange' : timelines.timeRange()})
261
262 if not self.opts.silent:
263 print_("Write Times : ",timelines.writeTimes)
264 print_("Used Time : ",timelines.usedTime)
265 print_("Fields : ",timelines.values,end="")
266 if len(timelines.vectors)>0:
267 if not self.opts.silent:
268 print_(" Vectors: ",timelines.vectors)
269 self.setData({'vectors':timelines.vectors})
270 else:
271 if not self.opts.silent:
272 print_()
273 if not self.opts.silent:
274 print_("Positions : ",timelines.positions())
275 print_("Time range : ",timelines.timeRange())
276
277 if reference:
278 refData={'writeTimes' : reference.writeTimes,
279 'fields' : reference.values,
280 'positions' : reference.positions(),
281 'timeRange' : reference.timeRange()}
282
283 if not self.opts.silent:
284 print_("\nReference Data")
285 print_("Write Times : ",reference.writeTimes)
286 print_("Fields : ",reference.values,end="")
287 if len(reference.vectors)>0:
288 if not self.opts.silent:
289 print_(" Vectors: ",reference.vectors)
290 refData["vectors"]=reference.vectors
291 else:
292 if not self.opts.silent:
293 print_()
294 if not self.opts.silent:
295 print_("Positions : ",reference.positions())
296 print_("Time range : ",reference.timeRange())
297 self.setData({"reference":refData})
298
299 return 0
300
301 if self.opts.fields==None:
302 self.opts.fields=timelines.values
303 else:
304 for v in self.opts.fields:
305 if v not in timelines.values:
306 self.error("The requested value",v,"not in possible values",timelines.values)
307 if self.opts.positions==None:
308 self.opts.positions=timelines.positions()
309 else:
310 pos=self.opts.positions
311 self.opts.positions=[]
312 for p in pos:
313 try:
314 p=int(p)
315 if p<0 or p>=len(timelines.positions()):
316 self.error("Time index",p,"out of range for positons",timelines.positions())
317 else:
318 self.opts.positions.append(timelines.positions()[p])
319 except ValueError:
320 if p not in timelines.positions():
321 self.error("Position",p,"not in",timelines.positions())
322 else:
323 self.opts.positions.append(p)
324
325 if len(self.opts.positions)==0:
326 self.error("No valid positions")
327
328 result="set term png nocrop enhanced \n"
329
330 if self.opts.basicMode==None:
331 self.error("No mode selected. Do so with '--basic-mode'")
332 elif self.opts.basicMode=='bars':
333 if self.opts.time==None:
334 self.error("No times specified for bar-plots")
335 self.opts.time.sort()
336 if self.opts.referenceTime and reference!=None:
337 minTime,maxTime=reference.timeRange()
338 else:
339 minTime,maxTime=timelines.timeRange()
340 usedTimes=[]
341 hasMin=False
342 for t in self.opts.time:
343 if t<minTime:
344 if not hasMin:
345 usedTimes.append(minTime)
346 hasMin=True
347 elif t>maxTime:
348 usedTimes.append(maxTime)
349 break
350 else:
351 usedTimes.append(t)
352 data=timelines.getData(usedTimes,
353 value=self.opts.fields,
354 position=self.opts.positions,
355 vectorMode=self.opts.vectorMode)
356
357 result+="set style data histogram\n"
358 result+="set style histogram cluster gap 1\n"
359 result+="set style fill solid border -1\n"
360 result+="set boxwidth 0.9\n"
361 result+="set xtics border in scale 1,0.5 nomirror rotate by 90 offset character 0, 0, 0\n"
362
363 result+="set xtics ("
364 for i,p in enumerate(self.opts.positions):
365 if i>0:
366 result+=" , "
367 result+='"%s" %d' % (p,i)
368 result+=")\n"
369 for tm in usedTimes:
370 if abs(float(tm))>1e20:
371 continue
372 result+=self.setFile("%s_writeTime_%s_Time_%s.png" % (usedDirName,timelines.usedTime,tm))
373 result+='set title "Directory: %s WriteTime: %s Time: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,tm)
374 result+= "plot "
375 first=True
376 for val in self.opts.fields:
377 if first:
378 first=False
379 else:
380 result+=", "
381 result+='"-" title "%s" ' % val.replace("_","\\\\_")
382 result+="\n"
383 for v,t,vals in data:
384 if t==tm:
385 for v in vals:
386 result+="%g\n" % v
387 result+="e\n"
388 elif self.opts.basicMode=='lines':
389
390 oPlots=timelines.getDataLocation(value=self.opts.fields,
391 position=self.opts.positions,
392 vectorMode=self.opts.vectorMode)
393
394 plots=oPlots[:]
395 rPlots=None
396
397 if reference:
398 rPlots=reference.getDataLocation(value=self.opts.fields,
399 position=self.opts.positions,
400 vectorMode=self.opts.vectorMode)
401 for gp,pos,val,comp,tv in rPlots:
402 plots.append((gp,
403 pos,
404 self.opts.refprefix+" "+val,
405 comp,
406 tv))
407 if self.opts.referenceTime and reference!=None:
408 minTime,maxTime=reference.timeRange()
409 else:
410 minTime,maxTime=timelines.timeRange()
411 if self.opts.minTime:
412 minTime=self.opts.minTime
413 if self.opts.maxTime:
414 maxTime=self.opts.maxTime
415 result+= "set xrange [%g:%g]\n" % (minTime,maxTime)
416 if self.opts.collectLines=="fields":
417 for val in self.opts.fields:
418 vname=val
419 if val in timelines.vectors:
420 vname+="_"+self.opts.vectorMode
421 result+=self.setFile("%s_writeTime_%s_Value_%s.png" % (usedDirName,timelines.usedTime,vname))
422 result+='set title "Directory: %s WriteTime: %s Value: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,vname.replace("_","\\\\\\_"))
423 result+= "plot "
424 first=True
425 for f,v,p,i,tl in plots:
426 if v==val:
427 if first:
428 first=False
429 else:
430 result+=" , "
431 if type(i)==int:
432 result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,p.replace("_","\\\\_"))
433 else:
434 result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,p.replace("_","\\\\_"))
435
436 result+="\n"
437 elif self.opts.collectLines=="positions":
438 for pos in self.opts.positions:
439 result+=self.setFile("%s_writeTime_%s_Position_%s.png" % (usedDirName,timelines.usedTime,pos))
440 result+='set title "Directory: %s WriteTime: %s Position: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,pos.replace("_","\\\\_"))
441 result+= "plot "
442 first=True
443 for f,v,p,i,tl in plots:
444 if p==pos:
445 if first:
446 first=False
447 else:
448 result+=" , "
449 if type(i)==int:
450 result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,v.replace("_","\\\\_"))
451 else:
452 result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,v.replace("_","\\\\_"))
453 result+="\n"
454
455 else:
456 self.error("Unimplemented collection of lines:",self.opts.collectLines)
457 else:
458 self.error("Not implemented basicMode",self.opts.basicMode)
459
460 if self.opts.csvFile or self.opts.excelFile or self.opts.pandasData or self.opts.numpyData:
461 if self.opts.basicMode!='lines':
462 self.error("CSV and Excel-files currently only supported for lines-mode (also Pandas and Numpy-data)")
463 spread=plots[0][-1]()
464 usedFiles=set([plots[0][0]])
465 for line in plots[1:]:
466 if line[0] not in usedFiles:
467 usedFiles.add(line[0])
468 sp=line[-1]()
469 try:
470 spread+=sp
471 except WrongDataSize:
472 if self.opts.resample:
473 for n in sp.names()[1:]:
474 data=spread.resample(sp,
475 n,
476 extendData=self.opts.extendData)
477 try:
478 spread.append(n,data)
479 except ValueError:
480 spread.append(self.opts.refprefix+" "+n,data)
481 else:
482 self.warning("Try the --resample-option")
483 raise
484
485 if self.opts.csvFile:
486 spread.writeCSV(self.opts.csvFile)
487 if self.opts.excelFile:
488 spread.getData().to_excel(self.opts.excelFile)
489 if self.opts.pandasData:
490 self.setData({"series":spread.getSeries(),
491 "dataFrame":spread.getData()})
492 if self.opts.numpyData:
493 self.setData({"data":spread.data.copy()})
494
495 elif self.opts.compare or self.opts.metrics:
496 statData={}
497 if self.opts.compare:
498 statData["compare"]={}
499 if self.opts.metrics:
500 statData["metrics"]={}
501 for p in self.opts.positions:
502 if self.opts.compare:
503 statData["compare"][p]={}
504 if self.opts.metrics:
505 statData["metrics"][p]={}
506
507 if self.opts.basicMode!='lines':
508 self.error("Compare currently only supported for lines-mode")
509
510 if self.opts.compare:
511 if rPlots==None:
512 self.error("No reference data specified. Can't compare")
513 elif len(rPlots)!=len(oPlots):
514 self.error("Number of original data sets",len(oPlots),
515 "is not equal to the reference data sets",
516 len(rPlots))
517
518 for i,p in enumerate(oPlots):
519 pth,val,loc,ind,tl=p
520 if self.opts.compare:
521 rpth,rval,rloc,rind,rtl=rPlots[i]
522 if val!=rval or loc!=rloc or ind!=rind:
523 self.error("Original data",p,"and reference",rPlots[i],
524 "do not match")
525 data=tl()
526 try:
527 dataIndex=1+ind
528 if self.opts.metrics:
529 if not self.opts.silent:
530 print_("Metrics for",val,"on",loc,"index",ind,"(Path:",pth,")")
531 result=data.metrics(data.names()[dataIndex],
532 minTime=self.opts.minTime,
533 maxTime=self.opts.maxTime)
534 statData["metrics"][loc][val]=result
535 if not self.opts.silent:
536 print_(" Min :",result["min"])
537 print_(" Max :",result["max"])
538 print_(" Average :",result["average"])
539 print_(" Weighted average :",result["wAverage"])
540 if not self.opts.compare:
541 print_("Data size:",data.size())
542 print_(" Time Range :",result["tMin"],result["tMax"])
543 if self.opts.compare:
544 if not self.opts.silent:
545 print_("Comparing",val,"on",loc,"index",ind,"(path:",pth,")",end="")
546 ref=rtl()
547 if self.opts.compareOnOriginal:
548 if not self.opts.silent:
549 print_("on original data points")
550 result=data.compare(ref,
551 data.names()[dataIndex],
552 minTime=self.opts.minTime,
553 maxTime=self.opts.maxTime)
554 else:
555 if not self.opts.silent:
556 print_("on reference data points")
557 result=ref.compare(data,
558 data.names()[dataIndex],
559 minTime=self.opts.minTime,
560 maxTime=self.opts.maxTime)
561
562 statData["compare"][loc][val]=result
563 if not self.opts.silent:
564 print_(" Max difference :",result["max"])
565 print_(" Average difference :",result["average"])
566 print_(" Weighted average :",result["wAverage"])
567 print_("Data size:",data.size(),"Reference:",ref.size())
568 if not self.opts.metrics:
569 print_(" Time Range :",result["tMin"],result["tMax"])
570 if not self.opts.silent:
571 print_()
572 except TypeError:
573 if self.opts.vectorMode=="mag":
574 self.error("Vector-mode 'mag' not supported for --compare and --metrics")
575 else:
576 raise
577
578 self.setData(statData)
579 else:
580 dest=sys.stdout
581 if self.opts.gnuplotFile:
582 dest=open(self.opts.gnuplotFile,"w")
583
584 dest.write(result)
585
586
587