1
2 """
3 Application class that implements pyFoamSamplePlot.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.SampleDirectory import SampleDirectory
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 Reads data from the sample-dictionary and generates appropriate
22 gnuplot-commands. 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
34 modeChoices=["separate","timesInOne","fieldsInOne","linesInOne","complete"]
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("--line",
43 action="append",
44 default=None,
45 dest="line",
46 help="Thesample line from which data is plotted (can be used more than once)")
47 data.add_option("--field",
48 action="append",
49 default=None,
50 dest="field",
51 help="The fields that are plotted (can be used more than once). If none are specified all found fields are used")
52 data.add_option("--directory-name",
53 action="store",
54 default="samples",
55 dest="dirName",
56 help="Alternate name for the directory with the samples (Default: %default)")
57 data.add_option("--preferred-component",
58 action="store",
59 type="int",
60 default=None,
61 dest="component",
62 help="The component that should be used for vectors. Otherwise the absolute value is used")
63 data.add_option("--reference-directory",
64 action="store",
65 default=None,
66 dest="reference",
67 help="A reference directory. If fitting sample data is found there it is plotted alongside the regular data")
68 data.add_option("--reference-case",
69 action="store",
70 default=None,
71 dest="referenceCase",
72 help="A reference case where a directory with the same name is looked for. Mutual exclusive with --reference-directory")
73
74 time=OptionGroup(self.parser,
75 "Time",
76 "Select the times to plot")
77 self.parser.add_option_group(time)
78
79 time.add_option("--time",
80 action="append",
81 default=None,
82 dest="time",
83 help="The times that are plotted (can be used more than once). If none are specified all found times are used")
84 time.add_option("--min-time",
85 action="store",
86 type="float",
87 default=None,
88 dest="minTime",
89 help="The smallest time that should be used")
90 time.add_option("--max-time",
91 action="store",
92 type="float",
93 default=None,
94 dest="maxTime",
95 help="The biggest time that should be used")
96 time.add_option("--fuzzy-time",
97 action="store_true",
98 default=False,
99 dest="fuzzyTime",
100 help="Try to find the next timestep if the time doesn't match exactly")
101
102 output=OptionGroup(self.parser,
103 "Appearance",
104 "How it should be plotted")
105 self.parser.add_option_group(output)
106
107 output.add_option("--mode",
108 type="choice",
109 default="separate",
110 dest="mode",
111 action="store",
112 choices=self.modeChoices,
113 help="What kind of plots are generated: a) separate for every time, line and field b) all times of a field in one plot c) all fields of a time in one plot d) all lines in one plot e) everything in one plot (Names: "+string.join(self.modeChoices,", ")+") Default: %default")
114 output.add_option("--unscaled",
115 action="store_false",
116 dest="scaled",
117 default=True,
118 help="Don't scale a value to the same range for all plots")
119 output.add_option("--scale-all",
120 action="store_true",
121 dest="scaleAll",
122 default=False,
123 help="Use the same scale for all fields (else use one scale for each field)")
124 output.add_option("--gnuplot-file",
125 action="store",
126 dest="gnuplotFile",
127 default=None,
128 help="Write the necessary gnuplot commands to this file. Else they are written to the standard output")
129 output.add_option("--picture-destination",
130 action="store",
131 dest="pictureDest",
132 default=None,
133 help="Directory the pictures should be stored to")
134 output.add_option("--name-prefix",
135 action="store",
136 dest="namePrefix",
137 default=None,
138 help="Prefix to the picture-name")
139 output.add_option("--csv-file",
140 action="store",
141 dest="csvFile",
142 default=None,
143 help="Write the data to a CSV-file instead of the gnuplot-commands")
144
145 data.add_option("--info",
146 action="store_true",
147 dest="info",
148 default=False,
149 help="Print info about the sampled data and exit")
150 output.add_option("--style",
151 action="store",
152 default="lines",
153 dest="style",
154 help="Gnuplot-style for the data (Default: %default)")
155 output.add_option("--clean-filename",
156 action="store_true",
157 dest="cleanFilename",
158 default=False,
159 help="Clean filenames so that they can be used in HTML or Latex-documents")
160 output.add_option("--reference-prefix",
161 action="store",
162 dest="refprefix",
163 default="Reference",
164 help="Prefix that gets added to the reference lines. Default: %default")
165 output.add_option("--resample-reference",
166 action="store_true",
167 dest="resampleReference",
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("--compare",
176 action="store_true",
177 dest="compare",
178 default=None,
179 help="Compare all data sets that are also in the reference data")
180 output.add_option("--metrics",
181 action="store_true",
182 dest="metrics",
183 default=None,
184 help="Print the metrics of the data sets")
185
187 samples=SampleDirectory(self.parser.getArgs()[0],
188 dirName=self.opts.dirName)
189 reference=None
190 if self.opts.reference and self.opts.referenceCase:
191 self.error("Options --reference-directory and --reference-case are mutual exclusive")
192 if self.opts.csvFile and (self.opts.compare or self.opts.metrics):
193 self.error("Options --csv-file and --compare/--metrics are mutual exclusive")
194
195 if self.opts.reference:
196 reference=SampleDirectory(self.parser.getArgs()[0],
197 dirName=self.opts.reference)
198 elif self.opts.referenceCase:
199 reference=SampleDirectory(self.opts.referenceCase,
200 dirName=self.opts.dirName)
201
202 lines=samples.lines()
203 times=samples.times
204 values=samples.values()
205
206 if self.opts.info:
207 print "Times : ",samples.times
208 print "Lines : ",samples.lines()
209 print "Fields: ",samples.values()
210
211 if reference:
212 print "\nReference Data:"
213 print "Times : ",reference.times
214 print "Lines : ",reference.lines()
215 print "Fields: ",reference.values()
216
217 return 0
218
219 if self.opts.line==None:
220
221 self.opts.line=lines
222 else:
223 for l in self.opts.line:
224 if l not in lines:
225 error("The line",l,"does not exist in",lines)
226
227 if self.opts.maxTime or self.opts.minTime:
228 if self.opts.time:
229 error("Times",self.opts.time,"and range [",self.opts.minTime,",",self.opts.maxTime,"] set: contradiction")
230 self.opts.time=[]
231 if self.opts.maxTime==None:
232 self.opts.maxTime= 1e20
233 if self.opts.minTime==None:
234 self.opts.minTime=-1e20
235
236 for t in times:
237 if float(t)<=self.opts.maxTime and float(t)>=self.opts.minTime:
238 self.opts.time.append(t)
239
240 if len(self.opts.time)==0:
241 error("No times in range [",self.opts.minTime,",",self.opts.maxTime,"] found: ",times)
242 elif self.opts.time:
243 iTimes=self.opts.time
244 self.opts.time=[]
245 for t in iTimes:
246 if t in samples.times:
247 self.opts.time.append(t)
248 elif self.opts.fuzzyTime:
249 tf=float(t)
250 use=None
251 dist=1e20
252 for ts in samples.times:
253 if abs(tf-float(ts))<dist:
254 use=ts
255 dist=abs(tf-float(ts))
256 if use and use not in self.opts.time:
257 self.opts.time.append(use)
258 else:
259 pass
260
261
262 plots=[]
263 oPlots=[]
264 rPlots=[]
265
266 if self.opts.mode=="separate":
267 if self.opts.time==None:
268 self.opts.time=samples.times
269 if self.opts.field==None:
270 self.opts.field=samples.values()
271 if self.opts.line==None:
272 self.opts.line=samples.lines()
273 for t in self.opts.time:
274 for f in self.opts.field:
275 for l in self.opts.line:
276 plot=samples.getData(line=[l],
277 value=[f],
278 time=[t])
279 oPlots.append(plot[:])
280 if reference:
281 p=reference.getData(line=[l],
282 value=[f],
283 time=[t],
284 note=self.opts.refprefix+" ")
285 rPlots.append(p)
286 plot+=p
287 plots.append(plot)
288
289 elif self.opts.mode=="timesInOne":
290 if self.opts.field==None:
291 self.opts.field=samples.values()
292 if self.opts.line==None:
293 self.opts.line=samples.lines()
294 for f in self.opts.field:
295 for l in self.opts.line:
296 plot=samples.getData(line=[l],
297 value=[f],
298 time=self.opts.time)
299 oPlots.append(plot[:])
300
301 if reference:
302 p=reference.getData(line=[l],
303 value=[f],
304 time=self.opts.time,
305 note=self.opts.refprefix+" ")
306 rPlots.append(p)
307 plot+=p
308
309 plots.append(plot)
310
311 elif self.opts.mode=="fieldsInOne":
312 if self.opts.scaled and not self.opts.scaleAll:
313 warning("In mode '",self.opts.mode,"' all fields are scaled to the same value")
314 self.opts.scaleAll=True
315
316 if self.opts.time==None:
317 self.opts.time=samples.times
318 if self.opts.line==None:
319 self.opts.line=samples.lines()
320 for t in self.opts.time:
321 for l in self.opts.line:
322 plot=samples.getData(line=[l],
323 value=self.opts.field,
324 time=[t])
325 oPlots.append(plot[:])
326 if reference:
327 p=reference.getData(line=[l],
328 value=self.opts.field,
329 time=[t],
330 note=self.opts.refprefix+" ")
331 rPlots.append(p)
332 plot+=p
333
334 plots.append(plot)
335
336 elif self.opts.mode=="linesInOne":
337 if self.opts.field==None:
338 self.opts.field=samples.values()
339 if self.opts.time==None:
340 self.opts.time=samples.times()
341 for f in self.opts.field:
342 for t in self.opts.time:
343 plot=samples.getData(line=self.opts.line,
344 value=[f],
345 time=[t])
346 oPlots.append(plot[:])
347
348 if reference:
349 p=reference.getData(line=self.opts.line,
350 value=[f],
351 time=[t],
352 note=self.opts.refprefix+" ")
353 rPlots.append(p)
354 plot+=p
355
356 plots.append(plot)
357
358 elif self.opts.mode=="complete":
359 if self.opts.scaled and not self.opts.scaleAll:
360 warning("In mode '",self.opts.mode,"' all fields are scaled to the same value")
361 self.opts.scaleAll=True
362
363 plot=samples.getData(line=self.opts.line,
364 value=self.opts.field,
365 time=self.opts.time)
366 oPlots.append(plot[:])
367 if reference:
368 p=reference.getData(line=self.opts.line,
369 value=self.opts.field,
370 time=self.opts.time,
371 note=self.opts.refprefix+" ")
372 plot+=p
373 rPlots.append(p)
374
375 plots.append(plot)
376
377 if self.opts.scaled:
378 if self.opts.scaleAll:
379 vRange=None
380 else:
381 vRanges={}
382
383 for p in plots:
384 for d in p:
385 mi,ma=d.range(component=self.opts.component)
386 nm=d.name
387 if not self.opts.scaleAll:
388 if nm in vRanges:
389 vRange=vRanges[nm]
390 else:
391 vRange=None
392
393 if vRange==None:
394 vRange=mi,ma
395 else:
396 vRange=min(vRange[0],mi),max(vRange[1],ma)
397 if not self.opts.scaleAll:
398 vRanges[nm]=vRange
399
400 result="set term png\n"
401
402 for p in plots:
403 if len(p)<1:
404 continue
405
406 name=""
407
408 if self.opts.namePrefix:
409 name+=self.opts.namePrefix+"_"
410 name+=self.opts.dirName
411 title=None
412 tIndex=times.index(p[0].time())
413
414
415
416 if self.opts.mode=="separate":
417 name+="_%s" % (p[0].line())
418 name+="_%s_%04d" % (p[0].name,tIndex)
419 title="%s at t=%f on %s" % (p[0].name,float(p[0].time()),p[0].line())
420 elif self.opts.mode=="timesInOne":
421 name+="_%s" % (p[0].line())
422 if self.opts.time!=None:
423 name+="_"+"_".join(["t="+t for t in self.opts.time])
424 name+="_%s" % p[0].name
425 title="%s on %s" % (p[0].name,p[0].line())
426 elif self.opts.mode=="fieldsInOne":
427 name+="_%s" % (p[0].line())
428 if self.opts.field!=None:
429 name+="_"+string.join(self.opts.field,"_")
430 if self.opts.time!=None:
431 name+="_"+"_".join(["t="+t for t in self.opts.time])
432 name+="_%04d" % tIndex
433 title="t=%f on %s" % (float(p[0].time()),p[0].line())
434 elif self.opts.mode=="linesInOne":
435 name+="_%s" % (p[0].name)
436 if self.opts.line!=None:
437 name+="_"+string.join(self.opts.line,"_")
438 name+="_t=%f" % float(p[0].time())
439 title="%s at t=%f" % (p[0].name,float(p[0].time()))
440 elif self.opts.mode=="complete":
441 pass
442
443 name+=".png"
444 if self.opts.pictureDest:
445 name=path.join(self.opts.pictureDest,name)
446
447 if self.opts.cleanFilename:
448 name=cleanFilename(name)
449
450 result+='set output "%s"\n' % name
451 if title!=None:
452 result+='set title "%s"\n' % title
453
454 result+="plot "
455 if self.opts.scaled:
456 if not self.opts.scaleAll:
457 vRange=vRanges[p[0].name]
458
459
460 if abs(vRange[0]-vRange[1])>1e-5*max(abs(vRange[0]),abs(vRange[1])) and max(abs(vRange[0]),abs(vRange[1]))>1e-10:
461 result+="[][%g:%g] " % vRange
462
463 first=True
464
465 for d in p:
466 if first:
467 first=False
468 else:
469 result+=", "
470
471 colSpec="%s" % (d.index+1)
472 if d.isVector():
473 if self.opts.component:
474 colSpec="%d" % (d.index+1+self.opts.component)
475 else:
476 colSpec="(sqrt($%d**2+$%d**2+$%d**2))" % (d.index+1,d.index+2,d.index+3)
477
478 result+='"%s" using 1:%s ' % (d.file,colSpec)
479
480 title=d.note
481 if self.opts.mode=="separate":
482 title+=""
483 elif self.opts.mode=="timesInOne":
484 title+="t=%f" % float(d.time())
485 elif self.opts.mode=="fieldsInOne":
486 title+="%s" % d.name
487 elif self.opts.mode=="linesInOne":
488 title+="t=%f" % float(d.time())
489 elif self.opts.mode=="complete":
490 title+="%s at t=%f" % (d.name,float(d.time()))
491
492 if len(self.opts.line)>1:
493 title+=" on %s" % d.line()
494
495 if title=="":
496 result+="notitle "
497 else:
498 result+='title "%s" ' % title
499
500 result+="with %s " % self.opts.style
501
502 result+="\n"
503
504 if self.opts.csvFile:
505 tmp=sum(plots,[])
506 c=tmp[0]()
507 for p in tmp[1:]:
508 try:
509 c+=p()
510 except WrongDataSize,e:
511 if self.opts.resampleReference:
512 sp=p()
513 for n in sp.names()[1:]:
514 data=c.resample(sp,
515 n,
516 extendData=self.opts.extendData)
517 try:
518 c.append(n,data)
519 except ValueError:
520 c.append(self.opts.refprefix+" "+n,data)
521 else:
522 self.warning("Try the --resample-option")
523 raise
524
525 c.writeCSV(self.opts.csvFile)
526 elif self.opts.compare or self.opts.metrics:
527 oPlots=[item for sublist in oPlots for item in sublist]
528 rPlots=[item for sublist in rPlots for item in sublist]
529 if len(rPlots)!=len(oPlots) and self.opts.compare:
530 self.error("Number of original data sets",len(oPlots),
531 "is not equal to the reference data sets",
532 len(rPlots))
533 if len(rPlots)==0 and self.opts.metrics:
534 rPlots=[None]*len(oPlots)
535
536 for o,r in zip(oPlots,rPlots):
537 data=o()
538 if self.opts.compare:
539 if o.name!=r.name or o.index!=r.index:
540 self.error("Data from original",o.name,o.index,
541 "and reference",r.name,r.index,
542 "do not match")
543 ref=r()
544 else:
545 ref=None
546 for i,n in enumerate(data.names()):
547 if i==0:
548 continue
549 if self.opts.metrics:
550 print "Metrics for",o.name,"(Path:",o.file,")"
551 result=data.metrics(data.names()[i])
552 print " Min :",result["min"]
553 print " Max :",result["max"]
554 print " Average :",result["average"]
555 print " Weighted average :",result["wAverage"]
556 if not self.opts.compare:
557 print "Data size:",data.size()
558 print " Time Range :",result["tMin"],result["tMax"]
559 if self.opts.compare:
560 print "Comparing",o.name,"with name",n,"(Path:",o.file,")"
561 result=data.compare(ref,data.names()[i])
562 print " Max difference :",result["max"]
563 print " Average difference :",result["average"]
564 print " Weighted average :",result["wAverage"]
565 print "Data size:",data.size(),"Reference:",ref.size()
566 if not self.opts.metrics:
567 print " Time Range :",result["tMin"],result["tMax"]
568
569 print
570 else:
571 dest=sys.stdout
572 if self.opts.gnuplotFile:
573 dest=open(self.opts.gnuplotFile,"w")
574
575 dest.write(result)
576