diff --git a/include/HEJ/HDF5Writer.hh b/include/HEJ/HDF5Writer.hh new file mode 100644 index 0000000..01103bf --- /dev/null +++ b/include/HEJ/HDF5Writer.hh @@ -0,0 +1,55 @@ +/** \file + * \brief Contains the EventWriter for HDF5 Output. + * + * The output format is specified in arXiv:1905.05120. + * + * \authors The HEJ collaboration (see AUTHORS for details) + * \date 2019 + * \copyright GPLv2 or later + */ +#pragma once + +#include <memory> +#include <string> + +#include "HEJ/EventWriter.hh" + +namespace LHEF { + class HEPRUP; +} + +namespace HEJ{ + class Event; + + //! This is an event writer specifically for HDF5 output. + /** + * \internal Implementation note: This uses the pimpl ("pointer to + * implementation") idiom. HDF5 support is optional. Without pimpl, + * we would have to specify whether HDF5 is available via the + * preprocessor whenever this header is included. We don't want to + * burden users of the HEJ library (for example the HEJ fixed-order + * generator) with those details + */ + class HDF5Writer: public EventWriter{ + public: + //! Constructor + /** + * @param file name of the output file + * @param heprup general process information + */ + HDF5Writer(std::string const & file, LHEF::HEPRUP heprup); + ~HDF5Writer() override = default; + + //! Write an event to the output file + void write(Event const & ev) override; + + private: + struct HDF5WriterImpl; + struct HDF5WriterImplDeleter { + void operator()(HDF5WriterImpl* p); + }; + + std::unique_ptr<HDF5WriterImpl, HDF5WriterImplDeleter> impl_; + }; + +} diff --git a/include/HEJ/output_formats.hh b/include/HEJ/output_formats.hh index ef2a471..d1c08bd 100644 --- a/include/HEJ/output_formats.hh +++ b/include/HEJ/output_formats.hh @@ -1,40 +1,42 @@ /** \file * \brief Defines formats for output to event files * * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <stdexcept> #include <string> namespace HEJ{ //! Supported event file formats enum class FileFormat{ Les_Houches, /*!< Les Houches Output */ HepMC3, /*!< HepMC3 Output */ HepMC2, /*!< HepMC2 Output */ + HDF5, /*!< HDF5 Output */ HepMC=HepMC3 /*!< HepMC3 Output */ }; //! Convert a file format to a string inline std::string to_string(FileFormat f){ switch(f){ case FileFormat::Les_Houches: return "Les Houches"; case FileFormat::HepMC2: return "HepMC2"; case FileFormat::HepMC3: return "HepMC3"; + case FileFormat::HDF5: return "HDF5"; default: throw std::logic_error("unhandled file format"); } } //! Output file specification struct OutputFile{ std::string name; /**< Output File Name */ FileFormat format; /**< Output File Format */ }; } diff --git a/src/HDF5Writer.cc b/src/HDF5Writer.cc new file mode 100644 index 0000000..9c84e7f --- /dev/null +++ b/src/HDF5Writer.cc @@ -0,0 +1,33 @@ +/** + * \authors The HEJ collaboration (see AUTHORS for details) + * \date 2019 + * \copyright GPLv2 or later + */ +#include "HEJ/HDF5Writer.hh" + +#include <cassert> + +#include "LHEF/LHEF.h" + +namespace HEJ{ + + class HDF5Writer::HDF5WriterImpl{}; + + HDF5Writer::HDF5Writer(std::string const &, LHEF::HEPRUP){ + throw std::invalid_argument{ + "Failed to create HDF5 writer: " + "HEJ 2 was built without HDF5 support" + }; + } + + void HDF5Writer::write(Event const &){ + assert(false); + } + +} + +namespace HEJ { + void HDF5Writer::HDF5WriterImplDeleter::operator()(HDF5WriterImpl* p) { + delete p; + } +} diff --git a/src/YAMLreader.cc b/src/YAMLreader.cc index 720e528..943c4f0 100644 --- a/src/YAMLreader.cc +++ b/src/YAMLreader.cc @@ -1,553 +1,555 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019 * \copyright GPLv2 or later */ #include "HEJ/YAMLreader.hh" #include <algorithm> #include <iostream> #include <limits> #include <map> #include <string> #include <unordered_map> #include <vector> #include <dlfcn.h> #include "HEJ/ScaleFunction.hh" #include "HEJ/event_types.hh" #include "HEJ/output_formats.hh" #include "HEJ/Constants.hh" namespace HEJ{ class Event; namespace{ //! Get YAML tree of supported options /** * The configuration file is checked against this tree of options * in assert_all_options_known. */ YAML::Node const & get_supported_options(){ const static YAML::Node supported = [](){ YAML::Node supported; static const auto opts = { "trials", "min extparton pt", "max ext soft pt fraction", "scales", "scale factors", "max scale ratio", "import scales", "log correction", "event output", "analysis", "vev", "regulator parameter", "max events" }; // add subnodes to "supported" - the assigned value is irrelevant for(auto && opt: opts) supported[opt] = ""; for(auto && jet_opt: {"min pt", "algorithm", "R"}){ supported["resummation jets"][jet_opt] = ""; supported["fixed order jets"][jet_opt] = ""; } for(auto && opt: {"mt", "use impact factors", "include bottom", "mb"}){ supported["Higgs coupling"][opt] = ""; } for(auto && opt: {"name", "seed"}){ supported["random generator"][opt] = ""; } for(auto && opt: {"FKL", "unordered", "extremal qqx", "central qqx", "non-resummable"}){ supported["event treatment"][opt] = ""; } for(auto && particle_type: {"Higgs", "W", "Z"}){ for(auto && particle_opt: {"mass", "width"}){ supported["particle properties"][particle_type][particle_opt] = ""; } } for(auto && opt: {"type", "trials", "max deviation"}){ supported["unweight"][opt] = ""; } return supported; }(); return supported; } fastjet::JetAlgorithm to_JetAlgorithm(std::string const & algo){ using namespace fastjet; static const std::map<std::string, fastjet::JetAlgorithm> known = { {"kt", kt_algorithm}, {"cambridge", cambridge_algorithm}, {"antikt", antikt_algorithm}, {"cambridge for passive", cambridge_for_passive_algorithm}, {"plugin", plugin_algorithm} }; const auto res = known.find(algo); if(res == known.end()){ throw std::invalid_argument("Unknown jet algorithm \"" + algo + "\""); } return res->second; } EventTreatment to_EventTreatment(std::string const & name){ static const std::map<std::string, EventTreatment> known = { {"reweight", EventTreatment::reweight}, {"keep", EventTreatment::keep}, {"discard", EventTreatment::discard} }; const auto res = known.find(name); if(res == known.end()){ throw std::invalid_argument("Unknown event treatment \"" + name + "\""); } return res->second; } WeightType to_weight_type(std::string const & setting){ if(setting == "weighted") return WeightType::weighted; if(setting =="resummation") return WeightType::unweighted_resum; if(setting =="partial") return WeightType::partially_unweighted; throw std::invalid_argument{"Unknown weight type \"" + setting + "\""}; } } // namespace anonymous namespace detail{ void set_from_yaml(fastjet::JetAlgorithm & setting, YAML::Node const & yaml){ setting = to_JetAlgorithm(yaml.as<std::string>()); } void set_from_yaml(EventTreatment & setting, YAML::Node const & yaml){ setting = to_EventTreatment(yaml.as<std::string>()); } void set_from_yaml(ParticleID & setting, YAML::Node const & yaml){ setting = to_ParticleID(yaml.as<std::string>()); } void set_from_yaml(WeightType & setting, YAML::Node const & yaml){ setting = to_weight_type(yaml.as<std::string>()); } } // namespace detail JetParameters get_jet_parameters( YAML::Node const & node, std::string const & entry ){ assert(node); JetParameters result; fastjet::JetAlgorithm jet_algo = fastjet::antikt_algorithm; double R; set_from_yaml_if_defined(jet_algo, node, entry, "algorithm"); set_from_yaml(R, node, entry, "R"); result.def = fastjet::JetDefinition{jet_algo, R}; set_from_yaml(result.min_pt, node, entry, "min pt"); return result; } RNGConfig to_RNGConfig( YAML::Node const & node, std::string const & entry ){ assert(node); RNGConfig result; set_from_yaml(result.name, node, entry, "name"); set_from_yaml_if_defined(result.seed, node, entry, "seed"); return result; } ParticleProperties get_particle_properties( YAML::Node const & node, std::string const & entry, std::string const & boson ){ ParticleProperties result; set_from_yaml(result.mass, node, entry, boson, "mass"); set_from_yaml(result.width, node, entry, boson, "width"); return result; } EWConstants get_ew_parameters(YAML::Node const & node){ EWConstants result; double vev; set_from_yaml(vev, node, "vev"); result.set_vevWZH(vev, get_particle_properties(node, "particle properties", "W"), get_particle_properties(node, "particle properties", "Z"), get_particle_properties(node, "particle properties", "Higgs") ); return result; } HiggsCouplingSettings get_Higgs_coupling( YAML::Node const & node, std::string const & entry ){ assert(node); static constexpr double mt_max = 2e4; #ifndef HEJ_BUILD_WITH_QCDLOOP if(node[entry]){ throw std::invalid_argument{ "Higgs coupling settings require building HEJ 2 " "with QCDloop support" }; } #endif HiggsCouplingSettings settings; set_from_yaml_if_defined(settings.mt, node, entry, "mt"); set_from_yaml_if_defined(settings.mb, node, entry, "mb"); set_from_yaml_if_defined(settings.include_bottom, node, entry, "include bottom"); set_from_yaml_if_defined(settings.use_impact_factors, node, entry, "use impact factors"); if(settings.use_impact_factors){ if(settings.mt != std::numeric_limits<double>::infinity()){ throw std::invalid_argument{ "Conflicting settings: " "impact factors may only be used in the infinite top mass limit" }; } } else{ // huge values of the top mass are numerically unstable settings.mt = std::min(settings.mt, mt_max); } return settings; } FileFormat to_FileFormat(std::string const & name){ static const std::map<std::string, FileFormat> known = { {"Les Houches", FileFormat::Les_Houches}, {"HepMC", FileFormat::HepMC}, {"HepMC2", FileFormat::HepMC2}, - {"HepMC3", FileFormat::HepMC3} + {"HepMC3", FileFormat::HepMC3}, + {"HDF5", FileFormat::HDF5} }; const auto res = known.find(name); if(res == known.end()){ throw std::invalid_argument("Unknown file format \"" + name + "\""); } return res->second; } std::string extract_suffix(std::string const & filename){ size_t separator = filename.rfind('.'); if(separator == filename.npos) return {}; return filename.substr(separator + 1); } FileFormat format_from_suffix(std::string const & filename){ const std::string suffix = extract_suffix(filename); if(suffix == "lhe") return FileFormat::Les_Houches; if(suffix == "hepmc") return FileFormat::HepMC; if(suffix == "hepmc3") return FileFormat::HepMC3; if(suffix == "hepmc2") return FileFormat::HepMC2; + if(suffix == "hdf5") return FileFormat::HDF5; throw std::invalid_argument{ "Can't determine format for output file \"" + filename + "\"" }; } void assert_all_options_known( YAML::Node const & conf, YAML::Node const & supported ){ if(!conf.IsMap()) return; if(!supported.IsMap()) throw invalid_type{"must not have sub-entries"}; for(auto const & entry: conf){ const auto name = entry.first.as<std::string>(); if(! supported[name]) throw unknown_option{name}; /* check sub-options, e.g. 'resummation jets: min pt' * we don't check analysis sub-options * those depend on the analysis being used and should be checked there * similar for "import scales" */ if(name != "analysis" && name != "import scales"){ try{ assert_all_options_known(conf[name], supported[name]); } catch(unknown_option const & ex){ throw unknown_option{name + ": " + ex.what()}; } catch(invalid_type const & ex){ throw invalid_type{name + ": " + ex.what()}; } } } } } // namespace HEJ namespace YAML { Node convert<HEJ::OutputFile>::encode(HEJ::OutputFile const & outfile) { Node node; node[to_string(outfile.format)] = outfile.name; return node; }; bool convert<HEJ::OutputFile>::decode(Node const & node, HEJ::OutputFile & out) { switch(node.Type()){ case NodeType::Map: { YAML::const_iterator it = node.begin(); out.format = HEJ::to_FileFormat(it->first.as<std::string>()); out.name = it->second.as<std::string>(); return true; } case NodeType::Scalar: out.name = node.as<std::string>(); out.format = HEJ::format_from_suffix(out.name); return true; default: return false; } } } // namespace YAML namespace HEJ{ namespace detail{ void set_from_yaml(OutputFile & setting, YAML::Node const & yaml){ setting = yaml.as<OutputFile>(); } } namespace{ void update_fixed_order_jet_parameters( JetParameters & fixed_order_jets, YAML::Node const & yaml ){ if(!yaml["fixed order jets"]) return; set_from_yaml_if_defined( fixed_order_jets.min_pt, yaml, "fixed order jets", "min pt" ); fastjet::JetAlgorithm algo = fixed_order_jets.def.jet_algorithm(); set_from_yaml_if_defined(algo, yaml, "fixed order jets", "algorithm"); double R = fixed_order_jets.def.R(); set_from_yaml_if_defined(R, yaml, "fixed order jets", "R"); fixed_order_jets.def = fastjet::JetDefinition{algo, R}; } // like std::stod, but throw if not the whole string can be converted double to_double(std::string const & str){ std::size_t pos; const double result = std::stod(str, &pos); if(pos < str.size()){ throw std::invalid_argument(str + " is not a valid double value"); } return result; } using EventScale = double (*)(Event const &); void import_scale_functions( std::string const & file, std::vector<std::string> const & scale_names, std::unordered_map<std::string, EventScale> & known ) { auto handle = dlopen(file.c_str(), RTLD_NOW); char * error = dlerror(); if(error != nullptr) throw std::runtime_error{error}; for(auto const & scale: scale_names) { void * sym = dlsym(handle, scale.c_str()); error = dlerror(); if(error != nullptr) throw std::runtime_error{error}; known.emplace(scale, reinterpret_cast<EventScale>(sym)); } } auto get_scale_map( YAML::Node const & yaml ) { std::unordered_map<std::string, EventScale> scale_map; scale_map.emplace("H_T", H_T); scale_map.emplace("max jet pperp", max_jet_pt); scale_map.emplace("jet invariant mass", jet_invariant_mass); scale_map.emplace("m_j1j2", m_j1j2); if(yaml["import scales"]) { if(! yaml["import scales"].IsMap()) { throw invalid_type{"Entry 'import scales' is not a map"}; } for(auto const & import: yaml["import scales"]) { const auto file = import.first.as<std::string>(); const auto scale_names = import.second.IsSequence() ?import.second.as<std::vector<std::string>>() :std::vector<std::string>{import.second.as<std::string>()}; import_scale_functions(file, scale_names, scale_map); } } return scale_map; } // simple (as in non-composite) scale functions /** * An example for a simple scale function would be H_T, * H_T/2 is then composite (take H_T and then divide by 2) */ ScaleFunction parse_simple_ScaleFunction( std::string const & scale_fun, std::unordered_map<std::string, EventScale> const & known ) { assert( scale_fun.empty() || (!std::isspace(scale_fun.front()) && !std::isspace(scale_fun.back())) ); const auto it = known.find(scale_fun); if(it != end(known)) return {it->first, it->second}; try{ const double scale = to_double(scale_fun); return {scale_fun, FixedScale{scale}}; } catch(std::invalid_argument const &){} throw std::invalid_argument{"Unknown scale choice: \"" + scale_fun + "\""}; } std::string trim_front(std::string const & str){ const auto new_begin = std::find_if( begin(str), end(str), [](char c){ return ! std::isspace(c); } ); return std::string(new_begin, end(str)); } std::string trim_back(std::string str){ size_t pos = str.size() - 1; // use guaranteed wrap-around behaviour to check whether we have // traversed the whole string for(; pos < str.size() && std::isspace(str[pos]); --pos) {} str.resize(pos + 1); // note that pos + 1 can be 0 return str; } ScaleFunction parse_ScaleFunction( std::string const & scale_fun, std::unordered_map<std::string, EventScale> const & known ){ assert( scale_fun.empty() || (!std::isspace(scale_fun.front()) && !std::isspace(scale_fun.back())) ); // parse from right to left => a/b/c gives (a/b)/c const size_t delim = scale_fun.find_last_of("*/"); if(delim == scale_fun.npos){ return parse_simple_ScaleFunction(scale_fun, known); } const std::string first = trim_back(std::string{scale_fun, 0, delim}); const std::string second = trim_front(std::string{scale_fun, delim+1}); if(scale_fun[delim] == '/'){ return parse_ScaleFunction(first, known) / parse_ScaleFunction(second, known); } else{ assert(scale_fun[delim] == '*'); return parse_ScaleFunction(first, known) * parse_ScaleFunction(second, known); } } EventTreatMap get_event_treatment( YAML::Node const & node, std::string const & entry ){ using namespace event_type; EventTreatMap treat { {no_2_jets, EventTreatment::discard}, {bad_final_state, EventTreatment::discard}, {FKL, EventTreatment::discard}, {unob, EventTreatment::discard}, {unof, EventTreatment::discard}, {qqxexb, EventTreatment::discard}, {qqxexf, EventTreatment::discard}, {qqxmid, EventTreatment::discard}, {non_resummable, EventTreatment::discard} }; set_from_yaml(treat.at(FKL), node, entry, "FKL"); set_from_yaml(treat.at(unob), node, entry, "unordered"); treat.at(unof) = treat.at(unob); set_from_yaml(treat.at(qqxexb), node, entry, "extremal qqx"); treat.at(qqxexf) = treat.at(qqxexb); set_from_yaml(treat.at(qqxmid), node, entry, "central qqx"); set_from_yaml(treat.at(non_resummable), node, entry, "non-resummable"); if(treat[non_resummable] == EventTreatment::reweight){ throw std::invalid_argument{"Cannot reweight Fixed Order events"}; } return treat; } Config to_Config(YAML::Node const & yaml){ try{ assert_all_options_known(yaml, get_supported_options()); } catch(unknown_option const & ex){ throw unknown_option{std::string{"Unknown option '"} + ex.what() + "'"}; } Config config; config.resummation_jets = get_jet_parameters(yaml, "resummation jets"); config.fixed_order_jets = config.resummation_jets; update_fixed_order_jet_parameters(config.fixed_order_jets, yaml); set_from_yaml_if_defined(config.min_extparton_pt, yaml, "min extparton pt"); if(config.min_extparton_pt!=0) std::cerr << "WARNING: \"min extparton pt\" is deprecated." << " Please use \"max ext soft pt fraction\" instead.\n"; set_from_yaml( config.max_ext_soft_pt_fraction, yaml, "max ext soft pt fraction" ); // Sets the standard value, then changes this if defined config.regulator_lambda=CLAMBDA; set_from_yaml_if_defined(config.regulator_lambda, yaml, "regulator parameter"); set_from_yaml_if_defined(config.max_events, yaml, "max events"); set_from_yaml(config.trials, yaml, "trials"); config.weight_type = WeightType::weighted; set_from_yaml_if_defined(config.weight_type, yaml, "unweight", "type"); if(config.weight_type == WeightType::partially_unweighted) { config.unweight_config = PartialUnweightConfig{}; set_from_yaml( config.unweight_config->trials, yaml, "unweight", "trials" ); set_from_yaml( config.unweight_config->max_dev, yaml, "unweight", "max deviation" ); } else if(yaml["unweight"]) { for(auto && opt: {"trials", "max deviation"}) { if(yaml["unweight"][opt]) { throw std::invalid_argument{ "'unweight: " + std::string{opt} + "' " "is only supported if 'unweight: type' is set to 'partial'" }; } } } set_from_yaml(config.log_correction, yaml, "log correction"); config.treat = get_event_treatment(yaml, "event treatment"); set_from_yaml_if_defined(config.output, yaml, "event output"); config.rng = to_RNGConfig(yaml, "random generator"); set_from_yaml_if_defined(config.analysis_parameters, yaml, "analysis"); config.scales = to_ScaleConfig(yaml); config.ew_parameters = get_ew_parameters(yaml); config.Higgs_coupling = get_Higgs_coupling(yaml, "Higgs coupling"); return config; } } // namespace anonymous ScaleConfig to_ScaleConfig(YAML::Node const & yaml){ ScaleConfig config; auto scale_funs = get_scale_map(yaml); std::vector<std::string> scales; set_from_yaml(scales, yaml, "scales"); config.base.reserve(scales.size()); std::transform( begin(scales), end(scales), std::back_inserter(config.base), [scale_funs](auto const & entry){ return parse_ScaleFunction(entry, scale_funs); } ); set_from_yaml_if_defined(config.factors, yaml, "scale factors"); config.max_ratio = std::numeric_limits<double>::infinity(); set_from_yaml_if_defined(config.max_ratio, yaml, "max scale ratio"); return config; } Config load_config(std::string const & config_file){ try{ return to_Config(YAML::LoadFile(config_file)); } catch(...){ std::cerr << "Error reading " << config_file << ":\n "; throw; } } } // namespace HEJ diff --git a/src/make_writer.cc b/src/make_writer.cc index 5f87f01..10a71b0 100644 --- a/src/make_writer.cc +++ b/src/make_writer.cc @@ -1,36 +1,41 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019 * \copyright GPLv2 or later */ #include "HEJ/make_writer.hh" #include "HEJ/exceptions.hh" #include "HEJ/HepMC2Writer.hh" #include "HEJ/HepMC3Writer.hh" +#include "HEJ/HDF5Writer.hh" #include "HEJ/LesHouchesWriter.hh" namespace HEJ{ std::unique_ptr<EventWriter> make_format_writer( FileFormat format, std::string const & outfile, LHEF::HEPRUP const & heprup ){ switch(format){ case FileFormat::Les_Houches: return std::unique_ptr<EventWriter>{ new LesHouchesWriter{outfile, heprup} }; case FileFormat::HepMC2: return std::unique_ptr<EventWriter>{ new HepMC2Writer{outfile, heprup} }; case FileFormat::HepMC3: return std::unique_ptr<EventWriter>{ new HepMC3Writer{outfile, heprup} }; + case FileFormat::HDF5: + return std::unique_ptr<EventWriter>{ + new HDF5Writer{outfile, heprup} + }; default: throw std::logic_error("unhandled file format"); } } }