[Rivet] Rivet "ATLAS style" ?

Gavin Hesketh gavin.hesketh at ucl.ac.uk
Tue Jan 12 14:47:15 GMT 2016


sorry for the spam, had some time waiting for a meeting so worked out 
how to do all of these...

It would still be good to include a fuller legend entry in each 
reference .plot file (eg specifying the experiment and com energy), then 
we could just use eg a --ATLAS (or --CMS if they want a different set of 
defaults) option in rivet-mkhtml.

Anyway, see attached patched versions of rivet-mkhtml and 
rivet-cmphistos to implement the points I suggested below. It would be 
great to have these changes included in an upcoming release.

cheers,
Gavin

On 12/01/16 13:58, Gavin Hesketh wrote:
> Hi rivets,
> just been through a round of making Rivet plots acceptable to the ATLAS
> approval procedure, and have a couple of feature requests as a result!
>
> - option to change the legend entry for the reference data. As far as I
> could see this is hard coded in rivet-cmphistos, line 366
> https://rivet.hepforge.org/trac/browser/bin/rivet-cmphistos
> and I hacked my loval version to make this work.
> ideally we could call eg
> rivet-mkhtml --ref_title="ATLAS data $\sqrt{s}=7$~TeV" .....
> and/or set this property in the .plot reference file associated with
> each analysis (eg all CMS routines might also want "CMS data..." in the
> legend?)
>
> - add the option to use helvetica as a font in rivet-mkhtml - this is
> trivial, as it is already an option in make-plots; I've attached a
> patched rivet-mkhtl to implement this.
>
> - actually found a related bug!
> https://rivet.hepforge.org/trac/browser/bin/rivet-mkhtml
> L393 should be
> elif opts.OUTPUT_FONT == "MINION":
> also fixed in the attached rivet-mkhtml
>
> - finally, atlas does not like histogram titles... this can be fixed in
> each of the .plot files, but an option like --notitles to rivet_mkhtml
> would also be great.
>
>
> Of course, all of these could be combined into a --ATLAS option for
> rivet-mkhtml :)
>
> thanks!
> Gavin
>
>
> _______________________________________________
> Rivet mailing list
> Rivet at projects.hepforge.org
> https://www.hepforge.org/lists/listinfo/rivet
>
-------------- next part --------------
#! /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.
"""

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.iteritems()) + "\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("--no-rivet-refs", dest="RIVETREFS", action="store_false",
                      default=True, help="don't use Rivet reference data files")
    parser.add_option("--ref-title", dest="REFTITLE",
                      default='Data', help="Reference data legend entry")
    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)')

    stygroup = OptionGroup(parser, "Plot style")
    # stygroup.add_option("--refid", dest="REF_ID",
    #                     default="REF", help="ID of reference data set (file path for non-REF data)")
    stygroup.add_option("--linear", action="store_true", dest="LINEAR",
                        default=False, help="plot with linear scale")
    stygroup.add_option("--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 = {}
    import re
    re_path = re.compile(r"^(/REF)?(/[^\[\]\@\#]+)(\[\d+\])?$")
    for infile in filelist:
        mchistos.setdefault(infile, {})
        analysisobjects = yoda.read(infile, patterns=opts.PATHPATTERNS, unpatterns=opts.PATHUNPATTERNS)
        for path, ao in analysisobjects.iteritems():
            m = re_path.match(path)
            if m is None:
                print "Found analysis object with non-standard path structure:", path
                continue
            isref = bool(m.group(1))
            basepath = m.group(2)
            index = m.group(3)[1:-1] if m.group(3) else 0

            ## Conventionally don't plot data objects whose names start with an underscore
            if os.path.basename(basepath).startswith("_"):
                continue
            if isref and not refhistos.has_key(basepath):
                # TODO: Update to the brave new world without /REF prefixes
                if ao.path.startswith("/REF"):
                    ao.path = ao.path[4:]
                refhistos[basepath] = ao
                continue
            #if not mchistos[infile].has_key(basepath):
            mchistos[infile].setdefault(basepath, {})[index] = 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.iteritems():
                # TODO: Update to the brave new world without /REF prefixes
                if ao.path.startswith("/REF"):
                    ao.path = ao.path[4:]
                    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 xrange(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" % h
        output += special
        output += "# END SPECIAL\n\n"

    from cStringIO 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()

    ## 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)
    refhistos, mchistos = getHistos(filelist)
    hpaths, h2ds = [], []
    for aos in mchistos.values():
        for p in aos.keys():
            if p and p not in hpaths:
                hpaths.append(p)
            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 p not in h2ds:
                h2ds.append(p)

    ## Take reference data from the Rivet search paths, if there is not already
    if opts.RIVETREFS:
        refhistos2 = getRivetRefData()
        refhistos2.update(refhistos)
        refhistos = refhistos2

    ## Purge unmatched ref data entries to save memory
    for refhpath in refhistos.keys():
        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'
        for key, val in plotparser.getHeaders(hpath).iteritems():
            plot[key] = val
        if plotoptions.has_key("PLOT"):
            for key_val in plotoptions["PLOT"]:
                key, val = [s.strip() for s in key_val.split("=")]
                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 refhistos.has_key(hpath):
            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 mchistos.has_key(infile) and mchistos[infile].has_key(hpath):
                hmcs = mchistos[infile][hpath]
                ## 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
                    for key, val in plotparser.getHistogramOptions(hpath).iteritems():
                        hmc.setAnnotation(key, val)
                    ## Command line plot options
                    setOptions(hmc, plotoptions[infile])
                    ## Set path attribute
                    fullpath = "/"+infile+hpath
                    if not iscanonical:
                        fullpath += "["+str(i)+"]"
                    hmc.setAnnotation('Path', fullpath)
                    ## Add object / path to appropriate lists
                    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:
                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)
-------------- next part --------------
#! /usr/bin/env python

"""\
%prog [options] <yodafile1> [<yodafile2> <yodafile3>...] [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.
"""

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="./plots", help="directory for webpage 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)

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("--ref-title", dest="REFTITLE",
                    default="Data", help="legend entry for reference data on")
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("--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.")
stygroup.add_option("--booklet", dest="BOOKLET", action="store_true",
                    default=False, help="create booklet (currently only available for PDF with pdftk).")
stygroup.add_option("--palatino", dest="OUTPUT_FONT", action="store_const", const="PALATINO", default="PALATINO",
                    help="use Palatino as font (default).")
stygroup.add_option("--cm", dest="OUTPUT_FONT", action="store_const", const="CM", default="PALATINO",
                    help="use Computer Modern as font.")
stygroup.add_option("--times", dest="OUTPUT_FONT", action="store_const", const="TIMES", default="PALATINO",
                    help="use Times as font.")
stygroup.add_option("--helvetica", dest="OUTPUT_FONT", action="store_const", const="HELVETICA", default="PALATINO",
                    help="use Helvetica as font.")
stygroup.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.")
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", "--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", "--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()


## 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)
    ## Note: 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)

    for path, ao in analysisobjects.iteritems():
        ## If regexes have been provided, only add analyses which match and don't unmatch
        pathparts = path.strip('/').split('/')
        ## Skip hidden paths, which start with an underscore
        if pathparts[0].startswith("_"):
            continue
        ## Strip a leading /REF prefix
        # TODO: Remove when Rivet has migrated to marking ref data via attributes
        if pathparts[0] == "REF":
            del pathparts[0]
        ## Identify analysis/histo name parts
        analysis = "ANALYSIS"
        if len(pathparts) > 1:
            analysis = pathparts[0] #< TODO: for compatibility with rivet-cmphistos... generalise?
            #analysis = "_".join(pathparts[:-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.search(patt, analysis) is not None:
                    matched = True
                    break
        if matched and opts.ANAUNPATTERNS:
            for patt in opts.ANAUNPATTERNS:
                if re.search(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("--ref-title=%s" % opts.REFTITLE )
if opts.PATHPATTERNS:
    for patt in opts.PATHPATTERNS:
        ch_cmd += ["-m", patt]
if opts.PATHUNPATTERNS:
    for patt in opts.PATHUNPATTERNS:
        ch_cmd += ["-M", 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_REF_PATH", "RIVET_INFO_PATH", "RIVET_PLOT_PATH"):
        if var in os.environ:
            abspaths = map(os.path.abspath, 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) and out:
        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 = '''<style>
      html { font-family: sans-serif; }
      img { border: 0; }
      a { text-decoration: none; font-weight: bold; }
    </style>
    '''

    ## Include MathJax configuration
    script = ''
    if not opts.OFFLINE:
        script = '''\
        <script type="text/x-mathjax-config">
        MathJax.Hub.Config({
          tex2jax: {inlineMath: [["$","$"]]}
        });
        </script>
        <script type="text/javascript"
          src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
        </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 = '<p>Generated at %s</p>\n' % datetime.datetime.now().strftime("%A, %d. %B %Y %I:%M%p")

    index = open(os.path.join(opts.OUTPUTDIR, "index.html"), "w")
    index.write('<html>\n<head>\n<title>%s</title>\n%s</head>\n<body>' % (opts.TITLE, style + script))
    if opts.BOOKLET and opts.VECTORFORMAT == "PDF":
        index.write('<h2><a href="booklet.pdf">%s</a></h2>\n\n' % opts.TITLE)
    else:
        index.write('<h2>%s</h2>\n\n' % opts.TITLE)

    if opts.SINGLE:
        ## Write table of contents
        index.write('<ul>\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('<li><a href="#%s">%s</a>\n' % (analysis, htmlify(summary)) )
        index.write('</ul>\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<h3 style="clear:left; padding-top:2em;"><a name="%s">%s</a></h3>\n' % (analysis, summary))
        else:
            index.write('\n<h3><a href="%s/index.html" style="text-decoration:none;">%s</a></h3>\n' % (analysis, summary))

        reflist = []
        if inspireid:
            reflist.append('<a href="http://inspirehep.net/record/%s">Spires</a>' % inspireid)
        elif spiresid:
            reflist.append('<a href="http://durpdg.dur.ac.uk/cgi-bin/spiface/hep/www?irn+%s">Spires</a>' % spiresid)
        reflist += references
        index.write('<p>%s</p>\n' % " | ".join(reflist))

        if description:
            index.write('<p style="font-size:smaller;">%s</p>\n' % description)

        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('<html>\n<head>\n<title>%s – %s</title>\n%s</head>\n<body>\n' %
                           (htmlify(opts.TITLE), analysis, style + script))
            anaindex.write('<h3>%s</h3>\n' % htmlify(analysis))
            anaindex.write('<p><a href="../index.html">Back to index</a></p>\n')
            if description:
                anaindex.write('<p>\n  %s\n</p>\n' % description)
        else:
            anaindex = index

        datfiles = glob.glob("%s/*.dat" % anapath)
        #print datfiles

        anaindex.write('<div style="float:none; overflow:auto; width:100%">\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('  <div style="float:left; font-size:smaller; font-weight:bold;">\n')
            anaindex.write('    <a href="#%s-%s">⚓</a><a href="%s">&#8984</a> %s:<br/>\n' %
                           (analysis, obsname, srcfile, os.path.splitext(vecfile)[0]) )
            anaindex.write('    <a name="%s-%s"><a href="%s">\n' % (analysis, obsname, vecfile) )
            anaindex.write('      <img src="%s">\n' % pngfile )
            anaindex.write('    </a></a>\n')
            anaindex.write('  </div>\n')
        anaindex.write('</div>\n')

        if not opts.SINGLE:
            anaindex.write('<div style="float:none">%s</body>\n</html></div>\n' % timestamp)
            anaindex.close()
    index.write('<br>%s</body>\n</html>' % timestamp)
    index.close()


## 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.VECTORFORMAT == "PDF":
    mp_cmd.append("--pdfpng")
elif opts.VECTORFORMAT == "PS":
    mp_cmd.append("--pspng")
if opts.OUTPUT_FONT == "CM":
    mp_cmd.append("--cm")
elif opts.OUTPUT_FONT == "TIMES":
    mp_cmd.append("--times")
elif opts.OUTPUT_FONT == "HELVETICA":
    mp_cmd.append("--helvetica")
elif opts.OUTPUT_FONT == "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":
            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()


More information about the Rivet mailing list