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