diff --git a/bin/Makefile.am b/bin/Makefile.am --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -1,34 +1,35 @@ bin_SCRIPTS = rivet-config rivet-buildplugin dist_bin_SCRIPTS = make-plots make-pgfplots EXTRA_DIST = RIVETPROGS = \ rivet \ rivet-mkanalysis \ - rivet-findid rivet-which \ + rivet-findid rivet-which \ + rivet-diffhepdata \ rivet-cmphistos rivet-mkhtml if ENABLE_PYEXT dist_bin_SCRIPTS += $(RIVETPROGS) else EXTRA_DIST += $(RIVETPROGS) endif ## bash completion if ENABLE_PYEXT dist_pkgdata_DATA = rivet-completion bashcomp_dir = $(DESTDIR)$(prefix)/etc/bash_completion.d install-data-local: if [[ -d "$(bashcomp_dir)" && -w "$(bashcomp_dir)" ]]; then \ $(install_sh_DATA) rivet-completion $(bashcomp_dir)/; fi uninstall-local: rm -f $(bashcomp_dir)/rivet-completion else EXTRA_DIST += rivet-completion endif ## No-Python Rivet program noinst_PROGRAMS = rivet-nopy rivet_nopy_SOURCES = rivet-nopy.cc rivet_nopy_CPPFLAGS = -I$(top_srcdir)/include $(AM_CPPFLAGS) rivet_nopy_LDFLAGS = -L$(HEPMCLIBPATH) rivet_nopy_LDADD = $(top_builddir)/src/libRivet.la -lHepMC diff --git a/bin/rivet-diffhepdata b/bin/rivet-diffhepdata new file mode 100755 --- /dev/null +++ b/bin/rivet-diffhepdata @@ -0,0 +1,147 @@ +#! /usr/bin/env python + +"""\ +rivet-diffhepdata [-h|--help] [-r|--rivet_input RIVET_INPUT] [-i|--inspire_id INSPIRE_ID] [-d|--hepdata_input HEPDATA_INPUT] [-o|--output OUTPUT] [-a|--all] [-l|--lower_count LOWER_COUNT] [-u|--upper_count UPPER_COUNT] [-f|--force_download] + +Check compatibility of a YODA reference data file, intended for inclusion in Rivet, with the YODA file downloaded from HEPData. +Specify either the INSPIRE_ID (to download the YODA file from HEPData) or the already-downloaded HEPDATA_INPUT. +Make the comparison using the yodadiff script distributed with YODA (https://yoda.hepforge.org/trac/browser/bin/yodadiff). +Optionally write the yodadiff output to a file OUTPUT instead of stdout. +Note: If option --all is specified, check compatibility of *all* YODA reference data files, distributed with Rivet, +with the YODA files downloaded from HEPData in HEPDATA_INPUT and write the yodadiff to the output directory OUTPUT. +Optional arguments to loop over a range of INSPIRE IDs (for testing) and to force re-download of HEPData .yoda files. + +Examples: + rivet-diffhepdata -r ATLAS_2017_I1614149.yoda -i 1614149 + rivet-diffhepdata -r ATLAS_2017_I1614149.yoda -d HEPData-ins1614149-v2-yoda.yoda + + # Specify Rivet directory, HEPData and yodadiff output directories, and redirect output to a text file. + rivet-diffhepdata --all -r ../Rivet/analyses -d HEPDataYoda -o YodaDiffOutput > rivet-diffhepdata-all.txt + + # Loop over only the first 10 INSPIRE IDs (sorted by Rivet analysis name) and force HEPData re-download. + rivet-diffhepdata --all -l 1 -u 10 -f # for Rivet .yoda files located in a subdirectory of current path +""" + +from __future__ import print_function +import os, imp, glob, requests, argparse + +from rivet import hepdatautils + +parser = argparse.ArgumentParser(description='Check compatibility of YODA reference data files with HEPData for all Rivet analyses') +parser.add_argument('-r', '--rivet_input', nargs=1, default=['.'], help='input YODA file (or search directory if using --all flag)') +parser.add_argument('-i', '--inspire_id', nargs=1, default=[0], type=int, help='INSPIRE ID (to download the YODA file from HEPData') +parser.add_argument('-d', '--hepdata_input', nargs=1, default=[None], help='input HEPData reference file/directory for downloading files') +parser.add_argument('-o', '--output', nargs=1, default=[None], help='name of output file/directory to write yodadiff output') +parser.add_argument('-a', '--all', action='store_true', help='check all available files, unless lower and upper count are also specified') +parser.add_argument('-l', '--lower_count', nargs=1, type=int, default=[0], help='minimum count for loop over INSPIRE IDs') +parser.add_argument('-u', '--upper_count', nargs=1, type=int, default=[0], help='maximum count for loop over INSPIRE IDs') +parser.add_argument('-f', '--force_download', action='store_true', help='force re-download of HEPData .yoda files') +args = parser.parse_args() +rivet_input = args.rivet_input[0] +inspire_id = args.inspire_id[0] +hepdata_input = args.hepdata_input[0] +output_name = args.output[0] +do_all = args.all +lower_count = args.lower_count[0] +upper_count = args.upper_count[0] +force_download = args.force_download +print('Arguments specified:') +print(' rivet_input={}'.format(rivet_input)) +print(' inspire_id={}'.format(inspire_id)) +print(' hepdata_input={}'.format(hepdata_input)) +print(' output={}'.format(output_name)) +print(' all={}'.format(do_all)) +print(' lower_count={}'.format(lower_count)) +print(' upper_count={}'.format(upper_count)) +print(' force_download={}'.format(force_download)) + +def find(filename, path): + """ Function to return first matching 'filename' by walking the directory tree top-down from 'path'. """ + for root, dirs, files in os.walk(path): + if filename in files: + return os.path.join(root, filename) + +if do_all: # check multiple files + if output_name == None: + output_name = '.' + if hepdata_input == None: + hepdata_input = '.' + + # Create output directories if they don't already exist. + if not os.path.exists(hepdata_input): + os.makedirs(hepdata_input) + if not os.path.exists(output_name): + os.makedirs(output_name) + + # Get mapping between INSPIRE IDs and Rivet analysis names. + response = requests.get('http://rivet.hepforge.org/analyses.json') + analyses = response.json() + + # Loop over INSPIRE IDs and collect compatible and incompatible analyses. + # Sort analyses dict by the Rivet analysis name. + compatible_analyses = [] + incompatible_analyses = [] + for count, inspire_id in enumerate(sorted(analyses, key=analyses.get)): + + # Loop over a restricted range of INSPIRE IDs (useful for testing). + if count + 1 < lower_count or (upper_count and count + 1 > upper_count): + continue + + print() + num_analyses = len(analyses[inspire_id]) + if num_analyses != 1: + compatible = False + print('Multiple (or zero) Rivet analyses {} for INSPIRE ID {}.'.format(analyses[inspire_id], inspire_id)) + else: + analysis = analyses[inspire_id][0] + + yodafile = find(analysis + '.yoda', rivet_input) + outfile = os.path.join(output_name, analysis + '.txt') + + # Check if .yoda file has already been downloaded from HEPData, otherwise download. Override if force_download. + matched_files = glob.glob(os.path.join(hepdata_input, 'HEPData-ins' + inspire_id + '-v*-yoda.yoda')) + if not matched_files or force_download: + try: + yodafile_from_hepdata = hepdatautils.download_from_hepdata(inspire_id, analysis) + except: + print('Download from HEPData failed for Rivet analysis {}.'.format(analysis)) + if yodafile_from_hepdata: + os.rename(yodafile_from_hepdata, os.path.join(hepdata_input, yodafile_from_hepdata)) + yodafile_from_hepdata = os.path.join(hepdata_directory, yodafile_from_hepdata) + else: + print('Missing YODA reference data file from HEPData for Rivet analysis {}.'.format(analysis)) + else: + yodafile_from_hepdata = sorted(matched_files)[-1] # sort in case of multiple versions (works for v1 up to v9) + + # Run yodadiff between the .yoda files from Rivet and HEPData. + if yodafile: + compatible = hepdatautils.compare_with_hepdata(yodafile, yodafile_from_hepdata=yodafile_from_hepdata, output=outfile) + else: + print('Missing YODA reference data file from Rivet for analysis {}.'.format(analysis)) + compatible = False + + if compatible: + print('YODA reference data files from Rivet and HEPData are compatible!') + compatible_analyses.append(inspire_id) + else: + print('YODA reference data files from Rivet and HEPData are NOT compatible!') + incompatible_analyses.append(inspire_id) + + # Print out some summary information. + print() + print('Compatible Rivet analyses: {}'.format([analyses[inspire_id] for inspire_id in compatible_analyses])) + print('Incompatible Rivet analyses: {}'.format([analyses[inspire_id] for inspire_id in incompatible_analyses])) + print() + print('Of {:d} Rivet analyses in {}, {:d} ({:.1f}%) were compatible and {:d} ({:.1f}%) were incompatible.'.format( + len(analyses), rivet_input, + len(compatible_analyses), 100.*len(compatible_analyses)/len(analyses), + len(incompatible_analyses), 100.*len(incompatible_analyses)/len(analyses))) + +else: + # check single file only + compatible = hepdatautils.compare_with_hepdata(rivet_input, inspire_id, hepdata_input, output_name) + if compatible: + print('YODA reference data files from Rivet and HEPData are compatible!') + else: + print('YODA reference data files from Rivet and HEPData are NOT compatible!') + diff --git a/pyext/rivet/Makefile.am b/pyext/rivet/Makefile.am --- a/pyext/rivet/Makefile.am +++ b/pyext/rivet/Makefile.am @@ -1,22 +1,23 @@ EXTRA_DIST = \ __init__.py \ + hepdatautils.py \ spiresbib.py util.py \ plotinfo.py aopaths.py \ core.pyx rivet.pxd \ core.cpp MAINTAINERCLEANFILES = core.cpp BUILT_SOURCES = core.cpp if WITH_CYTHON RIVETINCLUDE = $(top_srcdir)/include/Rivet/ core.cpp: core.pyx rivet.pxd $(RIVETINCLUDE)/Analysis.hh $(RIVETINCLUDE)/AnalysisHandler.hh $(RIVETINCLUDE)/AnalysisLoader.hh $(RIVETINCLUDE)/Run.hh cython $(srcdir)/core.pyx --cplus \ -I $(srcdir) -I $(srcdir)/include \ -I $(builddir) -I $(builddir)/include \ -o $@ else core.cpp: @echo "Not (re)generating core.cpp since Cython is not installed" endif diff --git a/pyext/rivet/hepdatautils.py b/pyext/rivet/hepdatautils.py new file mode 100755 --- /dev/null +++ b/pyext/rivet/hepdatautils.py @@ -0,0 +1,77 @@ +from __future__ import print_function + +def compare_with_hepdata(yodafile, inspire_id=0, yodafile_from_hepdata=None, output=None): + """\ + Compare a YODA reference data file, intended for inclusion in Rivet, with the YODA file downloaded from HEPData. + Make the comparison using the yodadiff script distributed with YODA (https://yoda.hepforge.org/trac/browser/bin/yodadiff). + + :param yodafile: name of YODA reference data file (intended for inclusion in Rivet) + :param inspire_id: INSPIRE ID (to download the YODA file from HEPData) + :param yodafile_from_hepdata: name of YODA file already downloaded from HEPData + :param output: name of output file for yodadiff instead of stdout (note: -o option of yodadiff not implemented) + :return: True or False depending on whether YODA files are compatible + """ + + if inspire_id: + # Extract Rivet analysis name from last pathname component of yodafile (and discard extension). + import os + tail = os.path.split(yodafile)[1] + rivet_analysis_name = os.path.splitext(tail)[0] + yodafile_from_hepdata = download_from_hepdata(inspire_id, rivet_analysis_name) + + if yodafile_from_hepdata: + print('Comparing {} with {}'.format(yodafile, yodafile_from_hepdata)) + import subprocess + args = ['yodadiff', yodafile, yodafile_from_hepdata] + if output: + print('Writing output of "{}" to {}'.format(' '.join(args), output)) + with open(output, 'w') as out: + # Note: -o|--output option of yodadiff is not implemented. + exit_status = subprocess.call(args, stdout=out, stderr=out) + else: + exit_status = subprocess.call(args) + if exit_status: + return False + else: + print('No YODA file from HEPData specified!') + return False + + return True + + +def download_from_hepdata(inspire_id, rivet_analysis_name=None): + """\ + Download the latest YODA reference data file from HEPData identified by the INSPIRE ID. + Optionally pass the Rivet analysis name to write in the YODA file exported from HEPData. + + :param inspire_id: INSPIRE ID + :param rivet_analysis_name: Rivet analysis name to override default + :return: name of YODA file downloaded from HEPData + """ + + import tarfile, io, os, requests + + hdurl = 'https://hepdata.net/record/ins{}'.format(inspire_id) + payload = {'format': 'yoda', 'rivet': rivet_analysis_name} + response = requests.get(hdurl, params=payload) + if response.history: + print('Downloading from {}'.format(response.history[0].url)) + else: + print('Downloading from {}'.format(response.url)) + + if response.status_code != 200: + print('Download failed ({} {}), does {} exist?'.format(response.status_code, response.reason, hdurl)) + return None + + try: + tar = tarfile.open(mode='r:gz', fileobj=io.BytesIO(response.content)) + tar.extractall() + yodafile_from_hepdata = tar.getnames()[0] + os.chmod(yodafile_from_hepdata, 0644) + except tarfile.TarError as e: + print('Error reading tarfile ({})'.format(str(e))) + return None + + print('Downloaded {}'.format(yodafile_from_hepdata)) + return yodafile_from_hepdata +