# 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