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 functionObject
22 and generates the commands to gnuplot it. As an option the data can be written to a
23 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("--values",
41 action="append",
42 default=None,
43 dest="values",
44 help="The values 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="values",
122 choices=["values","positions"],
123 help="Collect lines for lineplotting either by 'values' 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
188 if self.opts.namePrefix:
189 fName=self.opts.namePrefix+"_"+fName
190 if self.opts.pictureDest:
191 fName=path.join(self.opts.pictureDest,fName)
192
193 name=fName
194 if self.opts.cleanFilename:
195 name=cleanFilename(fName)
196 return 'set output "%s"\n' % name
197
199
200 if self.opts.dirName[-1]==path.sep:
201 self.opts.dirName=self.opts.dirName[:-1]
202
203 timelines=TimelineDirectory(self.parser.getArgs()[0],
204 dirName=self.opts.dirName,
205 writeTime=self.opts.writeTime)
206 reference=None
207 if self.opts.reference and self.opts.referenceCase:
208 self.error("Options --reference-directory and --reference-case are mutual exclusive")
209 if self.opts.csvFile and (self.opts.compare or self.opts.metrics):
210 self.error("Options --csv-file and --compare/--metrics are mutual exclusive")
211
212 if self.opts.reference:
213 reference=TimelineDirectory(self.parser.getArgs()[0],
214 dirName=self.opts.reference,
215 writeTime=self.opts.writeTime)
216 elif self.opts.referenceCase:
217 reference=TimelineDirectory(self.opts.referenceCase,
218 dirName=self.opts.dirName,
219 writeTime=self.opts.writeTime)
220
221 if self.opts.info:
222 print "Write Times : ",timelines.writeTimes
223 print "Used Time : ",timelines.usedTime
224 print "Values : ",timelines.values,
225 if len(timelines.vectors)>0:
226 print " Vectors: ",timelines.vectors
227 else:
228 print
229 print "Positions : ",timelines.positions()
230 print "Time range : ",timelines.timeRange()
231
232 if reference:
233 print "\nReference Data"
234 print "Write Times : ",reference.writeTimes
235 print "Fields : ",reference.values,
236 if len(reference.vectors)>0:
237 print " Vectors: ",reference.vectors
238 else:
239 print
240 print "Positions : ",reference.positions()
241 print "Time range : ",reference.timeRange()
242
243 return 0
244
245 if self.opts.values==None:
246 self.opts.values=timelines.values
247 else:
248 for v in self.opts.values:
249 if v not in timelines.values:
250 self.error("The requested value",v,"not in possible values",timelines.values)
251 if self.opts.positions==None:
252 self.opts.positions=timelines.positions()
253 else:
254 pos=self.opts.positions
255 self.opts.positions=[]
256 for p in pos:
257 try:
258 p=int(p)
259 if p<0 or p>=len(timelines.positions()):
260 self.error("Time index",p,"out of range for positons",timelines.positions())
261 else:
262 self.opts.positions.append(timelines.positions()[p])
263 except ValueError:
264 if p not in timelines.positions():
265 self.error("Position",p,"not in",timelines.positions())
266 else:
267 self.opts.positions.append(p)
268
269 result="set term png nocrop enhanced \n"
270
271 if self.opts.basicMode==None:
272 self.error("No mode selected. Do so with '--basic-mode'")
273 elif self.opts.basicMode=='bars':
274 if self.opts.time==None:
275 self.error("No times specified for bar-plots")
276 self.opts.time.sort()
277 if self.opts.referenceTime and reference!=None:
278 minTime,maxTime=reference.timeRange()
279 else:
280 minTime,maxTime=timelines.timeRange()
281 usedTimes=[]
282 hasMin=False
283 for t in self.opts.time:
284 if t<minTime:
285 if not hasMin:
286 usedTimes.append(minTime)
287 hasMin=True
288 elif t>maxTime:
289 usedTimes.append(maxTime)
290 break
291 else:
292 usedTimes.append(t)
293 data=timelines.getData(usedTimes,
294 value=self.opts.values,
295 position=self.opts.positions)
296
297 result+="set style data histogram\n"
298 result+="set style histogram cluster gap 1\n"
299 result+="set style fill solid border -1\n"
300 result+="set boxwidth 0.9\n"
301 result+="set xtics border in scale 1,0.5 nomirror rotate by 90 offset character 0, 0, 0\n"
302
303 result+="set xtics ("
304 for i,p in enumerate(self.opts.positions):
305 if i>0:
306 result+=" , "
307 result+='"%s" %d' % (p,i)
308 result+=")\n"
309 for tm in usedTimes:
310 if abs(float(tm))>1e20:
311 continue
312 result+=self.setFile("%s_writeTime_%s_Time_%s.png" % (self.opts.dirName,timelines.usedTime,tm))
313 result+='set title "Directory: %s WriteTime: %s Time: %s"\n' % (self.opts.dirName,timelines.usedTime,tm)
314 result+= "plot "
315 first=True
316 for val in self.opts.values:
317 if first:
318 first=False
319 else:
320 result+=", "
321 result+='"-" title "%s" ' % val
322 result+="\n"
323 for v,t,vals in data:
324 if t==tm:
325 for v in vals:
326 result+="%g\n" % v
327 result+="e\n"
328 elif self.opts.basicMode=='lines':
329
330 oPlots=timelines.getDataLocation(value=self.opts.values,
331 position=self.opts.positions,
332 vectorMode=self.opts.vectorMode)
333
334 plots=oPlots[:]
335 rPlots=None
336
337 if reference:
338 rPlots=reference.getDataLocation(value=self.opts.values,
339 position=self.opts.positions,
340 vectorMode=self.opts.vectorMode)
341 for gp,pos,val,comp,tv in rPlots:
342 plots.append((gp,
343 pos,
344 self.opts.refprefix+" "+val,
345 comp,
346 tv))
347 if self.opts.referenceTime and reference!=None:
348 minTime,maxTime=reference.timeRange()
349 else:
350 minTime,maxTime=timelines.timeRange()
351 if self.opts.minTime:
352 minTime=self.opts.minTime
353 if self.opts.maxTime:
354 maxTime=self.opts.maxTime
355 result+= "set xrange [%g:%g]\n" % (minTime,maxTime)
356 if self.opts.collectLines=="values":
357 for val in self.opts.values:
358 vname=val
359 if val in timelines.vectors:
360 vname+="_"+self.opts.vectorMode
361 result+=self.setFile("%s_writeTime_%s_Value_%s.png" % (self.opts.dirName,timelines.usedTime,vname))
362 result+='set title "Directory: %s WriteTime: %s Value: %s"\n' % (self.opts.dirName,timelines.usedTime,vname)
363 result+= "plot "
364 first=True
365 for f,v,p,i,tl in plots:
366 if v==val:
367 if first:
368 first=False
369 else:
370 result+=" , "
371 if type(i)==int:
372 result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,p)
373 else:
374 result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,p)
375
376 result+="\n"
377 elif self.opts.collectLines=="positions":
378 for pos in self.opts.positions:
379 result+=self.setFile("%s_writeTime_%s_Position_%s.png" % (self.opts.dirName,timelines.usedTime,pos))
380 result+='set title "Directory: %s WriteTime: %s Position: %s"\n' % (self.opts.dirName,timelines.usedTime,pos)
381 result+= "plot "
382 first=True
383 for f,v,p,i in plots:
384 if p==pos:
385 if first:
386 first=False
387 else:
388 result+=" , "
389 if type(i)==int:
390 result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,v)
391 else:
392 result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,v)
393 result+="\n"
394
395 else:
396 self.error("Unimplemented collection of lines:",self.opts.collectLines)
397 else:
398 self.error("Not implemented basicMode",self.opts.basicMode)
399
400 if self.opts.csvFile:
401 if self.opts.basicMode!='lines':
402 self.error("CSV-files currently only supported for lines-mode")
403 spread=plots[0][-1]()
404 for line in plots[1:]:
405 if line[3]==0:
406 sp=line[-1]()
407 try:
408 spread+=sp
409 except WrongDataSize,e:
410 if self.opts.resample:
411 for n in sp.names()[1:]:
412 data=spread.resample(sp,
413 n,
414 extendData=self.opts.extendData)
415 try:
416 spread.append(n,data)
417 except ValueError:
418 spread.append(self.opts.refprefix+" "+n,data)
419 else:
420 self.warning("Try the --resample-option")
421 raise
422
423 spread.writeCSV(self.opts.csvFile)
424 elif self.opts.compare or self.opts.metrics:
425 if self.opts.basicMode!='lines':
426 self.error("Compare currently only supported for lines-mode")
427
428 if self.opts.compare:
429 if rPlots==None:
430 self.error("No reference data specified. Can't compare")
431 elif len(rPlots)!=len(oPlots):
432 self.error("Number of original data sets",len(oPlots),
433 "is not equal to the reference data sets",
434 len(rPlots))
435
436 for i,p in enumerate(oPlots):
437 pth,val,loc,ind,tl=p
438 if self.opts.compare:
439 rpth,rval,rloc,rind,rtl=rPlots[i]
440 if val!=rval or loc!=rloc or ind!=rind:
441 self.error("Original data",p,"and reference",rPlots[i],
442 "do not match")
443 data=tl()
444 try:
445 dataIndex=1+ind
446 if self.opts.metrics:
447 print "Metrics for",val,"on",loc,"index",ind,"(Path:",pth,")"
448 result=data.metrics(data.names()[dataIndex])
449 print " Min :",result["min"]
450 print " Max :",result["max"]
451 print " Average :",result["average"]
452 print " Weighted average :",result["wAverage"]
453 if not self.opts.compare:
454 print "Data size:",data.size()
455 print " Time Range :",result["tMin"],result["tMax"]
456 if self.opts.compare:
457 print "Comparing",val,"on",loc,"index",ind,"(path:",pth,")"
458 ref=rtl()
459 result=data.compare(ref,data.names()[dataIndex])
460 print " Max difference :",result["max"]
461 print " Average difference :",result["average"]
462 print " Weighted average :",result["wAverage"]
463 print "Data size:",data.size(),"Reference:",ref.size()
464 if not self.opts.metrics:
465 print " Time Range :",result["tMin"],result["tMax"]
466 print
467 except TypeError:
468 if self.opts.vectorMode=="mag":
469 self.error("Vector-mode 'mag' not supported for --compare and --metrics")
470 else:
471 raise
472 else:
473 dest=sys.stdout
474 if self.opts.gnuplotFile:
475 dest=open(self.opts.gnuplotFile,"w")
476
477 dest.write(result)
478