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