diff --git a/bin/make-pgfplots b/bin/make-pgfplots --- a/bin/make-pgfplots +++ b/bin/make-pgfplots @@ -1,2802 +1,2831 @@ #! /usr/bin/env python """\ Usage: %prog [options] file.dat [file2.dat ...] TODO * Optimise output for e.g. lots of same-height bins in a row * Add a RatioFullRange directive to show the full range of error bars + MC envelope in the ratio * Tidy LaTeX-writing code -- faster to compile one doc only, then split it? * Handle boolean values flexibly (yes, no, true, false, etc. as well as 1, 0) """ from __future__ import print_function ## ## This program is copyright by Christian Gutschow and ## the Rivet team https://rivet.hepforge.org. It may be used ## for scientific and private purposes. Patches are welcome, but please don't ## redistribute changed versions yourself. ## ## Check the Python version import sys if sys.version_info[:3] < (2,6,0): print("make-plots requires Python version >= 2.6.0... exiting") sys.exit(1) ## Try to rename the process on Linux try: import ctypes libc = ctypes.cdll.LoadLibrary('libc.so.6') libc.prctl(15, 'make-plots', 0, 0, 0) except Exception as e: pass import os, logging, re import tempfile import getopt import string import copy from math import * ## Regex patterns pat_begin_block = re.compile(r'^#+\s*BEGIN ([A-Z0-9_]+) ?(\S+)?') pat_end_block = re.compile('^#+\s*END ([A-Z0-9_]+)') pat_comment = re.compile('^#|^\s*$') pat_property = re.compile('^(\w+?)=(.*)$') pat_path_property = re.compile('^(\S+?)::(\w+?)=(.*)$') +pat_options = re.compile(r"^.*(:\w+=[^:/]+)+") def fuzzyeq(a, b, tolerance=1e-6): "Fuzzy equality comparison function for floats, with given fractional tolerance" # if type(a) is not float or type(a) is not float: # print(a, b) if (a == 0 and abs(b) < 1e-12) or (b == 0 and abs(a) < 1e-12): return True return 2.0*abs(a-b) < tolerance * abs(a+b) def inrange(x, a, b): return x >= a and x < b def floatify(x): if type(x) is str: x = x.split() if not hasattr(x, "__len__"): x = [x] x = [float(a) for a in x] return x[0] if len(x) == 1 else x def floatpair(x): if type(x) is str: x = x.split() if hasattr(x, "__len__"): assert len(x) == 2 return [float(a) for a in x] return [float(x), float(x)] def is_end_marker(line, blockname): m = pat_end_block.match(line) return m and m.group(1) == blockname def is_comment(line): return pat_comment.match(line) is not None def checkColor(line): if '[RGB]' in line: # e.g. '{[RGB]{1,2,3}}' if line[0] == '{' and line[-1] == '}': line = line[1:-1] # i.e. '[RGB]{1,2,3}' composition = line.split('{')[1][:-1] # e.g. '1,2,3' line = '{rgb,255:red,%s;green,%s;blue,%s}' % tuple(composition.split(',')) return line +def stripOptions(path): + return re.sub(r':\w+=[^:/]+', "", path) + + +def extractOptionString(path): + m = pat_options.match(path) + if not m: + return "" + opts = list(m.groups()) + for i in range(len(opts)): + opts[i] = opts[i].strip(':') + return ",".join(opts) + + + class Described(object): "Inherited functionality for objects holding a 'props' dictionary" def __init__(self): pass def has_attr(self, key): return key in self.props def set_attr(self, key, val): self.props[key] = val def attr(self, key, default=None): return self.props.get(key, default) def attr_bool(self, key, default=None): x = self.attr(key, default) if str(x).lower() in ["1", "true", "yes", "on"]: return True if str(x).lower() in ["0", "false", "no", "off"]: return False return None def attr_int(self, key, default=None): x = self.attr(key, default) try: x = int(x) except: x = None return x def attr_float(self, key, default=None): x = self.attr(key, default) try: x = float(x) except: x = None return x #def doGrid(self, pre = ''): # grid_major = self.attr_bool(pre + 'Grid', False) or self.attr_bool(pre + 'GridMajor', False) # grid_minor = self.attr_bool(pre + 'GridMinor', False) # if grid_major and grid_minor: return 'both' # elif grid_major: return 'major' # elif grid_minor: return 'minor' def ratio_names(self, skipFirst = False): offset = 1 if skipFirst else 0 return [ ('RatioPlot%s' % (str(i) if i else ''), i) for i in range(skipFirst, 9) ] def legend_names(self, skipFirst = False): offset = 1 if skipFirst else 0 return [ ('Legend%s' % (str(i) if i else ''), i) for i in range(skipFirst, 9) ] class InputData(Described): def __init__(self, filename): self.filename=filename if not self.filename.endswith(".dat"): self.filename += ".dat" self.props = {} self.histos = {} self.ratios = {} self.special = {} self.functions = {} # not sure what this is good for ... yet self.histomangler = {} self.normalised = False + self.props['_OptSubs'] = { } self.props['is2dim'] = False # analyse input dat file f = open(self.filename) for line in f: m = pat_begin_block.match(line) if m: name, path = m.group(1,2) if path is None and name != 'PLOT': raise Exception('BEGIN sections need a path name.') if name == 'PLOT': self.read_input(f); elif name == 'SPECIAL': self.special[path] = Special(f) elif name == 'HISTOGRAM' or name == 'HISTOGRAM2D': self.histos[path] = Histogram(f, p=path) self.props['is2dim'] = self.histos[path].is2dim self.histos[path].zlog = self.attr_bool('LogZ') if self.attr_bool('Is3D', 0): self.histos[path].zlog = False if not self.histos[path].getName() == '': newname = self.histos[path].getName() self.histos[newname] = copy.deepcopy(self.histos[path]) del self.histos[path] elif name == 'HISTO1D': self.histos[path] = Histo1D(f, p=path) if not self.histos[path].getName() == '': newname = self.histos[path].getName() self.histos[newname] = copy.deepcopy(self.histos[path]) del self.histos[path] elif name == 'HISTO2D': self.histos[path] = Histo2D(f, p=path) self.props['is2dim'] = True self.histos[path].zlog = self.attr_bool('LogZ') if self.attr_bool('Is3D', 0): self.histos[path].zlog = False if not self.histos[path].getName() == '': newname = self.histos[path].getName() self.histos[newname] = copy.deepcopy(self.histos[path]) del self.histos[path] elif name == 'HISTOGRAMMANGLER': self.histomangler[path] = PlotFunction(f) elif name == 'COUNTER': self.histos[path] = Counter(f, p=path) elif name == 'VALUE': self.histos[path] = Value(f, p=path) elif name == 'FUNCTION': self.functions[path] = Function(f) f.close() self.apply_config_files(opts.CONFIGFILES) self.props.setdefault('PlotSizeX', 10.) self.props.setdefault('PlotSizeY', 6.) if self.props['is2dim']: self.props['PlotSizeX'] -= 1.7 self.props['PlotSizeY'] = 10. self.props['RatioPlot'] = '0' if self.props.get('PlotSize', '') != '': plotsizes = self.props['PlotSize'].split(',') self.props['PlotSizeX'] = float(plotsizes[0]) self.props['PlotSizeY'] = float(plotsizes[1]) if len(plotsizes) == 3: self.props['RatioPlotSizeY'] = float(plotsizes[2]) del self.props['PlotSize'] self.props['RatioPlotSizeY'] = 0. # default is no ratio if self.attr('MainPlot') == '0': ## has RatioPlot, but no MainPlot self.props['PlotSizeY'] = 0. # size of MainPlot self.props['RatioPlot'] = '1' #< don't allow both to be zero! if self.attr_bool('RatioPlot'): if self.has_attr('RatioPlotYSize') and self.attr('RatioPlotYSize') != '': self.props['RatioPlotSizeY'] = self.attr_float('RatioPlotYSize') else: self.props['RatioPlotSizeY'] = 6. if self.attr_bool('MainPlot') else 3. if self.props['is2dim']: self.props['RatioPlotSizeY'] *= 2. for rname, _ in self.ratio_names(True): if self.attr_bool(rname, False): if self.props.get(rname+'YSize') != '': self.props[rname+'SizeY'] = self.attr_float(rname+'YSize') else: self.props[rname+'SizeY'] = 3. if self.attr('MainPlot') == '0' else 6. if self.props['is2dim']: self.props[rname+'SizeY'] *= 2. ## Ensure numbers, not strings self.props['PlotSizeX'] = float(self.props['PlotSizeX']) self.props['PlotSizeY'] = float(self.props['PlotSizeY']) self.props['RatioPlotSizeY'] = float(self.props['RatioPlotSizeY']) # self.props['TopMargin'] = float(self.props['TopMargin']) # self.props['BottomMargin'] = float(self.props['BottomMargin']) self.props['LogX'] = self.attr_bool('LogX', 0) self.props['LogY'] = self.attr_bool('LogY', 0) self.props['LogZ'] = self.attr_bool('LogZ', 0) for rname, _ in self.ratio_names(True): self.props[rname+'LogY'] = self.attr_bool(rname+'LogY', 0) self.props[rname+'LogZ'] = self.attr_bool(rname+'LogZ', 0) if self.has_attr('Rebin'): for key in self.histos: self.histos[key].props['Rebin'] = self.props['Rebin'] if self.has_attr('ConnectBins'): for key in self.histos: self.histos[key].props['ConnectBins'] = self.props['ConnectBins'] self.histo_sorting('DrawOnly') for curves, _ in self.ratio_names(): if self.has_attr(curves+'DrawOnly'): self.histo_sorting(curves+'DrawOnly') else: self.props[curves+'DrawOnly'] = self.props['DrawOnly'] ## Inherit various values from histograms if not explicitly set #for k in ['LogX', 'LogY', 'LogZ', # 'XLabel', 'YLabel', 'ZLabel', # 'XCustomMajorTicks', 'YCustomMajorTicks', 'ZCustomMajorTicks']: # self.inherit_from_histos(k) @property def is2dim(self): return self.attr_bool("is2dim", False) @is2dim.setter def is2dim(self, val): self.set_attr("is2dim", val) @property def drawonly(self): x = self.attr("DrawOnly") if type(x) is str: self.drawonly = x #< use setter to listify return x if x else [] @drawonly.setter def drawonly(self, val): if type(val) is str: val = val.strip().split() self.set_attr("DrawOnly", val) @property def stacklist(self): x = self.attr("Stack") if type(x) is str: self.stacklist = x #< use setter to listify return x if x else [] @stacklist.setter def stacklist(self, val): if type(val) is str: val = val.strip().split() self.set_attr("Stack", val) @property def plotorder(self): x = self.attr("PlotOrder") if type(x) is str: self.plotorder = x #< use setter to listify return x if x else [] @plotorder.setter def plotorder(self, val): if type(val) is str: val = val.strip().split() self.set_attr("PlotOrder", val) @property def plotsizex(self): return self.attr_float("PlotSizeX") @plotsizex.setter def plotsizex(self, val): self.set_attr("PlotSizeX", val) @property def plotsizey(self): return self.attr_float("PlotSizeY") @plotsizey.setter def plotsizey(self, val): self.set_attr("PlotSizeY", val) @property def plotsize(self): return [self.plotsizex, self.plotsizey] @plotsize.setter def plotsize(self, val): if type(val) is str: val = [float(x) for x in val.split(",")] assert len(val) == 2 self.plotsizex = val[0] self.plotsizey = val[1] @property def ratiosizey(self): return self.attr_float("RatioPlotSizeY") @ratiosizey.setter def ratiosizey(self, val): self.set_attr("RatioPlotSizeY", val) @property def scale(self): return self.attr_float("Scale") @scale.setter def scale(self, val): self.set_attr("Scale", val) @property def xmin(self): return self.attr_float("XMin") @xmin.setter def xmin(self, val): self.set_attr("XMin", val) @property def xmax(self): return self.attr_float("XMax") @xmax.setter def xmax(self, val): self.set_attr("XMax", val) @property def xrange(self): return [self.xmin, self.xmax] @xrange.setter def xrange(self, val): if type(val) is str: val = [float(x) for x in val.split(",")] assert len(val) == 2 self.xmin = val[0] self.xmax = val[1] @property def ymin(self): return self.attr_float("YMin") @ymin.setter def ymin(self, val): self.set_attr("YMin", val) @property def ymax(self): return self.attr_float("YMax") @ymax.setter def ymax(self, val): self.set_attr("YMax", val) @property def yrange(self): return [self.ymin, self.ymax] @yrange.setter def yrange(self, val): if type(val) is str: val = [float(y) for y in val.split(",")] assert len(val) == 2 self.ymin = val[0] self.ymax = val[1] # TODO: add more rw properties for plotsize(x,y), ratiosize(y), # show_mainplot, show_ratioplot, show_legend, log(x,y,z), rebin, # drawonly, legendonly, plotorder, stack, # label(x,y,z), majorticks(x,y,z), minorticks(x,y,z), # min(x,y,z), max(x,y,z), range(x,y,z) def getLegendPos(self, prefix = ''): xpos = self.attr_float(prefix+'LegendXPos', 0.95 if self.getLegendAlign() == 'right' else 0.53) ypos = self.attr_float(prefix+'LegendYPos', 0.93) return (xpos, ypos) def getLegendAlign(self, prefix = ''): la = self.attr(prefix+'LegendAlign', 'left') if la == 'l': return 'left' elif la == 'c': return 'center' elif la == 'r': return 'right' else: return la #def inherit_from_histos(self, k): # """Note: this will inherit the key from a random histogram: # only use if you're sure all histograms have this key!""" # if k not in self.props: # h = list(self.histos.values())[0] # if k in h.props: # self.props[k] = h.props[k] def read_input(self, f): for line in f: if is_end_marker(line, 'PLOT'): break elif is_comment(line): continue m = pat_property.match(line) if m: prop, value = m.group(1,2) + prop = prop.strip() + value = value.strip() + if prop == 'ReplaceOption': + opt, subst = value.split(" ", 1) + self.props['_OptSubs'][opt] = subst + continue if prop in self.props: logging.debug("Overwriting property %s = %s -> %s" % (prop, self.props[prop], value)) ## Use strip here to deal with DOS newlines containing \r self.props[prop.strip()] = value.strip() def apply_config_files(self, conffiles): """Use config file to overwrite cosmetic properties.""" if conffiles is not None: for filename in conffiles: cf = open(filename, 'r') lines = cf.readlines() for i in range(len(lines)): ## First evaluate PLOT sections m = pat_begin_block.match(lines[i]) if m and m.group(1) == 'PLOT' and re.match(m.group(2),self.filename): while i %s" % (prop, self.props[prop], value)) ## Use strip here to deal with DOS newlines containing \r self.props[prop.strip()] = value.strip() elif is_comment(lines[i]): continue else: ## Then evaluate path-based settings, e.g. for HISTOGRAMs m = pat_path_property.match(lines[i]) if m: regex, prop, value = m.group(1,2,3) for obj_dict in [self.special, self.histos, self.functions]: for path, obj in obj_dict.items(): if re.match(regex, path): ## Use strip here to deal with DOS newlines containing \r obj.props.update({prop.strip() : value.strip()}) cf.close() def histo_sorting(self, curves): """Determine in what order to draw curves.""" histoordermap = {} histolist = self.histos.keys() if self.has_attr(curves): histolist = filter(self.histos.keys().count, self.attr(curves).strip().split()) for histo in histolist: order = 0 if self.histos[histo].has_attr('PlotOrder'): order = int(self.histos[histo].attr['PlotOrder']) if not order in histoordermap: histoordermap[order] = [] histoordermap[order].append(histo) sortedhistolist = [] for i in sorted(histoordermap.keys()): sortedhistolist.extend(histoordermap[i]) self.props[curves] = sortedhistolist class Plot(object): def __init__(self): #, inputdata): self.customCols = {} def panel_header(self, **kwargs): out = '' out += ('\\begin{axis}[\n') out += ('at={(0,%4.3fcm)},\n' % kwargs['PanelOffset']) out += ('xmode=%s,\n' % kwargs['Xmode']) out += ('ymode=%s,\n' % kwargs['Ymode']) #if kwargs['Zmode']: out += ('zmode=log,\n') out += ('scale only axis=true,\n') out += ('scaled ticks=false,\n') out += ('clip marker paths=true,\n') out += ('axis on top,\n') out += ('axis line style={line width=0.3pt},\n') out += ('height=%scm,\n' % kwargs['PanelHeight']) out += ('width=%scm,\n' % kwargs['PanelWidth']) out += ('xmin=%s,\n' % kwargs['Xmin']) out += ('xmax=%s,\n' % kwargs['Xmax']) out += ('ymin=%s,\n' % kwargs['Ymin']) out += ('ymax=%s,\n' % kwargs['Ymax']) if kwargs['is2D']: out += ('zmin=%s,\n' % kwargs['Zmin']) out += ('zmax=%s,\n' % kwargs['Zmax']) #out += ('legend style={\n') #out += (' draw=none, fill=none, anchor = north west,\n') #out += (' at={(%4.3f,%4.3f)},\n' % kwargs['LegendPos']) #out += ('},\n') #out += ('legend cell align=%s,\n' % kwargs['LegendAlign']) #out += ('legend image post style={sharp plot, -},\n') if kwargs['is2D']: if kwargs['is3D']: hrotate = 45 + kwargs['HRotate'] vrotate = 30 + kwargs['VRotate'] out += ('view={%i}{%s}, zticklabel pos=right,\n' % (hrotate, vrotate)) else: out += ('view={0}{90}, colorbar,\n') out += ('colormap/%s,\n' % kwargs['ColorMap']) if not kwargs['is3D'] and kwargs['Zmode']: out += ('colorbar style={yticklabel=$\\,10^{\\pgfmathprintnumber{\\tick}}$},\n') #if kwargs['Grid']: # out += ('grid=%s,\n' % kwargs['Grid']) for axis, label in kwargs['Labels'].iteritems(): out += ('%s={%s},\n' % (axis.lower(), label)) if kwargs['XLabelSep'] != None: if not kwargs['is3D']: out += ('xlabel style={at={(1,0)},below left,yshift={-%4.3fcm}},\n' % kwargs['XLabelSep']) out += ('xticklabel shift=%4.3fcm,\n' % kwargs['XTickShift']) else: out += ('xticklabels={,,},\n') if kwargs['YLabelSep'] != None: if not kwargs['is3D']: out += ('ylabel style={at={(0,1)},left,yshift={%4.3fcm}},\n' % kwargs['YLabelSep']) out += ('yticklabel shift=%4.3fcm,\n' % kwargs['YTickShift']) out += ('major tick length={%4.3fcm},\n' % kwargs['MajorTickLength']) out += ('minor tick length={%4.3fcm},\n' % kwargs['MinorTickLength']) # check if 'number of minor tick divisions' is specified for axis, nticks in kwargs['MinorTicks'].iteritems(): if nticks: out += ('minor %s tick num=%i,\n' % (axis.lower(), nticks)) # check if actual major/minor tick divisions have been specified out += ('max space between ticks=20,\n') for axis, tickinfo in kwargs['CustomTicks'].iteritems(): majorlabels, majorticks, minorticks = tickinfo if len(minorticks): out += ('minor %stick={%s},\n' % (axis.lower(), ','.join(minorticks))) if len(majorticks): if float(majorticks[0]) > float(kwargs['%smin' % axis]): majorticks = [ str(2 * float(majorticks[0]) - float(majorticks[1])) ] + majorticks if len(majorlabels): majorlabels = [ '.' ] + majorlabels # dummy label if float(majorticks[-1]) < float(kwargs['%smax' % axis]): majorticks.append(str(2 * float(majorticks[-1]) - float(majorticks[-2]))) if len(majorlabels): majorlabels.append('.') # dummy label out += ('%stick={%s},\n' % (axis.lower(), ','.join(majorticks))) if kwargs['NeedsXLabels'] and len(majorlabels): out += ('%sticklabels={{%s}},\n' % (axis.lower(), '},{'.join(majorlabels))) out += ('every %s tick/.style={black},\n' % axis.lower()) out += (']\n') return out def panel_footer(self): out = '' out += ('\\end{axis}\n') return out def set_normalisation(self, inputdata): if inputdata.normalised: return for method in ['NormalizeToIntegral', 'NormalizeToSum']: if inputdata.has_attr(method): for key in inputdata.props['DrawOnly']: if not inputdata.histos[key].has_attr(method): inputdata.histos[key].props[method] = inputdata.props[method] if inputdata.has_attr('Scale'): for key in inputdata.props['DrawOnly']: inputdata.histos[key].props['Scale'] = inputdata.attr_float('Scale') for key in inputdata.histos.keys(): inputdata.histos[key].mangle_input() inputdata.normalised = True def stack_histograms(self, inputdata): if inputdata.has_attr('Stack'): stackhists = [h for h in inputdata.attr('Stack').strip().split() if h in inputdata.histos] previous = '' for key in stackhists: if previous != '': inputdata.histos[key].add(inputdata.histos[previous]) previous = key def set_histo_options(self, inputdata): if inputdata.has_attr('ConnectGaps'): for key in inputdata.histos.keys(): if not inputdata.histos[key].has_attr('ConnectGaps'): inputdata.histos[i].props['ConnectGaps'] = inputdata.props['ConnectGaps'] # Counter and Value only have dummy x-axis, ticks wouldn't make sense here, so suppress them: if 'Value object' in str(inputdata.histos) or 'Counter object' in str(inputdata.histos): inputdata.props['XCustomMajorTicks'] = '' inputdata.props['XCustomMinorTicks'] = '' def set_borders(self, inputdata): self.set_xmax(inputdata) self.set_xmin(inputdata) self.set_ymax(inputdata) self.set_ymin(inputdata) self.set_zmax(inputdata) self.set_zmin(inputdata) inputdata.props['Borders'] = (self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax) def set_xmin(self, inputdata): self.xmin = inputdata.xmin if self.xmin is None: xmins = [inputdata.histos[h].getXMin() for h in inputdata.props['DrawOnly']] self.xmin = min(xmins) if xmins else 0.0 def set_xmax(self,inputdata): self.xmax = inputdata.xmax if self.xmax is None: xmaxs = [inputdata.histos[h].getXMax() for h in inputdata.props['DrawOnly']] self.xmax = min(xmaxs) if xmaxs else 1.0 def set_ymin(self,inputdata): if inputdata.ymin is not None: self.ymin = inputdata.ymin else: ymins = [inputdata.histos[i].getYMin(self.xmin, self.xmax, inputdata.props['LogY']) for i in inputdata.attr('DrawOnly')] minymin = min(ymins) if ymins else 0.0 if inputdata.props['is2dim']: self.ymin = minymin else: showzero = inputdata.attr_bool("ShowZero", True) if showzero: self.ymin = 0. if minymin > -1e-4 else 1.1*minymin else: self.ymin = 1.1*minymin if minymin < -1e-4 else 0 if minymin < 1e-4 else 0.9*minymin if inputdata.props['LogY']: ymins = [ymin for ymin in ymins if ymin > 0.0] if not ymins: if self.ymax == 0: self.ymax = 1 ymins.append(2e-7*self.ymax) minymin = min(ymins) fullrange = opts.FULL_RANGE if inputdata.has_attr('FullRange'): fullrange = inputdata.attr_bool('FullRange') self.ymin = minymin/1.7 if fullrange else max(minymin/1.7, 2e-7*self.ymax) if self.ymin == self.ymax: self.ymin -= 1 self.ymax += 1 def set_ymax(self,inputdata): if inputdata.has_attr('YMax'): self.ymax = inputdata.attr_float('YMax') else: ymaxs = [inputdata.histos[h].getYMax(self.xmin, self.xmax) for h in inputdata.attr('DrawOnly')] self.ymax = max(ymaxs) if ymaxs else 1.0 if not inputdata.is2dim: self.ymax *= (1.7 if inputdata.attr_bool('LogY') else 1.1) def set_zmin(self,inputdata): if inputdata.has_attr('ZMin'): self.zmin = inputdata.attr_float('ZMin') else: zmins = [inputdata.histos[i].getZMin(self.xmin, self.xmax, self.ymin, self.ymax) for i in inputdata.attr('DrawOnly')] minzmin = min(zmins) if zmins else 0.0 self.zmin = minzmin if zmins: showzero = inputdata.attr_bool('ShowZero', True) if showzero: self.zmin = 0 if minzmin > -1e-4 else 1.1*minzmin else: self.zmin = 1.1*minzmin if minzmin < -1e-4 else 0. if minzmin < 1e-4 else 0.9*minzmin if inputdata.attr_bool('LogZ', False): zmins = [zmin for zmin in zmins if zmin > 0] if not zmins: if self.zmax == 0: self.zmax = 1 zmins.append(2e-7*self.zmax) minzmin = min(zmins) fullrange = inputdata.attr_bool("FullRange", opts.FULL_RANGE) self.zmin = minzmin/1.7 if fullrange else max(minzmin/1.7, 2e-7*self.zmax) if self.zmin == self.zmax: self.zmin -= 1 self.zmax += 1 def set_zmax(self,inputdata): self.zmax = inputdata.attr_float('ZMax') if self.zmax is None: zmaxs = [inputdata.histos[h].getZMax(self.xmin, self.xmax, self.ymin, self.ymax) for h in inputdata.attr('DrawOnly')] self.zmax = max(zmaxs) if zmaxs else 1.0 def getTicks(self, inputdata, axis): majorticks = []; majorlabels = [] ticktype = '%sCustomMajorTicks' % axis if inputdata.attr(ticktype): ticks = inputdata.attr(ticktype).strip().split() if not len(ticks) % 2: for i in range(0,len(ticks),2): majorticks.append(ticks[i]) majorlabels.append(ticks[i+1]) minorticks = [] ticktype = '%sCustomMinorTicks' % axis if inputdata.attr(ticktype): ticks = inputdata.attr(ticktype).strip().split() for val in ticks: minorticks.append(val) return (majorlabels, majorticks, minorticks) def draw(self): pass def write_header(self,inputdata): inputdata.props.setdefault('TopMargin', 0.8) inputdata.props.setdefault('LeftMargin', 1.4) inputdata.props.setdefault('BottomMargin', 0.75) inputdata.props.setdefault('RightMargin', 0.35) if inputdata.attr('is2dim'): inputdata.props['RightMargin'] += 1.8 papersizex = inputdata.attr_float('PlotSizeX') + 0.1 papersizex += inputdata.attr_float('LeftMargin') + inputdata.attr_float('RightMargin') papersizey = inputdata.attr_float('PlotSizeY') + 0.2 papersizey += inputdata.attr_float('TopMargin') + inputdata.attr_float('BottomMargin') for rname, _ in inputdata.ratio_names(): if inputdata.has_attr(rname+'SizeY'): papersizey += inputdata.attr_float(rname+'SizeY') # out = "" out += '\\documentclass{article}\n' if opts.OUTPUT_FONT == "MINION": out += ('\\usepackage{minion}\n') elif opts.OUTPUT_FONT == "PALATINO_OSF": out += ('\\usepackage[osf,sc]{mathpazo}\n') elif opts.OUTPUT_FONT == "PALATINO": out += ('\\usepackage{mathpazo}\n') elif opts.OUTPUT_FONT == "TIMES": out += ('\\usepackage{mathptmx}\n') elif opts.OUTPUT_FONT == "HELVETICA": out += ('\\renewcommand{\\familydefault}{\\sfdefault}\n') out += ('\\usepackage{sfmath}\n') out += ('\\usepackage{helvet}\n') out += ('\\usepackage[symbolgreek]{mathastext}\n') for pkg in opts.LATEXPKGS: out += ('\\usepackage{%s}\n' % pkg) out += ('\\usepackage[dvipsnames]{xcolor}\n') out += ('\\selectcolormodel{rgb}\n') out += ('\\definecolor{red}{HTML}{EE3311}\n') # (Google uses 'DC3912') out += ('\\definecolor{blue}{HTML}{3366FF}\n') out += ('\\definecolor{green}{HTML}{109618}\n') out += ('\\definecolor{orange}{HTML}{FF9900}\n') out += ('\\definecolor{lilac}{HTML}{990099}\n') out += ('\\usepackage{amsmath}\n') out += ('\\usepackage{amssymb}\n') out += ('\\usepackage{relsize}\n') out += ('\\usepackage{graphicx}\n') out += ('\\usepackage[dvips,\n') out += (' left=%4.3fcm,\n' % (inputdata.attr_float('LeftMargin')-0.45)) out += (' right=0cm,\n') out += (' top=%4.3fcm,\n' % (inputdata.attr_float('TopMargin')-0.70)) out += (' bottom=0cm,\n') out += (' paperwidth=%scm,paperheight=%scm\n' % (papersizex, papersizey)) out += (']{geometry}\n') if inputdata.has_attr('DefineColor'): out += ('% user defined colours\n') for color in inputdata.attr('DefineColor').split('\t'): out += ('%s\n' % color) col_count = 0 for obj in inputdata.histos: for col in inputdata.histos[obj].customCols: if col in self.customCols: # already seen, look up name inputdata.histos[obj].customCols[col] = self.customCols[col] elif ']{' in col: colname = 'MyColour%i' % col_count # assign custom name inputdata.histos[obj].customCols[col] = colname self.customCols[col] = colname col_count += 1 # remove outer {...} if present while col[0] == '{' and col[-1] == '}': col = col[1:-1] model, specs = tuple(col[1:-1].split(']{')) out += ('\\definecolor{%s}{%s}{%s}\n' % (colname, model, specs)) out += ('\\usepackage{pgfplots}\n') out += ('\\usepgfplotslibrary{fillbetween}\n') #out += ('\\usetikzlibrary{positioning,shapes.geometric,patterns}\n') out += ('\\usetikzlibrary{patterns}\n') out += ('\\pgfplotsset{ compat=1.16,\n') out += (' title style={at={(0,1)},right,yshift={0.10cm}},\n') out += ('}\n') out += ('\\begin{document}\n') out += ('\\pagestyle{empty}\n') out += ('\\begin{tikzpicture}[\n') out += (' inner sep=0,\n') out += (' trim axis left = %4.3f,\n' % (inputdata.attr_float('LeftMargin') + 0.1)) out += (' trim axis right,\n') out += (' baseline,') out += (' hatch distance/.store in=\\hatchdistance,\n') out += (' hatch distance=8pt,\n') out += (' hatch thickness/.store in=\\hatchthickness,\n') out += (' hatch thickness=1pt,\n') out += (']\n') out += ('\\makeatletter\n') out += ('\\pgfdeclarepatternformonly[\hatchdistance,\hatchthickness]{diagonal hatch}\n') out += ('{\\pgfqpoint{0pt}{0pt}}\n') out += ('{\\pgfqpoint{\\hatchdistance}{\\hatchdistance}}\n') out += ('{\\pgfpoint{\\hatchdistance-1pt}{\\hatchdistance-1pt}}%\n') out += ('{\n') out += (' \\pgfsetcolor{\\tikz@pattern@color}\n') out += (' \\pgfsetlinewidth{\\hatchthickness}\n') out += (' \\pgfpathmoveto{\\pgfqpoint{0pt}{0pt}}\n') out += (' \\pgfpathlineto{\\pgfqpoint{\\hatchdistance}{\\hatchdistance}}\n') out += (' \\pgfusepath{stroke}\n') out += ('}\n') out += ('\\pgfdeclarepatternformonly[\hatchdistance,\hatchthickness]{antidiagonal hatch}\n') out += ('{\\pgfqpoint{0pt}{0pt}}\n') out += ('{\\pgfqpoint{\\hatchdistance}{\\hatchdistance}}\n') out += ('{\\pgfpoint{\\hatchdistance-1pt}{\\hatchdistance-1pt}}%\n') out += ('{\n') out += (' \\pgfsetcolor{\\tikz@pattern@color}\n') out += (' \\pgfsetlinewidth{\\hatchthickness}\n') out += (' \\pgfpathmoveto{\\pgfqpoint{0pt}{\\hatchdistance}}\n') out += (' \\pgfpathlineto{\\pgfqpoint{\\hatchdistance}{0pt}}\n') out += (' \\pgfusepath{stroke}\n') out += ('}\n') out += ('\\pgfdeclarepatternformonly[\hatchdistance,\hatchthickness]{cross hatch}\n') out += ('{\\pgfqpoint{0pt}{0pt}}\n') out += ('{\\pgfqpoint{\\hatchdistance}{\\hatchdistance}}\n') out += ('{\\pgfpoint{\\hatchdistance-1pt}{\\hatchdistance-1pt}}%\n') out += ('{\n') out += (' \\pgfsetcolor{\\tikz@pattern@color}\n') out += (' \\pgfsetlinewidth{\\hatchthickness}\n') out += (' \\pgfpathmoveto{\\pgfqpoint{0pt}{0pt}}\n') out += (' \\pgfpathlineto{\\pgfqpoint{\\hatchdistance}{\\hatchdistance}}\n') out += (' \\pgfusepath{stroke}\n') out += (' \\pgfsetcolor{\\tikz@pattern@color}\n') out += (' \\pgfsetlinewidth{\\hatchthickness}\n') out += (' \\pgfpathmoveto{\\pgfqpoint{0pt}{\\hatchdistance}}\n') out += (' \\pgfpathlineto{\\pgfqpoint{\\hatchdistance}{0pt}}\n') out += (' \\pgfusepath{stroke}\n') out += ('}\n') out += ('\makeatother\n') if inputdata.attr_bool('is2dim'): colorseries = '{hsb}{grad}[rgb]{0,0,1}{-.700,0,0}' if inputdata.attr('ColorSeries', ''): colorseries = inputdata.attr('ColorSeries') out += ('\\definecolorseries{gradientcolors}%s\n' % colorseries) out += ('\\resetcolorseries[130]{gradientcolors}\n') return out def write_footer(self): out = "" out += ('\\end{tikzpicture}\n') out += ('\\end{document}\n') return out class MainPlot(Plot): def __init__(self, inputdata): self.name = 'MainPlot' inputdata.props['PlotStage'] = 'MainPlot' self.set_normalisation(inputdata) self.stack_histograms(inputdata) do_gof = inputdata.props.get('GofLegend', '0') == '1' or inputdata.props.get('GofFrame', '') != '' do_taylor = inputdata.props.get('TaylorPlot', '0') == '1' if do_gof and not do_taylor: self.calculate_gof(inputdata) self.set_histo_options(inputdata) self.set_borders(inputdata) self.yoffset = inputdata.props['PlotSizeY'] def draw(self, inputdata): out = "" out += ('\n%\n% MainPlot\n%\n') offset = 0. for rname, i in inputdata.ratio_names(): if inputdata.has_attr(rname+'SizeY'): offset += inputdata.attr_float(rname+'SizeY') labels = self.getLabels(inputdata) out += self.panel_header( PanelOffset = offset, Xmode = 'log' if inputdata.attr_bool('LogX') else 'normal', Ymode = 'log' if inputdata.attr_bool('LogY') else 'normal', Zmode = inputdata.attr_bool('LogZ'), PanelHeight = inputdata.props['PlotSizeY'], PanelWidth = inputdata.props['PlotSizeX'], Xmin = self.xmin, Xmax = self.xmax, Ymin = self.ymin, Ymax = self.ymax, Zmin = self.zmin, Zmax = self.zmax, Labels = { l : inputdata.attr(l) for l in labels if inputdata.has_attr(l) }, XLabelSep = inputdata.attr_float('XLabelSep', 0.7) if 'XLabel' in labels else None, YLabelSep = inputdata.attr_float('YLabelSep', 1.2) if 'YLabel' in labels else None, XTickShift = inputdata.attr_float('XTickShift', 0.1) if 'XLabel' in labels else None, YTickShift = inputdata.attr_float('YTickShift', 0.1) if 'YLabel' in labels else None, MajorTickLength = inputdata.attr_float('MajorTickLength', 0.30), MinorTickLength = inputdata.attr_float('MinorTickLength', 0.15), MinorTicks = { axis : inputdata.attr_int('%sMinorTickMarks' % axis, 4) for axis in ['X', 'Y', 'Z'] }, CustomTicks = { axis : self.getTicks(inputdata, axis) for axis in ['X', 'Y', 'Z'] }, NeedsXLabels = self.needsXLabel(inputdata), #Grid = inputdata.doGrid(), is2D = inputdata.is2dim, is3D = inputdata.attr_bool('Is3D', 0), HRotate = inputdata.attr_int('HRotate', 0), VRotate = inputdata.attr_int('VRotate', 0), ColorMap = inputdata.attr('ColorMap', 'jet'), #LegendAlign = inputdata.getLegendAlign(), #LegendPos = inputdata.getLegendPos(), ) out += self.plot_object(inputdata) out += self.panel_footer() return out def plot_object(self, inputdata): out = "" if inputdata.attr_bool('DrawSpecialFirst', False): for s in inputdata.special.values(): out += s.draw(inputdata) if inputdata.attr_bool('DrawFunctionFirst', False): for f in inputdata.functions.values(): out += f.draw(inputdata, self.props['Borders'][0], self.props['Borders'][1]) for key in inputdata.props['DrawOnly']: #add_legend = inputdata.attr_bool('Legend') out += inputdata.histos[key].draw() #add_legend) if not inputdata.attr_bool('DrawSpecialFirst', False): for s in inputdata.special.values(): out += s.draw(inputdata) if not inputdata.attr_bool('DrawFunctionFirst', False): for f in inputdata.functions.values(): out += f.draw(inputdata, self.props['Borders'][0], self.props['Borders'][1]) for lname, i in inputdata.legend_names(): if inputdata.attr_bool(lname, False): legend = Legend(inputdata.props,inputdata.histos,inputdata.functions, lname, i) out += legend.draw() return out def needsXLabel(self, inputdata): if inputdata.attr('PlotTickLabels') == '0': return False # only draw the x-axis label if there are no ratio panels drawlabels = not any([ inputdata.attr_bool(rname) for rname, _ in inputdata.ratio_names() ]) return drawlabels def getLabels(self, inputdata): labels = ['Title', 'YLabel'] if self.needsXLabel(inputdata): labels.append('XLabel') if inputdata.props['is2dim']: labels.append('ZLabel') return labels def calculate_gof(self, inputdata): refdata = inputdata.props.get('GofReference') if refdata is None: refdata = inputdata.props.get('RatioPlotReference') if refdata is None: inputdata.props['GofLegend'] = '0' inputdata.props['GofFrame'] = '' return def pickcolor(gof): color = None colordefs = {} for i in inputdata.props.setdefault('GofFrameColor', '0:green 3:yellow 6:red!70').strip().split(): foo = i.split(':') if len(foo) != 2: continue colordefs[float(foo[0])] = foo[1] for col in sorted(colordefs.keys()): if gof >= col: color=colordefs[col] return color inputdata.props.setdefault('GofLegend', '0') inputdata.props.setdefault('GofFrame', '') inputdata.props.setdefault('FrameColor', None) for key in inputdata.props['DrawOnly']: if key == refdata: continue if inputdata.props['GofLegend'] != '1' and key != inputdata.props['GofFrame']: continue if inputdata.props.get('GofType', 'chi2') != 'chi2': return gof = inputdata.histos[key].getChi2(inputdata.histos[refdata]) if key == inputdata.props['GofFrame'] and inputdata.props['FrameColor'] is None: inputdata.props['FrameColor'] = pickcolor(gof) if inputdata.histos[key].props.setdefault('Title', '') != '': inputdata.histos[key].props['Title'] += ', ' inputdata.histos[key].props['Title'] += '$\\chi^2/n={}$%1.2f' %gof class TaylorPlot(Plot): def __init__(self, inputdata): self.refdata = inputdata.props['TaylorPlotReference'] self.calculate_taylorcoordinates(inputdata) def calculate_taylorcoordinates(self,inputdata): foo = inputdata.props['DrawOnly'].pop(inputdata.props['DrawOnly'].index(self.refdata)) inputdata.props['DrawOnly'].append(foo) for i in inputdata.props['DrawOnly']: print(i) print('meanbinval = ', inputdata.histos[i].getMeanBinValue()) print('sigmabinval = ', inputdata.histos[i].getSigmaBinValue()) print('chi2/nbins = ', inputdata.histos[i].getChi2(inputdata.histos[self.refdata])) print('correlation = ', inputdata.histos[i].getCorrelation(inputdata.histos[self.refdata])) print('distance = ', inputdata.histos[i].getRMSdistance(inputdata.histos[self.refdata])) class RatioPlot(Plot): def __init__(self, inputdata, i): self.number = i self.name='RatioPlot%s' % (str(i) if i else '') # initialise histograms even when no main plot self.set_normalisation(inputdata) self.refdata = inputdata.props[self.name+'Reference'] if not inputdata.histos.has_key(self.refdata): print('ERROR: %sReference=%s not found in:' % (self.name,self.refdata)) for i in inputdata.histos.keys(): print(' ', i) sys.exit(1) if not inputdata.has_attr('RatioPlotYOffset'): inputdata.props['RatioPlotYOffset'] = inputdata.props['PlotSizeY'] if not inputdata.has_attr(self.name + 'SameStyle'): inputdata.props[self.name+'SameStyle'] = '1' self.yoffset = inputdata.props['RatioPlotYOffset'] + inputdata.props[self.name+'SizeY'] inputdata.props['PlotStage'] = self.name inputdata.props['RatioPlotYOffset'] = self.yoffset inputdata.props['PlotSizeY'] = inputdata.props[self.name+'SizeY'] inputdata.props['LogY'] = inputdata.props.get(self.name+"LogY", False) # TODO: It'd be nice it this wasn't so MC-specific rpmode = inputdata.props.get(self.name+'Mode', "mcdata") if rpmode=='deviation': inputdata.props['YLabel']='$(\\text{MC}-\\text{data})$' inputdata.props['YMin']=-2.99 inputdata.props['YMax']=2.99 elif rpmode=='delta': inputdata.props['YLabel']='\\delta' inputdata.props['YMin']=-0.5 inputdata.props['YMax']=0.5 elif rpmode=='deltapercent': inputdata.props['YLabel']='\\delta\;[\%]' inputdata.props['YMin']=-50. inputdata.props['YMax']=50. elif rpmode=='deltamc': inputdata.props['YLabel']='Data/MC' inputdata.props['YMin']=0.5 inputdata.props['YMax']=1.5 else: inputdata.props['YLabel'] = 'MC/Data' inputdata.props['YMin'] = 0.5 inputdata.props['YMax'] = 1.5 if inputdata.has_attr(self.name+'YLabel'): inputdata.props['YLabel'] = inputdata.props[self.name+'YLabel'] if inputdata.has_attr(self.name+'YMin'): inputdata.props['YMin'] = inputdata.props[self.name+'YMin'] if inputdata.has_attr(self.name+'YMax'): inputdata.props['YMax'] = inputdata.props[self.name+'YMax'] if inputdata.has_attr(self.name+'YLabelSep'): inputdata.props['YLabelSep'] = inputdata.props[self.name+'YLabelSep'] if not inputdata.has_attr(self.name+'ErrorBandColor'): inputdata.props[self.name+'ErrorBandColor'] = 'yellow' if inputdata.props[self.name+'SameStyle']=='0': inputdata.histos[self.refdata].props['ErrorBandColor'] = inputdata.props[self.name+'ErrorBandColor'] inputdata.histos[self.refdata].props['ErrorBandOpacity'] = inputdata.props[self.name+'ErrorBandOpacity'] inputdata.histos[self.refdata].props['ErrorBands'] = '1' inputdata.histos[self.refdata].props['ErrorBars'] = '0' inputdata.histos[self.refdata].props['ErrorTubes'] = '0' inputdata.histos[self.refdata].props['LineStyle'] = 'solid' inputdata.histos[self.refdata].props['LineColor'] = 'black' inputdata.histos[self.refdata].props['LineWidth'] = '0.3pt' inputdata.histos[self.refdata].props['MarkerStyle'] = '' inputdata.histos[self.refdata].props['ConnectGaps'] = '1' self.calculate_ratios(inputdata) self.set_borders(inputdata) def draw(self, inputdata): out = '' out += ('\n%\n% RatioPlot\n%\n') offset = 0. for rname, i in inputdata.ratio_names(): if i > self.number and inputdata.has_attr(rname+'SizeY'): offset += inputdata.attr_float(rname+'SizeY') labels = self.getLabels(inputdata) out += self.panel_header( PanelOffset = offset, Xmode = 'log' if inputdata.attr_bool('LogX') else 'normal', Ymode = 'log' if inputdata.attr_bool('LogY') else 'normal', Zmode = inputdata.attr_bool('LogZ'), PanelHeight = inputdata.props['PlotSizeY'], PanelWidth = inputdata.props['PlotSizeX'], Xmin = self.xmin, Xmax = self.xmax, Ymin = self.ymin, Ymax = self.ymax, Zmin = self.zmin, Zmax = self.zmax, Labels = { l : inputdata.attr(l) for l in labels if inputdata.has_attr(l) }, XLabelSep = inputdata.attr_float('XLabelSep', 0.7) if 'XLabel' in labels else None, YLabelSep = inputdata.attr_float('YLabelSep', 1.2) if 'YLabel' in labels else None, XTickShift = inputdata.attr_float('XTickShift', 0.1) if 'XLabel' in labels else None, YTickShift = inputdata.attr_float('YTickShift', 0.1) if 'YLabel' in labels else None, MajorTickLength = inputdata.attr_float('MajorTickLength', 0.30), MinorTickLength = inputdata.attr_float('MinorTickLength', 0.15), MinorTicks = { axis : self.getMinorTickMarks(inputdata, axis) for axis in ['X', 'Y', 'Z'] }, CustomTicks = { axis : self.getTicks(inputdata, axis) for axis in ['X', 'Y', 'Z'] }, NeedsXLabels = self.needsXLabel(inputdata), #Grid = inputdata.doGrid(self.name), is2D = inputdata.is2dim, is3D = inputdata.attr_bool('Is3D', 0), HRotate = inputdata.attr_int('HRotate', 0), VRotate = inputdata.attr_int('VRotate', 0), ColorMap = inputdata.attr('ColorMap', 'jet'), #LegendAlign = inputdata.getLegendAlign(self.name), #LegendPos = inputdata.getLegendPos(self.name), ) out += self.add_object(inputdata) for lname, i in inputdata.legend_names(): if inputdata.attr_bool(self.name + lname, False): legend = Legend(inputdata.props,inputdata.histos,inputdata.functions, self.name + lname, i) out += legend.draw() out += self.panel_footer() return out def calculate_ratios(self, inputdata): inputdata.ratios = {} inputdata.ratios = copy.deepcopy(inputdata.histos) name = inputdata.attr(self.name+'DrawOnly').pop(inputdata.attr(self.name+'DrawOnly').index(self.refdata)) reffirst = inputdata.attr(self.name+'DrawReferenceFirst') != '0' if reffirst and inputdata.histos[self.refdata].attr_bool('ErrorBands'): inputdata.props[self.name+'DrawOnly'].insert(0, name) else: inputdata.props[self.name+'DrawOnly'].append(name) rpmode = inputdata.props.get(self.name+'Mode', 'mcdata') for i in inputdata.props[self.name+'DrawOnly']: # + [ self.refdata ]: if i != self.refdata: if rpmode == 'deviation': inputdata.ratios[i].deviation(inputdata.ratios[self.refdata]) elif rpmode == 'delta': inputdata.ratios[i].delta(inputdata.ratios[self.refdata]) elif rpmode == 'deltapercent': inputdata.ratios[i].deltapercent(inputdata.ratios[self.refdata]) elif rpmode == 'datamc': inputdata.ratios[i].dividereverse(inputdata.ratios[self.refdata]) inputdata.ratios[i].props['ErrorBars'] = '1' else: inputdata.ratios[i].divide(inputdata.ratios[self.refdata]) if rpmode == 'deviation': inputdata.ratios[self.refdata].deviation(inputdata.ratios[self.refdata]) elif rpmode == 'delta': inputdata.ratios[self.refdata].delta(inputdata.ratios[self.refdata]) elif rpmode == 'deltapercent': inputdata.ratios[self.refdata].deltapercent(inputdata.ratios[self.refdata]) elif rpmode == 'datamc': inputdata.ratios[self.refdata].dividereverse(inputdata.ratios[self.refdata]) else: inputdata.ratios[self.refdata].divide(inputdata.ratios[self.refdata]) def add_object(self, inputdata): out = "" if inputdata.attr_bool('DrawSpecialFirst', False): for s in inputdata.special.values(): out += s.draw(inputdata) if inputdata.attr_bool('DrawFunctionFirst'): for i in inputdata.functions.keys(): out += inputdata.functions[i].draw(inputdata, self.props['Borders'][0], self.props['Borders'][1]) for key in inputdata.props[self.name+'DrawOnly']: if inputdata.has_attr(self.name+'Mode') and inputdata.attr(self.name+'Mode') == 'datamc': if key == self.refdata: continue #add_legend = inputdata.attr_bool(self.name+'Legend') out += inputdata.ratios[key].draw() #add_legend) if not inputdata.attr_bool('DrawFunctionFirst'): for i in inputdata.functions.keys(): out += inputdata.functions[i].draw(inputdata, self.props['Borders'][0], self.props['Borders'][1]) if not inputdata.attr_bool('DrawSpecialFirst', False): for s in inputdata.special.values(): out += s.draw(inputdata) return out def getMinorTickMarks(self, inputdata, axis): tag = '%sMinorTickMarks' % axis if inputdata.has_attr(self.name + tag): return inputdata.attr_int(self.name + tag) return inputdata.attr_int(tag, 4) def needsXLabel(self, inputdata): # only plot x label if it's the last ratio panel ratios = [ i for rname, i in inputdata.ratio_names() if inputdata.attr_bool(rname, True) and inputdata.attr(rname + 'Reference', False) ] return ratios[-1] == self.number def getLabels(self, inputdata): labels = ['YLabel'] drawtitle = inputdata.has_attr('MainPlot') and not inputdata.attr_bool('MainPlot') if drawtitle and not any([inputdata.attr_bool(rname) for rname, i in inputdata.ratio_names() if i < self.number]): labels.append('Title') if self.needsXLabel(inputdata): labels.append('XLabel') return labels class Legend(Described): def __init__(self, props, histos, functions, name, number): self.name = name self.number = number self.histos = histos self.functions = functions self.props = props def draw(self): legendordermap = {} legendlist = self.props['DrawOnly'] + list(self.functions.keys()) if self.name + 'Only' in self.props: legendlist = [] for legend in self.props[self.name+'Only'].strip().split(): if legend in self.histos or legend in self.functions: legendlist.append(legend) for legend in legendlist: order = 0 if legend in self.histos and 'LegendOrder' in self.histos[legend].props: order = int(self.histos[legend].props['LegendOrder']) if legend in self.functions and 'LegendOrder' in self.functions[legend].props: order = int(self.functions[legend].props['LegendOrder']) if not order in legendordermap: legendordermap[order] = [] legendordermap[order].append(legend) orderedlegendlist=[] for i in sorted(legendordermap.keys()): orderedlegendlist.extend(legendordermap[i]) if self.props['is2dim']: return self.draw_2dlegend(orderedlegendlist) out = "" out += '\n%\n% Legend\n%\n' talign = 'right' if self.getLegendAlign() == 'left' else 'left' posalign = 'left' if talign == 'right' else 'right' legx = float(self.getLegendXPos()); legy = float(self.getLegendYPos()) ypos = legy -0.05*6/self.props['PlotSizeY'] if self.props.has_key(self.name+'Title'): for i in self.props[self.name+'Title'].strip().split('\\\\'): out += ('\\node[black, inner sep=0, align=%s, %s,\n' % (posalign, talign)) out += ('] at (rel axis cs: %4.3f,%4.3f) {%s};\n' % (legx, ypos, i)) ypos -= 0.075*6/self.props['PlotSizeY'] offset = self.attr_float(self.name+'EntryOffset', 0.) separation = self.attr_float(self.name+'EntrySeparation', 0.) hline = True; vline = True if self.props.has_key(self.name+'HorizontalLine'): hline = self.props[self.name+'HorizontalLine'] != '0' if self.props.has_key(self.name+'VerticalLine'): vline = self.props[self.name+'VerticalLine'] != '0' rel_xpos_sign = 1.0 if self.getLegendAlign() == 'right': rel_xpos_sign = -1.0 xwidth = self.getLegendIconWidth() xpos1 = legx -0.02*rel_xpos_sign-0.08*xwidth*rel_xpos_sign xpos2 = legx -0.02*rel_xpos_sign xposc = legx -0.02*rel_xpos_sign-0.04*xwidth*rel_xpos_sign xpostext = 0.1*rel_xpos_sign for i in orderedlegendlist: if self.histos.has_key(i): drawobject=self.histos[i] elif self.functions.has_key(i): drawobject=self.functions[i] else: continue title = drawobject.getTitle() + opt = extractOptionString(drawobject.path) + if opt and not self.props.get("RemoveOptions", 0): + if opt in self.props['_OptSubs']: + title += ' %s' % self.props['_OptSubs'][opt] + else: + title += ' [%s]' % opt if title == '': continue else: titlelines=[] for i in title.strip().split('\\\\'): titlelines.append(i) ypos -= 0.075*6/self.props['PlotSizeY']*separation boxtop = 0.045*(6./self.props['PlotSizeY']) boxbottom = 0. lineheight = 0.5*(boxtop-boxbottom) xico = xpostext + xposc xhi = xpostext + xpos2 xlo = xpostext + xpos1 yhi = ypos + lineheight ylo = ypos - lineheight xleg = legx + xpostext; # options set -> lineopts setup = ('%s,\n' % drawobject.getLineColor()) linewidth = drawobject.getLineWidth() try: float(linewidth) linewidth += 'cm' except ValueError: pass setup += ('draw opacity=%s,\n' % drawobject.getLineOpacity()) if drawobject.getErrorBands(): out += ('\\fill[\n') out += (' fill=none, fill opacity=%s,\n' % drawobject.getErrorBandOpacity()) if drawobject.getPatternFill(): out += ('fill=%s,\n' % drawobject.getErrorBandFillColor()) out += ('] (rel axis cs: %4.3f, %4.3f) rectangle (rel axis cs: %4.3f, %4.3f);\n' % (xlo,ylo,xhi,yhi)) if drawobject.getPattern() != '': out += ('\\fill[\n') out += ('pattern = %s,\n' % drawobject.getPattern()) if drawobject.getErroBandHatchDistance() != "": out += ('hatch distance = %s,\n' % drawobject.getErroBandHatchDistance()) if drawobject.getPatternColor() != '': out += ('pattern color = %s,\n' % drawobject.getPatternColor()) out += ('] (rel axis cs: %4.3f, %4.3f) rectangle (rel axis cs: %4.3f, %4.3f);\n' % (xlo,ylo,xhi,yhi)) if drawobject.getFillBorder(): out += ('\\draw[line style=solid, thin, %s] (rel axis cs: %4.3f,%4.3f)' % (setup, xlo, yhi)) out += ('-- (rel axis cs: %4.3f, %4.3f);\n' % (xhi, yhi)) out += ('\\draw[line style=solid, thin, %s] (rel axis cs: %4.3f,%4.3f)' % (setup, xlo, ylo)) out += ('-- (rel axis cs: %4.3f, %4.3f);\n' % (xhi, ylo)) setup += ('line width={%s},\n' % linewidth) setup += ('style={%s},\n' % drawobject.getLineStyle()) if drawobject.getLineDash(): setup += ('dash pattern=%s,\n' % drawobject.getLineDash()) if drawobject.getErrorBars() and vline: out += ('\\draw[%s] (rel axis cs: %4.3f,%4.3f)' % (setup, xico, ylo)) out += (' -- (rel axis cs: %4.3f, %4.3f);\n' % (xico, yhi)) if hline: out += ('\\draw[%s] (rel axis cs: %4.3f,%4.3f)' % (setup, xlo, ypos)) out += ('-- (rel axis cs: %4.3f, %4.3f);\n' % (xhi, ypos)) if drawobject.getMarkerStyle() != 'none': setup += ('mark options={\n') setup += (' %s, fill color=%s,\n' % (drawobject.getMarkerColor(), drawobject.getMarkerColor())) setup += (' mark size={%s}, scale=%s,\n' % (drawobject.getMarkerSize(), drawobject.getMarkerScale())) setup += ('},\n') out += ('\\draw[mark=*, %s] plot coordinates {\n' % setup) out += ('(rel axis cs: %4.3f,%4.3f)};\n' % (xico, ypos)) ypos -= 0.075*6/self.props['PlotSizeY']*offset for i in titlelines: out += ('\\node[black, inner sep=0, align=%s, %s,\n' % (posalign, talign)) out += ('] at (rel axis cs: %4.3f,%4.3f) {%s};\n' % (xleg, ypos, i)) ypos -= 0.075*6/self.props['PlotSizeY'] if 'CustomLegend' in self.props: for i in self.props['CustomLegend'].strip().split('\\\\'): out += ('\\node[black, inner sep=0, align=%s, %s,\n' % (posalign, talign)) out += ('] at (rel axis cs: %4.3f, %4.3f) {%s};\n' % (xleg, ypos, i)) ypos -= 0.075*6/self.props['PlotSizeY'] return out def draw_2dlegend(self,orderedlegendlist): histos = "" for i in range(0,len(orderedlegendlist)): if self.histos.has_key(orderedlegendlist[i]): drawobject=self.histos[orderedlegendlist[i]] elif self.functions.has_key(orderedlegendlist[i]): drawobject=self.functions[orderedlegendlist[i]] else: continue title = drawobject.getTitle() if title == '': continue else: histos += title.strip().split('\\\\')[0] if not i==len(orderedlegendlist)-1: histos += ', ' #out = '\\rput(1,1){\\rput[rB](0, 1.7\\labelsep){\\normalsize '+histos+'}}\n' out += ('\\node[black, inner sep=0, align=left]\n') out += ('at (rel axis cs: 1,1) {\\normalsize{%s}};\n' % histos) return out def getLegendXPos(self): return self.props.get(self.name+'XPos', '0.95' if self.getLegendAlign() == 'right' else '0.53') def getLegendYPos(self): return self.props.get(self.name+'YPos', '0.93') def getLegendAlign(self): la = self.props.get(self.name+'Align', 'left') if la == 'l': return 'left' elif la == 'c': return 'center' elif la == 'r': return 'right' else: return la def getLegendIconWidth(self): return float(self.props.get(self.name+'IconWidth', '1.0')) class PlotFunction(object): def __init__(self, f): self.props = {} self.read_input(f) def read_input(self, f): self.code='def histomangler(x):\n' iscode=False for line in f: if is_end_marker(line, 'HISTOGRAMMANGLER'): break elif is_comment(line): continue else: m = pat_property.match(line) if iscode: self.code+=' '+line elif m: prop, value = m.group(1,2) if prop=='Code': iscode=True else: self.props[prop] = value if not iscode: print('++++++++++ ERROR: No code in function') else: foo = compile(self.code, '', 'exec') exec(foo) self.histomangler = histomangler def transform(self, x): return self.histomangler(x) class Special(Described): def __init__(self, f): self.props = {} self.data = [] self.read_input(f) if not self.props.has_key('Location'): self.props['Location']='MainPlot' self.props['Location']=self.props['Location'].split('\t') if not self.props.has_key('Coordinates'): self.props['Coordinates'] = 'Relative' def read_input(self, f): for line in f: if is_end_marker(line, 'SPECIAL'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.props[prop] = value else: self.data.append(line) def draw(self, inputdata): drawme = False for i in self.props['Location']: if i in inputdata.props['PlotStage']: drawme = True break if not drawme: return "" out = "" out += ('\n%\n% Special\n%\n') out += ('\\pgfplotsset{\n') out += (' after end axis/.append code={\n') for l in self.data: cs = 'axis cs:' if self.props['Coordinates'].lower() == 'relative': cs = 'rel ' + cs atpos = l.index('at') cspos = l[atpos:].index('(') + 1 l = l[:atpos+cspos] + cs + l[atpos+cspos:] out += ' %s%s\n' % (l, ';' if l[-1:] != ';' else '') out += (' }\n}\n') return out class DrawableObject(Described): def __init__(self, f): pass def getName(self): return self.props.get('Name', '') def getTitle(self): return self.props.get('Title', '') def getLineStyle(self): if 'LineStyle' in self.props: ## I normally like there to be "only one way to do it", but providing ## this dashdotted/dotdashed synonym just seems humane ;-) if self.props['LineStyle'] in ('dashdotted', 'dotdashed'): self.props['LineStyle']='dashed' self.props['LineDash']='3pt 3pt .8pt 3pt' return self.props['LineStyle'] else: return 'solid' def getLineDash(self): pattern = self.props.get('LineDash', '') if pattern: # converting this into pgfplots syntax # "3pt 3pt .8pt 3pt" becomes # "on 3pt off 3pt on .8pt off 3pt" for i, val in enumerate(pattern.split(' ')): if i == 0: pattern = 'on %s' % val else: pattern += ' %s %s' % ('on' if i % 2 == 0 else 'off', val) return pattern def getLineWidth(self): return self.props.get("LineWidth", "0.8pt") def getColor(self, col): if col in self.customCols: return self.customCols[col] return col def getLineColor(self): return self.getColor(self.props.get("LineColor", "black")) def getLineOpacity(self): return self.props.get("LineOpacity", "1.0") def getFillOpacity(self): return self.props.get("FillOpacity", "1.0") def getFillBorder(self): return self.attr_bool("FillBorder", "1") def getHatchColor(self): return self.getColor(self.props.get("HatchColor", self.getErrorBandColor())) def getMarkerStyle(self): return self.props.get("MarkerStyle", "*" if self.getErrorBars() else "none") def getMarkerSize(self): return self.props.get("MarkerSize", "1.5pt") def getMarkerScale(self): return self.props.get("MarkerScale", "1") def getMarkerColor(self): return self.getColor(self.props.get("MarkerColor", "black")) def getErrorMarkStyle(self): return self.props.get("ErrorMarkStyle", "none") def getErrorBars(self): return bool(int(self.props.get("ErrorBars", "0"))) def getErrorBands(self): return bool(int(self.props.get("ErrorBands", "0"))) def getErrorBandColor(self): return self.getColor(self.props.get("ErrorBandColor", self.getLineColor())) def getErrorBandStyle(self): return self.props.get("ErrorBandStyle", "solid") def getPattern(self): return self.props.get("ErrorBandPattern", "") def getPatternColor(self): return self.getColor(self.props.get("ErrorBandPatternColor", "")) def getPatternFill(self): return bool(int(self.props.get("ErrorBandFill", self.getPattern() == ""))) def getErrorBandFillColor(self): return self.getColor(self.props.get("ErrorBandFillColor", self.getErrorBandColor())) def getErroBandHatchDistance(self): return self.props.get("ErrorBandHatchDistance", "") def getErrorBandOpacity(self): return self.props.get("ErrorBandOpacity", "1.0") def removeXerrors(self): return bool(int(self.props.get("RemoveXerrors", "0"))) def getSmoothLine(self): return bool(int(self.props.get("SmoothLine", "0"))) def getShader(self): return self.props.get('Shader', 'flat') def makecurve(self, setup, metadata, data = None): #, legendName = None): out = '' out += ('\\addplot%s+[\n' % ('3' if self.is2dim else '')) out += setup out += (']\n') out += metadata out += ('{\n') if data: out += data out += ('};\n') #if legendName: # out += ('\\addlegendentry[\n') # out += (' image style={yellow, mark=*},\n') # out += (']{%s};\n' % legendName) return out def addcurve(self, points): #, legendName): setup = ''; #setup += ('color=%s,\n' % self.getLineColor()) setup += ('%s,\n' % self.getLineColor()) linewidth = self.getLineWidth() try: float(linewidth) linewidth += 'cm' except ValueError: pass setup += ('line width={%s},\n' % linewidth) setup += ('style={%s},\n' % self.getLineStyle()) setup += ('mark=%s,\n' % self.getMarkerStyle()) if self.getLineDash(): setup += ('dash pattern=%s,\n' % self.getLineDash()) if self.getSmoothLine(): setup += ('smooth,\n') #if not legendName: # setup += ('forget plot,\n') if self.getMarkerStyle() != 'none': setup += ('mark options={\n') setup += (' %s, fill color=%s,\n' % (self.getMarkerColor(), self.getMarkerColor())) setup += (' mark size={%s}, scale=%s,\n' % (self.getMarkerSize(), self.getMarkerScale())) setup += ('},\n') if self.getErrorBars(): setup += ('only marks,\n') setup += ('error bars/.cd,\n') setup += ('x dir=both,x explicit,\n') setup += ('y dir=both,y explicit,\n') setup += ('error bar style={%s, line width={%s}},\n' % (self.getLineColor(), linewidth)) setup += ('error mark=%s,\n' % self.getErrorMarkStyle()) if self.getErrorMarkStyle() != 'none': setup += ('error mark options={line width=%s},\n' % linewidth) metadata = 'coordinates\n' if self.getErrorBars(): metadata = 'table [x error plus=ex+, x error minus=ex-, y error plus=ey+, y error minus=ey-]\n' return self.makecurve(setup, metadata, points) #, legendName) def makeband(self, points): setup = '' setup += ('%s,\n' % self.getErrorBandColor()) setup += ('fill opacity=%s,\n' % self.getErrorBandOpacity()) setup += ('forget plot,\n') setup += ('solid,\n') setup += ('draw = %s,\n' % self.getErrorBandColor()) if self.getPatternFill(): setup += ('fill=%s,\n' % self.getErrorBandFillColor()) if self.getPattern() != '': if self.getPatternFill(): setup += ('postaction={\n') setup += ('pattern = %s,\n' % self.getPattern()) if self.getErroBandHatchDistance() != "": setup += ('hatch distance = %s,\n' % self.getErroBandHatchDistance()) if self.getPatternColor() != '': setup += ('pattern color = %s,\n' % self.getPatternColor()) if self.getPatternFill(): setup += ('fill opacity=1,\n') setup += ('},\n') aux = 'draw=none, no markers, forget plot, name path=%s\n' env = 'table [x=x, y=%s]\n' out = '' out += self.makecurve(aux % 'pluserr', env % 'y+', points) out += self.makecurve(aux % 'minuserr', env % 'y-', points) out += self.makecurve(setup, 'fill between [of=pluserr and minuserr]\n') return out def make2dee(self, points, zlog, zmin, zmax): setup = 'mark=none, surf, shader=%s,\n' % self.getShader() metadata = 'table [x index=0, y index=1, z index=2]\n' setup += 'restrict z to domain=%s:%s,\n' % (log10(zmin) if zlog else zmin, log10(zmax) if zlog else zmax) if zlog: metadata = 'table [x index=0, y index=1, z expr=log10(\\thisrowno{2})]\n' return self.makecurve(setup, metadata, points) class Function(DrawableObject): def __init__(self, f): self.props = {} self.read_input(f) if not self.props.has_key('Location'): self.props['Location']='MainPlot' self.props['Location']=self.props['Location'].split('\t') def read_input(self, f): self.code='def plotfunction(x):\n' iscode=False for line in f: if is_end_marker(line, 'FUNCTION'): break elif is_comment(line): continue else: m = pat_property.match(line) if iscode: self.code+=' '+line elif m: prop, value = m.group(1,2) if prop=='Code': iscode=True else: self.props[prop] = value if not iscode: print('++++++++++ ERROR: No code in function') else: foo = compile(self.code, '', 'exec') exec(foo) self.plotfunction = plotfunction def draw(self, inputdata, xmin, xmax): drawme = False for key in self.attr('Location'): if key in inputdata.attr('PlotStage'): drawme = True break if not drawme: return '' if self.has_attr('XMin') and self.attr('XMin'): xmin = self.attr_float('XMin') if self.props.has_attr('FunctionXMin') and self.attr('FunctionXMin'): xmin = max(xmin, self.attr_float('FunctionXMin')) if self.has_attr('XMax') and self.attr('XMax'): xmax = self.attr_float('XMax') if self.has_attr('FunctionXMax') and self.attr('FunctionXMax'): xmax = min(xmax, self.attr_float('FunctionXMax')) xmin = min(xmin, xmax) xmax = max(xmin, xmax) # TODO: Space sample points logarithmically if LogX=1 points = '' xsteps = 500. if self.has_attr('XSteps') and self.attr('XSteps'): xsteps = self.attr_float('XSteps') dx = (xmax - xmin) / xsteps x = xmin - dx while x < (xmax+2*dx): y = self.plotfunction(x) points += ('(%s,%s)\n' % (x, y)) x += dx setup = ''; setup += ('%s,\n' % self.getLineColor()) linewidth = self.getLineWidth() try: float(linewidth) linewidth += 'cm' except ValueError: pass setup += ('line width={%s},\n' % linewidth) setup += ('style={%s},\n' % self.getLineStyle()) setup += ('smooth, mark=none,\n') if self.getLineDash(): setup += ('dash pattern=%s,\n' % self.getLineDash()) metadata = 'coordinates\n' return self.makecurve(setup, metadata, points) class BinData(object): """\ Store bin edge and value+error(s) data for a 1D or 2D bin. TODO: generalise/alias the attr names to avoid mention of x and y """ def __init__(self, low, high, val, err): #print("@", low, high, val, err) self.low = floatify(low) self.high = floatify(high) self.val = float(val) self.err = floatpair(err) @property def is2D(self): return hasattr(self.low, "__len__") and hasattr(self.high, "__len__") @property def isValid(self): invalid_val = (isnan(self.val) or isnan(self.err[0]) or isnan(self.err[1])) if invalid_val: return False if self.is2D: invalid_low = any(isnan(x) for x in self.low) invalid_high = any(isnan(x) for x in self.high) else: invalid_low, invalid_high = isnan(self.low), isnan(self.high) return not (invalid_low or invalid_high) @property def xmin(self): return self.low @xmin.setter def xmin(self,x): self.low = x @property def xmax(self): return self.high @xmax.setter def xmax(self,x): self.high = x @property def xmid(self): # TODO: Generalise to 2D return 0.5 * (self.xmin + self.xmax) @property def xwidth(self): # TODO: Generalise to 2D assert self.xmin <= self.xmax return self.xmax - self.xmin @property def y(self): return self.val @y.setter def y(self, x): self.val = x @property def ey(self): return self.err @ey.setter def ey(self, x): self.err = x @property def ymin(self): return self.y - self.ey[0] @property def ymax(self): return self.y + self.ey[1] def __getitem__(self, key): "dict-like access for backward compatibility" if key in ("LowEdge"): return self.xmin elif key == ("UpEdge", "HighEdge"): return self.xmax elif key == "Content": return self.y elif key == "Errors": return self.ey class Histogram(DrawableObject, Described): def __init__(self, f, p=None): self.props = {} self.customCols = {} self.is2dim = False self.zlog = False self.data = [] self.read_input_data(f) self.sigmabinvalue = None self.meanbinvalue = None self.path = p def read_input_data(self, f): for line in f: if is_end_marker(line, 'HISTOGRAM'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.props[prop] = value if 'Color' in prop and '{' in value: self.customCols[value] = value else: ## Detect symm errs linearray = line.split() if len(linearray) == 4: self.data.append(BinData(*linearray)) ## Detect asymm errs elif len(linearray) == 5: self.data.append(BinData(linearray[0], linearray[1], linearray[2], [linearray[3],linearray[4]])) ## Detect two-dimensionality elif len(linearray) in [6,7]: self.is2dim = True # If asymm z error, use the max or average of +- error err = float(linearray[5]) if len(linearray) == 7: if self.props.get("ShowMaxZErr", 1): err = max(err, float(linearray[6])) else: err = 0.5 * (err + float(linearray[6])) self.data.append(BinData([linearray[0], linearray[2]], [linearray[1], linearray[3]], linearray[4], err)) ## Unknown histo format else: raise RuntimeError("Unknown HISTOGRAM data line format with %d entries" % len(linearray)) def mangle_input(self): norm2int = self.attr_bool("NormalizeToIntegral", False) norm2sum = self.attr_bool("NormalizeToSum", False) if norm2int or norm2sum: if norm2int and norm2sum: print("Cannot normalise to integral and to sum at the same time. Will normalise to the integral.") foo = 0.0 for point in self.data: if norm2int: foo += point.val*point.xwidth else: foo += point.val if foo != 0: for point in self.data: point.val /= foo point.err[0] /= foo point.err[1] /= foo scale = self.attr_float('Scale', 1.0) if scale != 1.0: # TODO: change to "in self.data"? for point in self.data: point.val *= scale point.err[0] *= scale point.err[1] *= scale if self.attr_float("ScaleError", 0.0): scale = self.attr_float("ScaleError") for point in self.data: point.err[0] *= scale point.err[1] *= scale if self.attr_float('Shift', 0.0): shift = self.attr_float("Shift") for point in self.data: point.val += shift if self.has_attr('Rebin') and self.attr('Rebin') != '': rawrebins = self.attr('Rebin').strip().split('\t') rebins = [] maxindex = len(self.data)-1 if len(rawrebins) % 2 == 1: rebins.append({'Start': self.data[0].xmin, 'Rebin': int(rawrebins[0])}) rawrebins.pop(0) for i in range(0,len(rawrebins),2): if float(rawrebins[i]) self.data[0].xmin): rebins.insert(0,{'Start': self.data[0].xmin, 'Rebin': 1}) errortype = self.attr("ErrorType", "stat") newdata = [] lower = self.getBin(rebins[0]['Start']) for k in range(0,len(rebins),1): rebin = rebins[k]['Rebin'] upper = maxindex end = 1 if (kmaxindex: break binwidth = self.data[i+j].xwidth foo += self.data[i+j].val * binwidth if errortype=="stat": barl += (binwidth * self.data[i+j].err[0])**2 baru += (binwidth * self.data[i+j].err[1])**2 elif errortype == "env": barl += self.data[i+j].ymin * binwidth baru += self.data[i+j].ymax * binwidth else: logging.error("Rebinning for ErrorType not implemented.") sys.exit(1) upedge = min(i+rebin-1,maxindex) newbinwidth=self.data[upedge].xmax-self.data[i].xmin newcentral=foo/newbinwidth if errortype=="stat": newerror=[sqrt(barl)/newbinwidth,sqrt(baru)/newbinwidth] elif errortype=="env": newerror=[(foo-barl)/newbinwidth,(baru-foo)/newbinwidth] newdata.append(BinData(self.data[i].xmin, self.data[i+rebin-1].xmax, newcentral, newerror)) lower = (upper/rebin)*rebin+(upper%rebin) self.data=newdata def add(self, name): if len(self.data) != len(name.data): print('+++ Error in Histogram.add() for %s: different numbers of bins' % self.path) for i, b in enumerate(self.data): if fuzzyeq(b.xmin, name.data[i].xmin) and fuzzyeq(b.xmax, name.data[i].xmax): b.val += name.data[i].val b.err[0] = sqrt(b.err[0]**2 + name.data[i].err[0]**2) b.err[1] = sqrt(b.err[1]**2 + name.data[i].err[1]**2) else: print('+++ Error in Histogram.add() for %s: binning of histograms differs' % self.path) def divide(self, name): #print(name.path, self.path) if len(self.data) != len(name.data): print('+++ Error in Histogram.divide() for %s: different numbers of bins' % self.path) for i,b in enumerate(self.data): if fuzzyeq(b.xmin, name.data[i].xmin) and fuzzyeq(b.xmax, name.data[i].xmax): try: b.err[0] /= name.data[i].val except ZeroDivisionError: b.err[0] = 0. try: b.err[1] /= name.data[i].val except ZeroDivisionError: b.err[1] = 0. try: b.val /= name.data[i].val except ZeroDivisionError: b.val = 1. # b.err[0] = sqrt(b.err[0]**2 + name.data[i].err[0]**2) # b.err[1] = sqrt(b.err[1]**2 + name.data[i].err[1]**2) else: print('+++ Error in Histogram.divide() for %s: binning of histograms differs' % self.path) def dividereverse(self, name): if len(self.data) != len(name.data): print('+++ Error in Histogram.dividereverse() for %s: different numbers of bins' % self.path) for i, b in enumerate(self.data): if fuzzyeq(b.xmin, name.data[i].xmin) and fuzzyeq(b.xmax, name.data[i].xmax): try: b.err[0] = name.data[i].err[0]/b.val except ZeroDivisionError: b.err[0] = 0. try: b.err[1] = name.data[i].err[1]/b.val except ZeroDivisionError: b.err[1] = 0. try: b.val = name.data[i].val/b.val except ZeroDivisionError: b.val = 1. else: print('+++ Error in Histogram.dividereverse(): binning of histograms differs') def deviation(self, name): if len(self.data) != len(name.data): print('+++ Error in Histogram.deviation() for %s: different numbers of bins' % self.path) for i, b in enumerate(self.data): if fuzzyeq(b.xmin, name.data[i].xmin) and fuzzyeq(b.xmax, name.data[i].xmax): b.val -= name.data[i].val try: b.val /= 0.5*sqrt((name.data[i].err[0] + name.data[i].err[1])**2 + (b.err[0] + b.err[1])**2) except ZeroDivisionError: b.val = 0.0 try: b.err[0] /= name.data[i].err[0] except ZeroDivisionError: b.err[0] = 0.0 try: b.err[1] /= name.data[i].err[1] except ZeroDivisionError: b.err[1] = 0.0 else: print('+++ Error in Histogram.deviation() for %s: binning of histograms differs' % self.path) def delta(self,name): self.divide(name) for point in self.data: point.val -= 1. def deltapercent(self,name): self.delta(name) for point in self.data: point.val *= 100. point.err[0] *= 100. point.err[1] *= 100. def getBin(self,x): if xself.data[len(self.data)-1].xmax: print('+++ Error in Histogram.getBin(): x out of range') return float('nan') for i in range(1,len(self.data)-1,1): if x xmin or b.xmin >= xmin) and (b.xmin < xmax or b.xmax <= xmax): foo = b.val if self.getErrorBars() or self.getErrorBands(): foo -= b.err[0] if not isnan(foo) and (not logy or foo > 0): yvalues.append(foo) return min(yvalues) if yvalues else self.data[0].val def getYMax(self, xmin, xmax): if not self.data: return 1 elif self.is2dim: return max(b.high[1] for b in self.data) else: yvalues = [] for b in self.data: if (b.xmax > xmin or b.xmin >= xmin) and (b.xmin < xmax or b.xmax <= xmax): foo = b.val if self.getErrorBars() or self.getErrorBands(): foo += b.err[1] if not isnan(foo): # and (not logy or foo > 0): yvalues.append(foo) return max(yvalues) if yvalues else self.data[0].val def getZMin(self, xmin, xmax, ymin, ymax): if not self.is2dim: return 0 zvalues = [] for b in self.data: if (b.xmax[0] > xmin and b.xmin[0] < xmax) and (b.xmax[1] > ymin and b.xmin[1] < ymax): zvalues.append(b.val) return min(zvalues) def getZMax(self, xmin, xmax, ymin, ymax): if not self.is2dim: return 0 zvalues = [] for b in self.data: if (b.xmax[0] > xmin and b.xmin[0] < xmax) and (b.xmax[1] > ymin and b.xmin[1] < ymax): zvalues.append(b.val) return max(zvalues) class Value(Histogram): def read_input_data(self, f): for line in f: if is_end_marker(line, 'VALUE'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.props[prop] = value else: linearray = line.split() if len(linearray) == 3: self.data.append(BinData(0.0, 1.0, linearray[0], [ linearray[1], linearray[2] ])) # dummy x-values else: raise Exception('Value does not have the expected number of columns. ' + line) # TODO: specialise draw() here class Counter(Histogram): def read_input_data(self, f): for line in f: if is_end_marker(line, 'COUNTER'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.props[prop] = value else: linearray = line.split() if len(linearray) == 2: self.data.append(BinData(0.0, 1.0, linearray[0], [ linearray[1], linearray[1] ])) # dummy x-values else: raise Exception('Counter does not have the expected number of columns. ' + line) # TODO: specialise draw() here class Histo1D(Histogram): def read_input_data(self, f): for line in f: if is_end_marker(line, 'HISTO1D'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.props[prop] = value if 'Color' in prop and '{' in value: self.customCols[value] = value else: linearray = line.split() ## Detect symm errs # TODO: Not sure what the 8-param version is for... auto-compatibility with YODA format? if len(linearray) in [4,8]: self.data.append(BinData(linearray[0], linearray[1], linearray[2], linearray[3])) ## Detect asymm errs elif len(linearray) == 5: self.data.append(BinData(linearray[0], linearray[1], linearray[2], [linearray[3],linearray[4]])) else: raise Exception('Histo1D does not have the expected number of columns. ' + line) # TODO: specialise draw() here class Histo2D(Histogram): def read_input_data(self, f): self.is2dim = True #< Should really be done in a constructor, but this is easier for now... for line in f: if is_end_marker(line, 'HISTO2D'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.props[prop] = value if 'Color' in prop and '{' in value: self.customCols[value] = value else: linearray = line.split() if len(linearray) in [6,7]: # If asymm z error, use the max or average of +- error err = float(linearray[5]) if len(linearray) == 7: if self.props.get("ShowMaxZErr", 1): err = max(err, float(linearray[6])) else: err = 0.5 * (err + float(linearray[6])) self.data.append(BinData([linearray[0], linearray[2]], [linearray[1], linearray[3]], float(linearray[4]), err)) else: raise Exception('Histo2D does not have the expected number of columns. '+line) # TODO: specialise draw() here #################### def try_cmd(args): "Run the given command + args and return True/False if it succeeds or not" import subprocess try: subprocess.check_output(args, stderr=subprocess.STDOUT) return True except: return False def have_cmd(cmd): return try_cmd(["which", cmd]) import shutil, subprocess def process_datfile(datfile): global opts if not os.access(datfile, os.R_OK): raise Exception("Could not read data file '%s'" % datfile) datpath = os.path.abspath(datfile) datfile = os.path.basename(datpath) datdir = os.path.dirname(datpath) outdir = opts.OUTPUT_DIR if opts.OUTPUT_DIR else datdir filename = datfile.replace('.dat','') ## Create a temporary directory # cwd = os.getcwd() tempdir = tempfile.mkdtemp('.make-plots') tempdatpath = os.path.join(tempdir, datfile) shutil.copy(datpath, tempdir) if opts.NO_CLEANUP: logging.info('Keeping temp-files in %s' % tempdir) ## Make TeX file inputdata = InputData(datpath) if inputdata.attr_bool('IgnorePlot', False): return texpath = os.path.join(tempdir, '%s.tex' % filename) texfile = open(texpath, 'w') p = Plot() texfile.write(p.write_header(inputdata)) if inputdata.attr_bool("MainPlot", True): mp = MainPlot(inputdata) texfile.write(mp.draw(inputdata)) if not inputdata.attr_bool("is2dim", False): for rname, i in inputdata.ratio_names(): if inputdata.attr_bool(rname, True) and inputdata.attr(rname + 'Reference', False): rp = RatioPlot(inputdata, i) texfile.write(rp.draw(inputdata)) #for s in inputdata.special.values(): # texfile.write(p.write_special(inputdata)) texfile.write(p.write_footer()) texfile.close() if opts.OUTPUT_FORMAT != ["TEX"]: ## Check for the required programs latexavailable = have_cmd("latex") dvipsavailable = have_cmd("dvips") convertavailable = have_cmd("convert") ps2pnmavailable = have_cmd("ps2pnm") pnm2pngavailable = have_cmd("pnm2png") # TODO: It'd be nice to be able to control the size of the PNG between thumb and full-size... # currently defaults (and is used below) to a size suitable for thumbnails def mkpngcmd(infile, outfile, outsize=450, density=300): if convertavailable: pngcmd = ["convert", "-flatten", "-density", str(density), infile, "-quality", "100", "-resize", "{size:d}x{size:d}>".format(size=outsize), #"-sharpen", "0x1.0", outfile] #logging.debug(" ".join(pngcmd)) #pngproc = subprocess.Popen(pngcmd, stdout=subprocess.PIPE, cwd=tempdir) #pngproc.wait() return pngcmd else: raise Exception("Required PNG maker program (convert) not found") # elif ps2pnmavailable and pnm2pngavailable: # pstopnm = "pstopnm -stdout -xsize=461 -ysize=422 -xborder=0.01 -yborder=0.01 -portrait " + infile # p1 = subprocess.Popen(pstopnm.split(), stdout=subprocess.PIPE, stderr=open("/dev/null", "w"), cwd=tempdir) # p2 = subprocess.Popen(["pnmtopng"], stdin=p1.stdout, stdout=open("%s/%s.png" % (tempdir, outfile), "w"), stderr=open("/dev/null", "w"), cwd=tempdir) # p2.wait() # else: # raise Exception("Required PNG maker programs (convert, or ps2pnm and pnm2png) not found") ## Run LaTeX (in no-stop mode) logging.debug(os.listdir(tempdir)) texcmd = ["latex", "\scrollmode\input", texpath] logging.debug("TeX command: " + " ".join(texcmd)) texproc = subprocess.Popen(texcmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tempdir) logging.debug(texproc.communicate()[0]) logging.debug(os.listdir(tempdir)) ## Run dvips dvcmd = ["dvips", filename] if not logging.getLogger().isEnabledFor(logging.DEBUG): dvcmd.append("-q") ## Handle Minion Font if opts.OUTPUT_FONT == "MINION": dvcmd.append('-Pminion') ## Choose format # TODO: Rationalise... this is a mess! Maybe we can use tex2pix? if "PS" in opts.OUTPUT_FORMAT: dvcmd += ["-o", "%s.ps" % filename] logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) dvproc.wait() if "PDF" in opts.OUTPUT_FORMAT: dvcmd.append("-f") logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) cnvproc = subprocess.Popen(["ps2pdf", "-"], stdin=dvproc.stdout, stdout=subprocess.PIPE, cwd=tempdir) f = open(os.path.join(tempdir, "%s.pdf" % filename), "wb") f.write(cnvproc.communicate()[0]) f.close() if "EPS" in opts.OUTPUT_FORMAT: dvcmd.append("-f") logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) cnvproc = subprocess.Popen(["ps2eps"], stdin=dvproc.stdout, stderr=subprocess.PIPE, stdout=subprocess.PIPE, cwd=tempdir) f = open(os.path.join(tempdir, "%s.eps" % filename), "wb") f.write(cnvproc.communicate()[0]) f.close() if "PNG" in opts.OUTPUT_FORMAT: dvcmd.append("-f") logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) #pngcmd = ["convert", "-flatten", "-density", "110", "-", "-quality", "100", "-sharpen", "0x1.0", "%s.png" % filename] pngcmd = mkpngcmd("-", "%s.png" % filename) logging.debug(" ".join(pngcmd)) pngproc = subprocess.Popen(pngcmd, stdin=dvproc.stdout, stdout=subprocess.PIPE, cwd=tempdir) pngproc.wait() logging.debug(os.listdir(tempdir)) ## Copy results back to main dir for fmt in opts.OUTPUT_FORMAT: outname = "%s.%s" % (filename, fmt.lower()) outpath = os.path.join(tempdir, outname) if os.path.exists(outpath): shutil.copy(outpath, outdir) else: logging.error("No output file '%s' from processing %s" % (outname, datfile)) ## Clean up if not opts.NO_CLEANUP: shutil.rmtree(tempdir, ignore_errors=True) #################### if __name__ == '__main__': ## Try to rename the process on Linux try: import ctypes libc = ctypes.cdll.LoadLibrary('libc.so.6') libc.prctl(15, 'make-plots', 0, 0, 0) except Exception: pass ## Try to use Psyco optimiser try: import psyco psyco.full() except ImportError: pass ## Find number of (virtual) processing units import multiprocessing try: numcores = multiprocessing.cpu_count() except: numcores = 1 ## Parse command line options from optparse import OptionParser, OptionGroup parser = OptionParser(usage=__doc__) parser.add_option("-j", "-n", "--num-threads", dest="NUM_THREADS", type="int", default=numcores, help="max number of threads to be used [%s]" % numcores) parser.add_option("-o", "--outdir", dest="OUTPUT_DIR", default=None, help="choose the output directory (default = .dat dir)") parser.add_option("--font", dest="OUTPUT_FONT", choices="palatino,cm,times,helvetica,minion".split(","), default="palatino", help="choose the font to be used in the plots") parser.add_option("-f", "--format", dest="OUTPUT_FORMAT", default="PDF", help="choose plot format, perhaps multiple comma-separated formats e.g. 'pdf' or 'tex,pdf,png' (default = PDF).") parser.add_option("--no-cleanup", dest="NO_CLEANUP", action="store_true", default=False, help="keep temporary directory and print its filename.") parser.add_option("--no-subproc", dest="NO_SUBPROC", action="store_true", default=False, help="don't use subprocesses to render the plots in parallel -- useful for debugging.") parser.add_option("--full-range", dest="FULL_RANGE", action="store_true", default=False, help="plot full y range in log-y plots.") parser.add_option("-c", "--config", dest="CONFIGFILES", action="append", default=None, help="plot config file to be used. Overrides internal config blocks.") verbgroup = OptionGroup(parser, "Verbosity control") verbgroup.add_option("-v", "--verbose", action="store_const", const=logging.DEBUG, dest="LOGLEVEL", default=logging.INFO, help="print debug (very verbose) messages") verbgroup.add_option("-q", "--quiet", action="store_const", const=logging.WARNING, dest="LOGLEVEL", default=logging.INFO, help="be very quiet") parser.add_option_group(verbgroup) opts, args = parser.parse_args() ## Tweak the opts output logging.basicConfig(level=opts.LOGLEVEL, format="%(message)s") opts.OUTPUT_FONT = opts.OUTPUT_FONT.upper() opts.OUTPUT_FORMAT = opts.OUTPUT_FORMAT.upper().split(",") if opts.NUM_THREADS == 1: opts.NO_SUBPROC = True ## Check for no args if len(args) == 0: logging.error(parser.get_usage()) sys.exit(2) ## Check that the files exist for f in args: if not os.access(f, os.R_OK): print("Error: cannot read from %s" % f) sys.exit(1) ## Test for external programs (kpsewhich, latex, dvips, ps2pdf/ps2eps, and convert) opts.LATEXPKGS = [] if opts.OUTPUT_FORMAT != ["TEX"]: try: ## latex if not have_cmd("latex"): logging.error("ERROR: required program 'latex' could not be found. Exiting...") sys.exit(1) ## dvips if not have_cmd("dvips"): logging.error("ERROR: required program 'dvips' could not be found. Exiting...") sys.exit(1) ## ps2pdf / ps2eps if "PDF" in opts.OUTPUT_FORMAT: if not have_cmd("ps2pdf"): logging.error("ERROR: required program 'ps2pdf' (for PDF output) could not be found. Exiting...") sys.exit(1) elif "EPS" in opts.OUTPUT_FORMAT: if not have_cmd("ps2eps"): logging.error("ERROR: required program 'ps2eps' (for EPS output) could not be found. Exiting...") sys.exit(1) ## PNG output converter if "PNG" in opts.OUTPUT_FORMAT: if not have_cmd("convert"): logging.error("ERROR: required program 'convert' (for PNG output) could not be found. Exiting...") sys.exit(1) ## kpsewhich: required for LaTeX package testing if not have_cmd("kpsewhich"): logging.warning("WARNING: required program 'kpsewhich' (for LaTeX package checks) could not be found") else: ## Check minion font if opts.OUTPUT_FONT == "MINION": p = subprocess.Popen(["kpsewhich", "minion.sty"], stdout=subprocess.PIPE) p.wait() if p.returncode != 0: logging.warning('Warning: Using "--minion" requires minion.sty to be installed. Ignoring it.') opts.OUTPUT_FONT = "PALATINO" ## Check for HEP LaTeX packages # TODO: remove HEP-specifics/non-standards? for pkg in ["hepnames", "hepunits", "underscore"]: p = subprocess.Popen(["kpsewhich", "%s.sty" % pkg], stdout=subprocess.PIPE) p.wait() if p.returncode == 0: opts.LATEXPKGS.append(pkg) ## Check for Palatino old style figures and small caps if opts.OUTPUT_FONT == "PALATINO": p = subprocess.Popen(["kpsewhich", "ot1pplx.fd"], stdout=subprocess.PIPE) p.wait() if p.returncode == 0: opts.OUTPUT_FONT = "PALATINO_OSF" except Exception as e: logging.warning("Problem while testing for external packages. I'm going to try and continue without testing, but don't hold your breath...") def init_worker(): import signal signal.signal(signal.SIGINT, signal.SIG_IGN) ## Run rendering jobs datfiles = args plotword = "plots" if len(datfiles) > 1 else "plot" logging.info("Making %d %s" % (len(datfiles), plotword)) if opts.NO_SUBPROC: init_worker() for i, df in enumerate(datfiles): logging.info("Plotting %s (%d/%d remaining)" % (df, len(datfiles)-i, len(datfiles))) process_datfile(df) else: pool = multiprocessing.Pool(opts.NUM_THREADS, init_worker) try: for i, _ in enumerate(pool.imap(process_datfile, datfiles)): logging.info("Plotting %s (%d/%d remaining)" % (datfiles[i], len(datfiles)-i, len(datfiles))) pool.close() except KeyboardInterrupt: print("Caught KeyboardInterrupt, terminating workers") pool.terminate() except ValueError as e: print(e) print("Perhaps your .dat file is corrupt?") pool.terminate() pool.join() diff --git a/bin/make-plots b/bin/make-plots --- a/bin/make-plots +++ b/bin/make-plots @@ -1,3546 +1,3573 @@ #! /usr/bin/env python """\ Usage: %prog [options] file.dat [file2.dat ...] TODO * Optimise output for e.g. lots of same-height bins in a row * Add a RatioFullRange directive to show the full range of error bars + MC envelope in the ratio * Tidy LaTeX-writing code -- faster to compile one doc only, then split it? * Handle boolean values flexibly (yes, no, true, false, etc. as well as 1, 0) """ from __future__ import print_function ## ## This program is copyright by Hendrik Hoeth and ## the Rivet team https://rivet.hepforge.org. It may be used ## for scientific and private purposes. Patches are welcome, but please don't ## redistribute changed versions yourself. ## ## Check the Python version import sys if sys.version_info[:3] < (2,6,0): print("make-plots requires Python version >= 2.6.0... exiting") sys.exit(1) ## Try to rename the process on Linux try: import ctypes libc = ctypes.cdll.LoadLibrary('libc.so.6') libc.prctl(15, 'make-plots', 0, 0, 0) except Exception as e: pass import os, logging, re import tempfile import getopt import string import copy from math import * ## Regex patterns pat_begin_block = re.compile(r'^#+\s*BEGIN ([A-Z0-9_]+) ?(\S+)?') pat_end_block = re.compile('^#+\s*END ([A-Z0-9_]+)') pat_comment = re.compile('^#|^\s*$') pat_property = re.compile('^(\w+?)=(.*)$') pat_path_property = re.compile('^(\S+?)::(\w+?)=(.*)$') +pat_options = re.compile(r"^.*(:\w+=[^:/]+)+") def fuzzyeq(a, b, tolerance=1e-6): "Fuzzy equality comparison function for floats, with given fractional tolerance" # if type(a) is not float or type(a) is not float: # print(a, b) if (a == 0 and abs(b) < 1e-12) or (b == 0 and abs(a) < 1e-12): return True return 2.0*abs(a-b)/abs(a+b) < tolerance def inrange(x, a, b): return x >= a and x < b def floatify(x): if type(x) is str: x = x.split() if not hasattr(x, "__len__"): x = [x] x = [float(a) for a in x] return x[0] if len(x) == 1 else x def floatpair(x): if type(x) is str: x = x.split() if hasattr(x, "__len__"): assert len(x) == 2 return [float(a) for a in x] return [float(x), float(x)] def is_end_marker(line, blockname): m = pat_end_block.match(line) return m and m.group(1) == blockname def is_comment(line): return pat_comment.match(line) is not None +def stripOptions(path): + return re.sub(r':\w+=[^:/]+', "", path) + + +def extractOptionString(path): + m = pat_options.match(path) + if not m: + return "" + opts = list(m.groups()) + for i in range(len(opts)): + opts[i] = opts[i].strip(':') + return ",".join(opts) + class Described(object): "Inherited functionality for objects holding a 'description' dictionary" def __init__(self): pass def has_attr(self, key): return key in self.description def set_attr(self, key, val): self.description[key] = val def attr(self, key, default=None): return self.description.get(key, default) def attr_bool(self, key, default=None): x = self.attr(key, default) if x is None: return None if str(x).lower() in ["1", "true", "yes", "on"]: return True if str(x).lower() in ["0", "false", "no", "off"]: return False return None def attr_int(self, key, default=None): x = self.attr(key, default) try: x = int(x) except: x = None return x def attr_float(self, key, default=None): x = self.attr(key, default) try: x = float(x) except: x = None return x class InputData(Described): def __init__(self, filename): self.filename=filename if not self.filename.endswith(".dat"): self.filename += ".dat" self.normalized=False self.histos = {} self.ratiohistos = {} self.histomangler = {} self.special = {} self.functions = {} self.description = {} self.pathdescriptions = [] + self.description['_OptSubs'] = { } self.description['is2dim'] = False f = open(filename) for line in f: m = pat_begin_block.match(line) if m: name, path = m.group(1,2) if path is None and name != 'PLOT': raise Exception('BEGIN sections need a path name.') if name == 'PLOT': self.read_input(f); elif name == 'SPECIAL': self.special[path] = Special(f) elif name == 'HISTOGRAM' or name == 'HISTOGRAM2D': self.histos[path] = Histogram(f, p=path) self.description['is2dim'] = self.histos[path].is2dim if not self.histos[path].getName() == '': newname = self.histos[path].getName() self.histos[newname] = copy.deepcopy(self.histos[path]) del self.histos[path] elif name == 'HISTO1D': self.histos[path] = Histo1D(f, p=path) if not self.histos[path].getName() == '': newname = self.histos[path].getName() self.histos[newname] = copy.deepcopy(self.histos[path]) del self.histos[path] elif name == 'HISTO2D': self.histos[path] = Histo2D(f, p=path) self.description['is2dim'] = True if not self.histos[path].getName() == '': newname = self.histos[path].getName() self.histos[newname] = copy.deepcopy(self.histos[path]) del self.histos[path] elif name == 'HISTOGRAMMANGLER': self.histomangler[path] = PlotFunction(f) elif name == 'COUNTER': self.histos[path] = Counter(f, p=path) elif name == 'VALUE': self.histos[path] = Value(f, p=path) elif name == 'FUNCTION': self.functions[path] = Function(f) # elif is_comment(line): # continue # else: # self.read_path_based_input(line) f.close() self.apply_config_files(opts.CONFIGFILES) self.description.setdefault('PlotSizeX', 10.) self.description.setdefault('PlotSizeY', 6.) if self.description['is2dim']: self.description['PlotSizeX'] -= 1.7 self.description['PlotSizeY'] = 10. self.description['MainPlot'] = '1' self.description['RatioPlot'] = '0' if self.description.get('PlotSize', '') != '': plotsizes = self.description['PlotSize'].split(',') self.description['PlotSizeX'] = float(plotsizes[0]) self.description['PlotSizeY'] = float(plotsizes[1]) if len(plotsizes) == 3: self.description['RatioPlotSizeY'] = float(plotsizes[2]) del self.description['PlotSize'] self.description['RatioPlotSizeY'] = 0. if self.description.get('MainPlot') == '0': ## Ratio, no main self.description['RatioPlot'] = '1' #< don't allow both to be zero! self.description['PlotSizeY'] = 0. #if self.description.has_key('RatioPlot') and self.description['RatioPlot']=='1': if self.attr_bool('RatioPlot'): if self.has_attr('RatioPlotYSize') and self.attr('RatioPlotYSize') != '': self.description['RatioPlotSizeY'] = self.attr_float('RatioPlotYSize') else: if self.attr_bool('MainPlot'): self.description['RatioPlotSizeY'] = 6. else: self.description['RatioPlotSizeY'] = 3. if self.description['is2dim']: self.description['RatioPlotSizeY'] *= 2. for i in range(1,9): if self.description.get('RatioPlot'+str(i), '0') == '1': if self.description.get('RatioPlot'+str(i)+'YSize') != '': self.description['RatioPlot'+str(i)+'SizeY'] = float(self.description['RatioPlot'+str(i)+'YSize']) else: if self.description.get('MainPlot') == '0': self.description['RatioPlot'+str(i)+'SizeY'] = 6. else: self.description['RatioPlot'+str(i)+'SizeY'] = 3. if self.description['is2dim']: self.description['RatioPlot'+str(i)+'SizeY'] *= 2. ## Ensure numbers, not strings self.description['PlotSizeX'] = float(self.description['PlotSizeX']) self.description['PlotSizeY'] = float(self.description['PlotSizeY']) self.description['RatioPlotSizeY'] = float(self.description['RatioPlotSizeY']) # self.description['TopMargin'] = float(self.description['TopMargin']) # self.description['BottomMargin'] = float(self.description['BottomMargin']) self.description['LogX'] = str(self.description.get('LogX', 0)) in ["1", "yes", "true"] self.description['LogY'] = str(self.description.get('LogY', 0)) in ["1", "yes", "true"] self.description['LogZ'] = str(self.description.get('LogZ', 0)) in ["1", "yes", "true"] self.description['RatioPlotLogY'] = str(self.description.get('RatioPlotLogY', 0)) in ["1", "yes", "true"] self.description['RatioPlotLogZ'] = str(self.description.get('RatioPlotLogZ', 0)) in ["1", "yes", "true"] for i in range(1,9): self.description['RatioPlot'+str(i)+'LogY'] = str(self.description.get('RatioPlot'+str(i)+'LogY', 0)) in ["1", "yes", "true"] self.description['RatioPlot'+str(i)+'LogZ'] = str(self.description.get('RatioPlot'+str(i)+'LogZ', 0)) in ["1", "yes", "true"] if 'Rebin' in self.description: for i in self.histos: self.histos[i].description['Rebin'] = self.description['Rebin'] if 'ConnectBins' in self.description: for i in self.histos: self.histos[i].description['ConnectBins'] = self.description['ConnectBins'] histoordermap = {} histolist = self.histos.keys() if 'DrawOnly' in self.description: histolist = filter(self.histos.keys().count, self.description['DrawOnly'].strip().split()) for histo in histolist: order = 0 if 'PlotOrder' in self.histos[histo].description: order = int(self.histos[histo].description['PlotOrder']) if not order in histoordermap: histoordermap[order] = [] histoordermap[order].append(histo) sortedhistolist = [] for i in sorted(histoordermap.keys()): sortedhistolist.extend(histoordermap[i]) self.description['DrawOnly']=sortedhistolist refhistoordermap = {} refhistolist = self.histos.keys() if 'RatioPlotDrawOnly' in self.description: refhistolist = filter(self.histos.keys().count, self.description['RatioPlotDrawOnly'].strip().split()) for histo in refhistolist: order = 0 if 'PlotOrder' in self.histos[histo].description: order = int(self.histos[histo].description['PlotOrder']) if not order in refhistoordermap: refhistoordermap[order] = [] refhistoordermap[order].append(histo) sortedrefhistolist = [] for i in sorted(refhistoordermap.keys()): sortedrefhistolist.extend(refhistoordermap[i]) self.description['RatioPlotDrawOnly']=sortedrefhistolist else: self.description['RatioPlotDrawOnly']=self.description['DrawOnly'] for i in range(1,9): refhistoordermap = {} refhistolist = self.histos.keys() if self.description.has_key('RatioPlot'+str(i)+'DrawOnly'): refhistolist = filter(self.histos.keys().count, self.description['RatioPlot'+str(i)+'DrawOnly'].strip().split()) for histo in refhistolist: order = 0 if self.histos[histo].description.has_key('PlotOrder'): order = int(self.histos[histo].description['PlotOrder']) if not order in refhistoordermap: refhistoordermap[order] = [] refhistoordermap[order].append(histo) sortedrefhistolist = [] for key in sorted(refhistoordermap.keys()): sortedrefhistolist.extend(refhistoordermap[key]) self.description['RatioPlot'+str(i)+'DrawOnly']=sortedrefhistolist else: self.description['RatioPlot'+str(i)+'DrawOnly']=self.description['DrawOnly'] ## Inherit various values from histograms if not explicitly set for k in ['LogX', 'LogY', 'LogZ', 'XLabel', 'YLabel', 'ZLabel', 'XCustomMajorTicks', 'YCustomMajorTicks', 'ZCustomMajorTicks']: self.inherit_from_histos(k) @property def is2dim(self): return self.attr_bool("is2dim", False) @is2dim.setter def is2dim(self, val): self.set_attr("is2dim", val) @property def drawonly(self): x = self.attr("DrawOnly") if type(x) is str: self.drawonly = x #< use setter to listify return x if x else [] @drawonly.setter def drawonly(self, val): if type(val) is str: val = val.strip().split() self.set_attr("DrawOnly", val) @property def stacklist(self): x = self.attr("Stack") if type(x) is str: self.stacklist = x #< use setter to listify return x if x else [] @stacklist.setter def stacklist(self, val): if type(val) is str: val = val.strip().split() self.set_attr("Stack", val) @property def plotorder(self): x = self.attr("PlotOrder") if type(x) is str: self.plotorder = x #< use setter to listify return x if x else [] @plotorder.setter def plotorder(self, val): if type(val) is str: val = val.strip().split() self.set_attr("PlotOrder", val) @property def plotsizex(self): return self.attr_float("PlotSizeX") @plotsizex.setter def plotsizex(self, val): self.set_attr("PlotSizeX", val) @property def plotsizey(self): return self.attr_float("PlotSizeY") @plotsizey.setter def plotsizey(self, val): self.set_attr("PlotSizeY", val) @property def plotsize(self): return [self.plotsizex, self.plotsizey] @plotsize.setter def plotsize(self, val): if type(val) is str: val = [float(x) for x in val.split(",")] assert len(val) == 2 self.plotsizex = val[0] self.plotsizey = val[1] @property def ratiosizey(self): return self.attr_float("RatioPlotSizeY") @ratiosizey.setter def ratiosizey(self, val): self.set_attr("RatioPlotSizeY", val) @property def scale(self): return self.attr_float("Scale") @scale.setter def scale(self, val): self.set_attr("Scale", val) @property def xmin(self): return self.attr_float("XMin") @xmin.setter def xmin(self, val): self.set_attr("XMin", val) @property def xmax(self): return self.attr_float("XMax") @xmax.setter def xmax(self, val): self.set_attr("XMax", val) @property def xrange(self): return [self.xmin, self.xmax] @xrange.setter def xrange(self, val): if type(val) is str: val = [float(x) for x in val.split(",")] assert len(val) == 2 self.xmin = val[0] self.xmax = val[1] @property def ymin(self): return self.attr_float("YMin") @ymin.setter def ymin(self, val): self.set_attr("YMin", val) @property def ymax(self): return self.attr_float("YMax") @ymax.setter def ymax(self, val): self.set_attr("YMax", val) @property def yrange(self): return [self.ymin, self.ymax] @yrange.setter def yrange(self, val): if type(val) is str: val = [float(y) for y in val.split(",")] assert len(val) == 2 self.ymin = val[0] self.ymax = val[1] # TODO: add more rw properties for plotsize(x,y), ratiosize(y), # show_mainplot, show_ratioplot, show_legend, log(x,y,z), rebin, # drawonly, legendonly, plotorder, stack, # label(x,y,z), majorticks(x,y,z), minorticks(x,y,z), # min(x,y,z), max(x,y,z), range(x,y,z) def inherit_from_histos(self, k): """Note: this will inherit the key from a random histogram: only use if you're sure all histograms have this key!""" if k not in self.description: h = list(self.histos.values())[0] if k in h.description: self.description[k] = h.description[k] def read_input(self, f): for line in f: if is_end_marker(line, 'PLOT'): break elif is_comment(line): continue m = pat_property.match(line) if m: prop, value = m.group(1,2) + prop = prop.strip() + value = value.strip() + if prop == 'ReplaceOption': + opt, subst = value.split(" ", 1) + self.description['_OptSubs'][opt] = subst + continue if prop in self.description: logging.debug("Overwriting property %s = %s -> %s" % (prop, self.description[prop], value)) ## Use strip here to deal with DOS newlines containing \r self.description[prop.strip()] = value.strip() def apply_config_files(self, conffiles): if conffiles is not None: for filename in conffiles: cf = open(filename,'r') lines = cf.readlines() for i in range(0, len(lines)): ## First evaluate PLOT sections m = pat_begin_block.match(lines[i]) if m and m.group(1) == 'PLOT' and re.match(m.group(2),self.filename): while i %s" % (prop, self.description[prop], value)) ## Use strip here to deal with DOS newlines containing \r self.description[prop.strip()] = value.strip() elif is_comment(lines[i]): continue else: ## Then evaluate path-based settings, e.g. for HISTOGRAMs m = pat_path_property.match(lines[i]) if m: regex, prop, value = m.group(1,2,3) for obj_dict in [self.special, self.histos, self.functions]: for path, obj in obj_dict.items(): if re.match(regex, path): ## Use strip here to deal with DOS newlines containing \r obj.description.update({prop.strip() : value.strip()}) cf.close() class Plot(object): def __init__(self, inputdata): pass def set_normalization(self,inputdata): if inputdata.normalized==True: return for method in ['NormalizeToIntegral', 'NormalizeToSum']: if inputdata.description.has_key(method): for i in inputdata.description['DrawOnly']: if not inputdata.histos[i].description.has_key(method): inputdata.histos[i].description[method] = inputdata.description[method] if inputdata.description.has_key('Scale'): for i in inputdata.description['DrawOnly']: inputdata.histos[i].description['Scale'] = float(inputdata.description['Scale']) for i in inputdata.histos.keys(): inputdata.histos[i].mangle_input() inputdata.normalized = True def stack_histograms(self,inputdata): if 'Stack' in inputdata.description: stackhists = [h for h in inputdata.attr('Stack').strip().split() if h in inputdata.histos] previous = '' for i in stackhists: if previous != '': inputdata.histos[i].add(inputdata.histos[previous]) previous = i def set_histo_options(self,inputdata): if 'ConnectGaps' in inputdata.description: for i in inputdata.histos.keys(): if 'ConnectGaps' not in inputdata.histos[i].description: inputdata.histos[i].description['ConnectGaps'] = inputdata.description['ConnectGaps'] # Counter and Value only have dummy x-axis, ticks wouldn't make sense here, so suppress them: if 'Value object' in str(inputdata.histos) or 'Counter object' in str(inputdata.histos): inputdata.description['XCustomMajorTicks'] = '' inputdata.description['XCustomMinorTicks'] = '' def set_borders(self, inputdata): self.set_xmax(inputdata) self.set_xmin(inputdata) self.set_ymax(inputdata) self.set_ymin(inputdata) self.set_zmax(inputdata) self.set_zmin(inputdata) inputdata.description['Borders'] = (self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax) def set_xmin(self, inputdata): self.xmin = inputdata.xmin if self.xmin is None: xmins = [inputdata.histos[h].getXMin() for h in inputdata.description['DrawOnly']] self.xmin = min(xmins) if xmins else 0.0 def set_xmax(self,inputdata): self.xmax = inputdata.xmax if self.xmax is None: xmaxs = [inputdata.histos[h].getXMax() for h in inputdata.description['DrawOnly']] self.xmax = min(xmaxs) if xmaxs else 1.0 def set_ymin(self,inputdata): if inputdata.ymin is not None: self.ymin = inputdata.ymin else: ymins = [inputdata.histos[i].getYMin(self.xmin, self.xmax, inputdata.description['LogY']) for i in inputdata.attr('DrawOnly')] minymin = min(ymins) if ymins else 0.0 if inputdata.description['is2dim']: self.ymin = minymin else: showzero = inputdata.attr_bool("ShowZero", True) if showzero: self.ymin = 0. if minymin > -1e-4 else 1.1*minymin else: self.ymin = 1.1*minymin if minymin < -1e-4 else 0 if minymin < 1e-4 else 0.9*minymin if inputdata.description['LogY']: ymins = [ymin for ymin in ymins if ymin > 0.0] if not ymins: if self.ymax == 0: self.ymax = 1 ymins.append(2e-7*self.ymax) minymin = min(ymins) fullrange = opts.FULL_RANGE if inputdata.has_attr('FullRange'): fullrange = inputdata.attr_bool('FullRange') self.ymin = minymin/1.7 if fullrange else max(minymin/1.7, 2e-7*self.ymax) if self.ymin == self.ymax: self.ymin -= 1 self.ymax += 1 def set_ymax(self,inputdata): if inputdata.has_attr('YMax'): self.ymax = inputdata.attr_float('YMax') else: ymaxs = [inputdata.histos[h].getYMax(self.xmin, self.xmax) for h in inputdata.attr('DrawOnly')] self.ymax = max(ymaxs) if ymaxs else 1.0 if not inputdata.is2dim: self.ymax *= (1.7 if inputdata.attr_bool('LogY') else 1.1) def set_zmin(self,inputdata): if inputdata.has_attr('ZMin'): self.zmin = inputdata.attr_float('ZMin') else: zmins = [inputdata.histos[i].getZMin(self.xmin, self.xmax, self.ymin, self.ymax) for i in inputdata.attr('DrawOnly')] minzmin = min(zmins) if zmins else 0.0 self.zmin = minzmin if zmins: showzero = inputdata.attr_bool('ShowZero', True) if showzero: self.zmin = 0 if minzmin > -1e-4 else 1.1*minzmin else: self.zmin = 1.1*minzmin if minzmin < -1e-4 else 0. if minzmin < 1e-4 else 0.9*minzmin if inputdata.attr_bool('LogZ', False): zmins = [zmin for zmin in zmins if zmin > 0] if not zmins: if self.zmax == 0: self.zmax = 1 zmins.append(2e-7*self.zmax) minzmin = min(zmins) fullrange = inputdata.attr_bool("FullRange", opts.FULL_RANGE) self.zmin = minzmin/1.7 if fullrange else max(minzmin/1.7, 2e-7*self.zmax) if self.zmin == self.zmax: self.zmin -= 1 self.zmax += 1 def set_zmax(self,inputdata): self.zmax = inputdata.attr_float('ZMax') if self.zmax is None: zmaxs = [inputdata.histos[h].getZMax(self.xmin, self.xmax, self.ymin, self.ymax) for h in inputdata.attr('DrawOnly')] self.zmax = max(zmaxs) if zmaxs else 1.0 def draw(self): pass def write_header(self,inputdata): if inputdata.description.get('LeftMargin', '') != '': inputdata.description['LeftMargin'] = float(inputdata.description['LeftMargin']) else: inputdata.description['LeftMargin'] = 1.4 if inputdata.description.get('RightMargin', '') != '': inputdata.description['RightMargin'] = float(inputdata.description['RightMargin']) else: inputdata.description['RightMargin'] = 0.35 if inputdata.description.get('TopMargin', '') != '': inputdata.description['TopMargin'] = float(inputdata.description['TopMargin']) else: inputdata.description['TopMargin'] = 0.65 if inputdata.description.get('BottomMargin', '') != '': inputdata.description['BottomMargin'] = float(inputdata.description['BottomMargin']) else: inputdata.description['BottomMargin'] = 0.95 if inputdata.description['is2dim']: inputdata.description['RightMargin'] += 1.8 papersizex = inputdata.description['PlotSizeX'] + 0.1 + \ inputdata.description['LeftMargin'] + inputdata.description['RightMargin'] papersizey = inputdata.description['PlotSizeY'] + 0.1 + \ inputdata.description['TopMargin'] + inputdata.description['BottomMargin'] papersizey += inputdata.description['RatioPlotSizeY'] for i in range(1,9): if inputdata.description.has_key('RatioPlot'+str(i)+'SizeY'): papersizey += inputdata.description['RatioPlot'+str(i)+'SizeY'] # out = "" out += '\\documentclass{article}\n' if opts.OUTPUT_FONT == "MINION": out += ('\\usepackage{minion}\n') elif opts.OUTPUT_FONT == "PALATINO_OSF": out += ('\\usepackage[osf,sc]{mathpazo}\n') elif opts.OUTPUT_FONT == "PALATINO": out += ('\\usepackage{mathpazo}\n') elif opts.OUTPUT_FONT == "TIMES": out += ('\\usepackage{mathptmx}\n') elif opts.OUTPUT_FONT == "HELVETICA": out += ('\\renewcommand{\\familydefault}{\\sfdefault}\n') out += ('\\usepackage{sfmath}\n') out += ('\\usepackage{helvet}\n') out += ('\\usepackage[symbolgreek]{mathastext}\n') for pkg in opts.LATEXPKGS: out += ('\\usepackage{%s}\n' % pkg) out += ('\\usepackage[dvipsnames]{xcolor}\n') out += ('\\usepackage{pst-all}\n') out += ('\\selectcolormodel{rgb}\n') out += ('\\definecolor{red}{HTML}{EE3311}\n') # (Google uses 'DC3912') out += ('\\definecolor{blue}{HTML}{3366FF}') out += ('\\definecolor{green}{HTML}{109618}') out += ('\\definecolor{orange}{HTML}{FF9900}') out += ('\\definecolor{lilac}{HTML}{990099}') out += ('\\usepackage{amsmath}\n') out += ('\\usepackage{amssymb}\n') out += ('\\usepackage{relsize}\n') out += ('\\usepackage{graphicx}\n') out += ('\\usepackage[dvips,\n') out += (' left=%4.3fcm, right=0cm,\n' % (inputdata.description['LeftMargin']-0.45,)) out += (' top=%4.3fcm, bottom=0cm,\n' % (inputdata.description['TopMargin']-0.30,)) out += (' paperwidth=%scm,paperheight=%scm\n' % (papersizex,papersizey)) out += (']{geometry}\n') if inputdata.description.has_key('DefineColor'): out += ('% user defined colours\n') for color in inputdata.description['DefineColor'].split('\t'): out += ('%s\n' %color) if inputdata.description.has_key('UseExtendedPSTricks') and inputdata.description['UseExtendedPSTricks']=='1': out += self.write_extended_pstricks() out += ('\\begin{document}\n') out += ('\\pagestyle{empty}\n') out += ('\\SpecialCoor\n') out += ('\\begin{pspicture}(0,0)(0,0)\n') out += ('\\psset{xunit=%scm}\n' %(inputdata.description['PlotSizeX'])) if inputdata.description['is2dim']: if inputdata.description.get('ColorSeries', '') != '': colorseries = inputdata.description['ColorSeries'] else: colorseries = '{hsb}{grad}[rgb]{0,0,1}{-.700,0,0}' out += ('\\definecolorseries{gradientcolors}%s\n' % colorseries) out += ('\\resetcolorseries[130]{gradientcolors}\n') return out def write_extended_pstricks(self): out = '' out += ('\\usepackage{pstricks-add}\n') out += ('\\makeatletter\n') out += ('\\def\\pshs@solid{0 setlinecap }\n') out += ('\\def\\pshs@dashed{[ \\psk@dash ] 0 setdash }\n') out += ('\\def\\pshs@dotted{[ 0 \\psk@dotsep CLW add ] 0 setdash 1 setlinecap }\n') out += ('\\def\\psset@hatchstyle#1{%\n') out += ('\\@ifundefined{pshs@#1}%\n') out += ('{\\@pstrickserr{Hatch style `#1\' not defined}\\@eha}%\n') out += ('{\\edef\\pshatchstyle{#1}}}\n') out += ('\\psset@hatchstyle{solid}\n') out += ('\\def\\pst@linefill{%\n') out += ('\\@nameuse{pshs@\\pshatchstyle}\n') out += ('\\psk@hatchangle rotate\n') out += ('\\psk@hatchwidth SLW\n') out += ('\\pst@usecolor\\pshatchcolor\n') out += ('\\psk@hatchsep\n') out += ('\\tx@LineFill}\n') out += ('\\pst@def{LineFill}<{%\n') out += ('gsave\n') out += (' abs CLW add\n') out += (' /a ED\n') out += (' a 0 dtransform\n') out += (' round exch round exch 2 copy idtransform\n') out += (' exch Atan rotate idtransform\n') out += (' pop\n') out += (' /a ED\n') out += (' .25 .25 itransform\n') out += (' pathbbox\n') out += (' /y2 ED\n') out += (' a Div ceiling cvi\n') out += (' /x2 ED\n') out += (' /y1 ED\n') out += (' a Div cvi\n') out += (' /x1 ED\n') out += (' /y2 y2 y1 sub def\n') out += (' clip\n') out += (' newpath\n') out += (' systemdict\n') out += (' /setstrokeadjust\n') out += (' known { true setstrokeadjust } if\n') out += (' x2 x1 sub 1 add\n') out += (' { x1 a mul y1 moveto\n') out += (' 0 y2 rlineto\n') out += (' stroke\n') out += (' /x1 x1 1 add def } repeat\n') out += (' grestore\n') out += ('pop pop}>\n') out += ('\makeatother\n') return out def write_footer(self): out = "" out += ('\\end{pspicture}\n') out += ('\\end{document}\n') return out class MainPlot(Plot): def __init__(self, inputdata): self.name = 'MainPlot' inputdata.description['PlotStage'] = 'MainPlot' self.set_normalization(inputdata) self.stack_histograms(inputdata) do_gof = inputdata.description.get('GofLegend', '0') == '1' or inputdata.description.get('GofFrame', '') != '' do_taylor = inputdata.description.get('TaylorPlot', '0') == '1' if do_gof and not do_taylor: self.calculate_gof(inputdata) self.set_histo_options(inputdata) self.set_borders(inputdata) self.yoffset = inputdata.description['PlotSizeY'] self.coors = Coordinates(inputdata) def draw(self, inputdata): out = "" out += ('\n%\n% MainPlot\n%\n') out += ('\\psset{yunit=%scm}\n' %(self.yoffset)) out += ('\\rput(0,-1){%\n') out += ('\\psset{yunit=%scm}\n' %(inputdata.description['PlotSizeY'])) out += self._draw(inputdata) out += ('}\n') return out def _draw(self, inputdata): out = "" frame = Frame(inputdata.description,self.name) out += frame.drawZeroLine(self.coors.phys2frameY(0)) out += frame.drawUnitLine(self.coors.phys2frameY(1)) if inputdata.attr_bool('DrawSpecialFirst', False): for s in inputdata.special.values(): out += s.draw(self.coors,inputdata) if inputdata.attr_bool('DrawFunctionFirst', False): for f in inputdata.functions.values(): out += f.draw(self.coors,inputdata) for i in inputdata.description['DrawOnly']: out += inputdata.histos[i].draw(self.coors) if not inputdata.attr_bool('DrawSpecialFirst', False): for s in inputdata.special.values(): out += s.draw(self.coors,inputdata) if not inputdata.attr_bool('DrawFunctionFirst', False): for f in inputdata.functions.values(): out += f.draw(self.coors,inputdata) if inputdata.attr_bool('Legend', False): legend = Legend(inputdata.description,inputdata.histos,inputdata.functions,'Legend',1) out += legend.draw() for i in range(1,10): if inputdata.attr_bool('Legend'+str(i), False): legend = Legend(inputdata.description,inputdata.histos,inputdata.functions,'Legend'+str(i),i) out += legend.draw() if inputdata.description['is2dim']: colorscale = ColorScale(inputdata.description, self.coors) out += colorscale.draw() out += frame.draw() xcustommajortickmarks = inputdata.attr_int('XMajorTickMarks', -1) xcustomminortickmarks = inputdata.attr_int('XMinorTickMarks', -1) xcustommajorticks = [] xcustomminorticks = [] xbreaks=[] if inputdata.attr('XCustomMajorTicks'): x_label_pairs = inputdata.attr('XCustomMajorTicks').strip().split() #'\t') if len(x_label_pairs) % 2 == 0: for i in range(0, len(x_label_pairs), 2): xcustommajorticks.append({'Value': float(x_label_pairs[i]), 'Label': x_label_pairs[i+1]}) else: print("Warning: XCustomMajorTicks requires an even number of alternating pos/label entries") if inputdata.attr('XCustomMinorTicks'): xs = inputdata.attr('XCustomMinorTicks').strip().split() #'\t') xcustomminorticks = [{'Value': float(x)} for x in xs] if inputdata.description.has_key('XBreaks') and inputdata.description['XBreaks']!='': FOO=inputdata.description['XBreaks'].strip().split('\t') xbreaks = [{'Value': float(FOO[i])} for i in range(len(FOO))] xticks = XTicks(inputdata.description, self.coors) drawlabels = True if (inputdata.description.has_key('PlotTickLabels') and inputdata.description['PlotTickLabels']=='0'): drawlabels=False if (inputdata.description.has_key('RatioPlot') and inputdata.description['RatioPlot']=='1'): drawlabels=False for i in range(1,9): if (inputdata.description.has_key('RatioPlot'+str(i)) and inputdata.description['RatioPlot'+str(i)]=='1'): drawlabels=False out += xticks.draw(custommajortickmarks=xcustommajortickmarks, customminortickmarks=xcustomminortickmarks, custommajorticks=xcustommajorticks, customminorticks=xcustomminorticks, drawlabels=drawlabels, breaks=xbreaks) ycustommajortickmarks = inputdata.attr_int('YMajorTickMarks', -1) ycustomminortickmarks = inputdata.attr_int('YMinorTickMarks', -1) ycustommajorticks = [] ycustomminorticks = [] ybreaks=[] if 'YCustomMajorTicks' in inputdata.description: y_label_pairs = inputdata.description['YCustomMajorTicks'].strip().split() #'\t') if len(y_label_pairs) % 2 == 0: for i in range(0, len(y_label_pairs), 2): ycustommajorticks.append({'Value': float(y_label_pairs[i]), 'Label': y_label_pairs[i+1]}) else: print("Warning: YCustomMajorTicks requires an even number of alternating pos/label entries") if inputdata.has_attr('YCustomMinorTicks'): ys = inputdata.attr('YCustomMinorTicks').strip().split() #'\t') ycustomminorticks = [{'Value': float(y)} for y in ys] yticks = YTicks(inputdata.description, self.coors) drawylabels = inputdata.attr_bool('PlotYTickLabels', True) if inputdata.description.has_key('YBreaks') and inputdata.description['YBreaks']!='': FOO=inputdata.description['YBreaks'].strip().split('\t') for i in range(len(FOO)): ybreaks.append({'Value': float(FOO[i])}) out += yticks.draw(custommajortickmarks=ycustommajortickmarks, customminortickmarks=ycustomminortickmarks, custommajorticks=ycustommajorticks, customminorticks=ycustomminorticks, drawlabels=drawylabels, breaks=ybreaks) labels = Labels(inputdata.description) if drawlabels: if inputdata.description['is2dim']: out += labels.draw(['Title','XLabel','YLabel','ZLabel']) else: out += labels.draw(['Title','XLabel','YLabel']) else: out += labels.draw(['Title','YLabel']) #if inputdata.attr_bool('RatioPlot', False): # out += labels.draw(['Title','YLabel']) #elif drawlabels: # if not inputdata.description['is2dim']: # out += labels.draw(['Title','XLabel','YLabel']) # else: # out += labels.draw(['Title','XLabel','YLabel','ZLabel']) return out def calculate_gof(self, inputdata): refdata = inputdata.description.get('GofReference') if refdata is None: refdata = inputdata.description.get('RatioPlotReference') if refdata is None: inputdata.description['GofLegend'] = '0' inputdata.description['GofFrame'] = '' return def pickcolor(gof): color = None colordefs = {} for i in inputdata.description.setdefault('GofFrameColor', '0:green 3:yellow 6:red!70').strip().split(): foo = i.split(':') if len(foo) != 2: continue colordefs[float(foo[0])] = foo[1] for i in sorted(colordefs.keys()): if gof>=i: color=colordefs[i] return color inputdata.description.setdefault('GofLegend', '0') inputdata.description.setdefault('GofFrame', '') inputdata.description.setdefault('FrameColor', None) for i in inputdata.description['DrawOnly']: if i == refdata: continue if inputdata.description['GofLegend']!='1' and i!=inputdata.description['GofFrame']: continue if inputdata.description.get('GofType', "chi2") != 'chi2': return gof = inputdata.histos[i].getChi2(inputdata.histos[refdata]) if i == inputdata.description['GofFrame'] and inputdata.description['FrameColor'] is None: inputdata.description['FrameColor'] = pickcolor(gof) if inputdata.histos[i].description.setdefault('Title', '') != '': inputdata.histos[i].description['Title'] += ', ' inputdata.histos[i].description['Title'] += '$\\chi^2/n={}$%1.2f' %gof class TaylorPlot(Plot): def __init__(self, inputdata): self.refdata = inputdata.description['TaylorPlotReference'] self.calculate_taylorcoordinates(inputdata) def calculate_taylorcoordinates(self,inputdata): foo = inputdata.description['DrawOnly'].pop(inputdata.description['DrawOnly'].index(self.refdata)) inputdata.description['DrawOnly'].append(foo) for i in inputdata.description['DrawOnly']: print(i) print('meanbinval = ', inputdata.histos[i].getMeanBinValue()) print('sigmabinval = ', inputdata.histos[i].getSigmaBinValue()) print('chi2/nbins = ', inputdata.histos[i].getChi2(inputdata.histos[self.refdata])) print('correlation = ', inputdata.histos[i].getCorrelation(inputdata.histos[self.refdata])) print('distance = ', inputdata.histos[i].getRMSdistance(inputdata.histos[self.refdata])) class RatioPlot(Plot): def __init__(self, inputdata, i): self.number=i self.name='RatioPlot'+str(i) if i==0: self.name='RatioPlot' # initialise histograms even when no main plot self.set_normalization(inputdata) self.refdata = inputdata.description[self.name+'Reference'] if not inputdata.histos.has_key(self.refdata): print('ERROR: %sReference=%s not found in:' % (self.name,self.refdata)) for i in inputdata.histos.keys(): print(' ',i) sys.exit(1) if not inputdata.description.has_key('RatioPlotYOffset'): inputdata.description['RatioPlotYOffset'] = inputdata.description['PlotSizeY'] if not inputdata.description.has_key(self.name + 'SameStyle'): inputdata.description[self.name+'SameStyle'] = '1' self.yoffset = inputdata.description['RatioPlotYOffset'] + inputdata.description[self.name+'SizeY'] inputdata.description['PlotStage'] = self.name inputdata.description['RatioPlotYOffset'] = self.yoffset inputdata.description['PlotSizeY'] = inputdata.description[self.name+'SizeY'] inputdata.description['LogY'] = inputdata.description.get(self.name+"LogY", False) # TODO: It'd be nice it this wasn't so MC-specific rpmode = inputdata.description.get(self.name+'Mode', "mcdata") if rpmode=='deviation': inputdata.description['YLabel']='$(\\text{MC}-\\text{data})$' inputdata.description['YMin']=-2.99 inputdata.description['YMax']=2.99 elif rpmode=='delta': inputdata.description['YLabel']='\\delta' inputdata.description['YMin']=-0.5 inputdata.description['YMax']=0.5 elif rpmode=='deltapercent': inputdata.description['YLabel']='\\delta\;[\%]' inputdata.description['YMin']=-50. inputdata.description['YMax']=50. elif rpmode=='deltamc': inputdata.description['YLabel']='Data/MC' inputdata.description['YMin']=0.5 inputdata.description['YMax']=1.5 else: inputdata.description['YLabel'] = 'MC/Data' inputdata.description['YMin'] = 0.5 inputdata.description['YMax'] = 1.5 if inputdata.description.has_key(self.name+'YLabel'): inputdata.description['YLabel']=inputdata.description[self.name+'YLabel'] inputdata.description['YLabel']='\\rput(-%s,0){%s}'%(0.5*inputdata.description['PlotSizeY']/inputdata.description['PlotSizeX'],inputdata.description['YLabel']) if inputdata.description.has_key(self.name+'YMin'): inputdata.description['YMin']=inputdata.description[self.name+'YMin'] if inputdata.description.has_key(self.name+'YMax'): inputdata.description['YMax']=inputdata.description[self.name+'YMax'] if inputdata.description.has_key(self.name+'YLabelSep'): inputdata.description['YLabelSep']=inputdata.description[self.name+'YLabelSep'] if not inputdata.description.has_key(self.name+'ErrorBandColor'): inputdata.description[self.name+'ErrorBandColor']='yellow' if not inputdata.description.has_key(self.name+'SameStyle') or inputdata.description[self.name+'SameStyle']=='0': inputdata.histos[self.refdata].description['ErrorBandColor']=inputdata.description[self.name+'ErrorBandColor'] inputdata.histos[self.refdata].description['ErrorBands'] = '1' inputdata.histos[self.refdata].description['ErrorBars'] = '0' inputdata.histos[self.refdata].description['ErrorTubes']='0' inputdata.histos[self.refdata].description['LineStyle'] = 'solid' inputdata.histos[self.refdata].description['LineColor'] = 'black' inputdata.histos[self.refdata].description['LineWidth'] = '0.3pt' inputdata.histos[self.refdata].description['PolyMarker'] = '' inputdata.histos[self.refdata].description['ConnectGaps'] = '1' self.calculate_ratios(inputdata) self.set_borders(inputdata) self.coors = Coordinates(inputdata) def draw(self, inputdata): out = "" out += ('\n%\n% RatioPlot\n%\n') out += ('\\psset{yunit=%scm}\n' %(self.yoffset)) out += ('\\rput(0,-1){%\n') out += ('\\psset{yunit=%scm}\n' %(inputdata.description['PlotSizeY'])) out += self._draw(inputdata) out += ('}\n') return out def calculate_ratios(self,inputdata): inputdata.ratiohistos = {} inputdata.ratiohistos = copy.deepcopy(inputdata.histos) foo=inputdata.description[self.name+'DrawOnly'].pop(inputdata.description[self.name+'DrawOnly'].index(self.refdata)) if not (inputdata.description.has_key(self.name+'DrawReferenceFirst') and inputdata.description[self.name+'DrawReferenceFirst']=='0'): if inputdata.histos[self.refdata].description.has_key('ErrorBands') and inputdata.histos[self.refdata].description['ErrorBands']=='1': inputdata.description[self.name+'DrawOnly'].insert(0,foo) else: inputdata.description[self.name+'DrawOnly'].append(foo) else: inputdata.description[self.name+'DrawOnly'].append(foo) rpmode = inputdata.description.get(self.name+'Mode', 'mcdata') for i in inputdata.description[self.name+'DrawOnly']: if i!=self.refdata: if rpmode == 'deviation': inputdata.ratiohistos[i].deviation(inputdata.ratiohistos[self.refdata]) elif rpmode == 'delta': inputdata.ratiohistos[i].delta(inputdata.ratiohistos[self.refdata]) elif rpmode == 'deltapercent': inputdata.ratiohistos[i].deltapercent(inputdata.ratiohistos[self.refdata]) elif rpmode == 'datamc': inputdata.ratiohistos[i].dividereverse(inputdata.ratiohistos[self.refdata]) inputdata.ratiohistos[i].description['ErrorBars']='1' else: inputdata.ratiohistos[i].divide(inputdata.ratiohistos[self.refdata]) if rpmode == 'deviation': inputdata.ratiohistos[self.refdata].deviation(inputdata.ratiohistos[self.refdata]) elif rpmode == 'delta': inputdata.ratiohistos[self.refdata].delta(inputdata.ratiohistos[self.refdata]) elif rpmode == 'deltapercent': inputdata.ratiohistos[self.refdata].deltapercent(inputdata.ratiohistos[self.refdata]) elif rpmode == 'datamc': inputdata.ratiohistos[self.refdata].dividereverse(inputdata.ratiohistos[self.refdata]) else: inputdata.ratiohistos[self.refdata].divide(inputdata.ratiohistos[self.refdata]) def _draw(self, inputdata): out = "" frame = Frame(inputdata.description,self.name) out += frame.drawZeroLine(self.coors.phys2frameY(0)) out += frame.drawUnitLine(self.coors.phys2frameY(1)) if inputdata.description.has_key('DrawSpecialFirst') and inputdata.description['DrawSpecialFirst']=='1': for i in inputdata.special.keys(): out += inputdata.special[i].draw(self.coors,inputdata) if inputdata.description.has_key('DrawFunctionFirst') and inputdata.description['DrawFunctionFirst']=='1': for i in inputdata.functions.keys(): out += inputdata.functions[i].draw(self.coors,inputdata) for i in inputdata.description[self.name+'DrawOnly']: if inputdata.description.has_key(self.name+'Mode') and inputdata.description[self.name+'Mode']=='datamc': if i!=self.refdata: out += inputdata.ratiohistos[i].draw(self.coors) else: out += inputdata.ratiohistos[i].draw(self.coors) if not inputdata.description.has_key('DrawFunctionFirst') or inputdata.description['DrawFunctionFirst']=='0': for i in inputdata.functions.keys(): out += inputdata.functions[i].draw(self.coors,inputdata) if not inputdata.description.has_key('DrawSpecialFirst') or inputdata.description['DrawSpecialFirst']=='0': for i in inputdata.special.keys(): out += inputdata.special[i].draw(self.coors,inputdata) out += frame.draw() # TODO: so much duplication with MainPlot... yuck! if inputdata.description.has_key('XMajorTickMarks') and inputdata.description['XMajorTickMarks']!='': xcustommajortickmarks=int(inputdata.description['XMajorTickMarks']) else: xcustommajortickmarks=-1 if inputdata.description.has_key('XMinorTickMarks') and inputdata.description['XMinorTickMarks']!='': xcustomminortickmarks=int(inputdata.description['XMinorTickMarks']) else: xcustomminortickmarks=-1 xcustommajorticks=[] xcustomminorticks=[] if inputdata.description.has_key('XCustomMajorTicks') and inputdata.description['XCustomMajorTicks']!='': FOO=inputdata.description['XCustomMajorTicks'].strip().split('\t') if not len(FOO)%2: for i in range(0,len(FOO),2): xcustommajorticks.append({'Value': float(FOO[i]), 'Label': FOO[i+1]}) if inputdata.description.has_key('XCustomMinorTicks') and inputdata.description['XCustomMinorTicks']!='': FOO=inputdata.description['XCustomMinorTicks'].strip().split('\t') for i in range(len(FOO)): xcustomminorticks.append({'Value': float(FOO[i])}) xticks = XTicks(inputdata.description, self.coors) # find out whether to draw title (only if no MainPlot and top RatioPlot) drawtitle=False if inputdata.description.has_key('MainPlot') and inputdata.description['MainPlot']=='0': drawtitle=True for i in range(0,self.number): if i==0: if inputdata.description.has_key('RatioPlot') and inputdata.description['RatioPlot']=='1': drawtitle=False else: if inputdata.description.has_key('RatioPlot'+str(i)) and inputdata.description['RatioPlot'+str(i)]=='1': drawtitle=False # find out whether to draw xlabels (only if lowest RatioPlot) drawlabels = True if (inputdata.description.has_key(self.name+'TickLabels') and inputdata.description[self.name+'TickLabels']=='0'): drawlabels=False for i in range(self.number+1,10): if (inputdata.description.has_key('RatioPlot'+str(i)) and inputdata.description['RatioPlot'+str(i)]=='1'): drawlabels=False out += xticks.draw(custommajortickmarks=xcustommajortickmarks, customminortickmarks=xcustomminortickmarks, custommajorticks=xcustommajorticks, customminorticks=xcustomminorticks, drawlabels=drawlabels) ycustommajortickmarks = inputdata.attr('YMajorTickMarks', '') ycustommajortickmarks = int(ycustommajortickmarks) if ycustommajortickmarks else -1 ycustomminortickmarks = inputdata.attr('YMinorTickMarks', '') ycustomminortickmarks = int(ycustomminortickmarks) if ycustomminortickmarks else -1 ycustommajorticks = [] if 'YCustomMajorTicks' in inputdata.description: tickstr = inputdata.description['YCustomMajorTicks'].strip().split() #'\t') if not len(tickstr) % 2: for i in range(0, len(tickstr), 2): ycustommajorticks.append({'Value': float(tickstr[i]), 'Label': tickstr[i+1]}) ycustomminorticks = [] if 'YCustomMinorTicks' in inputdata.description: tickstr = inputdata.description['YCustomMinorTicks'].strip().split() #'\t') for i in range(len(tickstr)): ycustomminorticks.append({'Value': float(tickstr[i])}) yticks = YTicks(inputdata.description, self.coors) out += yticks.draw(custommajortickmarks=ycustommajortickmarks, customminortickmarks=ycustomminortickmarks, custommajorticks=ycustommajorticks, customminorticks=ycustomminorticks) if inputdata.attr_bool(self.name+'Legend', False): legend = Legend(inputdata.description,inputdata.histos,inputdata.functions,self.name+'Legend',1) out += legend.draw() for i in range(1,10): if inputdata.attr_bool(self.name+'Legend'+str(i), False): legend = Legend(inputdata.description,inputdata.histos,inputdata.functions,self.name+'Legend'+str(i),i) out += legend.draw() labels = Labels(inputdata.description) if drawtitle: if drawlabels: out += labels.draw(['Title','XLabel','YLabel']) else: out += labels.draw(['Title','YLabel']) else: if drawlabels: out += labels.draw(['XLabel','YLabel']) else: out += labels.draw(['YLabel']) return out class Legend(Described): def __init__(self, description, histos, functions, name, number): self.name = name self.number = number self.histos = histos self.functions = functions self.description = description def draw(self): legendordermap = {} legendlist = self.description['DrawOnly'] + list(self.functions.keys()) if self.name + 'Only' in self.description: legendlist = [] for legend in self.description[self.name+'Only'].strip().split(): if legend in self.histos or legend in self.functions: legendlist.append(legend) for legend in legendlist: order = 0 if legend in self.histos and 'LegendOrder' in self.histos[legend].description: order = int(self.histos[legend].description['LegendOrder']) if legend in self.functions and 'LegendOrder' in self.functions[legend].description: order = int(self.functions[legend].description['LegendOrder']) if not order in legendordermap: legendordermap[order] = [] legendordermap[order].append(legend) orderedlegendlist=[] for i in sorted(legendordermap.keys()): orderedlegendlist.extend(legendordermap[i]) if self.description['is2dim']: return self.draw_2dlegend(orderedlegendlist) out = "" out += '\n%\n% Legend\n%\n' out += '\\rput[tr](%s,%s){%%\n' % (self.getLegendXPos(), self.getLegendYPos()) ypos = -0.05*6/self.description['PlotSizeY'] if self.description.has_key(self.name+'Title'): for i in self.description[self.name+'Title'].strip().split('\\\\'): out += '\\rput[Bl](0.,'+ str(ypos) + '){' + i + '}\n' ypos -= 0.075*6/self.description['PlotSizeY'] offset = 0. separation = 0. if self.description.has_key(self.name+'EntryOffset'): offset = float(self.description[self.name+'EntryOffset']) if self.description.has_key(self.name+'EntrySeparation'): separation = float(self.description[self.name+'EntrySeparation']) hline = True vline = True if self.description.has_key(self.name+'HorizontalLine'): hline = self.description[self.name+'HorizontalLine']!='0' if self.description.has_key(self.name+'VerticalLine'): vline = self.description[self.name+'VerticalLine']!='0' rel_xpos_sign = 1.0 if self.getLegendAlign()=='r': rel_xpos_sign = -1.0 xwidth = self.getLegendIconWidth() xpos1 = -0.02*rel_xpos_sign-0.08*xwidth*rel_xpos_sign xpos2 = -0.02*rel_xpos_sign xposc = -0.02*rel_xpos_sign-0.04*xwidth*rel_xpos_sign xpostext = 0.1*rel_xpos_sign for i in orderedlegendlist: if self.histos.has_key(i): drawobject=self.histos[i] elif self.functions.has_key(i): drawobject=self.functions[i] else: continue title = drawobject.getTitle() + opt = extractOptionString(drawobject.path) + if opt and not self.description.get("RemoveOptions", 0): + if opt in self.description['_OptSubs']: + title += ' %s' % self.description['_OptSubs'][opt] + else: + title += ' [%s]' % opt if title == '': continue else: titlelines=[] for i in title.strip().split('\\\\'): titlelines.append(i) ypos -= 0.075*6/self.description['PlotSizeY']*separation boxtop = 0.045*(6./self.description['PlotSizeY']) boxbottom = 0. lineheight = 0.5*(boxtop-boxbottom) out += ('\\rput[B%s](%s,%s){%s\n' %(self.getLegendAlign(),rel_xpos_sign*0.1,ypos,'%')) if drawobject.getErrorBands(): out += ('\\psframe[linewidth=0pt,linestyle=none,fillstyle=%s,fillcolor=%s,opacity=%s,hatchcolor=%s]' %(drawobject.getErrorBandStyle(),drawobject.getErrorBandColor(),drawobject.getErrorBandOpacity(),drawobject.getHatchColor())) out += ('(%s, %s)(%s, %s)\n' %(xpos1,boxtop,xpos2,boxbottom)) # set psline options for all lines to be drawn next lineopts = ('linestyle=' + drawobject.getLineStyle() \ + ', linecolor=' + drawobject.getLineColor() \ + ', linewidth=' + drawobject.getLineWidth() \ + ', strokeopacity=' + drawobject.getLineOpacity() \ + ', opacity=' + drawobject.getFillOpacity()) if drawobject.getLineDash()!='': lineopts += (', dash=' + drawobject.getLineDash()) if drawobject.getFillStyle()!='none': lineopts += (', fillstyle=' + drawobject.getFillStyle() \ + ', fillcolor=' + drawobject.getFillColor() \ + ', hatchcolor=' + drawobject.getHatchColor()) # options set -> lineopts if drawobject.getErrorBars() and vline: out += (' \\psline[' + lineopts + '](%s, %s)(%s, %s)\n') \ %(xposc, boxtop, xposc, boxbottom) if drawobject.getErrorTubes(): tubeopts = ('linestyle=' + drawobject.getErrorTubeLineStyle() \ + ', linecolor=' + drawobject.getErrorTubeLineColor() \ + ', linewidth=' + drawobject.getErrorTubeLineWidth() \ + ', strokeopacity=' + drawobject.getErrorTubeLineOpacity() \ + ', opacity=' + drawobject.getFillOpacity()) out += (' \\psline[' + tubeopts + '](%s, %s)(%s, %s)\n') \ %(xpos1, boxtop, xpos2, boxtop) out += (' \\psline[' + tubeopts + '](%s, %s)(%s, %s)\n') \ %(xpos1, boxbottom, xpos2, boxbottom) if hline: out += (' \\psline[' + lineopts ) if drawobject.getFillStyle()!='none': out += (']{C-C}(%s, %s)(%s, %s)(%s, %s)(%s, %s)(%s, %s)\n' \ %(xpos1, boxtop, xpos2, boxtop, xpos2, boxbottom, xpos1, boxbottom, xpos1, boxtop)) else: out += ('](%s, %s)(%s, %s)\n' %(xpos1, lineheight, xpos2, lineheight)) if drawobject.getPolyMarker() != '': out += (' \\psdot[dotstyle=' + drawobject.getPolyMarker() \ + ', dotsize=' + drawobject.getDotSize() \ + ', dotscale=' + drawobject.getDotScale() \ + ', linecolor=' + drawobject.getLineColor() \ + ', linewidth=' + drawobject.getLineWidth() \ + ', linestyle=' + drawobject.getLineStyle() \ + ', fillstyle=' + drawobject.getFillStyle() \ + ', fillcolor=' + drawobject.getFillColor() \ + ', strokeopacity=' + drawobject.getLineOpacity() \ + ', opacity=' + drawobject.getFillOpacity() \ + ', hatchcolor=' + drawobject.getHatchColor()) if drawobject.getFillStyle()!='none': out += ('](%s, %s)\n' % (xposc, 0.95*boxtop)) else: out += ('](%s, %s)\n' % (xposc, lineheight)) out += ('}\n') ypos -= 0.075*6/self.description['PlotSizeY']*offset for i in titlelines: out += ('\\rput[B%s](%s,%s){%s}\n' %(self.getLegendAlign(),xpostext,ypos,i)) ypos -= 0.075*6/self.description['PlotSizeY'] if 'CustomLegend' in self.description: for i in self.description['CustomLegend'].strip().split('\\\\'): out += ('\\rput[B%s](%s,%s){%s}\n' %(self.getLegendAlign(),xpostext,ypos,i)) ypos -= 0.075*6/self.description['PlotSizeY'] out += ('}\n') return out def draw_2dlegend(self,orderedlegendlist): histos = "" for i in range(0,len(orderedlegendlist)): if self.histos.has_key(orderedlegendlist[i]): drawobject=self.histos[orderedlegendlist[i]] elif self.functions.has_key(orderedlegendlist[i]): drawobject=self.functions[orderedlegendlist[i]] else: continue title = drawobject.getTitle() if title == '': continue else: histos += title.strip().split('\\\\')[0] if not i==len(orderedlegendlist)-1: histos += ', ' out = '\\rput(1,1){\\rput[rB](0, 1.7\\labelsep){\\normalsize '+histos+'}}\n' return out def getLegendXPos(self): return self.description.get(self.name+'XPos', '0.95' if self.getLegendAlign() == 'r' else '0.53') def getLegendYPos(self): return self.description.get(self.name+'YPos', '0.93') def getLegendAlign(self): return self.description.get(self.name+'Align', 'l') def getLegendIconWidth(self): return float(self.description.get(self.name+'IconWidth', '1.0')) class PlotFunction: def __init__(self, f): self.description = {} self.read_input(f) def read_input(self, f): self.code='def histomangler(x):\n' iscode=False for line in f: if is_end_marker(line, 'HISTOGRAMMANGLER'): break elif is_comment(line): continue else: m = pat_property.match(line) if iscode: self.code+=' '+line elif m: prop, value = m.group(1,2) if prop=='Code': iscode=True else: self.description[prop] = value if not iscode: print('++++++++++ ERROR: No code in function') else: foo = compile(self.code, '', 'exec') exec(foo) self.histomangler = histomangler def transform(self, x): return self.histomangler(x) class ColorScale(Described): def __init__(self, description, coors): self.description = description self.coors = coors def draw(self): out = '' out += '\n%\n% ColorScale\n%\n' out += '\\rput(1,0){\n' out += ' \\psset{xunit=4mm}\n' out += ' \\rput(0.5,0){\n' out += ' \\psset{yunit=0.0076923, linestyle=none, fillstyle=solid}\n' out += ' \\multido{\\ic=0+1,\\id=1+1}{130}{\n' out += ' \\psframe[fillcolor={gradientcolors!![\\ic]},dimen=inner,linewidth=0.1pt](0, \\ic)(1, \\id)\n' out += ' }\n' out += ' }\n' out += ' \\rput(0.5,0){\n' out += ' \\psframe[linewidth=0.3pt,dimen=middle](0,0)(1,1)\n' zcustommajortickmarks = self.attr_int('ZMajorTickMarks', -1) zcustomminortickmarks = self.attr_int('ZMinorTickMarks', -1) zcustommajorticks = zcustomminorticks = None if self.attr('ZCustomMajorTicks'): zcustommajorticks = [] z_label_pairs = self.attr('ZCustomMajorTicks').strip().split() #'\t') if len(z_label_pairs) % 2 == 0: for i in range(0, len(z_label_pairs), 2): zcustommajorticks.append({'Value': float(x_label_pairs[i]), 'Label': x_label_pairs[i+1]}) else: print("Warning: ZCustomMajorTicks requires an even number of alternating pos/label entries") if self.attr('ZCustomMinorTicks'): zs = self.attr('ZCustomMinorTicks').strip().split() #'\t') zcustomminorticks = [{'Value': float(x)} for x in xs] drawzlabels = self.attr_bool('PlotZTickLabels', True) zticks = ZTicks(self.description, self.coors) out += zticks.draw(custommajortickmarks=zcustommajortickmarks,\ customminortickmarks=zcustomminortickmarks,\ custommajorticks=zcustommajorticks,\ customminorticks=zcustomminorticks, drawlabels=drawzlabels) out += ' }\n' out += '}\n' return out class Labels(Described): def __init__(self, description): self.description = description def draw(self, axis=[]): out = "" out += ('\n%\n% Labels\n%\n') if 'Title' in self.description and (axis.count('Title') or axis==[]): out += ('\\rput(0,1){\\rput[lB](0, 1.7\\labelsep){\\normalsize '+self.description['Title']+'}}\n') if 'XLabel' in self.description and (axis.count('XLabel') or axis==[]): xlabelsep = 4.7 if 'XLabelSep' in self.description: xlabelsep=float(self.description['XLabelSep']) out += ('\\rput(1,0){\\rput[rB](0,-%4.3f\\labelsep){\\normalsize '%(xlabelsep) +self.description['XLabel']+'}}\n') if 'YLabel' in self.description and (axis.count('YLabel') or axis==[]): ylabelsep = 6.5 if 'YLabelSep' in self.description: ylabelsep=float(self.description['YLabelSep']) out += ('\\rput(0,1){\\rput[rB]{90}(-%4.3f\\labelsep,0){\\normalsize '%(ylabelsep) +self.description['YLabel']+'}}\n') if 'ZLabel' in self.description and (axis.count('ZLabel') or axis==[]): zlabelsep = 6.5 if 'ZLabelSep' in self.description: zlabelsep=float(self.description['ZLabelSep']) out += ('\\rput(1,1){\\rput(%4.3f\\labelsep,0){\\psset{xunit=4mm}\\rput[lB]{270}(1.5,0){\\normalsize '%(zlabelsep) +self.description['ZLabel']+'}}}\n') return out class Special(Described): def __init__(self, f): self.description = {} self.data = [] self.read_input(f) if not self.description.has_key('Location'): self.description['Location']='MainPlot' self.description['Location']=self.description['Location'].split('\t') def read_input(self, f): for line in f: if is_end_marker(line, 'SPECIAL'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.description[prop] = value else: self.data.append(line) def draw(self, coors, inputdata): drawme = False for i in self.description['Location']: if i in inputdata.description['PlotStage']: drawme = True break if not drawme: return "" out = "" out += ('\n%\n% Special\n%\n') import re regex = re.compile(r'^(.*?)(\\physics[xy]?coor)\(\s?([0-9\.eE+-]+)\s?,\s?([0-9\.eE+-]+)\s?\)(.*)') # TODO: More precise number string matching, something like this: # num = r"-?[0-9]*(?:\.[0-9]*)(?:[eE][+-]?\d+]" # regex = re.compile(r'^(.*?)(\\physics[xy]?coor)\(\s?(' + num + ')\s?,\s?(' + num + ')\s?\)(.*)') for l in self.data: while regex.search(l): match = regex.search(l) xcoor, ycoor = float(match.group(3)), float(match.group(4)) if match.group(2)[1:] in ["physicscoor", "physicsxcoor"]: xcoor = coors.phys2frameX(xcoor) if match.group(2)[1:] in ["physicscoor", "physicsycoor"]: ycoor = coors.phys2frameY(ycoor) line = "%s(%f, %f)%s" % (match.group(1), xcoor, ycoor, match.group(5)) l = line out += l + "\n" return out class DrawableObject(Described): def __init__(self, f): pass def getName(self): return self.description.get("Name", "") def getTitle(self): return self.description.get("Title", "") def getLineStyle(self): if 'LineStyle' in self.description: ## I normally like there to be "only one way to do it", but providing ## this dashdotted/dotdashed synonym just seems humane ;-) if self.description['LineStyle'] in ('dashdotted', 'dotdashed'): self.description['LineStyle']='dashed' self.description['LineDash']='3pt 3pt .8pt 3pt' return self.description['LineStyle'] else: return 'solid' def getLineDash(self): if 'LineDash' in self.description: # Check if LineStyle=='dashdotted' before returning something self.getLineStyle() return self.description['LineDash'] else: return '' def getLineWidth(self): return self.description.get("LineWidth", "0.8pt") def getLineColor(self): return self.description.get("LineColor", "black") def getLineOpacity(self): return self.description.get("LineOpacity", "1.0") def getFillColor(self): return self.description.get("FillColor", "white") def getFillOpacity(self): return self.description.get("FillOpacity", "1.0") def getHatchColor(self): return self.description.get("HatchColor", self.getErrorBandColor()) def getFillStyle(self): return self.description.get("FillStyle", "none") def getPolyMarker(self): return self.description.get("PolyMarker", "") def getDotSize(self): return self.description.get("DotSize", "2pt 2") def getDotScale(self): return self.description.get("DotScale", "1") def getErrorBars(self): return bool(int(self.description.get("ErrorBars", "0"))) def getErrorBands(self): return bool(int(self.description.get("ErrorBands", "0"))) def getErrorBandColor(self): return self.description.get("ErrorBandColor", "yellow") def getErrorBandStyle(self): return self.description.get("ErrorBandStyle", "solid") def getErrorBandOpacity(self): return self.description.get("ErrorBandOpacity", "1.0") def getErrorTubes(self): if self.description.has_key('ErrorTubes'): return bool(int(self.description['ErrorTubes'])) else: return False def getErrorTubeLineStyle(self): if self.description.has_key('ErrorTubeLineStyle'): if self.description['ErrorTubeLineStyle'] in ('dashdotted', 'dotdashed'): self.description['ErrorTubeLineStyle']='dashed' self.description['ErrorTubeLineDash']='3pt 3pt .8pt 3pt' return self.description['ErrorTubeLineStyle'] else: return self.getLineStyle() def getErrorTubeLineColor(self): if self.description.has_key('ErrorTubeLineColor'): return self.description['ErrorTubeLineColor'] else: return self.getLineColor() def getErrorTubeLineDash(self): if self.description.has_key('ErrorTubeLineDash'): return self.description['ErrorTubeLineDash'] else: return self.getLineDash() def getErrorTubeLineWidth(self): if self.description.has_key('ErrorTubeLineWidth'): return self.description['ErrorTubeLineWidth'] else: return '0.3pt' def getErrorTubeLineOpacity(self): if self.description.has_key('ErrorTubeLineOpacity'): return self.description['ErrorTubeLineOpacity'] else: return self.getLineOpacity() def getSmoothLine(self): return bool(int(self.description.get("SmoothLine", "0"))) def startclip(self): return '\\psclip{\\psframe[linewidth=0, linestyle=none](0,0)(1,1)}\n' def stopclip(self): return '\\endpsclip\n' def startpsset(self): out = "" out += ('\\psset{linecolor='+self.getLineColor()+'}\n') out += ('\\psset{linewidth='+self.getLineWidth()+'}\n') out += ('\\psset{linestyle='+self.getLineStyle()+'}\n') out += ('\\psset{fillstyle='+self.getFillStyle()+'}\n') out += ('\\psset{fillcolor='+self.getFillColor()+'}\n') out += ('\\psset{hatchcolor='+self.getHatchColor()+'}\n') out += ('\\psset{strokeopacity='+self.getLineOpacity()+'}\n') out += ('\\psset{opacity='+self.getFillOpacity()+'}\n') if self.getLineDash()!='': out += ('\\psset{dash='+self.getLineDash()+'}\n') return out def stoppsset(self): out = "" out += ('\\psset{linecolor=black}\n') out += ('\\psset{linewidth=0.8pt}\n') out += ('\\psset{linestyle=solid}\n') out += ('\\psset{fillstyle=none}\n') out += ('\\psset{fillcolor=white}\n') out += ('\\psset{hatchcolor=black}\n') out += ('\\psset{strokeopacity=1.0}\n') out += ('\\psset{opacity=1.0}\n') return out class Function(DrawableObject): def __init__(self, f): self.description = {} self.read_input(f) if not self.description.has_key('Location'): self.description['Location']='MainPlot' self.description['Location']=self.description['Location'].split('\t') def read_input(self, f): self.code='def plotfunction(x):\n' iscode=False for line in f: if is_end_marker(line, 'FUNCTION'): break elif is_comment(line): continue else: m = pat_property.match(line) if iscode: self.code+=' '+line elif m: prop, value = m.group(1,2) if prop=='Code': iscode=True else: self.description[prop] = value if not iscode: print('++++++++++ ERROR: No code in function') else: foo = compile(self.code, '', 'exec') exec(foo) self.plotfunction = plotfunction def draw(self,coors,inputdata): drawme = False for i in self.description['Location']: if i in inputdata.description['PlotStage']: drawme = True break if not drawme: return "" out = "" out += self.startclip() out += self.startpsset() xmin = coors.xmin() if 'XMin' in self.description and self.description['XMin']: xmin = float(self.description['XMin']) if self.description.has_key('FunctionXMin') and self.description['FunctionXMin']: xmin = max(xmin,float(self.description['FunctionXMin'])) xmax=coors.xmax() if 'XMax' in self.description and self.description['XMax']: xmax=float(self.description['XMax']) if self.description.has_key('FunctionXMax') and self.description['FunctionXMax']: xmax = min(xmax,float(self.description['FunctionXMax'])) xmin=min(xmin,xmax) xmax=max(xmin,xmax) # TODO: Space sample points logarithmically if LogX=1 xsteps=500. if self.description.has_key('XSteps') and self.description['XSteps']: xsteps=float(self.description['XSteps']) dx = (xmax-xmin)/xsteps x = xmin-dx funcstyle = '\\pscurve' if self.description.has_key('FunctionStyle') and self.description['FunctionStyle']: if (self.description['FunctionStyle']=='curve'): funcstyle = '\\pscurve' elif (self.description['FunctionStyle']=='line'): funcstyle = '\\psline' out += funcstyle if 'FillStyle' in self.description and self.description['FillStyle']!='none': out += '(%s,%s)\n' % (coors.strphys2frameX(xmin),coors.strphys2frameY(coors.ymin())) while x < (xmax+2*dx): y = self.plotfunction(x) out += ('(%s,%s)\n' % (coors.strphys2frameX(x), coors.strphys2frameY(y))) x += dx if 'FillStyle' in self.description and self.description['FillStyle']!='none': out += '(%s,%s)\n' % (coors.strphys2frameX(xmax),coors.strphys2frameY(coors.ymin())) out += self.stoppsset() out += self.stopclip() return out class BinData(object): """\ Store bin edge and value+error(s) data for a 1D or 2D bin. TODO: generalise/alias the attr names to avoid mention of x and y """ def __init__(self, low, high, val, err): #print("@", low, high, val, err) self.low = floatify(low) self.high = floatify(high) self.val = float(val) self.err = floatpair(err) @property def is2D(self): return hasattr(self.low, "__len__") and hasattr(self.high, "__len__") @property def isValid(self): invalid_val = (isnan(self.val) or isnan(self.err[0]) or isnan(self.err[1])) if invalid_val: return False if self.is2D: invalid_low = any(isnan(x) for x in self.low) invalid_high = any(isnan(x) for x in self.high) else: invalid_low, invalid_high = isnan(self.low), isnan(self.high) return not (invalid_low or invalid_high) @property def xmin(self): return self.low @xmin.setter def xmin(self,x): self.low = x @property def xmax(self): return self.high @xmax.setter def xmax(self,x): self.high = x @property def xmid(self): # TODO: Generalise to 2D return (self.xmin + self.xmax) / 2.0 @property def xwidth(self): # TODO: Generalise to 2D assert self.xmin <= self.xmax return self.xmax - self.xmin @property def y(self): return self.val @y.setter def y(self, x): self.val = x @property def ey(self): return self.err @ey.setter def ey(self, x): self.err = x @property def ymin(self): return self.y - self.ey[0] @property def ymax(self): return self.y + self.ey[1] def __getitem__(self, key): "dict-like access for backward compatibility" if key in ("LowEdge"): return self.xmin elif key == ("UpEdge", "HighEdge"): return self.xmax elif key == "Content": return self.y elif key == "Errors": return self.ey class Histogram(DrawableObject, Described): def __init__(self, f, p=None): self.description = {} self.is2dim = False self.data = [] self.read_input_data(f) self.sigmabinvalue = None self.meanbinvalue = None self.path = p def read_input_data(self, f): for line in f: if is_end_marker(line, 'HISTOGRAM'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.description[prop] = value else: ## Detect symm errs linearray = line.split() if len(linearray) == 4: self.data.append(BinData(*linearray)) ## Detect asymm errs elif len(linearray) == 5: self.data.append(BinData(linearray[0], linearray[1], linearray[2], [linearray[3],linearray[4]])) ## Detect two-dimensionality elif len(linearray) in [6,7]: self.is2dim = True # If asymm z error, use the max or average of +- error err = float(linearray[5]) if len(linearray) == 7: if self.description.get("ShowMaxZErr", 1): err = max(err, float(linearray[6])) else: err = 0.5 * (err + float(linearray[6])) self.data.append(BinData([linearray[0], linearray[2]], [linearray[1], linearray[3]], linearray[4], err)) ## Unknown histo format else: raise RuntimeError("Unknown HISTOGRAM data line format with %d entries" % len(linearray)) def mangle_input(self): norm2int = self.attr_bool("NormalizeToIntegral", False) norm2sum = self.attr_bool("NormalizeToSum", False) if norm2int or norm2sum: if norm2int and norm2sum: print("Can't normalize to Integral and to Sum at the same time. Will normalize to the sum.") foo = 0.0 # TODO: change to "in self.data"? for i in range(len(self.data)): if norm2sum: foo += self.data[i].val else: foo += self.data[i].val*(self.data[i].xmax-self.data[i].xmin) # TODO: change to "in self.data"? if foo != 0: for i in range(len(self.data)): self.data[i].val /= foo self.data[i].err[0] /= foo self.data[i].err[1] /= foo scale = self.attr_float('Scale', 1.0) if scale != 1.0: # TODO: change to "in self.data"? for i in range(len(self.data)): self.data[i].val *= scale self.data[i].err[0] *= scale self.data[i].err[1] *= scale if self.attr_float("ScaleError", 0.0): scale = float(self.attr_float("ScaleError")) for i in range(len(self.data)): self.data[i]['Error'][0] *= scale self.data[i]['Error'][1] *= scale if self.attr_float('Shift', 0.0): shift = float(self.attr_float("Shift")) for i in range(len(self.data)): self.data[i]['Content'] += shift #if self.description.has_key('Rebin') and self.description['Rebin']!='': if self.has_attr('Rebin') and self.attr('Rebin') != '': rawrebins = self.attr('Rebin').strip().split('\t') rebins = [] maxindex = len(self.data)-1 if len(rawrebins)%2==1: rebins.append({'Start': self.data[0].xmin, 'Rebin': int(rawrebins[0])}) rawrebins.pop(0) for i in range(0,len(rawrebins),2): if float(rawrebins[i]) self.data[0].xmin): rebins.insert(0,{'Start': self.data[0].xmin, 'Rebin': 1}) errortype = self.attr("ErrorType", "stat") newdata=[] lower = self.getBin(rebins[0]['Start']) for k in range(0,len(rebins),1): rebin = rebins[k]['Rebin'] upper = maxindex end = 1 if (kmaxindex: break binwidth = self.data[i+j].xwidth foo += self.data[i+j].val * binwidth if errortype=="stat": barl += (binwidth * self.data[i+j].err[0])**2 baru += (binwidth * self.data[i+j].err[1])**2 elif errortype == "env": barl += self.data[i+j].ymin * binwidth baru += self.data[i+j].ymax * binwidth else: logging.error("Rebinning for ErrorType not implemented.") sys.exit(1) upedge = min(i+rebin-1,maxindex) newbinwidth=self.data[upedge].xmax-self.data[i].xmin newcentral=foo/newbinwidth if errortype=="stat": newerror=[sqrt(barl)/newbinwidth,sqrt(baru)/newbinwidth] elif errortype=="env": newerror=[(foo-barl)/newbinwidth,(baru-foo)/newbinwidth] newdata.append(BinData(self.data[i].xmin, self.data[i+rebin-1].xmax, newcentral, newerror)) lower = (upper/rebin)*rebin+(upper%rebin) self.data=newdata def add(self, name): if len(self.data) != len(name.data): print('+++ Error in Histogram.add() for %s: different numbers of bins' % self.path) for i in range(len(self.data)): if fuzzyeq(self.data[i].xmin, name.data[i].xmin) and \ fuzzyeq(self.data[i].xmax, name.data[i].xmax): self.data[i].val += name.data[i].val self.data[i].err[0] = sqrt(self.data[i].err[0]**2 + name.data[i].err[0]**2) self.data[i].err[1] = sqrt(self.data[i].err[1]**2 + name.data[i].err[1]**2) else: print('+++ Error in Histogram.add() for %s: binning of histograms differs' % self.path) def divide(self, name): #print(name.path, self.path) if len(self.data) != len(name.data): print('+++ Error in Histogram.divide() for %s: different numbers of bins' % self.path) for i in range(len(self.data)): if fuzzyeq(self.data[i].xmin, name.data[i].xmin) and \ fuzzyeq(self.data[i].xmax, name.data[i].xmax): try: self.data[i].err[0] /= name.data[i].val except ZeroDivisionError: self.data[i].err[0]=0. try: self.data[i].err[1] /= name.data[i].val except ZeroDivisionError: self.data[i].err[1]=0. try: self.data[i].val /= name.data[i].val except ZeroDivisionError: self.data[i].val=1. # self.data[i].err[0] = sqrt(self.data[i].err[0]**2 + name.data[i].err[0]**2) # self.data[i].err[1] = sqrt(self.data[i].err[1]**2 + name.data[i].err[1]**2) else: print('+++ Error in Histogram.divide() for %s: binning of histograms differs' % self.path) def dividereverse(self, name): if len(self.data) != len(name.data): print('+++ Error in Histogram.dividereverse() for %s: different numbers of bins' % self.path) for i in range(len(self.data)): if fuzzyeq(self.data[i].xmin, name.data[i].xmin) and \ fuzzyeq(self.data[i].xmax, name.data[i].xmax): try: self.data[i].err[0] = name.data[i].err[0]/self.data[i].val except ZeroDivisionError: self.data[i].err[0]=0. try: self.data[i].err[1] = name.data[i].err[1]/self.data[i].val except ZeroDivisionError: self.data[i].err[1]=0. try: self.data[i].val = name.data[i].val/self.data[i].val except ZeroDivisionError: self.data[i].val=1. else: print('+++ Error in Histogram.dividereverse(): binning of histograms differs') def deviation(self, name): if len(self.data) != len(name.data): print('+++ Error in Histogram.deviation() for %s: different numbers of bins' % self.path) for i in range(len(self.data)): if fuzzyeq(self.data[i].xmin, name.data[i].xmin) and \ fuzzyeq(self.data[i].xmax, name.data[i].xmax): self.data[i].val -= name.data[i].val try: self.data[i].val /= 0.5*sqrt((name.data[i].err[0] + name.data[i].err[1])**2 + \ (self.data[i].err[0] + self.data[i].err[1])**2) except ZeroDivisionError: self.data[i].val = 0.0 try: self.data[i].err[0] /= name.data[i].err[0] except ZeroDivisionError: self.data[i].err[0] = 0.0 try: self.data[i].err[1] /= name.data[i].err[1] except ZeroDivisionError: self.data[i].err[1] = 0.0 else: print('+++ Error in Histogram.deviation() for %s: binning of histograms differs' % self.path) def delta(self,name): self.divide(name) for i in range(len(self.data)): self.data[i]['Content'] -= 1. def deltapercent(self,name): self.delta(name) for i in range(len(self.data)): self.data[i]['Content'] *= 100. self.data[i]['Error'][0] *= 100. self.data[i]['Error'][1] *= 100. def getBin(self,x): if xself.data[len(self.data)-1].xmax: print('+++ Error in Histogram.getBin(): x out of range') return float('nan') for i in range(1,len(self.data)-1,1): if x coors.zmax(): color = 129 if b.val < coors.zmin(): color = 0 if b.val <= coors.zmin(): out += ('[linewidth=0pt, linestyle=none, fillstyle=solid, fillcolor=white]') else: out += ('[linewidth=0pt, linestyle=none, fillstyle=solid, fillcolor={gradientcolors!!['+str(color)+']}]') out += ('(' + coors.strphys2frameX(b.low[0]) + ', ' \ + coors.strphys2frameY(b.low[1]) + ')(' \ + coors.strphys2frameX(b.high[0]) + ', ' \ + coors.strphys2frameY(b.high[1]) + ')\n') else: if self.getErrorBands(): self.description['SmoothLine'] = 0 for b in self.data: if isnan(b.val) or isnan(b.err[0]) or isnan(b.err[1]): seen_nan = True continue out += ('\\psframe[dimen=inner,linewidth=0pt,linestyle=none,fillstyle=%s,fillcolor=%s,opacity=%s,hatchcolor=%s]' %(self.getErrorBandStyle(),self.getErrorBandColor(),self.getErrorBandOpacity(),self.getHatchColor())) out += ('(' + coors.strphys2frameX(b.xmin) + ', ' \ + coors.strphys2frameY(b.val - b.err[0]) + ')(' \ + coors.strphys2frameX(b.xmax) + ', ' \ + coors.strphys2frameY(b.val + b.err[1]) + ')\n') if self.getErrorBars(): for b in self.data: if isnan(b.val) or isnan(b.err[0]) or isnan(b.err[1]): seen_nan = True continue if b.val == 0. and b.err == [0.,0.]: continue out += ('\\psline') out += ('(' + coors.strphys2frameX(b.xmin) + ', ' \ + coors.strphys2frameY(b.val) + ')(' \ + coors.strphys2frameX(b.xmax) + ', ' \ + coors.strphys2frameY(b.val) + ')\n') out += ('\\psline') bincenter = coors.strphys2frameX(.5*(b.xmin+b.xmax)) out += ('(' + bincenter + ', ' \ + coors.strphys2frameY(b.val-b.err[0]) + ')(' \ + bincenter + ', ' \ + coors.strphys2frameY(b.val+b.err[1]) + ')\n') if self.getErrorTubes(): for i in range(len(self.data)): if self.data[i].val==0. and self.data[i].err==[0.,0.]: continue tubeopts = ('linestyle=' + self.getErrorTubeLineStyle() \ + ', linecolor=' + self.getErrorTubeLineColor() \ + ', linewidth=' + self.getErrorTubeLineWidth() \ + ', strokeopacity=' + self.getErrorTubeLineOpacity() \ + ', opacity=' + self.getFillOpacity()) out += ('\psline['+tubeopts+']') out += ('(' + coors.strphys2frameX(self.data[i].xmin) + ', ' \ + coors.strphys2frameY(self.data[i].val-self.data[i].err[0]) + ')(' \ + coors.strphys2frameX(self.data[i].xmax) + ', ' \ + coors.strphys2frameY(self.data[i].val-self.data[i].err[0]) + ')\n') out += ('\psline['+tubeopts+']') out += ('(' + coors.strphys2frameX(self.data[i].xmin) + ', ' \ + coors.strphys2frameY(self.data[i].val+self.data[i].err[1]) + ')(' \ + coors.strphys2frameX(self.data[i].xmax) + ', ' \ + coors.strphys2frameY(self.data[i].val+self.data[i].err[1]) + ')\n') if self.description.get('ConnectBins', '1') == '1': if i>0: out += ('\psline['+tubeopts+']') out += ('(' + coors.strphys2frameX(self.data[i].xmin) + ', ' \ + coors.strphys2frameY(self.data[i].val-self.data[i].err[0]) + ')(' \ + coors.strphys2frameX(self.data[i].xmin) + ', ' \ + coors.strphys2frameY(self.data[i-1].val-self.data[i-1].err[0]) + ')\n') out += ('\psline['+tubeopts+']') out += ('(' + coors.strphys2frameX(self.data[i].xmin) + ', ' \ + coors.strphys2frameY(self.data[i].val+self.data[i].err[1]) + ')(' \ + coors.strphys2frameX(self.data[i].xmin) + ', ' \ + coors.strphys2frameY(self.data[i-1].val+self.data[i-1].err[1]) + ')\n') if self.getSmoothLine(): out += '\\psbezier' else: out += '\\psline' if self.getFillStyle() != 'none': # make sure that filled areas go all the way down to the x-axis if coors.phys2frameX(self.data[0].xmin) > 1e-4: out += '(' + coors.strphys2frameX(self.data[0].xmin) + ', -0.1)\n' else: out += '(-0.1, -0.1)\n' for i, b in enumerate(self.data): if isnan(b.val): seen_nan = True continue if self.getSmoothLine(): out += ('(' + coors.strphys2frameX(0.5*(b.xmin+b.xmax)) + ', ' \ + coors.strphys2frameY(b.val) + ')\n') else: out += ('(' + coors.strphys2frameX(b.xmin) + ', ' \ + coors.strphys2frameY(b.val) + ')(' \ + coors.strphys2frameX(b.xmax) + ', ' \ + coors.strphys2frameY(b.val) + ')\n') ## Join/separate data points, with vertical/diagonal lines if i+1 < len(self.data): #< If this is not the last point if self.description.get('ConnectBins', '1') != '1': out += ('\\psline') else: ## If bins are joined, but there is a gap in binning, choose whether to fill the gap if (abs(coors.phys2frameX(b.xmax) - coors.phys2frameX(self.data[i+1].xmin)) > 1e-4): if self.description.get('ConnectGaps', '0') != '1': out += ('\\psline') # TODO: Perhaps use a new dashed line to fill the gap? if self.getFillStyle() != 'none': # make sure that filled areas go all the way down to the x-axis if (coors.phys2frameX(self.data[-1].xmax) < 1-1e-4): out += '(' + coors.strphys2frameX(self.data[-1].xmax) + ', -0.1)\n' else: out += '(1.1, -0.1)\n' # if self.getPolyMarker() != '': for b in self.data: if isnan(b.val): seen_nan = True continue if b.val == 0. and b.err == [0.,0.]: continue out += ('\\psdot[dotstyle=%s,dotsize=%s,dotscale=%s](' % (self.getPolyMarker(),self.getDotSize(),self.getDotScale()) \ + coors.strphys2frameX(.5*(b.xmin+b.xmax)) + ', ' \ + coors.strphys2frameY(b.val) + ')\n') out += "% END DATA\n" else: print("WARNING: No valid bin value/errors/edges to plot!") out += "% NO DATA!\n" out += self.stoppsset() out += self.stopclip() if seen_nan: print("WARNING: NaN-valued value or error bar!") return out # def is2dimensional(self): # return self.is2dim def getXMin(self): if not self.data: return 0 elif self.is2dim: return min(b.low[0] for b in self.data) else: return min(b.xmin for b in self.data) def getXMax(self): if not self.data: return 1 elif self.is2dim: return max(b.high[0] for b in self.data) else: return max(b.xmax for b in self.data) def getYMin(self, xmin, xmax, logy): if not self.data: return 0 elif self.is2dim: return min(b.low[1] for b in self.data) else: yvalues = [] for b in self.data: if (b.xmax > xmin or b.xmin >= xmin) and (b.xmin < xmax or b.xmax <= xmax): foo = b.val if self.getErrorBars() or self.getErrorBands(): foo -= b.err[0] if not isnan(foo) and (not logy or foo > 0): yvalues.append(foo) return min(yvalues) if yvalues else self.data[0].val def getYMax(self, xmin, xmax): if not self.data: return 1 elif self.is2dim: return max(b.high[1] for b in self.data) else: yvalues = [] for b in self.data: if (b.xmax > xmin or b.xmin >= xmin) and (b.xmin < xmax or b.xmax <= xmax): foo = b.val if self.getErrorBars() or self.getErrorBands(): foo += b.err[1] if not isnan(foo): # and (not logy or foo > 0): yvalues.append(foo) return max(yvalues) if yvalues else self.data[0].val def getZMin(self, xmin, xmax, ymin, ymax): if not self.is2dim: return 0 zvalues = [] for b in self.data: if (b.xmax[0] > xmin and b.xmin[0] < xmax) and (b.xmax[1] > ymin and b.xmin[1] < ymax): zvalues.append(b.val) return min(zvalues) def getZMax(self, xmin, xmax, ymin, ymax): if not self.is2dim: return 0 zvalues = [] for b in self.data: if (b.xmax[0] > xmin and b.xmin[0] < xmax) and (b.xmax[1] > ymin and b.xmin[1] < ymax): zvalues.append(b.val) return max(zvalues) class Value(Histogram): def read_input_data(self, f): for line in f: if is_end_marker(line, 'VALUE'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.description[prop] = value else: linearray = line.split() if len(linearray) == 3: self.data.append(BinData(0.0, 1.0, linearray[0], [ linearray[1], linearray[2] ])) # dummy x-values else: raise Exception('Value does not have the expected number of columns. ' + line) # TODO: specialise draw() here class Counter(Histogram): def read_input_data(self, f): for line in f: if is_end_marker(line, 'COUNTER'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.description[prop] = value else: linearray = line.split() if len(linearray) == 2: self.data.append(BinData(0.0, 1.0, linearray[0], [ linearray[1], linearray[1] ])) # dummy x-values else: raise Exception('Counter does not have the expected number of columns. ' + line) # TODO: specialise draw() here class Histo1D(Histogram): def read_input_data(self, f): for line in f: if is_end_marker(line, 'HISTO1D'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.description[prop] = value else: linearray = line.split() ## Detect symm errs # TODO: Not sure what the 8-param version is for... auto-compatibility with YODA format? if len(linearray) in [4,8]: self.data.append(BinData(linearray[0], linearray[1], linearray[2], linearray[3])) ## Detect asymm errs elif len(linearray) == 5: self.data.append(BinData(linearray[0], linearray[1], linearray[2], [linearray[3],linearray[4]])) else: raise Exception('Histo1D does not have the expected number of columns. ' + line) # TODO: specialise draw() here class Histo2D(Histogram): def read_input_data(self, f): self.is2dim = True #< Should really be done in a constructor, but this is easier for now... for line in f: if is_end_marker(line, 'HISTO2D'): break elif is_comment(line): continue else: line = line.rstrip() m = pat_property.match(line) if m: prop, value = m.group(1,2) self.description[prop] = value else: linearray = line.split() if len(linearray) in [6,7]: # If asymm z error, use the max or average of +- error err = float(linearray[5]) if len(linearray) == 7: if self.description.get("ShowMaxZErr", 1): err = max(err, float(linearray[6])) else: err = 0.5 * (err + float(linearray[6])) self.data.append(BinData([linearray[0], linearray[2]], [linearray[1], linearray[3]], float(linearray[4]), err)) else: raise Exception('Histo2D does not have the expected number of columns. '+line) # TODO: specialise draw() here ############################# class Frame(object): def __init__(self, description, name): self.description = description self.name = name self.framelinewidth = '0.3pt' self.framelinecolor = "black" self.zeroline = self.description.has_key(self.name+'ZeroLine') and self.description[self.name+'ZeroLine']=='1' self.unitline = self.description.has_key(self.name+'UnitLine') and self.description[self.name+'UnitLine']=='1' def drawZeroLine(self,coor): out = ('') if self.zeroline: out += ('\n%\n% ZeroLine\n%\n') out += ('\\psline[linewidth=%s,linecolor=%s](0,%s)(1,%s)\n' %(self.framelinewidth,self.framelinecolor,coor,coor)) return out def drawUnitLine(self,coor): out = ('') if self.unitline: out += ('\n%\n% UnitLine\n%\n') out += ('\\psline[linewidth=%s,linecolor=%s](0,%s)(1,%s)\n' %(self.framelinewidth,self.framelinecolor,coor,coor)) return out def draw(self): out = ('\n%\n% Frame\n%\n') if self.description.get('FrameColor') is not None: color = inputdata.description['FrameColor'] # We want to draw this frame only once, so set it to False for next time: inputdata.description['FrameColor']=None # Calculate how high and wide the overall plot is height = [0,0] width = inputdata.attr('PlotSizeX') if inputdata.attr_bool('RatioPlot', True): height[1] = -inputdata.description['RatioPlotSizeY'] if not (self.description.has_key('MainPlot') and self.description['MainPlot']=='0'): height[0] = inputdata.description['PlotSizeY'] else: height[0] = -height[1] height[1] = 0 # Get the margin widths left = inputdata.description['LeftMargin']+0.1 right = inputdata.description['RightMargin']+0.1 top = inputdata.description['TopMargin']+0.1 bottom = inputdata.description['BottomMargin']+0.1 # out += ('\\rput(0,1){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(top, color, -left, top/2, width+right, top/2)) out += ('\\rput(0,%scm){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(height[1], bottom, color, -left, -bottom/2, width+right, -bottom/2)) out += ('\\rput(0,0){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(left, color, -left/2, height[1]-0.05, -left/2, height[0]+0.05)) out += ('\\rput(1,0){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(right, color, right/2, height[1]-0.05, right/2, height[0]+0.05)) if self.description.has_key('FrameLineWidth'): self.framelinewidth=self.description['FrameLineWidth'] if self.description.has_key('FrameLineColor'): self.framelinecolor=self.description['FrameLineColor'] out += ('\\psframe[linewidth='+self.framelinewidth+',linecolor='+self.framelinecolor+',dimen=middle](0,0)(1,1)\n') return out class Ticks(object): def __init__(self, description, coors): self.description = description self.majorticklinewidth = self.getMajorTickLineWidth() self.minorticklinewidth = self.getMinorTickLineWidth() self.majorticklinecolor = self.getMajorTickLineColor() self.minorticklinecolor = self.getMinorTickLineColor() self.majorticklength = '9pt' self.minorticklength = '4pt' self.majorticklabelcolor = self.getMajorTickLabelColor() self.coors = coors self.decimal = 0 self.framelinewidth = '0.3pt' self.framelinecolor = 'black' def draw_ticks(self, vmin, vmax, plotlog=False, custommajorticks=[], customminorticks=[], custommajortickmarks=-1, customminortickmarks=-1, twosided=False, drawlabels=True): if vmax <= vmin: raise Exception("Cannot place tick marks. Inconsistent min=%s and max=%s" % (vmin,vmax)) out = "" if plotlog: if vmin <= 0 or vmax <= 0: raise Exception("Cannot place log axis min or max tick <= 0") if (custommajorticks!=[] or customminorticks!=[]): for i in range(len(custommajorticks)): value=custommajorticks[i]['Value'] label=custommajorticks[i]['Label'] if value>=vmin and value<=vmax: out += self.draw_majortick(value,twosided) if drawlabels: out += self.draw_majorticklabel(value, label=label) for i in range(len(customminorticks)): value=customminorticks[i]['Value'] if value>=vmin and value<=vmax: out += self.draw_minortick(value,twosided) else: x = int(log10(vmin)) n_labels = 0 while x < log10(vmax) + 1: if 10**x >= vmin: ticklabel = 10**x if ticklabel > vmin and ticklabel < vmax: out += self.draw_majortick(ticklabel,twosided) if drawlabels: out += self.draw_majorticklabel(ticklabel) n_labels += 1 if ticklabel == vmin or ticklabel == vmax: if drawlabels: out += self.draw_majorticklabel(ticklabel) n_labels+=1 for i in range(2,10): ticklabel = i*10**(x-1) if ticklabel > vmin and ticklabel < vmax: out += self.draw_minortick(ticklabel,twosided) if drawlabels and n_labels == 0: if (i+1)*10**(x-1) < vmax: # some special care for the last minor tick out += self.draw_minorticklabel(ticklabel) else: out += self.draw_minorticklabel(ticklabel, last=True) x += 1 elif custommajorticks != [] or customminorticks != []: for i in range(len(custommajorticks)): value = custommajorticks[i]['Value'] label = custommajorticks[i]['Label'] if value >= vmin and value <= vmax: out += self.draw_majortick(value,twosided) if drawlabels: out += self.draw_majorticklabel(value, label=label) for i in range(len(customminorticks)): value = customminorticks[i]['Value'] if value >= vmin and value <= vmax: out += self.draw_minortick(value,twosided) else: vrange = vmax - vmin if isnan(vrange): vrange, vmin, vmax = 1, 1, 2 digits = int(log10(vrange))+1 if vrange <= 1: digits -= 1 foo = int(vrange/(10**(digits-1))) if foo/9. > 0.5: tickmarks = 10 elif foo/9. > 0.2: tickmarks = 5 elif foo/9. > 0.1: tickmarks = 2 if custommajortickmarks > -1: if custommajortickmarks not in [1, 2, 5, 10, 20]: print('+++ Error in Ticks.draw_ticks(): MajorTickMarks must be in [1, 2, 5, 10, 20]') else: tickmarks = custommajortickmarks if tickmarks == 2 or tickmarks == 20: minortickmarks = 3 else: minortickmarks = 4 if customminortickmarks > -1: minortickmarks = customminortickmarks # x = 0 while x > vmin*10**digits: x -= tickmarks*100**(digits-1) while x <= vmax*10**digits: if x >= vmin*10**digits - tickmarks*100**(digits-1): ticklabel = 1.*x/10**digits if int(ticklabel) == ticklabel: ticklabel = int(ticklabel) if float(ticklabel-vmin)/vrange >= -1e-5: if abs(ticklabel-vmin)/vrange > 1e-5 and abs(ticklabel-vmax)/vrange > 1e-5: out += self.draw_majortick(ticklabel,twosided) if drawlabels: out += self.draw_majorticklabel(ticklabel) xminor = x for i in range(minortickmarks): xminor += 1.*tickmarks*100**(digits-1)/(minortickmarks+1) ticklabel = 1.*xminor/10**digits if ticklabel > vmin and ticklabel < vmax: if abs(ticklabel-vmin)/vrange > 1e-5 and abs(ticklabel-vmax)/vrange > 1e-5: out += self.draw_minortick(ticklabel,twosided) x += tickmarks*100**(digits-1) return out def draw_breaks(self, vmin, vmax, breaks=[], twosided=False): out = "" for b in breaks: value = b['Value'] if value>=vmin and value<=vmax: out += self.draw_break(value,twosided) return out def add_definitions(self): pass def read_parameters(self): if self.description.has_key('FrameLineWidth'): self.framelinewidth=self.description['FrameLineWidth'] if self.description.has_key('FrameLineColor'): self.framelinecolor=self.description['FrameLineColor'] def draw(self): pass def draw_minortick(self, ticklabel, twosided): pass def draw_majortick(self, ticklabel, twosided): pass def draw_majorticklabel(self, ticklabel): pass def draw_minorticklabel(self, value, label='', last=False): return '' def draw_break(self, pos, twosided): pass def get_ticklabel(self, value, plotlog=False, minor=False, lastminor=False): label='' prefix = '' if plotlog: bar = int(log10(value)) if bar < 0: sign='-' else: sign='\\,' if minor: # The power of ten is only to be added to the last minor tick label if lastminor: label = str(int(value/(10**bar))) + "\\cdot" + '10$^{'+sign+'\\text{'+str(abs(bar))+'}}$' else: label = str(int(value/(10**bar))) # The naked prefactor else: if bar==0: label = '1' else: label = '10$^{'+sign+'\\text{%s}}$' % str(abs(bar)) else: if fabs(value) < 1e-10: value = 0 label = '%.3g' % value if "e" in label: a, b = label.split("e") astr = "%2.1f" % float(a) bstr = str(int(b)) label = "\\smaller{%s $\\!\\cdot 10^{%s} $}" % (astr, bstr) return label def getMajorTickLineWidth(self): pass def getMinorTickLineWidth(self): pass def getMajorTickLineColor(self): pass def getMinorTickLineColor(self): pass def getMajorTickLabelColor(self): pass class XTicks(Ticks): def draw(self, custommajorticks=[], customminorticks=[], custommajortickmarks=-1, customminortickmarks=-1, drawlabels=True, breaks=[]): twosided = bool(int(self.description.get('XTwosidedTicks', '1'))) out = "" out += ('\n%\n% X-Ticks\n%\n') out += self.add_definitions() uselog = self.description['LogX'] and (self.coors.xmin() > 0 and self.coors.xmax() > 0) out += self.draw_ticks(self.coors.xmin(), self.coors.xmax(),\ plotlog=uselog,\ custommajorticks=custommajorticks,\ customminorticks=customminorticks,\ custommajortickmarks=custommajortickmarks,\ customminortickmarks=customminortickmarks,\ twosided=twosided,\ drawlabels=drawlabels) out += self.draw_breaks(self.coors.ymin(), self.coors.ymax(),\ breaks, twosided) return out def add_definitions(self): self.read_parameters() out = '' out += ('\\def\\majortickmarkx{\\psline[linewidth='+self.majorticklinewidth+',linecolor='+self.majorticklinecolor+'](0,0)(0,'+self.majorticklength+')}%\n') out += ('\\def\\minortickmarkx{\\psline[linewidth='+self.minorticklinewidth+',linecolor='+self.minorticklinecolor+'](0,0)(0,'+self.minorticklength+')}%\n') out += ('\\def\\breakx{%\n \\rput{270}(0,0){\\psline[linewidth='+self.framelinewidth+',linecolor=white](0,-1pt)(0,1pt)}\n \\rput{180}(0,0){%\n \\rput{20}(0,1pt){%\n \\psline[linewidth='+self.framelinewidth+',linecolor='+self.framelinecolor+'](-5pt,0)(5pt,0)\n }\n \\rput{20}(0,-1pt){%\n \\psline[linewidth='+self.framelinewidth+',linecolor='+self.framelinecolor+'](-5pt,0)(5pt,0)\n }\n }\n }\n') return out def draw_minortick(self, ticklabel, twosided): out = '' out += '\\rput('+self.coors.strphys2frameX(ticklabel)+', 0){\\minortickmarkx}\n' if twosided: out += '\\rput{180}('+self.coors.strphys2frameX(ticklabel)+', 1){\\minortickmarkx}\n' return out def draw_minorticklabel(self, value, label='', last=False): if not label: label=self.get_ticklabel(value, int(self.description['LogX']), minor=True, lastminor=last) if last: # Some more indentation for the last minor label return ('\\rput('+self.coors.strphys2frameX(value)+', 0){\\rput[B](1.9\\labelsep,-2.3\\labelsep){\\strut{}'+label+'}}\n') else: return ('\\rput('+self.coors.strphys2frameX(value)+', 0){\\rput[B](0,-2.3\\labelsep){\\strut{}'+label+'}}\n') def draw_majortick(self, ticklabel, twosided): out = '' out += '\\rput('+self.coors.strphys2frameX(ticklabel)+', 0){\\majortickmarkx}\n' if twosided: out += '\\rput{180}('+self.coors.strphys2frameX(ticklabel)+', 1){\\majortickmarkx}\n' return out def draw_majorticklabel(self, value, label=''): if not label: label = self.get_ticklabel(value, int(self.description['LogX']) and self.coors.xmin() > 0 and self.coors.xmax() > 0) labelparts = label.split("\\n") labelcode = label if len(labelparts) == 1 else ("\\shortstack{" + "\\\\ ".join(labelparts) + "}") rtn = "\\rput(" + self.coors.strphys2frameX(value) + ", 0){\\rput[t](0,-\\labelsep){\\textcolor{" +self.majorticklabelcolor + "}{" + labelcode + "}}}\n" return rtn def draw_break(self, pos, twosided): out = '' out += '\\rput(0, '+self.coors.strphys2frameX(pos)+', 0){\\breakx}\n' if twosided: out += '\\rput('+self.coors.strphys2frameX(pos)+', 1){\\breakx}\n' return out def getMajorTickLineWidth(self): if self.description.has_key('XMajorTickLineWidth'): return self.description['XMajorTickLineWidth'] return '0.3pt' def getMinorTickLineWidth(self): if self.description.has_key('XMinorTickLineWidth'): return self.description['XMinorTickLineWidth'] return '0.3pt' def getMajorTickLineColor(self): if self.description.has_key('XMajorTickLineColor'): return self.description['XMajorTickLineColor'] return 'black' def getMinorTickLineColor(self): if self.description.has_key('XMinorTickLineColor'): return self.description['XMinorTickLineColor'] return 'black' def getMajorTickLabelColor(self): if self.description.has_key('XMajorTickLabelColor'): return self.description['XMajorTickLabelColor'] return 'black' class YTicks(Ticks): def draw(self, custommajorticks=[], customminorticks=[], custommajortickmarks=-1, customminortickmarks=-1, drawlabels=True, breaks=[]): twosided = bool(int(self.description.get('YTwosidedTicks', '1'))) out = "" out += ('\n%\n% Y-Ticks\n%\n') out += self.add_definitions(); uselog = self.description['LogY'] and self.coors.ymin() > 0 and self.coors.ymax() > 0 out += self.draw_ticks(self.coors.ymin(), self.coors.ymax(), plotlog=uselog, custommajorticks=custommajorticks, customminorticks=customminorticks, custommajortickmarks=custommajortickmarks, customminortickmarks=customminortickmarks, twosided=twosided, drawlabels=drawlabels) if self.description.has_key('ShortenLargeNumbers') and \ self.description['ShortenLargeNumbers'] and \ not (self.description.has_key('PlotStage') and \ 'RatioPlot' in self.description['PlotStage']): bar = int(self.decimal) if bar<0: sign='-' else: sign='\\,' if bar==0: pass else: pos = self.coors.strphys2frameY(self.coors.ymax()) label = \ ('\\times 10$^{'+sign+'\\text{'+str(abs(self.decimal))+'}}$') out += ('\\uput[180]{0}(0, '+pos+'){\\strut{}'+label+'}\n') out += self.draw_breaks(self.coors.ymin(), self.coors.ymax(),\ breaks, twosided) return out def add_definitions(self): self.read_parameters() out = '' out += ('\\def\\majortickmarky{\\psline[linewidth='+self.majorticklinewidth+',linecolor='+self.majorticklinecolor+'](0,0)('+self.majorticklength+',0)}%\n') out += ('\\def\\minortickmarky{\\psline[linewidth='+self.minorticklinewidth+',linecolor='+self.minorticklinecolor+'](0,0)('+self.minorticklength+',0)}%\n') out += ('\\def\\breaky{%\n \\rput{180}(0,0){\\psline[linewidth='+self.framelinewidth+',linecolor=white](0,-1pt)(0,1pt)}\n \\rput{180}(0,0){%\n \\rput{20}(0,1pt){%\n \\psline[linewidth='+self.framelinewidth+',linecolor='+self.framelinecolor+'](-5pt,0)(5pt,0)\n }\n \\rput{20}(0,-1pt){%\n \\psline[linewidth='+self.framelinewidth+',linecolor='+self.framelinecolor+'](-5pt,0)(5pt,0)\n }\n }\n }\n') return out def draw_minortick(self, ticklabel, twosided): out = '' out += '\\rput(0, '+self.coors.strphys2frameY(ticklabel)+'){\\minortickmarky}\n' if twosided: out += '\\rput{180}(1, '+self.coors.strphys2frameY(ticklabel)+'){\\minortickmarky}\n' return out def draw_majortick(self, ticklabel, twosided): out = '' out += '\\rput(0, '+self.coors.strphys2frameY(ticklabel)+'){\\majortickmarky}\n' if twosided: out += '\\rput{180}(1, '+self.coors.strphys2frameY(ticklabel)+'){\\majortickmarky}\n' return out def draw_majorticklabel(self, value, label=''): if not label: label = self.get_ticklabel(value, int(self.description['LogY']) and self.coors.ymin() > 0 and self.coors.ymax() > 0) if self.description.get('RatioPlotMode', 'mcdata') == 'deviation' \ and self.description.has_key('PlotStage') and 'RatioPlot' in self.description['PlotStage']: rtn = '\\uput[180]{0}(0, '+self.coors.strphys2frameY(value)+'){\\strut{}\\textcolor{'+self.majorticklabelcolor+'}{'+label+'\\,$\\sigma$}}\n' else: labelparts = label.split("\\n") labelcode = label if len(labelparts) == 1 else ("\\shortstack{" + "\\\\ ".join(labelparts) + "}") rtn = "\\rput(0, " + self.coors.strphys2frameY(value) + "){\\rput[r](-\\labelsep,0){\\textcolor{"+self.majorticklabelcolor+"}{" + labelcode + "}}}\n" return rtn def draw_break(self, pos, twosided): out = '' out += '\\rput(0, '+self.coors.strphys2frameY(pos)+'){\\breaky}\n' if twosided: out += '\\rput(1, '+self.coors.strphys2frameY(pos)+'){\\breaky}\n' return out def getMajorTickLineWidth(self): if self.description.has_key('XMajorTickLineWidth'): return self.description['XMajorTickLineWidth'] return '0.3pt' def getMinorTickLineWidth(self): if self.description.has_key('XMinorTickLineWidth'): return self.description['XMinorTickLineWidth'] return '0.3pt' def getMajorTickLineColor(self): if self.description.has_key('XMajorTickLineColor'): return self.description['XMajorTickLineColor'] return 'black' def getMinorTickLineColor(self): if self.description.has_key('XMinorTickLineColor'): return self.description['XMinorTickLineColor'] return 'black' def getMajorTickLabelColor(self): if self.description.has_key('XMajorTickLabelColor'): return self.description['XMajorTickLabelColor'] return 'black' class ZTicks(Ticks): def __init__(self, description, coors): self.majorticklinewidth = '0.3pt' self.minorticklinewidth = '0.3pt' self.majorticklength = '6pt' self.minorticklength = '2.6pt' self.description = description self.coors = coors def draw(self, custommajorticks=None, customminorticks=None, custommajortickmarks=-1, customminortickmarks=-1, drawlabels=True): out = "" out += ('\n%\n% Z-Ticks\n%\n') out += ('\\def\\majortickmarkz{\\psline[linewidth='+self.majorticklinewidth+',linecolor='+self.majorticklinecolor+'](0,0)('+self.majorticklength+',0)}%\n') out += ('\\def\\minortickmarkz{\\psline[linewidth='+self.minorticklinewidth+',linecolor='+self.minorticklinecolor+'](0,0)('+self.minorticklength+',0)}%\n') out += self.draw_ticks(self.coors.zmin(), self.coors.zmax(),\ plotlog=self.description['LogZ'],\ custommajorticks=custommajorticks,\ customminorticks=customminorticks,\ custommajortickmarks=custommajortickmarks,\ customminortickmarks=customminortickmarks,\ twosided=False,\ drawlabels=drawlabels) return out def draw_minortick(self, ticklabel, twosided): return '\\rput{180}(1, '+self.coors.strphys2frameZ(ticklabel)+'){\\minortickmarkz}\n' def draw_majortick(self, ticklabel, twosided): return '\\rput{180}(1, '+self.coors.strphys2frameZ(ticklabel)+'){\\majortickmarkz}\n' def draw_majorticklabel(self, value, label=''): if label=='': label = self.get_ticklabel(value, int(self.description['LogZ'])) if self.description.get('RatioPlotMode', "mcdata") == 'deviation' \ and self.description.has_key('PlotStage') and 'RatioPlot' in self.description['PlotStage']: return ('\\uput[0]{0}(1, '+self.coors.strphys2frameZ(value)+'){\\strut{}'+label+'\\,$\\sigma$}\n') else: return ('\\uput[0]{0}(1, '+self.coors.strphys2frameZ(value)+'){\\strut{}'+label+'}\n') def getMajorTickLineWidth(self): if self.description.has_key('ZMajorTickLineWidth'): return self.description['ZMajorTickLineWidth'] return '0.3pt' def getMinorTickLineWidth(self): if self.description.has_key('ZMinorTickLineWidth'): return self.description['ZMinorTickLineWidth'] return '0.3pt' def getMajorTickLabelColor(self): if self.description.has_key('ZMajorTickLabelColor'): return self.description['ZMajorTickLabelColor'] return 'black' def getMajorTickLineColor(self): if self.description.has_key('ZMajorTickLineColor'): return self.description['ZMajorTickLineColor'] return 'black' def getMinorTickLineColor(self): if self.description.has_key('ZMinorTickLineColor'): return self.description['ZMinorTickLineColor'] return 'black' class Coordinates(object): def __init__(self, inputdata): self.description = inputdata.description def phys2frameX(self, x): if self.description['LogX']: if x>0: result = 1.*(log10(x)-log10(self.xmin()))/(log10(self.xmax())-log10(self.xmin())) else: return -10 else: result = 1.*(x-self.xmin())/(self.xmax()-self.xmin()) if (fabs(result) < 1e-4): return 0 else: return min(max(result,-10),10) def phys2frameY(self, y): if self.description['LogY']: if y > 0 and self.ymin() > 0 and self.ymax() > 0: result = 1.*(log10(y)-log10(self.ymin()))/(log10(self.ymax())-log10(self.ymin())) else: return -10 else: result = 1.*(y-self.ymin())/(self.ymax()-self.ymin()) if (fabs(result) < 1e-4): return 0 else: return min(max(result,-10),10) def phys2frameZ(self, z): if self.description['LogZ']: if z>0: result = 1.*(log10(z)-log10(self.zmin()))/(log10(self.zmax())-log10(self.zmin())) else: return -10 else: result = 1.*(z-self.zmin())/(self.zmax()-self.zmin()) if (fabs(result) < 1e-4): return 0 else: return min(max(result,-10),10) # TODO: Add frame2phys functions (to allow linear function sampling in the frame space rather than the physical space) def strphys2frameX(self, x): return str(self.phys2frameX(x)) def strphys2frameY(self, y): return str(self.phys2frameY(y)) def strphys2frameZ(self, z): return str(self.phys2frameZ(z)) def xmin(self): return self.description['Borders'][0] def xmax(self): return self.description['Borders'][1] def ymin(self): return self.description['Borders'][2] def ymax(self): return self.description['Borders'][3] def zmin(self): return self.description['Borders'][4] def zmax(self): return self.description['Borders'][5] #################### def try_cmd(args): "Run the given command + args and return True/False if it succeeds or not" import subprocess try: subprocess.check_output(args, stderr=subprocess.STDOUT) return True except: return False def have_cmd(cmd): return try_cmd(["which", cmd]) import shutil, subprocess def process_datfile(datfile): global opts if not os.access(datfile, os.R_OK): raise Exception("Could not read data file '%s'" % datfile) datpath = os.path.abspath(datfile) datfile = os.path.basename(datpath) datdir = os.path.dirname(datpath) outdir = opts.OUTPUT_DIR if opts.OUTPUT_DIR else datdir filename = datfile.replace('.dat','') ## Create a temporary directory # cwd = os.getcwd() tempdir = tempfile.mkdtemp('.make-plots') tempdatpath = os.path.join(tempdir, datfile) shutil.copy(datpath, tempdir) if opts.NO_CLEANUP: logging.info('Keeping temp-files in %s' % tempdir) ## Make TeX file inputdata = InputData(datpath) if inputdata.attr_bool('IgnorePlot', False): return texpath = os.path.join(tempdir, '%s.tex' % filename) texfile = open(texpath, 'w') p = Plot(inputdata) texfile.write(p.write_header(inputdata)) if inputdata.attr_bool("MainPlot", True): mp = MainPlot(inputdata) texfile.write(mp.draw(inputdata)) if not inputdata.attr_bool("is2dim", False): if inputdata.attr_bool("RatioPlot", True) and inputdata.attr("RatioPlotReference"): # is not None: rp = RatioPlot(inputdata,0) texfile.write(rp.draw(inputdata)) for i in range(1,9): if inputdata.attr_bool('RatioPlot'+str(i), False): # and inputdata.attr('RatioPlot'+str(i)+'Reference'): rp = RatioPlot(inputdata,i) texfile.write(rp.draw(inputdata)) texfile.write(p.write_footer()) texfile.close() if opts.OUTPUT_FORMAT != ["TEX"]: ## Check for the required programs latexavailable = have_cmd("latex") dvipsavailable = have_cmd("dvips") convertavailable = have_cmd("convert") ps2pnmavailable = have_cmd("ps2pnm") pnm2pngavailable = have_cmd("pnm2png") # TODO: It'd be nice to be able to control the size of the PNG between thumb and full-size... # currently defaults (and is used below) to a size suitable for thumbnails def mkpngcmd(infile, outfile, outsize=450, density=300): if convertavailable: pngcmd = ["convert", "-flatten", "-density", str(density), infile, "-quality", "100", "-resize", "{size:d}x{size:d}>".format(size=outsize), #"-sharpen", "0x1.0", outfile] #logging.debug(" ".join(pngcmd)) #pngproc = subprocess.Popen(pngcmd, stdout=subprocess.PIPE, cwd=tempdir) #pngproc.wait() return pngcmd else: raise Exception("Required PNG maker program (convert) not found") # elif ps2pnmavailable and pnm2pngavailable: # pstopnm = "pstopnm -stdout -xsize=461 -ysize=422 -xborder=0.01 -yborder=0.01 -portrait " + infile # p1 = subprocess.Popen(pstopnm.split(), stdout=subprocess.PIPE, stderr=open("/dev/null", "w"), cwd=tempdir) # p2 = subprocess.Popen(["pnmtopng"], stdin=p1.stdout, stdout=open("%s/%s.png" % (tempdir, outfile), "w"), stderr=open("/dev/null", "w"), cwd=tempdir) # p2.wait() # else: # raise Exception("Required PNG maker programs (convert, or ps2pnm and pnm2png) not found") ## Run LaTeX (in no-stop mode) logging.debug(os.listdir(tempdir)) texcmd = ["latex", "\scrollmode\input", texpath] logging.debug("TeX command: " + " ".join(texcmd)) texproc = subprocess.Popen(texcmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tempdir) logging.debug(texproc.communicate()[0]) logging.debug(os.listdir(tempdir)) ## Run dvips dvcmd = ["dvips", filename] if not logging.getLogger().isEnabledFor(logging.DEBUG): dvcmd.append("-q") ## Handle Minion Font if opts.OUTPUT_FONT == "MINION": dvcmd.append('-Pminion') ## Choose format # TODO: Rationalise... this is a mess! Maybe we can use tex2pix? if "PS" in opts.OUTPUT_FORMAT: dvcmd += ["-o", "%s.ps" % filename] logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) dvproc.wait() if "PDF" in opts.OUTPUT_FORMAT: dvcmd.append("-f") logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) cnvproc = subprocess.Popen(["ps2pdf", "-"], stdin=dvproc.stdout, stdout=subprocess.PIPE, cwd=tempdir) f = open(os.path.join(tempdir, "%s.pdf" % filename), "wb") f.write(cnvproc.communicate()[0]) f.close() if "EPS" in opts.OUTPUT_FORMAT: dvcmd.append("-f") logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) cnvproc = subprocess.Popen(["ps2eps"], stdin=dvproc.stdout, stderr=subprocess.PIPE, stdout=subprocess.PIPE, cwd=tempdir) f = open(os.path.join(tempdir, "%s.eps" % filename), "wb") f.write(cnvproc.communicate()[0]) f.close() if "PNG" in opts.OUTPUT_FORMAT: dvcmd.append("-f") logging.debug(" ".join(dvcmd)) dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir) #pngcmd = ["convert", "-flatten", "-density", "110", "-", "-quality", "100", "-sharpen", "0x1.0", "%s.png" % filename] pngcmd = mkpngcmd("-", "%s.png" % filename) logging.debug(" ".join(pngcmd)) pngproc = subprocess.Popen(pngcmd, stdin=dvproc.stdout, stdout=subprocess.PIPE, cwd=tempdir) pngproc.wait() logging.debug(os.listdir(tempdir)) ## Copy results back to main dir for fmt in opts.OUTPUT_FORMAT: outname = "%s.%s" % (filename, fmt.lower()) outpath = os.path.join(tempdir, outname) if os.path.exists(outpath): shutil.copy(outpath, outdir) else: logging.error("No output file '%s' from processing %s" % (outname, datfile)) ## Clean up if not opts.NO_CLEANUP: shutil.rmtree(tempdir, ignore_errors=True) #################### if __name__ == '__main__': ## Try to rename the process on Linux try: import ctypes libc = ctypes.cdll.LoadLibrary('libc.so.6') libc.prctl(15, 'make-plots', 0, 0, 0) except Exception: pass ## Try to use Psyco optimiser try: import psyco psyco.full() except ImportError: pass ## Find number of (virtual) processing units import multiprocessing try: numcores = multiprocessing.cpu_count() except: numcores = 1 ## Parse command line options from optparse import OptionParser, OptionGroup parser = OptionParser(usage=__doc__) parser.add_option("-j", "-n", "--num-threads", dest="NUM_THREADS", type="int", default=numcores, help="max number of threads to be used [%s]" % numcores) parser.add_option("-o", "--outdir", dest="OUTPUT_DIR", default=None, help="choose the output directory (default = .dat dir)") parser.add_option("--font", dest="OUTPUT_FONT", choices="palatino,cm,times,helvetica,minion".split(","), default="palatino", help="choose the font to be used in the plots") parser.add_option("--palatino", dest="OUTPUT_FONT", action="store_const", const="palatino", default="palatino", help="use Palatino as font (default). DEPRECATED: Use --font") parser.add_option("--cm", dest="OUTPUT_FONT", action="store_const", const="cm", default="palatino", help="use Computer Modern as font. DEPRECATED: Use --font") parser.add_option("--times", dest="OUTPUT_FONT", action="store_const", const="times", default="palatino", help="use Times as font. DEPRECATED: Use --font") parser.add_option("--minion", dest="OUTPUT_FONT", action="store_const", const="minion", default="palatino", help="use Adobe Minion Pro as font. Note: You need to set TEXMFHOME first. DEPRECATED: Use --font") parser.add_option("--helvetica", dest="OUTPUT_FONT", action="store_const", const="helvetica", default="palatino", help="use Helvetica as font. DEPRECATED: Use --font") parser.add_option("-f", "--format", dest="OUTPUT_FORMAT", default="PDF", help="choose plot format, perhaps multiple comma-separated formats e.g. 'pdf' or 'tex,pdf,png' (default = PDF).") parser.add_option("--ps", dest="OUTPUT_FORMAT", action="store_const", const="PS", default="PDF", help="create PostScript output (default). DEPRECATED") parser.add_option("--pdf", dest="OUTPUT_FORMAT", action="store_const", const="PDF", default="PDF", help="create PDF output. DEPRECATED") parser.add_option("--eps", dest="OUTPUT_FORMAT", action="store_const", const="EPS", default="PDF", help="create Encapsulated PostScript output. DEPRECATED") parser.add_option("--png", dest="OUTPUT_FORMAT", action="store_const", const="PNG", default="PDF", help="create PNG output. DEPRECATED") parser.add_option("--pspng", dest="OUTPUT_FORMAT", action="store_const", const="PS,PNG", default="PDF", help="create PS and PNG output. DEPRECATED") parser.add_option("--pdfpng", dest="OUTPUT_FORMAT", action="store_const", const="PDF,PNG", default="PDF", help="create PDF and PNG output. DEPRECATED") parser.add_option("--epspng", dest="OUTPUT_FORMAT", action="store_const", const="EPS,PNG", default="PDF", help="create EPS and PNG output. DEPRECATED") parser.add_option("--tex", dest="OUTPUT_FORMAT", action="store_const", const="TEX", default="PDF", help="create TeX/LaTeX output.") parser.add_option("--no-cleanup", dest="NO_CLEANUP", action="store_true", default=False, help="keep temporary directory and print its filename.") parser.add_option("--no-subproc", dest="NO_SUBPROC", action="store_true", default=False, help="don't use subprocesses to render the plots in parallel -- useful for debugging.") parser.add_option("--full-range", dest="FULL_RANGE", action="store_true", default=False, help="plot full y range in log-y plots.") parser.add_option("-c", "--config", dest="CONFIGFILES", action="append", default=None, help="plot config file to be used. Overrides internal config blocks.") verbgroup = OptionGroup(parser, "Verbosity control") verbgroup.add_option("-v", "--verbose", action="store_const", const=logging.DEBUG, dest="LOGLEVEL", default=logging.INFO, help="print debug (very verbose) messages") verbgroup.add_option("-q", "--quiet", action="store_const", const=logging.WARNING, dest="LOGLEVEL", default=logging.INFO, help="be very quiet") parser.add_option_group(verbgroup) opts, args = parser.parse_args() ## Tweak the opts output logging.basicConfig(level=opts.LOGLEVEL, format="%(message)s") opts.OUTPUT_FONT = opts.OUTPUT_FONT.upper() opts.OUTPUT_FORMAT = opts.OUTPUT_FORMAT.upper().split(",") if opts.NUM_THREADS == 1: opts.NO_SUBPROC = True ## Check for no args if len(args) == 0: logging.error(parser.get_usage()) sys.exit(2) ## Check that the files exist for f in args: if not os.access(f, os.R_OK): print("Error: cannot read from %s" % f) sys.exit(1) ## Test for external programs (kpsewhich, latex, dvips, ps2pdf/ps2eps, and convert) opts.LATEXPKGS = [] if opts.OUTPUT_FORMAT != ["TEX"]: try: ## latex if not have_cmd("latex"): logging.error("ERROR: required program 'latex' could not be found. Exiting...") sys.exit(1) ## dvips if not have_cmd("dvips"): logging.error("ERROR: required program 'dvips' could not be found. Exiting...") sys.exit(1) ## ps2pdf / ps2eps if "PDF" in opts.OUTPUT_FORMAT: if not have_cmd("ps2pdf"): logging.error("ERROR: required program 'ps2pdf' (for PDF output) could not be found. Exiting...") sys.exit(1) elif "EPS" in opts.OUTPUT_FORMAT: if not have_cmd("ps2eps"): logging.error("ERROR: required program 'ps2eps' (for EPS output) could not be found. Exiting...") sys.exit(1) ## PNG output converter if "PNG" in opts.OUTPUT_FORMAT: if not have_cmd("convert"): logging.error("ERROR: required program 'convert' (for PNG output) could not be found. Exiting...") sys.exit(1) ## kpsewhich: required for LaTeX package testing if not have_cmd("kpsewhich"): logging.warning("WARNING: required program 'kpsewhich' (for LaTeX package checks) could not be found") else: ## Check minion font if opts.OUTPUT_FONT == "MINION": p = subprocess.Popen(["kpsewhich", "minion.sty"], stdout=subprocess.PIPE) p.wait() if p.returncode != 0: logging.warning('Warning: Using "--minion" requires minion.sty to be installed. Ignoring it.') opts.OUTPUT_FONT = "PALATINO" ## Check for HEP LaTeX packages # TODO: remove HEP-specifics/non-standards? for pkg in ["hepnames", "hepunits", "underscore"]: p = subprocess.Popen(["kpsewhich", "%s.sty" % pkg], stdout=subprocess.PIPE) p.wait() if p.returncode == 0: opts.LATEXPKGS.append(pkg) ## Check for Palatino old style figures and small caps if opts.OUTPUT_FONT == "PALATINO": p = subprocess.Popen(["kpsewhich", "ot1pplx.fd"], stdout=subprocess.PIPE) p.wait() if p.returncode == 0: opts.OUTPUT_FONT = "PALATINO_OSF" except Exception as e: logging.warning("Problem while testing for external packages. I'm going to try and continue without testing, but don't hold your breath...") def init_worker(): import signal signal.signal(signal.SIGINT, signal.SIG_IGN) ## Run rendering jobs datfiles = args plotword = "plots" if len(datfiles) > 1 else "plot" logging.info("Making %d %s" % (len(datfiles), plotword)) if opts.NO_SUBPROC: init_worker() for i, df in enumerate(datfiles): logging.info("Plotting %s (%d/%d remaining)" % (df, len(datfiles)-i, len(datfiles))) process_datfile(df) else: pool = multiprocessing.Pool(opts.NUM_THREADS, init_worker) try: for i, _ in enumerate(pool.imap(process_datfile, datfiles)): logging.info("Plotting %s (%d/%d remaining)" % (datfiles[i], len(datfiles)-i, len(datfiles))) pool.close() except KeyboardInterrupt: print("Caught KeyboardInterrupt, terminating workers") pool.terminate() except ValueError as e: print(e) print("Perhaps your .dat file is corrupt?") pool.terminate() pool.join() diff --git a/bin/rivet-cmphistos b/bin/rivet-cmphistos --- a/bin/rivet-cmphistos +++ b/bin/rivet-cmphistos @@ -1,490 +1,490 @@ #! /usr/bin/env python """\ %prog - generate histogram comparison plots USAGE: %prog [options] yodafile1[:'PlotOption1=Value':'PlotOption2=Value':...] [path/to/yodafile2 ...] [PLOT:Key1=Val1:...] where the plot options are described in the make-plots manual in the HISTOGRAM section. ENVIRONMENT: * RIVET_ANALYSIS_PATH: list of paths to be searched for plugin analysis libraries at runtime * RIVET_DATA_PATH: list of paths to be searched for data files """ from __future__ import print_function import rivet, yoda, sys, os rivet.util.check_python_version() rivet.util.set_process_name(os.path.basename(__file__)) class Plot(dict): "A tiny Plot object to help writing out the head in the .dat file" def __repr__(self): return "# BEGIN PLOT\n" + "\n".join("%s=%s" % (k,v) for k,v in self.items()) + "\n# END PLOT\n\n" def sanitiseString(s): #s = s.replace('_','\\_') #s = s.replace('^','\\^{}') #s = s.replace('$','\\$') s = s.replace('#','\\#') s = s.replace('%','\\%') return s def getCommandLineOptions(): "Parse command line options" from optparse import OptionParser, OptionGroup parser = OptionParser(usage=__doc__) parser.add_option('-o', '--outdir', dest='OUTDIR', default='.', help='write data files into this directory') parser.add_option("--hier-out", action="store_true", dest="HIER_OUTPUT", default=False, help="write output dat files into a directory hierarchy which matches the analysis paths") parser.add_option('--plotinfodir', dest='PLOTINFODIRS', action='append', default=['.'], help='directory which may contain plot header information (in addition ' 'to standard Rivet search paths)') parser.add_option("--no-rivet-refs", dest="RIVETREFS", action="store_false", default=True, help="don't use Rivet reference data files") # parser.add_option("--refid", dest="REF_ID", # default="REF", help="ID of reference data set (file path for non-REF data)") parser.add_option("--reftitle", dest="REFTITLE", default='Data', help="Reference data legend entry") parser.add_option("--pwd", dest="PATH_PWD", action="store_true", default=False, help="append the current directory (pwd) to the analysis/data search paths (cf. $RIVET_ANALYSIS/DATA_PATH)") parser.add_option("-v", "--verbose", dest="VERBOSE", action="store_true", default=False, help="produce debug output to the terminal") stygroup = OptionGroup(parser, "Plot style") stygroup.add_option("--linear", action="store_true", dest="LINEAR", default=False, help="plot with linear scale") stygroup.add_option("--errs", "--mcerrs", "--mc-errs", action="store_true", dest="MC_ERRS", default=False, help="show vertical error bars on the MC lines") stygroup.add_option("--no-ratio", action="store_false", dest="RATIO", default=True, help="disable the ratio plot") stygroup.add_option("--rel-ratio", action="store_true", dest="RATIO_DEVIATION", default=False, help="show the ratio plots scaled to the ref error") stygroup.add_option("--no-plottitle", action="store_true", dest="NOPLOTTITLE", default=False, help="don't show the plot title on the plot " "(useful when the plot description should only be given in a caption)") stygroup.add_option("--style", dest="STYLE", default="default", help="change plotting style: default|bw|talk") stygroup.add_option("-c", "--config", dest="CONFIGFILES", action="append", default=["~/.make-plots"], help="additional plot config file(s). Settings will be included in the output configuration.") parser.add_option_group(stygroup) selgroup = OptionGroup(parser, "Selective plotting") # selgroup.add_option("--show-single", dest="SHOW_SINGLE", choices=("no", "ref", "mc", "all"), # default="mc", help="control if a plot file is made if there is only one dataset to be plotted " # "[default=%default]. If the value is 'no', single plots are always skipped, for 'ref' and 'mc', " # "the plot will be written only if the single plot is a reference plot or an MC " # "plot respectively, and 'all' will always create single plot files.\n The 'ref' and 'all' values " # "should be used with great care, as they will also write out plot files for all reference " # "histograms without MC traces: combined with the -R/--rivet-refs flag, this is a great way to " # "write out several thousand irrelevant reference data histograms!") # selgroup.add_option("--show-mc-only", "--all", action="store_true", dest="SHOW_IF_MC_ONLY", # default=False, help="make a plot file even if there is only one dataset to be plotted and " # "it is an MC one. Deprecated and will be removed: use --show-single instead, which overrides this.") # # selgroup.add_option("-l", "--histogram-list", dest="HISTOGRAMLIST", # # default=None, help="specify a file containing a list of histograms to plot, in the format " # # "/ANALYSIS_ID/histoname, one per line, e.g. '/DELPHI_1996_S3430090/d01-x01-y01'.") selgroup.add_option("-m", "--match", action="append", help="only write out histograms whose $path/$name string matches these regexes. The argument " "may also be a text file.", dest="PATHPATTERNS") selgroup.add_option("-M", "--unmatch", action="append", help="exclude histograms whose $path/$name string matches these regexes", dest="PATHUNPATTERNS") parser.add_option_group(selgroup) return parser def getHistos(filelist): """Loop over all input files. Only use the first occurrence of any REF-histogram and the first occurrence in each MC file for every MC-histogram.""" refhistos, mchistos = {}, {} for infile in filelist: mchistos.setdefault(infile, {}) analysisobjects = yoda.read(infile, patterns=opts.PATHPATTERNS, unpatterns=opts.PATHUNPATTERNS) #print(analysisobjects) for path, ao in analysisobjects.items(): ## We can't plot non-histograms yet # TODO: support counter plotting with a faked x (or y) position and forced plot width/height if ao.type not in ("Counter", "Histo1D", "Histo2D", "Profile1D", "Profile2D", "Scatter1D", "Scatter2D", "Scatter3D"): continue ## Make a path object and ensure the path is in standard form try: aop = rivet.AOPath(path) except Exception as e: #print(e) print("Found analysis object with non-standard path structure:", path, "... skipping") continue ## We don't plot data objects with path components hidden by an underscore prefix if aop.istmp() or aop.israw(): continue ## Add it to the ref or mc paths, if this path isn't already known basepath = aop.basepath(keepref=False) if aop.isref() and basepath not in refhistos: ao.path = aop.varpath(keepref=False, defaultvarid=0) refhistos[basepath] = ao else: #if basepath not in mchistos[infile]: mchistos[infile].setdefault(basepath, {})[aop.varid(0)] = ao return refhistos, mchistos def getRivetRefData(anas=None): "Find all Rivet reference data files" refhistos = {} rivet_data_dirs = rivet.getAnalysisRefPaths() dirlist = [] for d in rivet_data_dirs: if anas is None: import glob dirlist.append(glob.glob(os.path.join(d, '*.yoda'))) else: dirlist.append([os.path.join(d, a+'.yoda') for a in anas]) for filelist in dirlist: # TODO: delegate to getHistos? for infile in filelist: analysisobjects = yoda.read(infile, patterns=opts.PATHPATTERNS, unpatterns=opts.PATHUNPATTERNS) for path, ao in analysisobjects.items(): aop = rivet.AOPath(ao.path) if aop.isref(): ao.path = aop.basepath(keepref=False) refhistos[ao.path] = ao return refhistos def parseArgs(args): """Look at the argument list and split it at colons, in order to separate the file names from the plotting options. Store the file names and file specific plotting options.""" filelist = [] plotoptions = {} for a in args: asplit = a.split(':') path = asplit[0] filelist.append(path) plotoptions[path] = [] has_title = False for i in range(1, len(asplit)): ## Add 'Title' if there is no = sign before math mode if '=' not in asplit[i] or ('$' in asplit[i] and asplit[i].index('$') < asplit[i].index('=')): asplit[i] = 'Title=%s' % asplit[i] if asplit[i].startswith('Title='): has_title = True plotoptions[path].append(asplit[i]) if path != "PLOT" and not has_title: plotoptions[path].append('Title=%s' % sanitiseString(os.path.basename( os.path.splitext(path)[0] )) ) return filelist, plotoptions def setStyle(ao, istyle, variation=False): """Set default plot styles (color and line width) colors borrowed from Google Ngrams""" # LINECOLORS = ['{[HTML]{EE3311}}', # red (Google uses 'DC3912') # '{[HTML]{3366FF}}', # blue # '{[HTML]{109618}}', # green # '{[HTML]{FF9900}}', # orange # '{[HTML]{990099}}'] # lilac LINECOLORS = ['red', 'blue', 'green', 'orange', 'lilac'] LINESTYLES = ['solid', 'dashed', 'dashdotted', 'dotted'] if opts.STYLE == 'talk': ao.setAnnotation('LineWidth', '1pt') if opts.STYLE == 'bw': LINECOLORS = ['black!90', 'black!50', 'black!30'] jc = istyle % len(LINECOLORS) c = LINECOLORS[jc] js = (istyle // len(LINECOLORS)) % len(LINESTYLES) s = LINESTYLES[js] ## If plotting a variation (i.e. band), fade the colour if variation: c += "!30" ao.setAnnotation('LineStyle', '%s' % s) ao.setAnnotation('LineColor', '%s' % c) def setOptions(ao, options): "Set arbitrary annotations" for opt in options: key, val = opt.split('=', 1) ao.setAnnotation(key, val) # TODO: move to rivet.utils def mkoutdir(outdir): "Function to make output directories" if not os.path.exists(outdir): try: os.makedirs(outdir) except: msg = "Can't make output directory '%s'" % outdir raise Exception(msg) if not os.access(outdir, os.W_OK): msg = "Can't write to output directory '%s'" % outdir raise Exception(msg) def mkOutput(hpath, aos, plot=None, special=None): """ Make the .dat file string. We can't use "yoda.writeFLAT(anaobjects, 'foobar.dat')" because the PLOT and SPECIAL blocks don't have a corresponding analysis object. """ output = '' if plot is not None: output += str(plot) if special is not None: output += "\n" output += "# BEGIN SPECIAL %s\n" % hpath output += special output += "# END SPECIAL\n\n" from io import StringIO sio = StringIO() yoda.writeFLAT(aos, sio) output += sio.getvalue() return output def writeOutput(output, h): "Choose output file name and dir" if opts.HIER_OUTPUT: hparts = h.strip("/").split("/", 1) ana = "_".join(hparts[:-1]) if len(hparts) > 1 else "ANALYSIS" outdir = os.path.join(opts.OUTDIR, ana) outfile = '%s.dat' % hparts[-1].replace("/", "_") else: hparts = h.strip("/").split("/") outdir = opts.OUTDIR outfile = '%s.dat' % "_".join(hparts) mkoutdir(outdir) outfilepath = os.path.join(outdir, outfile) f = open(outfilepath, 'w') f.write(output) f.close() #-------------------------------------------------------------------------------------------- if __name__ == '__main__': ## Command line parsing parser = getCommandLineOptions() opts, args = parser.parse_args() ## Add pwd to search paths if opts.PATH_PWD: rivet.addAnalysisLibPath(os.path.abspath(".")) rivet.addAnalysisDataPath(os.path.abspath(".")) ## Split the input file names and the associated plotting options ## given on the command line into two separate lists filelist, plotoptions = parseArgs(args) ## Remove the PLOT dummy file from the file list if "PLOT" in filelist: filelist.remove("PLOT") ## Check that the files exist for f in filelist: if not os.access(f, os.R_OK): print("Error: cannot read from %s" % f) sys.exit(1) ## Read the .plot files plotdirs = opts.PLOTINFODIRS + [os.path.abspath(os.path.dirname(f)) for f in filelist] plotparser = rivet.mkStdPlotParser(plotdirs, opts.CONFIGFILES) ## Create a list of all histograms to be plotted, and identify if they are 2D histos (which need special plotting) try: refhistos, mchistos = getHistos(filelist) except IOError as e: print("File reading error: ", e.strerror) exit(1) hpaths, h2ds = [], [] for aos in mchistos.values(): for p in aos.keys(): ps = rivet.stripOptions(p) if ps and ps not in hpaths: hpaths.append(ps) firstaop = aos[p][sorted(aos[p].keys())[0]] # TODO: Would be nicer to test via isHisto and dim or similar, or yoda.Scatter/Histo/Profile base classes if type(firstaop) in (yoda.Histo2D, yoda.Profile2D) and ps not in h2ds: h2ds.append(ps) ## Take reference data from the Rivet search paths, if there is not already if opts.RIVETREFS: try: refhistos2 = getRivetRefData() except IOError as e: print("File reading error: ", e.strerror) exit(1) refhistos2.update(refhistos) refhistos = refhistos2 ## Purge unmatched ref data entries to save memory keylist = list(refhistos.keys()) # can't modify for-loop target for refhpath in keylist: if refhpath not in hpaths: del refhistos[refhpath] ## Now loop over all MC histograms and plot them # TODO: factorize much of this into a rivet.utils mkplotfile(mchists, refhist, kwargs, is2d=False) function for hpath in hpaths: #print('Currently looking at', h) ## The analysis objects to be plotted anaobjects = [] ## List of histos to be drawn, to sync the legend and plotted lines mainlines = [] varlines = [] ## Is this a 2D histo? is2d = (hpath in h2ds) ## Will we be drawing a ratio plot? showratio = opts.RATIO and not is2d ## A Plot object to represent the PLOT section in the .dat file plot = Plot() if not is2d: plot['Legend'] = '1' plot['LogY'] = '1' headers = plotparser.getHeaders(hpath) if headers: plot.update(headers) # for key, val in headers.items(): # plot[key] = val if "PLOT" in plotoptions: for key_val in plotoptions["PLOT"]: key, val = [s.strip() for s in key_val.split("=",1)] plot[key] = val if opts.LINEAR: plot['LogY'] = '0' if opts.NOPLOTTITLE: plot['Title'] = '' if showratio and opts.RATIO_DEVIATION: plot['RatioPlotMode'] = 'deviation' if opts.STYLE == 'talk': plot['PlotSize'] = '8,6' elif opts.STYLE == 'bw' and showratio: plot['RatioPlotErrorBandColor'] = 'black!10' ## Get a special object, if there is one for this path special = plotparser.getSpecial(hpath) ## Handle reference data histogram, if there is one ratioreference, hasdataref = None, False if hpath in refhistos: hasdataref = True refdata = refhistos[hpath] refdata.setAnnotation('Title', opts.REFTITLE) if not is2d: refdata.setAnnotation('ErrorBars', '1') refdata.setAnnotation('PolyMarker', '*') refdata.setAnnotation('ConnectBins', '0') if showratio: ratioreference = hpath ## For 1D anaobjects.append(refdata) mainlines.append(hpath) ## For 2D if is2d: s = mkOutput(hpath, [refdata], plot, special) writeOutput(s, hpath) ## Loop over the MC files to plot all instances of the histogram styleidx = 0 for infile in filelist: if infile in mchistos: for xpath in sorted(mchistos[infile]): if rivet.stripOptions(xpath) != hpath: continue hmcs = mchistos[infile][xpath] ## For now, just plot all the different variation histograms (reversed, so [0] is on top) # TODO: calculate and plot an appropriate error band, somehow... for i in sorted(hmcs.keys(), reverse=True): iscanonical = (str(i) == "0") hmc = hmcs[i] ## Default linecolor, linestyle if not is2d: setStyle(hmc, styleidx, not iscanonical) if opts.MC_ERRS: hmc.setAnnotation('ErrorBars', '1') ## Plot defaults from .plot files histopts = plotparser.getHistogramOptions(hpath) if histopts: for key, val in histopts.items(): hmc.setAnnotation(key, val) ## Command line plot options setOptions(hmc, plotoptions[infile]) ## Set path attribute fullpath = "/"+infile+xpath if not iscanonical: fullpath += "["+str(i)+"]" hmc.setAnnotation('Path', fullpath) ## Add object / path to appropriate lists - if hmc.hasAnnotation("Title"): - hmc.setAnnotation("Title", hmc.annotation("Title") + - rivet.extractOptionString(xpath)) + #if hmc.hasAnnotation("Title"): + # hmc.setAnnotation("Title", hmc.annotation("Title") + + # rivet.extractOptionString(xpath)) anaobjects.append(hmc) if iscanonical: mainlines.append(fullpath) else: varlines.append(fullpath) if showratio and ratioreference is None and iscanonical: ratioreference = fullpath ## For 2D, plot each histo now (since overlay makes no sense) if is2d: s = mkOutput(hpath, [hmc], plot, special) writeOutput(s, fullpath) styleidx += 1 ## Finally render the combined plots; only show the first one if it's 2D # TODO: Only show the first *MC* one if 2D? if is2d: anaobjects = anaobjects[:1] ## Add final attrs to Plot plot['DrawOnly'] = ' '.join(varlines + mainlines).strip() plot['LegendOnly'] = ' '.join(mainlines).strip() if showratio and len(varlines + mainlines) > 1: plot['RatioPlot'] = '1' plot['RatioPlotReference'] = ratioreference if not hasdataref and "RatioPlotYLabel" not in plot: if plot.get('RatioPlotMode', '') == 'deviation': plot['RatioPlotYLabel'] = 'Deviation' #r'$\text{MC}-\text{MC}_\text{ref}$' else: plot['RatioPlotYLabel'] = 'Ratio' #r'$\text{MC}/\text{MC}_\text{ref}$' ## Make the output and write to file o = mkOutput(hpath, anaobjects, plot, special) writeOutput(o, hpath) diff --git a/bin/rivet-mkhtml b/bin/rivet-mkhtml --- a/bin/rivet-mkhtml +++ b/bin/rivet-mkhtml @@ -1,508 +1,507 @@ #! /usr/bin/env python """\ %prog [options] [ ...] [PLOT:Key1=Val1:...] Make web pages from histogram files written out by Rivet. You can specify multiple Monte Carlo YODA files to be compared in the same syntax as for rivet-cmphistos, i.e. including plotting options. Reference data, analysis metadata, and plot style information should be found automatically (if not, set the RIVET_ANALYSIS_PATH or similar variables appropriately). Any existing output directory will be overwritten. ENVIRONMENT: * RIVET_ANALYSIS_PATH: list of paths to be searched for plugin analysis libraries at runtime * RIVET_DATA_PATH: list of paths to be searched for data files """ from __future__ import print_function import rivet, sys, os rivet.util.check_python_version() rivet.util.set_process_name(os.path.basename(__file__)) import glob, shutil from subprocess import Popen,PIPE from optparse import OptionParser, OptionGroup parser = OptionParser(usage=__doc__) parser.add_option("-o", "--outputdir", dest="OUTPUTDIR", default="./rivet-plots", help="directory for Web page output") parser.add_option("-c", "--config", dest="CONFIGFILES", action="append", default=["~/.make-plots"], help="plot config file(s) to be used with rivet-cmphistos") parser.add_option("-n", "--num-threads", metavar="NUMTHREADS", dest="NUMTHREADS", type=int, default=None, help="request make-plots to use a specific number of threads") parser.add_option("--ignore-missing", dest="IGNORE_MISSING", action="store_true", default=False, help="ignore missing YODA files") parser.add_option("-i", "--ignore-unvalidated", dest="IGNORE_UNVALIDATED", action="store_true", default=False, help="ignore unvalidated analyses") # parser.add_option("--ref", "--refid", dest="REF_ID", # default=None, help="ID of reference data set (file path for non-REF data)") parser.add_option("--dry-run", help="don't actually do any plotting or HTML building", dest="DRY_RUN", action="store_true", default=False) parser.add_option("--no-cleanup", dest="NO_CLEANUP", action="store_true", default=False, help="keep plotting temporary directory") parser.add_option("--no-subproc", dest="NO_SUBPROC", action="store_true", default=False, help="don't use subprocesses to render the plots in parallel -- useful for debugging") parser.add_option("--pwd", dest="PATH_PWD", action="store_true", default=False, help="append the current directory (pwd) to the analysis/data search paths (cf. $RIVET_ANALYSIS_PATH)") stygroup = OptionGroup(parser, "Style options") stygroup.add_option("-t", "--title", dest="TITLE", default="Plots from Rivet analyses", help="title to be displayed on the main web page") stygroup.add_option("--reftitle", dest="REFTITLE", default="Data", help="legend entry for reference data") stygroup.add_option("--no-plottitle", dest="NOPLOTTITLE", action="store_true", default=False, help="don't show the plot title on the plot " "(useful when the plot description should only be given in a caption)") stygroup.add_option("-s", "--single", dest="SINGLE", action="store_true", default=False, help="display plots on single webpage.") stygroup.add_option("--no-ratio", dest="SHOW_RATIO", action="store_false", default=True, help="don't draw a ratio plot under each main plot.") stygroup.add_option("--errs", "--mcerrs", "--mc-errs", dest="MC_ERRS", action="store_true", default=False, help="plot error bars.") stygroup.add_option("--offline", dest="OFFLINE", action="store_true", default=False, help="generate HTML that does not use external URLs.") stygroup.add_option("--pdf", dest="VECTORFORMAT", action="store_const", const="PDF", default="PDF", help="use PDF as the vector plot format.") stygroup.add_option("--ps", dest="VECTORFORMAT", action="store_const", const="PS", default="PDF", help="use PostScript as the vector plot format. DEPRECATED") stygroup.add_option("--booklet", dest="BOOKLET", action="store_true", default=False, help="create booklet (currently only available for PDF with pdftk or pdfmerge).") stygroup.add_option("--font", dest="OUTPUT_FONT", choices="palatino,cm,times,helvetica,minion".split(","), default="palatino", help="choose the font to be used in the plots") stygroup.add_option("--palatino", dest="OUTPUT_FONT", action="store_const", const="palatino", default="palatino", help="use Palatino as font (default). DEPRECATED: Use --font") stygroup.add_option("--cm", dest="OUTPUT_FONT", action="store_const", const="cm", default="palatino", help="use Computer Modern as font. DEPRECATED: Use --font") stygroup.add_option("--times", dest="OUTPUT_FONT", action="store_const", const="times", default="palatino", help="use Times as font. DEPRECATED: Use --font") stygroup.add_option("--helvetica", dest="OUTPUT_FONT", action="store_const", const="helvetica", default="palatino", help="use Helvetica as font. DEPRECATED: Use --font") stygroup.add_option("--minion", dest="OUTPUT_FONT", action="store_const", const="minion", default="palatino", help="use Adobe Minion Pro as font. DEPRECATED: Use --font") parser.add_option_group(stygroup) selgroup = OptionGroup(parser, "Selective plotting") selgroup.add_option("-m", "--match", action="append", dest="PATHPATTERNS", default=[], help="only write out histograms whose $path/$name string matches any of these regexes") selgroup.add_option("-M", "--unmatch", action="append", dest="PATHUNPATTERNS", default=[], help="exclude histograms whose $path/$name string matches any of these regexes") selgroup.add_option(#"-a", #< these were confusing, and -m should be enough "--ana-match", action="append", dest="ANAPATTERNS", default=[], help="only write out histograms from analyses whose name matches any of these regexes") selgroup.add_option(#"-A", #< these were confusing, and -M should be enough "--ana-unmatch", action="append", dest="ANAUNPATTERNS", default=[], help="exclude histograms from analyses whose name matches any of these regexes") parser.add_option_group(selgroup) vrbgroup = OptionGroup(parser, "Verbosity control") vrbgroup.add_option("-v", "--verbose", help="add extra debug messages", dest="VERBOSE", action="store_true", default=False) parser.add_option_group(vrbgroup) opts, yodafiles = parser.parse_args() ## Add pwd to search paths if opts.PATH_PWD: rivet.addAnalysisLibPath(os.path.abspath(".")) rivet.addAnalysisDataPath(os.path.abspath(".")) ## Check that there are some arguments! if not yodafiles: print("Error: You need to specify some YODA files to be plotted!") sys.exit(1) ## Make output directory if not opts.DRY_RUN: if os.path.exists(opts.OUTPUTDIR) and not os.path.realpath(opts.OUTPUTDIR)==os.getcwd(): import shutil shutil.rmtree(opts.OUTPUTDIR) try: os.makedirs(opts.OUTPUTDIR) except: print("Error: failed to make new directory '%s'" % opts.OUTPUTDIR) sys.exit(1) ## Get set of analyses involved in the runs plotarg = None analyses = set() blocked_analyses = set() import yoda for yodafile in yodafiles: if yodafile.startswith("PLOT:"): plotarg = yodafile continue yodafilepath = os.path.abspath(yodafile.split(":")[0]) if not os.access(yodafilepath, os.R_OK): print("Error: cannot read from %s" % yodafilepath) if opts.IGNORE_MISSING: continue else: sys.exit(2) try: ## Note: we use -m/-M flags here as well as when calling rivet-cmphistos, to potentially speed this initial loading analysisobjects = yoda.read(yodafilepath, patterns=opts.PATHPATTERNS, unpatterns=opts.PATHUNPATTERNS) except IOError as e: print("File reading error: ", e.strerror) sys.exit(1) for path, ao in analysisobjects.items(): ## Make a path object and ensure the path is in standard form try: aop = rivet.AOPath(path) except Exception as e: #print(e) print("Found analysis object with non-standard path structure:", path, "... skipping") continue ## We don't plot data objects with path components hidden by an underscore prefix if aop.istmp() or aop.israw(): continue ## Identify analysis/histo name parts analysis = "ANALYSIS" if aop.basepathparts(keepref=False): - analysis = rivet.stripOptions(aop.basepathparts(keepref=False)[0]) - #< TODO: for compatibility with rivet-cmphistos... generalise? + analysis = rivet.stripOptions(aop.basepathparts(keepref=False)[0]) #< TODO: for compatibility with rivet-cmphistos... generalise? #analysis = "_".join(aop.dirnameparts(keepref=False)[:-1]) #< TODO: would this be nicer? Currently incompatible with rivet-cmphistos ## Optionally veto on analysis name pattern matching if analysis in analyses.union(blocked_analyses): continue import re matched = True if opts.ANAPATTERNS: matched = False for patt in opts.ANAPATTERNS: if re.match(patt, analysis) is not None: matched = True break if matched and opts.ANAUNPATTERNS: for patt in opts.ANAUNPATTERNS: if re.match(patt, analysis) is not None: matched = False break if matched: analyses.add(analysis) else: blocked_analyses.add(analysis) ## Sort analyses: group ascending by analysis name (could specialise grouping by collider), then ## descending by year, and finally descending by bibliographic archive ID code (INSPIRE first). def anasort(name): rtn = (1, name) if name.startswith("MC"): rtn = (99999999, name) else: stdparts = name.split("_") try: year = int(stdparts[1]) rtn = (0, stdparts[0], -year, 0) idcode = (0 if stdparts[2][0] == "I" else 1e10) - int(stdparts[2][1:]) rtn = (0, stdparts[0], -year, idcode) if len(stdparts) > 3: rtn += stdparts[3:] except: pass return rtn analyses = sorted(analyses, key=anasort) ## Uncomment to test analysis ordering on index page # print(analyses) # sys.exit(0) ## Run rivet-cmphistos to get plain .dat files from .yoda ## We do this here since it also makes the necessary directories ch_cmd = ["rivet-cmphistos"] if opts.MC_ERRS: ch_cmd.append("--mc-errs") if not opts.SHOW_RATIO: ch_cmd.append("--no-ratio") if opts.NOPLOTTITLE: ch_cmd.append("--no-plottitle") # if opts.REF_ID is not None: # ch_cmd.append("--refid=%s" % os.path.abspath(opts.REF_ID)) if opts.REFTITLE: ch_cmd.append("--reftitle=%s" % opts.REFTITLE ) if opts.PATHPATTERNS: for patt in opts.PATHPATTERNS: ch_cmd += ["-m", patt] #"'"+patt+"'"] if opts.PATHUNPATTERNS: for patt in opts.PATHUNPATTERNS: ch_cmd += ["-M", patt] #"'"+patt+"'"] ch_cmd.append("--hier-out") # TODO: Need to be able to override this: provide a --plotinfodir cmd line option? ch_cmd.append("--plotinfodir=%s" % os.path.abspath("../")) for af in yodafiles: yodafilepath = os.path.abspath(af.split(":")[0]) if af.startswith("PLOT:"): yodafilepath = "PLOT" elif not os.access(yodafilepath, os.R_OK): continue newarg = yodafilepath if ":" in af: newarg += ":" + af.split(":", 1)[1] # print(newarg) ch_cmd.append(newarg) ## Pass rivet-mkhtml -c args to rivet-cmphistos for configfile in opts.CONFIGFILES: configfile = os.path.abspath(os.path.expanduser(configfile)) if os.access(configfile, os.R_OK): ch_cmd += ["-c", configfile] if opts.VERBOSE: ch_cmd.append("--verbose") print("Calling rivet-cmphistos with the following command:") print(" ".join(ch_cmd)) ## Run rivet-cmphistos in a subdir, after fixing any relative paths in Rivet env vars if not opts.DRY_RUN: for var in ("RIVET_ANALYSIS_PATH", "RIVET_DATA_PATH", "RIVET_REF_PATH", "RIVET_INFO_PATH", "RIVET_PLOT_PATH"): if var in os.environ: abspaths = [os.path.abspath(p) for p in os.environ[var].split(":")] os.environ[var] = ":".join(abspaths) subproc = Popen(ch_cmd, cwd=opts.OUTPUTDIR, stdout=PIPE, stderr=PIPE) out, err = subproc.communicate() retcode = subproc.returncode if opts.VERBOSE or retcode != 0: print('Output from rivet-cmphistos:\n', out) if err : print('Errors from rivet-cmphistos:\n', err) if retcode != 0: print('Crash in rivet-cmphistos code = ', retcode, ' exiting') exit(retcode) ## Write web page containing all (matched) plots ## Make web pages first so that we can load it locally in ## a browser to view the output before all plots are made if not opts.DRY_RUN: style = ''' ''' ## Include MathJax configuration script = '' if not opts.OFFLINE: script = '''\ ''' ## A helper function for metadata LaTeX -> HTML conversion from rivet.util import htmlify ## A timestamp HTML fragment to be used on each page: import datetime timestamp = '

Generated at %s

\n' % datetime.datetime.now().strftime("%A, %d. %B %Y %I:%M%p") index = open(os.path.join(opts.OUTPUTDIR, "index.html"), "w") index.write('\n\n%s\n%s\n' % (opts.TITLE, style + script)) if opts.BOOKLET and opts.VECTORFORMAT == "PDF": index.write('

%s

\n\n' % opts.TITLE) else: index.write('

%s

\n\n' % opts.TITLE) if opts.SINGLE: ## Write table of contents index.write('
    \n') for analysis in analyses: summary = analysis ana = rivet.AnalysisLoader.getAnalysis(analysis) if ana: summary = "%s (%s)" % (ana.summary(), analysis) if opts.IGNORE_UNVALIDATED and ana.status() != "VALIDATED": continue index.write('
  • %s\n' % (analysis, htmlify(summary)) ) index.write('
\n') for analysis in analyses: references = [] summary = htmlify(analysis) description, inspireid, spiresid = None, None, None if analysis.find("_I") > 0: inspireid = analysis[analysis.rfind('_I')+2:len(analysis)] elif analysis.find("_S") > 0: spiresid = analysis[analysis.rfind('_S')+2:len(analysis)] ana = rivet.AnalysisLoader.getAnalysis(analysis) if ana: if ana.summary(): summary = htmlify("%s (%s)" % (ana.summary(), analysis)) references = ana.references() description = htmlify(ana.description()) spiresid = ana.spiresId() if opts.IGNORE_UNVALIDATED and ana.status().upper() != "VALIDATED": continue if opts.SINGLE: index.write('\n

%s

\n' % (analysis.encode("utf-8"), summary.encode("utf-8"))) else: index.write('\n

%s

\n' % (analysis.encode("utf-8"), summary.encode("utf-8"))) reflist = [] if inspireid: reflist.append('Inspire' % inspireid) reflist.append('HepData' % inspireid) elif spiresid: # elif ana.spiresId(): reflist.append('Inspire' % spiresid) reflist.append('HepData' % spiresid) reflist += references index.write('

%s

\n' % " | ".join(reflist)) if description: try: index.write('

%s

\n' % description) except UnicodeEncodeError as ue: print("Unicode error in analysis description for " + analysis + ": " + str(ue)) anapath = os.path.join(opts.OUTPUTDIR, analysis) if not opts.SINGLE: if not os.path.exists(anapath): os.makedirs(anapath) anaindex = open(os.path.join(anapath, "index.html"), 'w') anaindex.write('\n\n%s – %s\n%s\n\n' % (htmlify(opts.TITLE), analysis, style + script)) anaindex.write('

%s

\n' % htmlify(analysis)) anaindex.write('

Back to index

\n') if description: try: anaindex.write('

\n %s\n

\n' % description) except UnicodeEncodeError as ue: print("Unicode error in analysis description for " + analysis + ": " + str(ue)) else: anaindex = index datfiles = glob.glob("%s/*.dat" % anapath) #print(datfiles) anaindex.write('
\n') for datfile in sorted(datfiles): obsname = os.path.basename(datfile).replace(".dat", "") pngfile = obsname+".png" vecfile = obsname+"."+opts.VECTORFORMAT.lower() srcfile = obsname+".dat" if opts.SINGLE: pngfile = os.path.join(analysis, pngfile) vecfile = os.path.join(analysis, vecfile) srcfile = os.path.join(analysis, srcfile) anaindex.write('
\n') anaindex.write(' %s:
\n' % (analysis, obsname, srcfile, os.path.splitext(vecfile)[0]) ) anaindex.write(' \n' % (analysis, obsname, vecfile) ) anaindex.write(' \n' % pngfile ) anaindex.write(' \n') anaindex.write('
\n') anaindex.write('
\n') if not opts.SINGLE: anaindex.write('
%s\n
\n' % timestamp) anaindex.close() index.write('
%s\n' % timestamp) index.close() # http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python def which(program): import os def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None ## Run make-plots on all generated .dat files # sys.exit(0) mp_cmd = ["make-plots"] if opts.NUMTHREADS: mp_cmd.append("--num-threads=%d" % opts.NUMTHREADS) if opts.NO_CLEANUP: mp_cmd.append("--no-cleanup") if opts.NO_SUBPROC: mp_cmd.append("--no-subproc") if opts.VECTORFORMAT == "PDF": mp_cmd.append("--pdfpng") elif opts.VECTORFORMAT == "PS": mp_cmd.append("--pspng") if opts.OUTPUT_FONT: mp_cmd.append("--font=%s" % opts.OUTPUT_FONT) # if opts.OUTPUT_FONT.upper() == "PALATINO": # mp_cmd.append("--palatino") # if opts.OUTPUT_FONT.upper() == "CM": # mp_cmd.append("--cm") # elif opts.OUTPUT_FONT.upper() == "TIMES": # mp_cmd.append("--times") # elif opts.OUTPUT_FONT.upper() == "HELVETICA": # mp_cmd.append("--helvetica") # elif opts.OUTPUT_FONT.upper() == "MINION": # mp_cmd.append("--minion") datfiles = [] for analysis in analyses: anapath = os.path.join(opts.OUTPUTDIR, analysis) #print(anapath) anadatfiles = glob.glob("%s/*.dat" % anapath) datfiles += sorted(anadatfiles) if datfiles: mp_cmd += datfiles if opts.VERBOSE: mp_cmd.append("--verbose") print("Calling make-plots with the following options:") print(" ".join(mp_cmd)) if not opts.DRY_RUN: Popen(mp_cmd).wait() if opts.BOOKLET and opts.VECTORFORMAT=="PDF": if which("pdftk") is not None: bookletcmd = ["pdftk"] for analysis in analyses: anapath = os.path.join(opts.OUTPUTDIR, analysis) bookletcmd += sorted(glob.glob("%s/*.pdf" % anapath)) bookletcmd += ["cat", "output", "%s/booklet.pdf" % opts.OUTPUTDIR] print(bookletcmd) Popen(bookletcmd).wait() elif which("pdfmerge") is not None: bookletcmd = ["pdfmerge"] for analysis in analyses: anapath = os.path.join(opts.OUTPUTDIR, analysis) bookletcmd += sorted(glob.glob("%s/*.pdf" % anapath)) bookletcmd += ["%s/booklet.pdf" % opts.OUTPUTDIR] print(bookletcmd) Popen(bookletcmd).wait() else: print("Neither pdftk nor pdfmerge available --- not booklet output possible")