Source code for PyFoam.Applications.TimelinePlot

#  ICE Revision: $Id$
"""
Application class that implements pyFoamTimelinePlot.py
"""

import sys
from os import path
from optparse import OptionGroup

from .PyFoamApplication import PyFoamApplication
from PyFoam.RunDictionary.TimelineDirectory import TimelineDirectory
from PyFoam.Basics.SpreadsheetData import WrongDataSize
from PyFoam.ThirdParty.six import print_

from .PlotHelpers import cleanFilename

[docs]class TimelinePlot(PyFoamApplication): def __init__(self, args=None, **kwargs): description="""\ Searches a directory for timelines that were generated by some functionObject and generates the commands to gnuplot it. As an option the data can be written to a CSV-file. """ PyFoamApplication.__init__(self, args=args, description=description, usage="%prog [options] <casedir>", nr=1, changeVersion=False, interspersed=True, **kwargs)
[docs] def addOptions(self): data=OptionGroup(self.parser, "Data", "Select the data to plot") self.parser.add_option_group(data) data.add_option("--fields", action="append", default=None, dest="fields", help="The fields for which timelines should be plotted. All if unset") data.add_option("--positions", action="append", default=None, dest="positions", help="The positions for which timelines should be plotted. Either strings or integers (then the corresponding column number will be used). All if unset") data.add_option("--write-time", default=None, dest="writeTime", help="If more than one time-subdirectory is stored select which one is used") data.add_option("--directory-name", action="store", default="probes", dest="dirName", help="Alternate name for the directory with the samples (Default: %default)") data.add_option("--reference-directory", action="store", default=None, dest="reference", help="A reference directory. If fitting timeline data is found there it is plotted alongside the regular data") data.add_option("--reference-case", action="store", default=None, dest="referenceCase", help="A reference case where a directory with the same name is looked for. Mutual exclusive with --reference-directory") time=OptionGroup(self.parser, "Time", "Select the times to plot") self.parser.add_option_group(time) time.add_option("--time", action="append", type="float", default=None, dest="time", help="The times that are plotted (can be used more than once). Has to be specified for bars") time.add_option("--min-time", action="store", type="float", default=None, dest="minTime", help="The smallest time that should be used for lines") time.add_option("--max-time", action="store", type="float", default=None, dest="maxTime", help="The biggest time that should be used for lines") time.add_option("--reference-time", action="store_true", default=False, dest="referenceTime", help="Use the time of the reference data for scaling instead of the regular data") plot=OptionGroup(self.parser, "Plot", "How data should be plotted") self.parser.add_option_group(plot) plot.add_option("--basic-mode", type="choice", dest="basicMode", default=None, choices=["bars","lines"], help="Whether 'bars' of the values at selected times or 'lines' over the whole timelines should be plotted") vModes=["mag","x","y","z"] plot.add_option("--vector-mode", type="choice", dest="vectorMode", default="mag", choices=vModes, help="How vectors should be plotted. By magnitude or as a component. Possible values are "+str(vModes)+" Default: %default") plot.add_option("--collect-lines-by", type="choice", dest="collectLines", default="fields", choices=["fields","positions"], help="Collect lines for lineplotting either by 'fields' or 'positions'. Default: %default") output=OptionGroup(self.parser, "Output", "Where data should be plotted to") self.parser.add_option_group(output) output.add_option("--gnuplot-file", action="store", dest="gnuplotFile", default=None, help="Write the necessary gnuplot commands to this file. Else they are written to the standard output") output.add_option("--picture-destination", action="store", dest="pictureDest", default=None, help="Directory the pictures should be stored to") output.add_option("--name-prefix", action="store", dest="namePrefix", default=None, help="Prefix to the picture-name") output.add_option("--clean-filename", action="store_true", dest="cleanFilename", default=False, help="Clean filenames so that they can be used in HTML or Latex-documents") output.add_option("--csv-file", action="store", dest="csvFile", default=None, help="Write the data to a CSV-file instead of the gnuplot-commands") output.add_option("--excel-file", action="store", dest="excelFile", default=None, help="Write the data to a Excel-file instead of the gnuplot-commands") output.add_option("--pandas-data", action="store_true", dest="pandasData", default=False, help="Pass the raw data in pandas-format") output.add_option("--numpy-data", action="store_true", dest="numpyData", default=False, help="Pass the raw data in numpy-format") output.add_option("--reference-prefix", action="store", dest="refprefix", default="Reference", help="Prefix that gets added to the reference lines. Default: %default") data.add_option("--info", action="store_true", dest="info", default=False, help="Print info about the sampled data and exit") output.add_option("--resample", action="store_true", dest="resample", default=False, help="Resample the reference value to the current x-axis (for CSV and Excel-output)") output.add_option("--extend-data", action="store_true", dest="extendData", default=False, help="Extend the data range if it differs (for CSV and Excel-files)") output.add_option("--silent", action="store_true", dest="silent", default=False, help="Don't write to screen (with the silent and the compare-options)") numerics=OptionGroup(self.parser, "Quantify", "Metrics of the data and numerical comparisons") self.parser.add_option_group(numerics) numerics.add_option("--compare", action="store_true", dest="compare", default=None, help="Compare all data sets that are also in the reference data") numerics.add_option("--metrics", action="store_true", dest="metrics", default=None, help="Print the metrics of the data sets") numerics.add_option("--use-reference-for-comparison", action="store_false", dest="compareOnOriginal", default=True, help="Use the reference-data as the basis for the numerical comparison. Otherwise the original data will be used")
[docs] def setFile(self,fName): if self.opts.namePrefix: fName=self.opts.namePrefix+"_"+fName if self.opts.pictureDest: fName=path.join(self.opts.pictureDest,fName) name=fName if self.opts.cleanFilename: name=cleanFilename(fName) return 'set output "%s"\n' % name
[docs] def run(self): # remove trailing slashif present if self.opts.dirName[-1]==path.sep: self.opts.dirName=self.opts.dirName[:-1] usedDirName=self.opts.dirName.replace("/","_") timelines=TimelineDirectory(self.parser.getArgs()[0], dirName=self.opts.dirName, writeTime=self.opts.writeTime) reference=None if self.opts.reference and self.opts.referenceCase: self.error("Options --reference-directory and --reference-case are mutual exclusive") if (self.opts.csvFile or self.opts.excelFile or self.opts.pandasData or self.opts.numpyData) and (self.opts.compare or self.opts.metrics): self.error("Options --csv-file/excel-file/--pandas-data/--numpy-data and --compare/--metrics are mutual exclusive") if self.opts.reference: reference=TimelineDirectory(self.parser.getArgs()[0], dirName=self.opts.reference, writeTime=self.opts.writeTime) elif self.opts.referenceCase: reference=TimelineDirectory(self.opts.referenceCase, dirName=self.opts.dirName, writeTime=self.opts.writeTime) if self.opts.info: self.setData({'writeTimes' : timelines.writeTimes, 'usedTimes' : timelines.usedTime, 'fields' : timelines.values, 'positions' : timelines.positions(), 'timeRange' : timelines.timeRange()}) if not self.opts.silent: print_("Write Times : ",timelines.writeTimes) print_("Used Time : ",timelines.usedTime) print_("Fields : ",timelines.values,end="") if len(timelines.vectors)>0: if not self.opts.silent: print_(" Vectors: ",timelines.vectors) self.setData({'vectors':timelines.vectors}) else: if not self.opts.silent: print_() if not self.opts.silent: print_("Positions : ",timelines.positions()) print_("Time range : ",timelines.timeRange()) if reference: refData={'writeTimes' : reference.writeTimes, 'fields' : reference.values, 'positions' : reference.positions(), 'timeRange' : reference.timeRange()} if not self.opts.silent: print_("\nReference Data") print_("Write Times : ",reference.writeTimes) print_("Fields : ",reference.values,end="") if len(reference.vectors)>0: if not self.opts.silent: print_(" Vectors: ",reference.vectors) refData["vectors"]=reference.vectors else: if not self.opts.silent: print_() if not self.opts.silent: print_("Positions : ",reference.positions()) print_("Time range : ",reference.timeRange()) self.setData({"reference":refData}) return 0 if self.opts.fields==None: self.opts.fields=timelines.values else: for v in self.opts.fields: if v not in timelines.values: self.error("The requested value",v,"not in possible values",timelines.values) if self.opts.positions==None: self.opts.positions=timelines.positions() else: pos=self.opts.positions self.opts.positions=[] for p in pos: try: p=int(p) if p<0 or p>=len(timelines.positions()): self.error("Time index",p,"out of range for positons",timelines.positions()) else: self.opts.positions.append(timelines.positions()[p]) except ValueError: if p not in timelines.positions(): self.error("Position",p,"not in",timelines.positions()) else: self.opts.positions.append(p) if len(self.opts.positions)==0: self.error("No valid positions") result="set term png nocrop enhanced \n" if self.opts.basicMode==None: self.error("No mode selected. Do so with '--basic-mode'") elif self.opts.basicMode=='bars': if self.opts.time==None: self.error("No times specified for bar-plots") self.opts.time.sort() if self.opts.referenceTime and reference!=None: minTime,maxTime=reference.timeRange() else: minTime,maxTime=timelines.timeRange() usedTimes=[] hasMin=False for t in self.opts.time: if t<minTime: if not hasMin: usedTimes.append(minTime) hasMin=True elif t>maxTime: usedTimes.append(maxTime) break else: usedTimes.append(t) data=timelines.getData(usedTimes, value=self.opts.fields, position=self.opts.positions, vectorMode=self.opts.vectorMode) # print_(data) result+="set style data histogram\n" result+="set style histogram cluster gap 1\n" result+="set style fill solid border -1\n" result+="set boxwidth 0.9\n" result+="set xtics border in scale 1,0.5 nomirror rotate by 90 offset character 0, 0, 0\n" # set xtic rotate by -45\n" result+="set xtics (" for i,p in enumerate(self.opts.positions): if i>0: result+=" , " result+='"%s" %d' % (p,i) result+=")\n" for tm in usedTimes: if abs(float(tm))>1e20: continue result+=self.setFile("%s_writeTime_%s_Time_%s.png" % (usedDirName,timelines.usedTime,tm)) result+='set title "Directory: %s WriteTime: %s Time: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,tm) result+= "plot " first=True for val in self.opts.fields: if first: first=False else: result+=", " result+='"-" title "%s" ' % val.replace("_","\\\\_") result+="\n" for v,t,vals in data: if t==tm: for v in vals: result+="%g\n" % v result+="e\n" elif self.opts.basicMode=='lines': # print_(self.opts.positions) oPlots=timelines.getDataLocation(value=self.opts.fields, position=self.opts.positions, vectorMode=self.opts.vectorMode) plots=oPlots[:] rPlots=None if reference: rPlots=reference.getDataLocation(value=self.opts.fields, position=self.opts.positions, vectorMode=self.opts.vectorMode) for gp,pos,val,comp,tv in rPlots: plots.append((gp, pos, self.opts.refprefix+" "+val, comp, tv)) if self.opts.referenceTime and reference!=None: minTime,maxTime=reference.timeRange() else: minTime,maxTime=timelines.timeRange() if self.opts.minTime: minTime=self.opts.minTime if self.opts.maxTime: maxTime=self.opts.maxTime result+= "set xrange [%g:%g]\n" % (minTime,maxTime) if self.opts.collectLines=="fields": for val in self.opts.fields: vname=val if val in timelines.vectors: vname+="_"+self.opts.vectorMode result+=self.setFile("%s_writeTime_%s_Value_%s.png" % (usedDirName,timelines.usedTime,vname)) result+='set title "Directory: %s WriteTime: %s Value: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,vname.replace("_","\\\\\\_")) result+= "plot " first=True for f,v,p,i,tl in plots: if v==val: if first: first=False else: result+=" , " if type(i)==int: result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,p.replace("_","\\\\_")) else: result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,p.replace("_","\\\\_")) result+="\n" elif self.opts.collectLines=="positions": for pos in self.opts.positions: result+=self.setFile("%s_writeTime_%s_Position_%s.png" % (usedDirName,timelines.usedTime,pos)) result+='set title "Directory: %s WriteTime: %s Position: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,pos.replace("_","\\\\_")) result+= "plot " first=True for f,v,p,i,tl in plots: if p==pos: if first: first=False else: result+=" , " if type(i)==int: result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,v.replace("_","\\\\_")) else: result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,v.replace("_","\\\\_")) result+="\n" else: self.error("Unimplemented collection of lines:",self.opts.collectLines) else: self.error("Not implemented basicMode",self.opts.basicMode) if self.opts.csvFile or self.opts.excelFile or self.opts.pandasData or self.opts.numpyData: if self.opts.basicMode!='lines': self.error("CSV and Excel-files currently only supported for lines-mode (also Pandas and Numpy-data)") spread=plots[0][-1]() usedFiles=set([plots[0][0]]) for line in plots[1:]: if line[0] not in usedFiles: usedFiles.add(line[0]) sp=line[-1]() try: spread+=sp except WrongDataSize: if self.opts.resample: for n in sp.names()[1:]: data=spread.resample(sp, n, extendData=self.opts.extendData) try: spread.append(n,data) except ValueError: spread.append(self.opts.refprefix+" "+n,data) else: self.warning("Try the --resample-option") raise if self.opts.csvFile: spread.writeCSV(self.opts.csvFile) if self.opts.excelFile: spread.getData().to_excel(self.opts.excelFile) if self.opts.pandasData: self.setData({"series":spread.getSeries(), "dataFrame":spread.getData()}) if self.opts.numpyData: self.setData({"data":spread.data.copy()}) elif self.opts.compare or self.opts.metrics: statData={} if self.opts.compare: statData["compare"]={} if self.opts.metrics: statData["metrics"]={} for p in self.opts.positions: if self.opts.compare: statData["compare"][p]={} if self.opts.metrics: statData["metrics"][p]={} if self.opts.basicMode!='lines': self.error("Compare currently only supported for lines-mode") if self.opts.compare: if rPlots==None: self.error("No reference data specified. Can't compare") elif len(rPlots)!=len(oPlots): self.error("Number of original data sets",len(oPlots), "is not equal to the reference data sets", len(rPlots)) for i,p in enumerate(oPlots): pth,val,loc,ind,tl=p if self.opts.compare: rpth,rval,rloc,rind,rtl=rPlots[i] if val!=rval or loc!=rloc or ind!=rind: self.error("Original data",p,"and reference",rPlots[i], "do not match") data=tl() try: dataIndex=1+ind if self.opts.metrics: if not self.opts.silent: print_("Metrics for",val,"on",loc,"index",ind,"(Path:",pth,")") result=data.metrics(data.names()[dataIndex], minTime=self.opts.minTime, maxTime=self.opts.maxTime) statData["metrics"][loc][val]=result if not self.opts.silent: print_(" Min :",result["min"]) print_(" Max :",result["max"]) print_(" Average :",result["average"]) print_(" Weighted average :",result["wAverage"]) if not self.opts.compare: print_("Data size:",data.size()) print_(" Time Range :",result["tMin"],result["tMax"]) if self.opts.compare: if not self.opts.silent: print_("Comparing",val,"on",loc,"index",ind,"(path:",pth,")",end="") ref=rtl() if self.opts.compareOnOriginal: if not self.opts.silent: print_("on original data points") result=data.compare(ref, data.names()[dataIndex], minTime=self.opts.minTime, maxTime=self.opts.maxTime) else: if not self.opts.silent: print_("on reference data points") result=ref.compare(data, data.names()[dataIndex], minTime=self.opts.minTime, maxTime=self.opts.maxTime) statData["compare"][loc][val]=result if not self.opts.silent: print_(" Max difference :",result["max"]) print_(" Average difference :",result["average"]) print_(" Weighted average :",result["wAverage"]) print_("Data size:",data.size(),"Reference:",ref.size()) if not self.opts.metrics: print_(" Time Range :",result["tMin"],result["tMax"]) if not self.opts.silent: print_() except TypeError: if self.opts.vectorMode=="mag": self.error("Vector-mode 'mag' not supported for --compare and --metrics") else: raise self.setData(statData) else: dest=sys.stdout if self.opts.gnuplotFile: dest=open(self.opts.gnuplotFile,"w") dest.write(result)
# Should work with Python3 and Python2