Page MenuHomeHEPForge

No OneTemporary

diff --git a/config_generator/examples/pure_jets/Readme.md b/config_generator/examples/pure_jets/Readme.md
index 76e9d09..b830517 100644
--- a/config_generator/examples/pure_jets/Readme.md
+++ b/config_generator/examples/pure_jets/Readme.md
@@ -1,73 +1,73 @@
High-energy resummation for multijet production
===============================================
This example shows how to produce a state-of-the-art HEJ prediction
for the process p p => jets.
Prerequisites
-------------
You need HEJ, HEJFOG, and HEJC installed. It is assumed that HEJ was
compiled with Rivet support; otherwise you should remove the `analysis`
entry from `config_main.yml` and potentially add an event output file
instead.
Running the example
-------------------
-To generate the configuration files inside the `config_dir`
-subdirectory, run
+To generate the configuration files for 10 statistically independent
+runs inside the `config_dir` subdirectory, run
- HEJC config_main.yml config_generators.yml config_dir
+ HEJC --runs 10 config_main.yml config_generators.yml config_dir
The configuration assumes that you want a state-of-the-art
prediction. If that is not the case, you can remove the `Sherpa2NLO`
entry in `config_generators.yml` and move the higher jet
multiplicities from the `Sherpa2` entry to the `HEJFOG` entry to save
computing time.
The next steps are to change the working directory to
`config_dir/run_0` and run
Sherpa -f Run_2j.dat
HEJ config_HEJ.yml sherpa_events.lhe
mv HEJ.yoda HEJ_2j.yoda
HEJ config_HEJ_NLO_2j.yml sherpa_events.lhe
mv HEJ.yoda HEJ_NLO_2j.yoda
Instead of on-disk event files, you can instead use named pipes by
running `mkfifo sherpa_events.lhe` first. In that case you will have
to run Sherpa in the background (appending `&` to the command) and run
it again before the NLO HEJ run.
Then repeat for the higher jet multiplicities, but without the NLO HEJ
run for more than 3 jets.
Then, run
Sherpa -f Run_2j_lowpt.dat
HEJ config_HEJ_lowpt.yml sherpa_events.lhe
mv HEJ.yoda HEJ_2j_lowpt.yoda
HEJ config_HEJ_NLO_2j_lowpt.yml sherpa_events.lhe
mv HEJ.yoda HEJ_NLO_2j_lowpt.yoda
and again repeat for the higher multiplicities.
For the highest multiplicities, run
HEJFOG config_HEJFOG_7j.yml
HEJ config_HEJ_with_HEJFOG.yml HEJFO.lhe
mv HEJ.yoda HEJ_7j.yoda
and similar for even higher jet multiplicities. Finally, run
Sherpa -f RUN_NLO_2j.dat
Add all parts together with
yodastack -o HEJ.yoda HEJ_?j.yoda HEJ_?j_lowpt.yoda
yodastack -o HEJ_NLO.yoda HEJ_NLO_?j.yoda HEJ_NLO_?j_lowpt.yoda
Repeat all of the above for the remaining run directories (`run_1`,
`run_2`, etc.) until you have enough statistics. You can use
`yodamerge` to combine the runs.
diff --git a/config_generator/examples/pure_jets/config_main.yml b/config_generator/examples/pure_jets/config_main.yml
index 766bf45..c9e6f4b 100644
--- a/config_generator/examples/pure_jets/config_main.yml
+++ b/config_generator/examples/pure_jets/config_main.yml
@@ -1,124 +1,121 @@
-## Number of runs (with independent random seeds)
-runs: 10
-
## Scattering process
process: p p => jets
## Particle beam
beam:
energy: 6500
particles: [p, p]
## PDF set according to LHAPDF
pdf: NNPDF31_nlo_as_0118
jets: # jet properties
min pt: 30 # minimum jet transverse momentum
algorithm: antikt # jet clustering algorithm
R: 0.4 # jet R parameter
max rapidity: 4.5
## Number of attempted resummation phase space points for each input event
trials: 10
## Treatment of he various event classes
## the supported settings are: reweight, keep, discard, abort
## non-resummable, unknown, and invalid events cannot be reweighted
event treatment:
FKL: reweight
unordered: reweight
extremal qqbar: reweight
central qqbar: reweight
non-resummable: keep
## Central scale choice or choices
#
## multiple scales are allowed, e.g.
# scales: [125, max jet pperp, H_T/2, 2*jet invariant mass, m_j1j2]
scales: H_T/2
## Factors by which the central scales should be multiplied
## renormalisation and factorisation scales are varied independently
scale factors: [0.5, 0.7071, 1, 1.41421, 2]
## Maximum ratio between renormalisation and factorisation scale
max scale ratio: 2.0001
## Import scale setting functions
#
# import scales:
# lib_my_scales.so: [scale0,scale1]
## Unweighting setting
## remove to obtain weighted events
# unweight:
# # type of unweighting (one of 'weighted', 'resummation', 'partial')
# type: partial
# trials: 10000
# max deviation: 0
## Event output files
#
# the supported formats are
# - Les Houches (suffix .lhe)
# - HepMC2 (suffix .hepmc2)
# - HepMC3 (suffix .hepmc3 or .hepmc)
# - HDF5 (suffix .hdf5)
#
## An output file's format is deduced either automatically from the suffix
## or from an explicit specification, e.g.
## - Les Houches: outfile
#
# event output:
# - HEJ.lhe
# - HEJ_events.hepmc
## Analyses
#
analyses:
## Rivet analysis
- rivet: MC_JETS # rivet analysis name
output: HEJ # name of the yoda files, ".yoda" and scale suffix will be added
## Custom analysis
# - plugin: /path/to/libmyanalysis.so
# my analysis parameter: some value
## Selection of random number generator and seed
## The choices are
## - mixmax (seed is an integer)
## - ranlux64 (seed is a filename containing parameters)
random generator:
name: mixmax
# seed: 1
## Whether or not to include higher order logs
log correction: false
## Vacuum expectation value
vev: 246.2196508
## Properties of the weak gauge bosons
particle properties:
Higgs:
mass: 125
width: 0.004165
W:
mass: 80.385
width: 2.085
Z:
mass: 91.187
width: 2.495
## Parameters for Higgs-gluon couplings
## This requires compilation with QCDloop
#
# Higgs coupling:
# use impact factors: false
# mt: 174
# include bottom: true
# mb: 4.7
# # Tolerance towards numerical inaccuracies in input momenta
# off-shell tolerance: 0.01
diff --git a/config_generator/include/HEJC/Config.hh b/config_generator/include/HEJC/Config.hh
index f853701..8dd63a0 100644
--- a/config_generator/include/HEJC/Config.hh
+++ b/config_generator/include/HEJC/Config.hh
@@ -1,44 +1,43 @@
/**
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2023
* \copyright GPLv2 or later
*
* HEJC configuration
*/
#pragma once
#include <cstdint>
#include <memory>
#include "HEJC/Process.hh"
#include "HEJC/Generators.hh"
#include "HEJC/Range.hh"
#include "yaml-cpp/yaml.h"
namespace HEJC {
struct GeneratorConfig {
Generator generator;
Range<std::uint32_t> multiplicities;
YAML::Node config;
};
// TODO: code duplication with HEJFOG
struct Beam{
double energy{};
std::array<HEJ::ParticleID, 2> particles{{
HEJ::pid::proton, HEJ::pid::proton
}};
};
struct Config {
- std::uint64_t runs;
Process process;
Beam beam;
std::string pdf;
YAML::Node hej_config;
std::vector<GeneratorConfig> fo_configs;
};
Config into_Config(YAML::Node && yaml);
}
diff --git a/config_generator/include/HEJC/HEJC.hh b/config_generator/include/HEJC/HEJC.hh
index 7e91b4e..e404721 100644
--- a/config_generator/include/HEJC/HEJC.hh
+++ b/config_generator/include/HEJC/HEJC.hh
@@ -1,137 +1,141 @@
/**
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2023
* \copyright GPLv2 or later
*
* Contains the main class representing a run of the HEJC program.
*/
#pragma once
#include <cstdint>
#include <filesystem>
#include <iterator>
#include <memory>
#include <random>
#include <type_traits>
#include <yaml-cpp/yaml.h>
#include "HEJC/Config.hh"
#include "HEJC/Process.hh"
namespace HEJC {
namespace detail {
template<class Iterator>
YAML::Node combine_yaml_files(Iterator begin, Iterator end) {
YAML::Node res;
for(auto it = begin; it != end; ++it) {
const std::filesystem::path file{*it};
YAML::Node yaml = YAML::LoadFile(file);
if(!yaml.IsMap()) {
throw std::invalid_argument{
file.string() + " does not contain a YAML map"
};
}
for(auto && entry: std::move(yaml)) {
res[std::move(entry.first)] = std::move(entry.second);
}
}
return res;
}
}
//! Main class for writing HEJ and fixed-order generator configurations
class HEJC {
public:
//! Construct with the settings in `config_file`
explicit HEJC(std::filesystem::path const & config_file);
//! Construct with the combined settings from a range of input files
template<class Iterator,
// ensure that Iterator points to something that can be converted to a path
std::enable_if_t<
std::is_constructible_v<
std::filesystem::path,
typename std::iterator_traits<Iterator>::value_type
>,
bool
> = true
>
HEJC(Iterator begin, Iterator end):
HEJC{detail::combine_yaml_files(begin, end)}
{}
//! Construct with the given YAML settings
explicit HEJC(YAML::Node && config);
//! Construct with the given settings
explicit HEJC(Config const & config);
+ //! Random number generator type
+ using Rng = std::ranlux48;
+
//! Write configuration files to the directory `target_dir`
- void write_config(std::filesystem::path const & target_dir) const;
+ void write_config(
+ std::filesystem::path const & target_dir,
+ Rng & rng
+ ) const;
private:
- using Rng = std::ranlux48;
-
void write_HEJ(
std::filesystem::path const & target_dir,
Rng & rng
) const;
void write_HEJ_default(
std::filesystem::path const & target_dir,
Rng & rng
) const;
void write_HEJ_lowpt(
std::filesystem::path const & target_dir,
Rng & rng
) const;
void write_HEJ_with_HEJFOG(
std::filesystem::path const & target_dir,
Rng & rng
) const;
void write_HEJ_NLO(
std::filesystem::path const & target_dir,
std::uint32_t njets,
Rng & rng
) const;
void write_HEJ_NLO_lowpt(
std::filesystem::path const & target_dir,
std::uint32_t njets,
Rng & rng
) const;
void write_FO_config(
GeneratorConfig const & conf,
std::filesystem::path const & target_dir,
Rng & rng
) const;
void write_HEJFOG(
Range<std::uint32_t> multiplicities,
YAML::Node const & config,
std::filesystem::path const & target_dir,
Rng & rng
) const;
void write_Sherpa2(
Range<std::uint32_t> multiplicities,
YAML::Node const & config,
std::filesystem::path const & target_dir,
Rng & rng
) const;
void write_Sherpa2NLO(
Range<std::uint32_t> multiplicities,
YAML::Node const & config,
std::filesystem::path const & target_dir,
Rng & rng
) const;
YAML::Node gen_Sherpa2_config(YAML::Node const & base) const;
std::string gen_Sherpa2_jet_def(
std::string njets,
double min_jet_pt
) const;
std::uint64_t runs_;
Process process_;
std::string pdf_;
Beam beam_;
YAML::Node hej_config_;
std::vector<GeneratorConfig> fo_configs_;
};
}
diff --git a/config_generator/include/HEJC/Options.hh b/config_generator/include/HEJC/Options.hh
new file mode 100644
index 0000000..3bb9999
--- /dev/null
+++ b/config_generator/include/HEJC/Options.hh
@@ -0,0 +1,97 @@
+/**
+ * \authors The HEJ collaboration (see AUTHORS for details)
+ * \date 2024
+ * \copyright GPLv2 or later
+ *
+ * Command-line options
+ */
+#pragma once
+
+#include <HEJ/exceptions.hh>
+#include <cstdint>
+#include <filesystem>
+#include <stdexcept>
+#include <vector>
+
+#include "cxxopts.hpp"
+
+namespace HEJC {
+ namespace detail {
+ namespace fs = std::filesystem;
+
+ inline bool can_write_to(fs::path out_dir) {
+ fs::file_type type = fs::status(out_dir).type();
+ // first follow symbolic links
+ while(type == fs::file_type::symlink) {
+ out_dir = fs::read_symlink(out_dir);
+ type = fs::status(out_dir).type();
+ }
+ switch(type){
+ case fs::file_type::directory:
+ case fs::file_type::not_found:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ struct Opt {
+ Opt(int argc, char** argv):
+ help{false}
+ {
+ namespace fs = std::filesystem;
+ cxxopts::Options options{"HEJC", "HEJ configuration file generator"};
+ options.add_options()
+ ("r,runs", "Number of independent runs", cxxopts::value<std::uint64_t>())
+ (
+ "copy-dir",
+ "Directories to be copied to each run directory",
+ cxxopts::value<std::vector<fs::path>>()
+ )
+ ("h,help", "Print usage")
+ (
+ "filenames",
+ "configuration files followed by output directory",
+ cxxopts::value<std::vector<fs::path>>()
+ )
+ ;
+ options.parse_positional({"filenames"});
+ auto opt = options.parse(argc, argv);
+ if(opt.count("help")) {
+ help = true;
+ help_message = options.help();
+ return;
+ }
+ if(opt.count("runs") == 0) {
+ throw HEJ::missing_option{"Option `--runs` not set"};
+ }
+ runs = opt["runs"].as<std::uint64_t>();
+ config_files = opt["filenames"].as<std::vector<fs::path>>();
+ if(config_files.size() < 2) {
+ throw std::invalid_argument{
+ "Need at least one config file followed by the output directory"
+ };
+ }
+ out_dir = std::move(config_files.back());
+ config_files.pop_back();
+ // We check if `out_dir` can be written to *before* checking the input files
+ // This produces a nicer error message if the user forgot to specify the
+ // output directory, in which case `out_dir` will be a regular (config) file
+ if(!detail::can_write_to(out_dir)){
+ throw std::invalid_argument{
+ "Cannot write configuration to " + out_dir.string() + ":\n"
+ " " + out_dir.string() + " exists and is not a directory"
+ };
+ }
+ }
+
+ bool help;
+ std::uint64_t runs;
+ std::vector<std::filesystem::path> config_files;
+ std::filesystem::path out_dir;
+ std::string help_message;
+ };
+
+
+}
diff --git a/config_generator/include/cxxopts.hpp b/config_generator/include/cxxopts.hpp
new file mode 100644
index 0000000..991ba3f
--- /dev/null
+++ b/config_generator/include/cxxopts.hpp
@@ -0,0 +1,2908 @@
+/*
+
+Copyright (c) 2014-2022 Jarryd Beck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+// vim: ts=2:sw=2:expandtab
+
+#ifndef CXXOPTS_HPP_INCLUDED
+#define CXXOPTS_HPP_INCLUDED
+
+#include <cstdlib>
+#include <cstring>
+#include <exception>
+#include <limits>
+#include <initializer_list>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+#include <algorithm>
+#include <locale>
+
+#ifdef CXXOPTS_NO_EXCEPTIONS
+#include <iostream>
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__)
+# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49
+# define CXXOPTS_NO_REGEX true
+# endif
+#endif
+#if defined(_MSC_VER) && !defined(__clang__)
+#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern
+#define CXXOPTS_LINKONCE __declspec(selectany) extern
+#else
+#define CXXOPTS_LINKONCE_CONST
+#define CXXOPTS_LINKONCE
+#endif
+
+#ifndef CXXOPTS_NO_REGEX
+# include <regex>
+#endif // CXXOPTS_NO_REGEX
+
+// Nonstandard before C++17, which is coincidentally what we also need for <optional>
+#ifdef __has_include
+# if __has_include(<optional>)
+# include <optional>
+# ifdef __cpp_lib_optional
+# define CXXOPTS_HAS_OPTIONAL
+# endif
+# endif
+#endif
+
+#define CXXOPTS_FALLTHROUGH
+#ifdef __has_cpp_attribute
+ #if __has_cpp_attribute(fallthrough)
+ #undef CXXOPTS_FALLTHROUGH
+ #define CXXOPTS_FALLTHROUGH [[fallthrough]]
+ #endif
+#endif
+
+#if __cplusplus >= 201603L
+#define CXXOPTS_NODISCARD [[nodiscard]]
+#else
+#define CXXOPTS_NODISCARD
+#endif
+
+#ifndef CXXOPTS_VECTOR_DELIMITER
+#define CXXOPTS_VECTOR_DELIMITER ','
+#endif
+
+#define CXXOPTS__VERSION_MAJOR 3
+#define CXXOPTS__VERSION_MINOR 2
+#define CXXOPTS__VERSION_PATCH 1
+
+#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
+ #define CXXOPTS_NULL_DEREF_IGNORE
+#endif
+
+#if defined(__GNUC__)
+#define DO_PRAGMA(x) _Pragma(#x)
+#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push)
+#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop)
+#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x)
+#else
+// define other compilers here if needed
+#define CXXOPTS_DIAGNOSTIC_PUSH
+#define CXXOPTS_DIAGNOSTIC_POP
+#define CXXOPTS_IGNORE_WARNING(x)
+#endif
+
+#ifdef CXXOPTS_NO_RTTI
+#define CXXOPTS_RTTI_CAST static_cast
+#else
+#define CXXOPTS_RTTI_CAST dynamic_cast
+#endif
+
+namespace cxxopts {
+static constexpr struct {
+ uint8_t major, minor, patch;
+} version = {
+ CXXOPTS__VERSION_MAJOR,
+ CXXOPTS__VERSION_MINOR,
+ CXXOPTS__VERSION_PATCH
+};
+} // namespace cxxopts
+
+//when we ask cxxopts to use Unicode, help strings are processed using ICU,
+//which results in the correct lengths being computed for strings when they
+//are formatted for the help output
+//it is necessary to make sure that <unicode/unistr.h> can be found by the
+//compiler, and that icu-uc is linked in to the binary.
+
+#ifdef CXXOPTS_USE_UNICODE
+#include <unicode/unistr.h>
+
+namespace cxxopts {
+
+using String = icu::UnicodeString;
+
+inline
+String
+toLocalString(std::string s)
+{
+ return icu::UnicodeString::fromUTF8(std::move(s));
+}
+
+// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
+// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
+// This will be ignored under other compilers like LLVM clang.
+class UnicodeStringIterator
+{
+ public:
+
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = int32_t;
+ using difference_type = std::ptrdiff_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
+ : s(string)
+ , i(pos)
+ {
+ }
+
+ value_type
+ operator*() const
+ {
+ return s->char32At(i);
+ }
+
+ bool
+ operator==(const UnicodeStringIterator& rhs) const
+ {
+ return s == rhs.s && i == rhs.i;
+ }
+
+ bool
+ operator!=(const UnicodeStringIterator& rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ UnicodeStringIterator&
+ operator++()
+ {
+ ++i;
+ return *this;
+ }
+
+ UnicodeStringIterator
+ operator+(int32_t v)
+ {
+ return UnicodeStringIterator(s, i + v);
+ }
+
+ private:
+ const icu::UnicodeString* s;
+ int32_t i;
+};
+CXXOPTS_DIAGNOSTIC_POP
+
+inline
+String&
+stringAppend(String&s, String a)
+{
+ return s.append(std::move(a));
+}
+
+inline
+String&
+stringAppend(String& s, std::size_t n, UChar32 c)
+{
+ for (std::size_t i = 0; i != n; ++i)
+ {
+ s.append(c);
+ }
+
+ return s;
+}
+
+template <typename Iterator>
+String&
+stringAppend(String& s, Iterator begin, Iterator end)
+{
+ while (begin != end)
+ {
+ s.append(*begin);
+ ++begin;
+ }
+
+ return s;
+}
+
+inline
+size_t
+stringLength(const String& s)
+{
+ return static_cast<size_t>(s.length());
+}
+
+inline
+std::string
+toUTF8String(const String& s)
+{
+ std::string result;
+ s.toUTF8String(result);
+
+ return result;
+}
+
+inline
+bool
+empty(const String& s)
+{
+ return s.isEmpty();
+}
+
+} // namespace cxxopts
+
+namespace std {
+
+inline
+cxxopts::UnicodeStringIterator
+begin(const icu::UnicodeString& s)
+{
+ return cxxopts::UnicodeStringIterator(&s, 0);
+}
+
+inline
+cxxopts::UnicodeStringIterator
+end(const icu::UnicodeString& s)
+{
+ return cxxopts::UnicodeStringIterator(&s, s.length());
+}
+
+} // namespace std
+
+//ifdef CXXOPTS_USE_UNICODE
+#else
+
+namespace cxxopts {
+
+using String = std::string;
+
+template <typename T>
+T
+toLocalString(T&& t)
+{
+ return std::forward<T>(t);
+}
+
+inline
+std::size_t
+stringLength(const String& s)
+{
+ return s.length();
+}
+
+inline
+String&
+stringAppend(String&s, const String& a)
+{
+ return s.append(a);
+}
+
+inline
+String&
+stringAppend(String& s, std::size_t n, char c)
+{
+ return s.append(n, c);
+}
+
+template <typename Iterator>
+String&
+stringAppend(String& s, Iterator begin, Iterator end)
+{
+ return s.append(begin, end);
+}
+
+template <typename T>
+std::string
+toUTF8String(T&& t)
+{
+ return std::forward<T>(t);
+}
+
+inline
+bool
+empty(const std::string& s)
+{
+ return s.empty();
+}
+
+} // namespace cxxopts
+
+//ifdef CXXOPTS_USE_UNICODE
+#endif
+
+namespace cxxopts {
+
+namespace {
+CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
+CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
+} // namespace
+
+// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
+// want to silence it: warning: base class 'class
+// std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual
+// destructor This will be ignored under other compilers like LLVM clang.
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
+
+// some older versions of GCC warn under this warning
+CXXOPTS_IGNORE_WARNING("-Weffc++")
+class Value : public std::enable_shared_from_this<Value>
+{
+ public:
+
+ virtual ~Value() = default;
+
+ virtual
+ std::shared_ptr<Value>
+ clone() const = 0;
+
+ virtual void
+ add(const std::string& text) const = 0;
+
+ virtual void
+ parse(const std::string& text) const = 0;
+
+ virtual void
+ parse() const = 0;
+
+ virtual bool
+ has_default() const = 0;
+
+ virtual bool
+ is_container() const = 0;
+
+ virtual bool
+ has_implicit() const = 0;
+
+ virtual std::string
+ get_default_value() const = 0;
+
+ virtual std::string
+ get_implicit_value() const = 0;
+
+ virtual std::shared_ptr<Value>
+ default_value(const std::string& value) = 0;
+
+ virtual std::shared_ptr<Value>
+ implicit_value(const std::string& value) = 0;
+
+ virtual std::shared_ptr<Value>
+ no_implicit_value() = 0;
+
+ virtual bool
+ is_boolean() const = 0;
+};
+
+CXXOPTS_DIAGNOSTIC_POP
+
+namespace exceptions {
+
+class exception : public std::exception
+{
+ public:
+ explicit exception(std::string message)
+ : m_message(std::move(message))
+ {
+ }
+
+ CXXOPTS_NODISCARD
+ const char*
+ what() const noexcept override
+ {
+ return m_message.c_str();
+ }
+
+ private:
+ std::string m_message;
+};
+
+class specification : public exception
+{
+ public:
+
+ explicit specification(const std::string& message)
+ : exception(message)
+ {
+ }
+};
+
+class parsing : public exception
+{
+ public:
+ explicit parsing(const std::string& message)
+ : exception(message)
+ {
+ }
+};
+
+class option_already_exists : public specification
+{
+ public:
+ explicit option_already_exists(const std::string& option)
+ : specification("Option " + LQUOTE + option + RQUOTE + " already exists")
+ {
+ }
+};
+
+class invalid_option_format : public specification
+{
+ public:
+ explicit invalid_option_format(const std::string& format)
+ : specification("Invalid option format " + LQUOTE + format + RQUOTE)
+ {
+ }
+};
+
+class invalid_option_syntax : public parsing {
+ public:
+ explicit invalid_option_syntax(const std::string& text)
+ : parsing("Argument " + LQUOTE + text + RQUOTE +
+ " starts with a - but has incorrect syntax")
+ {
+ }
+};
+
+class no_such_option : public parsing
+{
+ public:
+ explicit no_such_option(const std::string& option)
+ : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist")
+ {
+ }
+};
+
+class missing_argument : public parsing
+{
+ public:
+ explicit missing_argument(const std::string& option)
+ : parsing(
+ "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
+ )
+ {
+ }
+};
+
+class option_requires_argument : public parsing
+{
+ public:
+ explicit option_requires_argument(const std::string& option)
+ : parsing(
+ "Option " + LQUOTE + option + RQUOTE + " requires an argument"
+ )
+ {
+ }
+};
+
+class gratuitous_argument_for_option : public parsing
+{
+ public:
+ gratuitous_argument_for_option
+ (
+ const std::string& option,
+ const std::string& arg
+ )
+ : parsing(
+ "Option " + LQUOTE + option + RQUOTE +
+ " does not take an argument, but argument " +
+ LQUOTE + arg + RQUOTE + " given"
+ )
+ {
+ }
+};
+
+class requested_option_not_present : public parsing
+{
+ public:
+ explicit requested_option_not_present(const std::string& option)
+ : parsing("Option " + LQUOTE + option + RQUOTE + " not present")
+ {
+ }
+};
+
+class option_has_no_value : public exception
+{
+ public:
+ explicit option_has_no_value(const std::string& option)
+ : exception(
+ !option.empty() ?
+ ("Option " + LQUOTE + option + RQUOTE + " has no value") :
+ "Option has no value")
+ {
+ }
+};
+
+class incorrect_argument_type : public parsing
+{
+ public:
+ explicit incorrect_argument_type
+ (
+ const std::string& arg
+ )
+ : parsing(
+ "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
+ )
+ {
+ }
+};
+
+} // namespace exceptions
+
+
+template <typename T>
+void throw_or_mimic(const std::string& text)
+{
+ static_assert(std::is_base_of<std::exception, T>::value,
+ "throw_or_mimic only works on std::exception and "
+ "deriving classes");
+
+#ifndef CXXOPTS_NO_EXCEPTIONS
+ // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
+ throw T{text};
+#else
+ // Otherwise manually instantiate the exception, print what() to stderr,
+ // and exit
+ T exception{text};
+ std::cerr << exception.what() << std::endl;
+ std::exit(EXIT_FAILURE);
+#endif
+}
+
+using OptionNames = std::vector<std::string>;
+
+namespace values {
+
+namespace parser_tool {
+
+struct IntegerDesc
+{
+ std::string negative = "";
+ std::string base = "";
+ std::string value = "";
+};
+struct ArguDesc {
+ std::string arg_name = "";
+ bool grouping = false;
+ bool set_value = false;
+ std::string value = "";
+};
+
+#ifdef CXXOPTS_NO_REGEX
+inline IntegerDesc SplitInteger(const std::string &text)
+{
+ if (text.empty())
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+ IntegerDesc desc;
+ const char *pdata = text.c_str();
+ if (*pdata == '-')
+ {
+ pdata += 1;
+ desc.negative = "-";
+ }
+ if (strncmp(pdata, "0x", 2) == 0)
+ {
+ pdata += 2;
+ desc.base = "0x";
+ }
+ if (*pdata != '\0')
+ {
+ desc.value = std::string(pdata);
+ }
+ else
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+ return desc;
+}
+
+inline bool IsTrueText(const std::string &text)
+{
+ const char *pdata = text.c_str();
+ if (*pdata == 't' || *pdata == 'T')
+ {
+ pdata += 1;
+ if (strncmp(pdata, "rue\0", 4) == 0)
+ {
+ return true;
+ }
+ }
+ else if (strncmp(pdata, "1\0", 2) == 0)
+ {
+ return true;
+ }
+ return false;
+}
+
+inline bool IsFalseText(const std::string &text)
+{
+ const char *pdata = text.c_str();
+ if (*pdata == 'f' || *pdata == 'F')
+ {
+ pdata += 1;
+ if (strncmp(pdata, "alse\0", 5) == 0)
+ {
+ return true;
+ }
+ }
+ else if (strncmp(pdata, "0\0", 2) == 0)
+ {
+ return true;
+ }
+ return false;
+}
+
+inline OptionNames split_option_names(const std::string &text)
+{
+ OptionNames split_names;
+
+ std::string::size_type token_start_pos = 0;
+ auto length = text.length();
+
+ if (length == 0)
+ {
+ throw_or_mimic<exceptions::invalid_option_format>(text);
+ }
+
+ while (token_start_pos < length) {
+ const auto &npos = std::string::npos;
+ auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos);
+ if (next_non_space_pos == npos) {
+ throw_or_mimic<exceptions::invalid_option_format>(text);
+ }
+ token_start_pos = next_non_space_pos;
+ auto next_delimiter_pos = text.find(',', token_start_pos);
+ if (next_delimiter_pos == token_start_pos) {
+ throw_or_mimic<exceptions::invalid_option_format>(text);
+ }
+ if (next_delimiter_pos == npos) {
+ next_delimiter_pos = length;
+ }
+ auto token_length = next_delimiter_pos - token_start_pos;
+ // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/
+ {
+ const char* option_name_valid_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "_-.?";
+
+ if (!std::isalnum(text[token_start_pos], std::locale::classic()) ||
+ text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) {
+ throw_or_mimic<exceptions::invalid_option_format>(text);
+ }
+ }
+ split_names.emplace_back(text.substr(token_start_pos, token_length));
+ token_start_pos = next_delimiter_pos + 1;
+ }
+ return split_names;
+}
+
+inline ArguDesc ParseArgument(const char *arg, bool &matched)
+{
+ ArguDesc argu_desc;
+ const char *pdata = arg;
+ matched = false;
+ if (strncmp(pdata, "--", 2) == 0)
+ {
+ pdata += 2;
+ if (isalnum(*pdata, std::locale::classic()))
+ {
+ argu_desc.arg_name.push_back(*pdata);
+ pdata += 1;
+ while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_')
+ {
+ argu_desc.arg_name.push_back(*pdata);
+ pdata += 1;
+ }
+ if (argu_desc.arg_name.length() > 1)
+ {
+ if (*pdata == '=')
+ {
+ argu_desc.set_value = true;
+ pdata += 1;
+ if (*pdata != '\0')
+ {
+ argu_desc.value = std::string(pdata);
+ }
+ matched = true;
+ }
+ else if (*pdata == '\0')
+ {
+ matched = true;
+ }
+ }
+ }
+ }
+ else if (strncmp(pdata, "-", 1) == 0)
+ {
+ pdata += 1;
+ argu_desc.grouping = true;
+ while (isalnum(*pdata, std::locale::classic()))
+ {
+ argu_desc.arg_name.push_back(*pdata);
+ pdata += 1;
+ }
+ matched = !argu_desc.arg_name.empty() && *pdata == '\0';
+ }
+ return argu_desc;
+}
+
+#else // CXXOPTS_NO_REGEX
+
+namespace {
+CXXOPTS_LINKONCE
+const char* const integer_pattern =
+ "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)";
+CXXOPTS_LINKONCE
+const char* const truthy_pattern =
+ "(t|T)(rue)?|1";
+CXXOPTS_LINKONCE
+const char* const falsy_pattern =
+ "(f|F)(alse)?|0";
+CXXOPTS_LINKONCE
+const char* const option_pattern =
+ "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)";
+CXXOPTS_LINKONCE
+const char* const option_specifier_pattern =
+ "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*";
+CXXOPTS_LINKONCE
+const char* const option_specifier_separator_pattern = ", *";
+
+} // namespace
+
+inline IntegerDesc SplitInteger(const std::string &text)
+{
+ static const std::basic_regex<char> integer_matcher(integer_pattern);
+
+ std::smatch match;
+ std::regex_match(text, match, integer_matcher);
+
+ if (match.length() == 0)
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+
+ IntegerDesc desc;
+ desc.negative = match[1];
+ desc.base = match[2];
+ desc.value = match[3];
+
+ if (match.length(4) > 0)
+ {
+ desc.base = match[5];
+ desc.value = "0";
+ return desc;
+ }
+
+ return desc;
+}
+
+inline bool IsTrueText(const std::string &text)
+{
+ static const std::basic_regex<char> truthy_matcher(truthy_pattern);
+ std::smatch result;
+ std::regex_match(text, result, truthy_matcher);
+ return !result.empty();
+}
+
+inline bool IsFalseText(const std::string &text)
+{
+ static const std::basic_regex<char> falsy_matcher(falsy_pattern);
+ std::smatch result;
+ std::regex_match(text, result, falsy_matcher);
+ return !result.empty();
+}
+
+// Gets the option names specified via a single, comma-separated string,
+// and returns the separate, space-discarded, non-empty names
+// (without considering which or how many are single-character)
+inline OptionNames split_option_names(const std::string &text)
+{
+ static const std::basic_regex<char> option_specifier_matcher(option_specifier_pattern);
+ if (!std::regex_match(text.c_str(), option_specifier_matcher))
+ {
+ throw_or_mimic<exceptions::invalid_option_format>(text);
+ }
+
+ OptionNames split_names;
+
+ static const std::basic_regex<char> option_specifier_separator_matcher(option_specifier_separator_pattern);
+ constexpr int use_non_matches { -1 };
+ auto token_iterator = std::sregex_token_iterator(
+ text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches);
+ std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
+ return split_names;
+}
+
+inline ArguDesc ParseArgument(const char *arg, bool &matched)
+{
+ static const std::basic_regex<char> option_matcher(option_pattern);
+ std::match_results<const char*> result;
+ std::regex_match(arg, result, option_matcher);
+ matched = !result.empty();
+
+ ArguDesc argu_desc;
+ if (matched) {
+ argu_desc.arg_name = result[1].str();
+ argu_desc.set_value = result[2].length() > 0;
+ argu_desc.value = result[3].str();
+ if (result[4].length() > 0)
+ {
+ argu_desc.grouping = true;
+ argu_desc.arg_name = result[4].str();
+ }
+ }
+
+ return argu_desc;
+}
+
+#endif // CXXOPTS_NO_REGEX
+#undef CXXOPTS_NO_REGEX
+} // namespace parser_tool
+
+namespace detail {
+
+template <typename T, bool B>
+struct SignedCheck;
+
+template <typename T>
+struct SignedCheck<T, true>
+{
+ template <typename U>
+ void
+ operator()(bool negative, U u, const std::string& text)
+ {
+ if (negative)
+ {
+ if (u > static_cast<U>((std::numeric_limits<T>::min)()))
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+ }
+ else
+ {
+ if (u > static_cast<U>((std::numeric_limits<T>::max)()))
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+ }
+ }
+};
+
+template <typename T>
+struct SignedCheck<T, false>
+{
+ template <typename U>
+ void
+ operator()(bool, U, const std::string&) const {}
+};
+
+template <typename T, typename U>
+void
+check_signed_range(bool negative, U value, const std::string& text)
+{
+ SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
+}
+
+} // namespace detail
+
+template <typename R, typename T>
+void
+checked_negate(R& r, T&& t, const std::string&, std::true_type)
+{
+ // if we got to here, then `t` is a positive number that fits into
+ // `R`. So to avoid MSVC C4146, we first cast it to `R`.
+ // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
+ r = static_cast<R>(-static_cast<R>(t-1)-1);
+}
+
+template <typename R, typename T>
+void
+checked_negate(R&, T&&, const std::string& text, std::false_type)
+{
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+}
+
+template <typename T>
+void
+integer_parser(const std::string& text, T& value)
+{
+ parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text);
+
+ using US = typename std::make_unsigned<T>::type;
+ constexpr bool is_signed = std::numeric_limits<T>::is_signed;
+
+ const bool negative = int_desc.negative.length() > 0;
+ const uint8_t base = int_desc.base.length() > 0 ? 16 : 10;
+ const std::string & value_match = int_desc.value;
+
+ US result = 0;
+
+ for (char ch : value_match)
+ {
+ US digit = 0;
+
+ if (ch >= '0' && ch <= '9')
+ {
+ digit = static_cast<US>(ch - '0');
+ }
+ else if (base == 16 && ch >= 'a' && ch <= 'f')
+ {
+ digit = static_cast<US>(ch - 'a' + 10);
+ }
+ else if (base == 16 && ch >= 'A' && ch <= 'F')
+ {
+ digit = static_cast<US>(ch - 'A' + 10);
+ }
+ else
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+
+ US limit = 0;
+ if (negative)
+ {
+ limit = static_cast<US>(std::abs(static_cast<intmax_t>((std::numeric_limits<T>::min)())));
+ }
+ else
+ {
+ limit = (std::numeric_limits<T>::max)();
+ }
+
+ if (base != 0 && result > limit / base)
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+ if (result * base > limit - digit)
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+
+ result = static_cast<US>(result * base + digit);
+ }
+
+ detail::check_signed_range<T>(negative, result, text);
+
+ if (negative)
+ {
+ checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
+ }
+ else
+ {
+ value = static_cast<T>(result);
+ }
+}
+
+template <typename T>
+void stringstream_parser(const std::string& text, T& value)
+{
+ std::stringstream in(text);
+ in >> value;
+ if (!in) {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+}
+
+template <typename T,
+ typename std::enable_if<std::is_integral<T>::value>::type* = nullptr
+ >
+void parse_value(const std::string& text, T& value)
+{
+ integer_parser(text, value);
+}
+
+inline
+void
+parse_value(const std::string& text, bool& value)
+{
+ if (parser_tool::IsTrueText(text))
+ {
+ value = true;
+ return;
+ }
+
+ if (parser_tool::IsFalseText(text))
+ {
+ value = false;
+ return;
+ }
+
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+}
+
+inline
+void
+parse_value(const std::string& text, std::string& value)
+{
+ value = text;
+}
+
+// The fallback parser. It uses the stringstream parser to parse all types
+// that have not been overloaded explicitly. It has to be placed in the
+// source code before all other more specialized templates.
+template <typename T,
+ typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr
+ >
+void
+parse_value(const std::string& text, T& value) {
+ stringstream_parser(text, value);
+}
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+template <typename T>
+void
+parse_value(const std::string& text, std::optional<T>& value)
+{
+ T result;
+ parse_value(text, result);
+ value = std::move(result);
+}
+#endif
+
+inline
+void parse_value(const std::string& text, char& c)
+{
+ if (text.length() != 1)
+ {
+ throw_or_mimic<exceptions::incorrect_argument_type>(text);
+ }
+
+ c = text[0];
+}
+
+template <typename T>
+void
+parse_value(const std::string& text, std::vector<T>& value)
+{
+ if (text.empty()) {
+ T v;
+ parse_value(text, v);
+ value.emplace_back(std::move(v));
+ return;
+ }
+ std::stringstream in(text);
+ std::string token;
+ while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
+ T v;
+ parse_value(token, v);
+ value.emplace_back(std::move(v));
+ }
+}
+
+template <typename T>
+void
+add_value(const std::string& text, T& value)
+{
+ parse_value(text, value);
+}
+
+template <typename T>
+void
+add_value(const std::string& text, std::vector<T>& value)
+{
+ T v;
+ add_value(text, v);
+ value.emplace_back(std::move(v));
+}
+
+template <typename T>
+struct type_is_container
+{
+ static constexpr bool value = false;
+};
+
+template <typename T>
+struct type_is_container<std::vector<T>>
+{
+ static constexpr bool value = true;
+};
+
+template <typename T>
+class abstract_value : public Value
+{
+ using Self = abstract_value<T>;
+
+ public:
+ abstract_value()
+ : m_result(std::make_shared<T>())
+ , m_store(m_result.get())
+ {
+ }
+
+ explicit abstract_value(T* t)
+ : m_store(t)
+ {
+ }
+
+ ~abstract_value() override = default;
+
+ abstract_value& operator=(const abstract_value&) = default;
+
+ abstract_value(const abstract_value& rhs)
+ {
+ if (rhs.m_result)
+ {
+ m_result = std::make_shared<T>();
+ m_store = m_result.get();
+ }
+ else
+ {
+ m_store = rhs.m_store;
+ }
+
+ m_default = rhs.m_default;
+ m_implicit = rhs.m_implicit;
+ m_default_value = rhs.m_default_value;
+ m_implicit_value = rhs.m_implicit_value;
+ }
+
+ void
+ add(const std::string& text) const override
+ {
+ add_value(text, *m_store);
+ }
+
+ void
+ parse(const std::string& text) const override
+ {
+ parse_value(text, *m_store);
+ }
+
+ bool
+ is_container() const override
+ {
+ return type_is_container<T>::value;
+ }
+
+ void
+ parse() const override
+ {
+ parse_value(m_default_value, *m_store);
+ }
+
+ bool
+ has_default() const override
+ {
+ return m_default;
+ }
+
+ bool
+ has_implicit() const override
+ {
+ return m_implicit;
+ }
+
+ std::shared_ptr<Value>
+ default_value(const std::string& value) override
+ {
+ m_default = true;
+ m_default_value = value;
+ return shared_from_this();
+ }
+
+ std::shared_ptr<Value>
+ implicit_value(const std::string& value) override
+ {
+ m_implicit = true;
+ m_implicit_value = value;
+ return shared_from_this();
+ }
+
+ std::shared_ptr<Value>
+ no_implicit_value() override
+ {
+ m_implicit = false;
+ return shared_from_this();
+ }
+
+ std::string
+ get_default_value() const override
+ {
+ return m_default_value;
+ }
+
+ std::string
+ get_implicit_value() const override
+ {
+ return m_implicit_value;
+ }
+
+ bool
+ is_boolean() const override
+ {
+ return std::is_same<T, bool>::value;
+ }
+
+ const T&
+ get() const
+ {
+ if (m_store == nullptr)
+ {
+ return *m_result;
+ }
+ return *m_store;
+ }
+
+ protected:
+ std::shared_ptr<T> m_result{};
+ T* m_store{};
+
+ bool m_default = false;
+ bool m_implicit = false;
+
+ std::string m_default_value{};
+ std::string m_implicit_value{};
+};
+
+template <typename T>
+class standard_value : public abstract_value<T>
+{
+ public:
+ using abstract_value<T>::abstract_value;
+
+ CXXOPTS_NODISCARD
+ std::shared_ptr<Value>
+ clone() const override
+ {
+ return std::make_shared<standard_value<T>>(*this);
+ }
+};
+
+template <>
+class standard_value<bool> : public abstract_value<bool>
+{
+ public:
+ ~standard_value() override = default;
+
+ standard_value()
+ {
+ set_default_and_implicit();
+ }
+
+ explicit standard_value(bool* b)
+ : abstract_value(b)
+ {
+ m_implicit = true;
+ m_implicit_value = "true";
+ }
+
+ std::shared_ptr<Value>
+ clone() const override
+ {
+ return std::make_shared<standard_value<bool>>(*this);
+ }
+
+ private:
+
+ void
+ set_default_and_implicit()
+ {
+ m_default = true;
+ m_default_value = "false";
+ m_implicit = true;
+ m_implicit_value = "true";
+ }
+};
+
+} // namespace values
+
+template <typename T>
+std::shared_ptr<Value>
+value()
+{
+ return std::make_shared<values::standard_value<T>>();
+}
+
+template <typename T>
+std::shared_ptr<Value>
+value(T& t)
+{
+ return std::make_shared<values::standard_value<T>>(&t);
+}
+
+class OptionAdder;
+
+CXXOPTS_NODISCARD
+inline
+const std::string&
+first_or_empty(const OptionNames& long_names)
+{
+ static const std::string empty{""};
+ return long_names.empty() ? empty : long_names.front();
+}
+
+class OptionDetails
+{
+ public:
+ OptionDetails
+ (
+ std::string short_,
+ OptionNames long_,
+ String desc,
+ std::shared_ptr<const Value> val
+ )
+ : m_short(std::move(short_))
+ , m_long(std::move(long_))
+ , m_desc(std::move(desc))
+ , m_value(std::move(val))
+ , m_count(0)
+ {
+ m_hash = std::hash<std::string>{}(first_long_name() + m_short);
+ }
+
+ OptionDetails(const OptionDetails& rhs)
+ : m_desc(rhs.m_desc)
+ , m_value(rhs.m_value->clone())
+ , m_count(rhs.m_count)
+ {
+ }
+
+ OptionDetails(OptionDetails&& rhs) = default;
+
+ CXXOPTS_NODISCARD
+ const String&
+ description() const
+ {
+ return m_desc;
+ }
+
+ CXXOPTS_NODISCARD
+ const Value&
+ value() const {
+ return *m_value;
+ }
+
+ CXXOPTS_NODISCARD
+ std::shared_ptr<Value>
+ make_storage() const
+ {
+ return m_value->clone();
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ short_name() const
+ {
+ return m_short;
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ first_long_name() const
+ {
+ return first_or_empty(m_long);
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ essential_name() const
+ {
+ return m_long.empty() ? m_short : m_long.front();
+ }
+
+ CXXOPTS_NODISCARD
+ const OptionNames &
+ long_names() const
+ {
+ return m_long;
+ }
+
+ std::size_t
+ hash() const
+ {
+ return m_hash;
+ }
+
+ private:
+ std::string m_short{};
+ OptionNames m_long{};
+ String m_desc{};
+ std::shared_ptr<const Value> m_value{};
+ int m_count;
+
+ std::size_t m_hash{};
+};
+
+struct HelpOptionDetails
+{
+ std::string s;
+ OptionNames l;
+ String desc;
+ bool has_default;
+ std::string default_value;
+ bool has_implicit;
+ std::string implicit_value;
+ std::string arg_help;
+ bool is_container;
+ bool is_boolean;
+};
+
+struct HelpGroupDetails
+{
+ std::string name{};
+ std::string description{};
+ std::vector<HelpOptionDetails> options{};
+};
+
+class OptionValue
+{
+ public:
+ void
+ add
+ (
+ const std::shared_ptr<const OptionDetails>& details,
+ const std::string& text
+ )
+ {
+ ensure_value(details);
+ ++m_count;
+ m_value->add(text);
+ m_long_names = &details->long_names();
+ }
+
+ void
+ parse
+ (
+ const std::shared_ptr<const OptionDetails>& details,
+ const std::string& text
+ )
+ {
+ ensure_value(details);
+ ++m_count;
+ m_value->parse(text);
+ m_long_names = &details->long_names();
+ }
+
+ void
+ parse_default(const std::shared_ptr<const OptionDetails>& details)
+ {
+ ensure_value(details);
+ m_default = true;
+ m_long_names = &details->long_names();
+ m_value->parse();
+ }
+
+ void
+ parse_no_value(const std::shared_ptr<const OptionDetails>& details)
+ {
+ m_long_names = &details->long_names();
+ }
+
+#if defined(CXXOPTS_NULL_DEREF_IGNORE)
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnull-dereference")
+#endif
+
+ CXXOPTS_NODISCARD
+ std::size_t
+ count() const noexcept
+ {
+ return m_count;
+ }
+
+#if defined(CXXOPTS_NULL_DEREF_IGNORE)
+CXXOPTS_DIAGNOSTIC_POP
+#endif
+
+ // TODO: maybe default options should count towards the number of arguments
+ CXXOPTS_NODISCARD
+ bool
+ has_default() const noexcept
+ {
+ return m_default;
+ }
+
+ template <typename T>
+ const T&
+ as() const
+ {
+ if (m_value == nullptr) {
+ throw_or_mimic<exceptions::option_has_no_value>(
+ m_long_names == nullptr ? "" : first_or_empty(*m_long_names));
+ }
+
+ return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get();
+ }
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+ template <typename T>
+ std::optional<T>
+ as_optional() const
+ {
+ if (m_value == nullptr) {
+ return std::nullopt;
+ }
+ return as<T>();
+ }
+#endif
+
+ private:
+ void
+ ensure_value(const std::shared_ptr<const OptionDetails>& details)
+ {
+ if (m_value == nullptr)
+ {
+ m_value = details->make_storage();
+ }
+ }
+
+
+ const OptionNames * m_long_names = nullptr;
+ // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
+ // where the key has the string we point to.
+ std::shared_ptr<Value> m_value{};
+ std::size_t m_count = 0;
+ bool m_default = false;
+};
+
+class KeyValue
+{
+ public:
+ KeyValue(std::string key_, std::string value_) noexcept
+ : m_key(std::move(key_))
+ , m_value(std::move(value_))
+ {
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ key() const
+ {
+ return m_key;
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ value() const
+ {
+ return m_value;
+ }
+
+ template <typename T>
+ T
+ as() const
+ {
+ T result;
+ values::parse_value(m_value, result);
+ return result;
+ }
+
+ private:
+ std::string m_key;
+ std::string m_value;
+};
+
+using ParsedHashMap = std::unordered_map<std::size_t, OptionValue>;
+using NameHashMap = std::unordered_map<std::string, std::size_t>;
+
+class ParseResult
+{
+ public:
+ class Iterator
+ {
+ public:
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = KeyValue;
+ using difference_type = void;
+ using pointer = const KeyValue*;
+ using reference = const KeyValue&;
+
+ Iterator() = default;
+ Iterator(const Iterator&) = default;
+
+// GCC complains about m_iter not being initialised in the member
+// initializer list
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Weffc++")
+ Iterator(const ParseResult *pr, bool end=false)
+ : m_pr(pr)
+ {
+ if (end)
+ {
+ m_sequential = false;
+ m_iter = m_pr->m_defaults.end();
+ }
+ else
+ {
+ m_sequential = true;
+ m_iter = m_pr->m_sequential.begin();
+
+ if (m_iter == m_pr->m_sequential.end())
+ {
+ m_sequential = false;
+ m_iter = m_pr->m_defaults.begin();
+ }
+ }
+ }
+CXXOPTS_DIAGNOSTIC_POP
+
+ Iterator& operator++()
+ {
+ ++m_iter;
+ if(m_sequential && m_iter == m_pr->m_sequential.end())
+ {
+ m_sequential = false;
+ m_iter = m_pr->m_defaults.begin();
+ return *this;
+ }
+ return *this;
+ }
+
+ Iterator operator++(int)
+ {
+ Iterator retval = *this;
+ ++(*this);
+ return retval;
+ }
+
+ bool operator==(const Iterator& other) const
+ {
+ return (m_sequential == other.m_sequential) && (m_iter == other.m_iter);
+ }
+
+ bool operator!=(const Iterator& other) const
+ {
+ return !(*this == other);
+ }
+
+ const KeyValue& operator*()
+ {
+ return *m_iter;
+ }
+
+ const KeyValue* operator->()
+ {
+ return m_iter.operator->();
+ }
+
+ private:
+ const ParseResult* m_pr;
+ std::vector<KeyValue>::const_iterator m_iter;
+ bool m_sequential = true;
+ };
+
+ ParseResult() = default;
+ ParseResult(const ParseResult&) = default;
+
+ ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential,
+ std::vector<KeyValue> default_opts, std::vector<std::string>&& unmatched_args)
+ : m_keys(std::move(keys))
+ , m_values(std::move(values))
+ , m_sequential(std::move(sequential))
+ , m_defaults(std::move(default_opts))
+ , m_unmatched(std::move(unmatched_args))
+ {
+ }
+
+ ParseResult& operator=(ParseResult&&) = default;
+ ParseResult& operator=(const ParseResult&) = default;
+
+ Iterator
+ begin() const
+ {
+ return Iterator(this);
+ }
+
+ Iterator
+ end() const
+ {
+ return Iterator(this, true);
+ }
+
+ std::size_t
+ count(const std::string& o) const
+ {
+ auto iter = m_keys.find(o);
+ if (iter == m_keys.end())
+ {
+ return 0;
+ }
+
+ auto viter = m_values.find(iter->second);
+
+ if (viter == m_values.end())
+ {
+ return 0;
+ }
+
+ return viter->second.count();
+ }
+
+ const OptionValue&
+ operator[](const std::string& option) const
+ {
+ auto iter = m_keys.find(option);
+
+ if (iter == m_keys.end())
+ {
+ throw_or_mimic<exceptions::requested_option_not_present>(option);
+ }
+
+ auto viter = m_values.find(iter->second);
+
+ if (viter == m_values.end())
+ {
+ throw_or_mimic<exceptions::requested_option_not_present>(option);
+ }
+
+ return viter->second;
+ }
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+ template <typename T>
+ std::optional<T>
+ as_optional(const std::string& option) const
+ {
+ auto iter = m_keys.find(option);
+ if (iter != m_keys.end())
+ {
+ auto viter = m_values.find(iter->second);
+ if (viter != m_values.end())
+ {
+ return viter->second.as_optional<T>();
+ }
+ }
+ return std::nullopt;
+ }
+#endif
+
+ const std::vector<KeyValue>&
+ arguments() const
+ {
+ return m_sequential;
+ }
+
+ const std::vector<std::string>&
+ unmatched() const
+ {
+ return m_unmatched;
+ }
+
+ const std::vector<KeyValue>&
+ defaults() const
+ {
+ return m_defaults;
+ }
+
+ const std::string
+ arguments_string() const
+ {
+ std::string result;
+ for(const auto& kv: m_sequential)
+ {
+ result += kv.key() + " = " + kv.value() + "\n";
+ }
+ for(const auto& kv: m_defaults)
+ {
+ result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n";
+ }
+ return result;
+ }
+
+ private:
+ NameHashMap m_keys{};
+ ParsedHashMap m_values{};
+ std::vector<KeyValue> m_sequential{};
+ std::vector<KeyValue> m_defaults{};
+ std::vector<std::string> m_unmatched{};
+};
+
+struct Option
+{
+ Option
+ (
+ std::string opts,
+ std::string desc,
+ std::shared_ptr<const Value> value = ::cxxopts::value<bool>(),
+ std::string arg_help = ""
+ )
+ : opts_(std::move(opts))
+ , desc_(std::move(desc))
+ , value_(std::move(value))
+ , arg_help_(std::move(arg_help))
+ {
+ }
+
+ std::string opts_;
+ std::string desc_;
+ std::shared_ptr<const Value> value_;
+ std::string arg_help_;
+};
+
+using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
+using PositionalList = std::vector<std::string>;
+using PositionalListIterator = PositionalList::const_iterator;
+
+class OptionParser
+{
+ public:
+ OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
+ : m_options(options)
+ , m_positional(positional)
+ , m_allow_unrecognised(allow_unrecognised)
+ {
+ }
+
+ ParseResult
+ parse(int argc, const char* const* argv);
+
+ bool
+ consume_positional(const std::string& a, PositionalListIterator& next);
+
+ void
+ checked_parse_arg
+ (
+ int argc,
+ const char* const* argv,
+ int& current,
+ const std::shared_ptr<OptionDetails>& value,
+ const std::string& name
+ );
+
+ void
+ add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg);
+
+ void
+ parse_option
+ (
+ const std::shared_ptr<OptionDetails>& value,
+ const std::string& name,
+ const std::string& arg = ""
+ );
+
+ void
+ parse_default(const std::shared_ptr<OptionDetails>& details);
+
+ void
+ parse_no_value(const std::shared_ptr<OptionDetails>& details);
+
+ private:
+
+ void finalise_aliases();
+
+ const OptionMap& m_options;
+ const PositionalList& m_positional;
+
+ std::vector<KeyValue> m_sequential{};
+ std::vector<KeyValue> m_defaults{};
+ bool m_allow_unrecognised;
+
+ ParsedHashMap m_parsed{};
+ NameHashMap m_keys{};
+};
+
+class Options
+{
+ public:
+
+ explicit Options(std::string program_name, std::string help_string = "")
+ : m_program(std::move(program_name))
+ , m_help_string(toLocalString(std::move(help_string)))
+ , m_custom_help("[OPTION...]")
+ , m_positional_help("positional parameters")
+ , m_show_positional(false)
+ , m_allow_unrecognised(false)
+ , m_width(76)
+ , m_tab_expansion(false)
+ , m_options(std::make_shared<OptionMap>())
+ {
+ }
+
+ Options&
+ positional_help(std::string help_text)
+ {
+ m_positional_help = std::move(help_text);
+ return *this;
+ }
+
+ Options&
+ custom_help(std::string help_text)
+ {
+ m_custom_help = std::move(help_text);
+ return *this;
+ }
+
+ Options&
+ show_positional_help()
+ {
+ m_show_positional = true;
+ return *this;
+ }
+
+ Options&
+ allow_unrecognised_options()
+ {
+ m_allow_unrecognised = true;
+ return *this;
+ }
+
+ Options&
+ set_width(std::size_t width)
+ {
+ m_width = width;
+ return *this;
+ }
+
+ Options&
+ set_tab_expansion(bool expansion=true)
+ {
+ m_tab_expansion = expansion;
+ return *this;
+ }
+
+ ParseResult
+ parse(int argc, const char* const* argv);
+
+ OptionAdder
+ add_options(std::string group = "");
+
+ void
+ add_options
+ (
+ const std::string& group,
+ std::initializer_list<Option> options
+ );
+
+ void
+ add_option
+ (
+ const std::string& group,
+ const Option& option
+ );
+
+ void
+ add_option
+ (
+ const std::string& group,
+ const std::string& s,
+ const OptionNames& l,
+ std::string desc,
+ const std::shared_ptr<const Value>& value,
+ std::string arg_help
+ );
+
+ void
+ add_option
+ (
+ const std::string& group,
+ const std::string& short_name,
+ const std::string& single_long_name,
+ std::string desc,
+ const std::shared_ptr<const Value>& value,
+ std::string arg_help
+ )
+ {
+ OptionNames long_names;
+ long_names.emplace_back(single_long_name);
+ add_option(group, short_name, long_names, desc, value, arg_help);
+ }
+
+ //parse positional arguments into the given option
+ void
+ parse_positional(std::string option);
+
+ void
+ parse_positional(std::vector<std::string> options);
+
+ void
+ parse_positional(std::initializer_list<std::string> options);
+
+ template <typename Iterator>
+ void
+ parse_positional(Iterator begin, Iterator end) {
+ parse_positional(std::vector<std::string>{begin, end});
+ }
+
+ std::string
+ help(const std::vector<std::string>& groups = {}, bool print_usage=true) const;
+
+ std::vector<std::string>
+ groups() const;
+
+ const HelpGroupDetails&
+ group_help(const std::string& group) const;
+
+ const std::string& program() const
+ {
+ return m_program;
+ }
+
+ private:
+
+ void
+ add_one_option
+ (
+ const std::string& option,
+ const std::shared_ptr<OptionDetails>& details
+ );
+
+ String
+ help_one_group(const std::string& group) const;
+
+ void
+ generate_group_help
+ (
+ String& result,
+ const std::vector<std::string>& groups
+ ) const;
+
+ void
+ generate_all_groups_help(String& result) const;
+
+ std::string m_program{};
+ String m_help_string{};
+ std::string m_custom_help{};
+ std::string m_positional_help{};
+ bool m_show_positional;
+ bool m_allow_unrecognised;
+ std::size_t m_width;
+ bool m_tab_expansion;
+
+ std::shared_ptr<OptionMap> m_options;
+ std::vector<std::string> m_positional{};
+ std::unordered_set<std::string> m_positional_set{};
+
+ //mapping from groups to help options
+ std::vector<std::string> m_group{};
+ std::map<std::string, HelpGroupDetails> m_help{};
+};
+
+class OptionAdder
+{
+ public:
+
+ OptionAdder(Options& options, std::string group)
+ : m_options(options), m_group(std::move(group))
+ {
+ }
+
+ OptionAdder&
+ operator()
+ (
+ const std::string& opts,
+ const std::string& desc,
+ const std::shared_ptr<const Value>& value
+ = ::cxxopts::value<bool>(),
+ std::string arg_help = ""
+ );
+
+ private:
+ Options& m_options;
+ std::string m_group;
+};
+
+namespace {
+constexpr std::size_t OPTION_LONGEST = 30;
+constexpr std::size_t OPTION_DESC_GAP = 2;
+
+String
+format_option
+(
+ const HelpOptionDetails& o
+)
+{
+ const auto& s = o.s;
+ const auto& l = first_or_empty(o.l);
+
+ String result = " ";
+
+ if (!s.empty())
+ {
+ result += "-" + toLocalString(s);
+ if (!l.empty())
+ {
+ result += ",";
+ }
+ }
+ else
+ {
+ result += " ";
+ }
+
+ if (!l.empty())
+ {
+ result += " --" + toLocalString(l);
+ }
+
+ auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
+
+ if (!o.is_boolean)
+ {
+ if (o.has_implicit)
+ {
+ result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
+ }
+ else
+ {
+ result += " " + arg;
+ }
+ }
+
+ return result;
+}
+
+String
+format_description
+(
+ const HelpOptionDetails& o,
+ std::size_t start,
+ std::size_t allowed,
+ bool tab_expansion
+)
+{
+ auto desc = o.desc;
+
+ if (o.has_default && (!o.is_boolean || o.default_value != "false"))
+ {
+ if(!o.default_value.empty())
+ {
+ desc += toLocalString(" (default: " + o.default_value + ")");
+ }
+ else
+ {
+ desc += toLocalString(" (default: \"\")");
+ }
+ }
+
+ String result;
+
+ if (tab_expansion)
+ {
+ String desc2;
+ auto size = std::size_t{ 0 };
+ for (auto c = std::begin(desc); c != std::end(desc); ++c)
+ {
+ if (*c == '\n')
+ {
+ desc2 += *c;
+ size = 0;
+ }
+ else if (*c == '\t')
+ {
+ auto skip = 8 - size % 8;
+ stringAppend(desc2, skip, ' ');
+ size += skip;
+ }
+ else
+ {
+ desc2 += *c;
+ ++size;
+ }
+ }
+ desc = desc2;
+ }
+
+ desc += " ";
+
+ auto current = std::begin(desc);
+ auto previous = current;
+ auto startLine = current;
+ auto lastSpace = current;
+
+ auto size = std::size_t{};
+
+ bool appendNewLine;
+ bool onlyWhiteSpace = true;
+
+ while (current != std::end(desc))
+ {
+ appendNewLine = false;
+ if (*previous == ' ' || *previous == '\t')
+ {
+ lastSpace = current;
+ }
+ if (*current != ' ' && *current != '\t')
+ {
+ onlyWhiteSpace = false;
+ }
+
+ while (*current == '\n')
+ {
+ previous = current;
+ ++current;
+ appendNewLine = true;
+ }
+
+ if (!appendNewLine && size >= allowed)
+ {
+ if (lastSpace != startLine)
+ {
+ current = lastSpace;
+ previous = current;
+ }
+ appendNewLine = true;
+ }
+
+ if (appendNewLine)
+ {
+ stringAppend(result, startLine, current);
+ startLine = current;
+ lastSpace = current;
+
+ if (*previous != '\n')
+ {
+ stringAppend(result, "\n");
+ }
+
+ stringAppend(result, start, ' ');
+
+ if (*previous != '\n')
+ {
+ stringAppend(result, lastSpace, current);
+ }
+
+ onlyWhiteSpace = true;
+ size = 0;
+ }
+
+ previous = current;
+ ++current;
+ ++size;
+ }
+
+ //append whatever is left but ignore whitespace
+ if (!onlyWhiteSpace)
+ {
+ stringAppend(result, startLine, previous);
+ }
+
+ return result;
+}
+
+} // namespace
+
+inline
+void
+Options::add_options
+(
+ const std::string &group,
+ std::initializer_list<Option> options
+)
+{
+ OptionAdder option_adder(*this, group);
+ for (const auto &option: options)
+ {
+ option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
+ }
+}
+
+inline
+OptionAdder
+Options::add_options(std::string group)
+{
+ return OptionAdder(*this, std::move(group));
+}
+
+inline
+OptionAdder&
+OptionAdder::operator()
+(
+ const std::string& opts,
+ const std::string& desc,
+ const std::shared_ptr<const Value>& value,
+ std::string arg_help
+)
+{
+ OptionNames option_names = values::parser_tool::split_option_names(opts);
+ // Note: All names will be non-empty; but we must separate the short
+ // (length-1) and longer names
+ std::string short_name {""};
+ auto first_short_name_iter =
+ std::partition(option_names.begin(), option_names.end(),
+ [&](const std::string& name) { return name.length() > 1; }
+ );
+ auto num_length_1_names = (option_names.end() - first_short_name_iter);
+ switch(num_length_1_names) {
+ case 1:
+ short_name = *first_short_name_iter;
+ option_names.erase(first_short_name_iter);
+ CXXOPTS_FALLTHROUGH;
+ case 0:
+ break;
+ default:
+ throw_or_mimic<exceptions::invalid_option_format>(opts);
+ };
+
+ m_options.add_option
+ (
+ m_group,
+ short_name,
+ option_names,
+ desc,
+ value,
+ std::move(arg_help)
+ );
+
+ return *this;
+}
+
+inline
+void
+OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)
+{
+ // TODO: remove the duplicate code here
+ auto& store = m_parsed[details->hash()];
+ store.parse_default(details);
+ m_defaults.emplace_back(details->essential_name(), details->value().get_default_value());
+}
+
+inline
+void
+OptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details)
+{
+ auto& store = m_parsed[details->hash()];
+ store.parse_no_value(details);
+}
+
+inline
+void
+OptionParser::parse_option
+(
+ const std::shared_ptr<OptionDetails>& value,
+ const std::string& /*name*/,
+ const std::string& arg
+)
+{
+ auto hash = value->hash();
+ auto& result = m_parsed[hash];
+ result.parse(value, arg);
+
+ m_sequential.emplace_back(value->essential_name(), arg);
+}
+
+inline
+void
+OptionParser::checked_parse_arg
+(
+ int argc,
+ const char* const* argv,
+ int& current,
+ const std::shared_ptr<OptionDetails>& value,
+ const std::string& name
+)
+{
+ if (current + 1 >= argc)
+ {
+ if (value->value().has_implicit())
+ {
+ parse_option(value, name, value->value().get_implicit_value());
+ }
+ else
+ {
+ throw_or_mimic<exceptions::missing_argument>(name);
+ }
+ }
+ else
+ {
+ if (value->value().has_implicit())
+ {
+ parse_option(value, name, value->value().get_implicit_value());
+ }
+ else
+ {
+ parse_option(value, name, argv[current + 1]);
+ ++current;
+ }
+ }
+}
+
+inline
+void
+OptionParser::add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg)
+{
+ auto hash = value->hash();
+ auto& result = m_parsed[hash];
+ result.add(value, arg);
+
+ m_sequential.emplace_back(value->essential_name(), arg);
+}
+
+inline
+bool
+OptionParser::consume_positional(const std::string& a, PositionalListIterator& next)
+{
+ while (next != m_positional.end())
+ {
+ auto iter = m_options.find(*next);
+ if (iter != m_options.end())
+ {
+ if (!iter->second->value().is_container())
+ {
+ auto& result = m_parsed[iter->second->hash()];
+ if (result.count() == 0)
+ {
+ add_to_option(iter->second, a);
+ ++next;
+ return true;
+ }
+ ++next;
+ continue;
+ }
+ add_to_option(iter->second, a);
+ return true;
+ }
+ throw_or_mimic<exceptions::no_such_option>(*next);
+ }
+
+ return false;
+}
+
+inline
+void
+Options::parse_positional(std::string option)
+{
+ parse_positional(std::vector<std::string>{std::move(option)});
+}
+
+inline
+void
+Options::parse_positional(std::vector<std::string> options)
+{
+ m_positional = std::move(options);
+
+ m_positional_set.insert(m_positional.begin(), m_positional.end());
+}
+
+inline
+void
+Options::parse_positional(std::initializer_list<std::string> options)
+{
+ parse_positional(std::vector<std::string>(options));
+}
+
+inline
+ParseResult
+Options::parse(int argc, const char* const* argv)
+{
+ OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
+
+ return parser.parse(argc, argv);
+}
+
+inline ParseResult
+OptionParser::parse(int argc, const char* const* argv)
+{
+ int current = 1;
+ bool consume_remaining = false;
+ auto next_positional = m_positional.begin();
+
+ std::vector<std::string> unmatched;
+
+ while (current != argc)
+ {
+ if (strcmp(argv[current], "--") == 0)
+ {
+ consume_remaining = true;
+ ++current;
+ break;
+ }
+ bool matched = false;
+ values::parser_tool::ArguDesc argu_desc =
+ values::parser_tool::ParseArgument(argv[current], matched);
+
+ if (!matched)
+ {
+ //not a flag
+
+ // but if it starts with a `-`, then it's an error
+ if (argv[current][0] == '-' && argv[current][1] != '\0') {
+ if (!m_allow_unrecognised) {
+ throw_or_mimic<exceptions::invalid_option_syntax>(argv[current]);
+ }
+ }
+
+ //if true is returned here then it was consumed, otherwise it is
+ //ignored
+ if (consume_positional(argv[current], next_positional))
+ {
+ }
+ else
+ {
+ unmatched.emplace_back(argv[current]);
+ }
+ //if we return from here then it was parsed successfully, so continue
+ }
+ else
+ {
+ //short or long option?
+ if (argu_desc.grouping)
+ {
+ const std::string& s = argu_desc.arg_name;
+
+ for (std::size_t i = 0; i != s.size(); ++i)
+ {
+ std::string name(1, s[i]);
+ auto iter = m_options.find(name);
+
+ if (iter == m_options.end())
+ {
+ if (m_allow_unrecognised)
+ {
+ unmatched.push_back(std::string("-") + s[i]);
+ continue;
+ }
+ //error
+ throw_or_mimic<exceptions::no_such_option>(name);
+ }
+
+ auto value = iter->second;
+
+ if (i + 1 == s.size())
+ {
+ //it must be the last argument
+ checked_parse_arg(argc, argv, current, value, name);
+ }
+ else if (value->value().has_implicit())
+ {
+ parse_option(value, name, value->value().get_implicit_value());
+ }
+ else if (i + 1 < s.size())
+ {
+ std::string arg_value = s.substr(i + 1);
+ parse_option(value, name, arg_value);
+ break;
+ }
+ else
+ {
+ //error
+ throw_or_mimic<exceptions::option_requires_argument>(name);
+ }
+ }
+ }
+ else if (argu_desc.arg_name.length() != 0)
+ {
+ const std::string& name = argu_desc.arg_name;
+
+ auto iter = m_options.find(name);
+
+ if (iter == m_options.end())
+ {
+ if (m_allow_unrecognised)
+ {
+ // keep unrecognised options in argument list, skip to next argument
+ unmatched.emplace_back(argv[current]);
+ ++current;
+ continue;
+ }
+ //error
+ throw_or_mimic<exceptions::no_such_option>(name);
+ }
+
+ auto opt = iter->second;
+
+ //equals provided for long option?
+ if (argu_desc.set_value)
+ {
+ //parse the option given
+
+ parse_option(opt, name, argu_desc.value);
+ }
+ else
+ {
+ //parse the next argument
+ checked_parse_arg(argc, argv, current, opt, name);
+ }
+ }
+
+ }
+
+ ++current;
+ }
+
+ for (auto& opt : m_options)
+ {
+ auto& detail = opt.second;
+ const auto& value = detail->value();
+
+ auto& store = m_parsed[detail->hash()];
+
+ if (value.has_default()) {
+ if (!store.count() && !store.has_default()) {
+ parse_default(detail);
+ }
+ }
+ else {
+ parse_no_value(detail);
+ }
+ }
+
+ if (consume_remaining)
+ {
+ while (current < argc)
+ {
+ if (!consume_positional(argv[current], next_positional)) {
+ break;
+ }
+ ++current;
+ }
+
+ //adjust argv for any that couldn't be swallowed
+ while (current != argc) {
+ unmatched.emplace_back(argv[current]);
+ ++current;
+ }
+ }
+
+ finalise_aliases();
+
+ ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(m_defaults), std::move(unmatched));
+ return parsed;
+}
+
+inline
+void
+OptionParser::finalise_aliases()
+{
+ for (auto& option: m_options)
+ {
+ auto& detail = *option.second;
+ auto hash = detail.hash();
+ m_keys[detail.short_name()] = hash;
+ for(const auto& long_name : detail.long_names()) {
+ m_keys[long_name] = hash;
+ }
+
+ m_parsed.emplace(hash, OptionValue());
+ }
+}
+
+inline
+void
+Options::add_option
+(
+ const std::string& group,
+ const Option& option
+)
+{
+ add_options(group, {option});
+}
+
+inline
+void
+Options::add_option
+(
+ const std::string& group,
+ const std::string& s,
+ const OptionNames& l,
+ std::string desc,
+ const std::shared_ptr<const Value>& value,
+ std::string arg_help
+)
+{
+ auto stringDesc = toLocalString(std::move(desc));
+ auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);
+
+ if (!s.empty())
+ {
+ add_one_option(s, option);
+ }
+
+ for(const auto& long_name : l) {
+ add_one_option(long_name, option);
+ }
+
+ //add the help details
+
+ if (m_help.find(group) == m_help.end())
+ {
+ m_group.push_back(group);
+ }
+
+ auto& options = m_help[group];
+
+ options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
+ value->has_default(), value->get_default_value(),
+ value->has_implicit(), value->get_implicit_value(),
+ std::move(arg_help),
+ value->is_container(),
+ value->is_boolean()});
+}
+
+inline
+void
+Options::add_one_option
+(
+ const std::string& option,
+ const std::shared_ptr<OptionDetails>& details
+)
+{
+ auto in = m_options->emplace(option, details);
+
+ if (!in.second)
+ {
+ throw_or_mimic<exceptions::option_already_exists>(option);
+ }
+}
+
+inline
+String
+Options::help_one_group(const std::string& g) const
+{
+ using OptionHelp = std::vector<std::pair<String, String>>;
+
+ auto group = m_help.find(g);
+ if (group == m_help.end())
+ {
+ return "";
+ }
+
+ OptionHelp format;
+
+ std::size_t longest = 0;
+
+ String result;
+
+ if (!g.empty())
+ {
+ result += toLocalString(" " + g + " options:\n");
+ }
+
+ for (const auto& o : group->second.options)
+ {
+ if (o.l.size() &&
+ m_positional_set.find(o.l.front()) != m_positional_set.end() &&
+ !m_show_positional)
+ {
+ continue;
+ }
+
+ auto s = format_option(o);
+ longest = (std::max)(longest, stringLength(s));
+ format.push_back(std::make_pair(s, String()));
+ }
+ longest = (std::min)(longest, OPTION_LONGEST);
+
+ //widest allowed description -- min 10 chars for helptext/line
+ std::size_t allowed = 10;
+ if (m_width > allowed + longest + OPTION_DESC_GAP)
+ {
+ allowed = m_width - longest - OPTION_DESC_GAP;
+ }
+
+ auto fiter = format.begin();
+ for (const auto& o : group->second.options)
+ {
+ if (o.l.size() &&
+ m_positional_set.find(o.l.front()) != m_positional_set.end() &&
+ !m_show_positional)
+ {
+ continue;
+ }
+
+ auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
+
+ result += fiter->first;
+ if (stringLength(fiter->first) > longest)
+ {
+ result += '\n';
+ result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
+ }
+ else
+ {
+ result += toLocalString(std::string(longest + OPTION_DESC_GAP -
+ stringLength(fiter->first),
+ ' '));
+ }
+ result += d;
+ result += '\n';
+
+ ++fiter;
+ }
+
+ return result;
+}
+
+inline
+void
+Options::generate_group_help
+(
+ String& result,
+ const std::vector<std::string>& print_groups
+) const
+{
+ for (std::size_t i = 0; i != print_groups.size(); ++i)
+ {
+ const String& group_help_text = help_one_group(print_groups[i]);
+ if (empty(group_help_text))
+ {
+ continue;
+ }
+ result += group_help_text;
+ if (i < print_groups.size() - 1)
+ {
+ result += '\n';
+ }
+ }
+}
+
+inline
+void
+Options::generate_all_groups_help(String& result) const
+{
+ generate_group_help(result, m_group);
+}
+
+inline
+std::string
+Options::help(const std::vector<std::string>& help_groups, bool print_usage) const
+{
+ String result = m_help_string;
+ if(print_usage)
+ {
+ result+= "\nUsage:\n " + toLocalString(m_program);
+ }
+
+ if (!m_custom_help.empty())
+ {
+ result += " " + toLocalString(m_custom_help);
+ }
+
+ if (!m_positional.empty() && !m_positional_help.empty()) {
+ result += " " + toLocalString(m_positional_help);
+ }
+
+ result += "\n\n";
+
+ if (help_groups.empty())
+ {
+ generate_all_groups_help(result);
+ }
+ else
+ {
+ generate_group_help(result, help_groups);
+ }
+
+ return toUTF8String(result);
+}
+
+inline
+std::vector<std::string>
+Options::groups() const
+{
+ return m_group;
+}
+
+inline
+const HelpGroupDetails&
+Options::group_help(const std::string& group) const
+{
+ return m_help.at(group);
+}
+
+} // namespace cxxopts
+
+#endif //CXXOPTS_HPP_INCLUDED
diff --git a/config_generator/src/Config.cc b/config_generator/src/Config.cc
index 89aa67e..0918dcf 100644
--- a/config_generator/src/Config.cc
+++ b/config_generator/src/Config.cc
@@ -1,165 +1,164 @@
#include "HEJC/Config.hh"
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <utility>
#include "HEJ/Config.hh"
#include "HEJ/exceptions.hh"
#include "HEJC/Generators.hh"
#include "HEJC/Process.hh"
#include "HEJC/Range.hh"
#include "HEJC/Utility.hh"
// TODO: this has to be declared (not necessarily defined)
// *before* including HEJ/YAMLreader.hh
namespace HEJ::detail {
void set_from_yaml(HEJC::Process & proc, YAML::Node const & yaml){
proc = HEJC::to_Process(yaml.as<std::string>());
}
}
#include "HEJ/YAMLreader.hh"
namespace HEJC {
namespace {
template<class T>
void extract(T & setting, YAML::Node & source, std::string const & key) {
HEJ::set_from_yaml(setting, source, key);
source.remove(key);
}
std::uint32_t extract_int(std::string_view & range) {
auto end = range.find_first_not_of("0123456789");
if(end == range.npos) end = range.size();
const std::string range_str{range.substr(0, end)};
const auto tmp = std::stoul(range_str);
if(tmp > std::numeric_limits<std::uint32_t>::max()) {
throw std::out_of_range{range_str};
}
range.remove_prefix(end);
return tmp;
}
Range<std::uint32_t> to_Range(std::string_view range) {
const std::uint32_t lowest = extract_int(range);
range = trim_start(range);
if(range.empty()) return Range{lowest};
if(range[0] != '-') {
throw std::invalid_argument{"Start of range not followed by '-'"};
}
range.remove_prefix(1);
const std::uint32_t highest = extract_int(range);
if(!range.empty()) {
throw std::invalid_argument{"Trailing text after end of range"};
}
return Range{lowest, highest};
}
std::pair<Generator, Range<std::uint32_t>> get_generator_range(
std::string_view key0,
std::string_view key1
) {
Generator gen;
try{
gen = to_Generator(key0);
} catch(...) {
std::swap(key0, key1);
try {
gen = to_Generator(key0);
} catch(...) {
throw std::invalid_argument{
"Neither '" + std::string{key0} + "'"
" nor '" + std::string{key1} + "' is a supported generator"
};
}
}
// now `key1` has to specify the multiplicities
// ignore any "jets" at the end
static constexpr std::string_view jets_word = "jets";
if(ends_with(key1, jets_word)) {
key1.remove_suffix(jets_word.size());
key1 = trim_end(key1);
}
return std::make_pair(gen, to_Range(key1));
}
std::vector<GeneratorConfig> extract_fo_configs(
YAML::Node & yaml
) {
// we need to find the keys, create the result,
// *and delete the entries*.
//
// Since we cannot (and should not) modify the number of entries
// in `yaml` during iteration, we first only find the keys and
// then construct the result and delete the entries
std::vector<YAML::Node> generator_keys;
for(auto const & entry: yaml) {
auto const & key = entry.first;
if(!key.IsSequence()) continue;
if(key.size() != 2) {
throw std::invalid_argument{
"Generator specification key has not exactly two entries"
};
}
generator_keys.emplace_back(key);
}
std::vector<GeneratorConfig> res;
res.reserve(generator_keys.size());
for(auto const & key: generator_keys) {
auto const [generator, multiplicities] = get_generator_range(
key[0].as<std::string>(), key[1].as<std::string>()
);
GeneratorConfig conf;
conf.generator = generator;
conf.multiplicities = multiplicities;
conf.config = yaml[key];
res.emplace_back(std::move(conf));
yaml.remove(key);
}
return res;
}
Beam extract_beam(YAML::Node & yaml) {
if(!yaml["beam"]) {
throw HEJ::missing_option{"beam"};
}
Beam beam;
HEJ::set_from_yaml(beam.energy, yaml, "beam", "energy");
std::vector<HEJ::ParticleID> particles;
HEJ::set_from_yaml_if_defined(particles, yaml, "beam", "particles");
if(! particles.empty()){
if(particles.size() != 2){
throw std::invalid_argument{"Not exactly two beam particles"};
}
beam.particles.front() = particles.front();
beam.particles.back() = particles.back();
}
yaml.remove("beam");
return beam;
}
}
Config into_Config(YAML::Node && yaml) {
if(!yaml.IsMap()) {
throw std::invalid_argument{"YAML node is not a map"};
}
Config config;
config.fo_configs = extract_fo_configs(yaml);
extract(config.process, yaml, "process");
extract(config.pdf, yaml, "pdf");
- extract(config.runs, yaml, "runs");
config.beam = extract_beam(yaml);
config.hej_config = std::move(yaml);
return config;
}
}
diff --git a/config_generator/src/HEJC.cc b/config_generator/src/HEJC.cc
index 4266ea3..30eb36d 100644
--- a/config_generator/src/HEJC.cc
+++ b/config_generator/src/HEJC.cc
@@ -1,578 +1,572 @@
#include "HEJC/HEJC.hh"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <functional>
#include <limits>
#include <random>
#include <string>
#include "HEJ/exceptions.hh"
#include "HEJ/Config.hh"
#include "HEJ/PDG_codes.hh"
#include "HEJ/YAMLreader.hh"
#include "yaml-cpp/yaml.h"
#include "HEJC/Config.hh"
#include "HEJC/Generators.hh"
#include "HEJC/Utility.hh"
namespace fs = std::filesystem;
namespace HEJC {
namespace {
using Rng = std::ranlux48;
constexpr double low_pt_frac = 0.8;
void update_HEJ_seed(
YAML::Node & node,
Rng & rng
) {
std::uniform_int_distribution<std::uint32_t> dist{
0,
std::numeric_limits<std::uint32_t>::max()
};
if(node["random generator"]["name"].as<std::string>() == "mixmax") {
node["random generator"]["seed"] = dist(rng);
}
}
void update_HEJ_jets(YAML::Node & node) {
node["jets"].remove("max rapidity");
node["resummation jets"] = node["jets"];
node["fixed order jets"] = clone(node["jets"]);
node.remove("jets");
}
void update_HEJ(YAML::Node & node, Rng & rng) {
update_HEJ_seed(node, rng);
update_HEJ_jets(node);
}
// HEJC generates a number of entries in the HEJ config
// if they already exist this is a user mistake
void assert_no_unused_entries(const YAML::Node & yaml) {
for(auto entry: {
"fixed order jets",
"resummation jets",
"NLO truncation",
"require low pt jet"
}) {
if(yaml[entry]) {
throw HEJ::unknown_option{entry};
}
}
}
void validate_HEJ_config(YAML::Node && yaml) {
assert_no_unused_entries(yaml);
// check it's a valid HEJ config after adding the entries
// we will generate when writing to a file
if(!yaml["jets"]) {
throw HEJ::missing_option{"jets"};
}
if(!yaml["jets"]["max rapidity"]) {
throw HEJ::missing_option{"jets: max rapidity"};
}
update_HEJ_jets(yaml);
yaml.remove("pdf");
yaml.remove("beams");
yaml["NLO truncation"]["enabled"] = false;
yaml["require low pt jet"] = false;
HEJ::to_Config(yaml);
}
void validate_HEJFOG_config(YAML::Node const & yaml) {
// TODO: would be nice to check against HEJFOG's `to_Config`,
// but that is not public
// TODO: add decays?
for(auto opt: {
"jets",
"process",
"vev",
"particle properties",
"Higgs coupling",
"pdf",
"beam"
}) {
if(yaml[opt]) {
throw HEJ::unknown_option{
"'" + std::string{opt} + "' in HEJFOG configuration"
};
}
}
}
void validate_Sherpa2_config(YAML::Node const & yaml) {
if(yaml["processes"]) {
throw HEJ::unknown_option{
"'processes' in Sherpa2 configuration"
};
}
for(auto opt: {
"PDF_LIBRARY",
"PDF_SET",
"USE_PDF_ALPHAS",
"BEAM_1",
"BEAM_2",
"FRAGMENTATION",
"YFS_MODE",
"MI_HANDLER",
"CSS_MAXEM",
"BEAM_REMNANTS",
"MASSIVE[4]",
"MASS[4]",
"MASSIVE[5]",
"MASS[5]",
"MASS[23]",
"MASS[23]",
"WIDTH[23]",
"MASS[24]",
"WIDTH[24]",
"MASS[25]",
"WIDTH[25]"
}) {
if(yaml["run"][opt]) {
throw HEJ::unknown_option{
"'" + std::string{opt} + "' in Sherpa2 configuration"
};
}
}
}
void validate_Sherpa2NLO_config(YAML::Node const & yaml) {
validate_Sherpa2_config(yaml);
}
void validate_FO_config(GeneratorConfig const & config) {
switch(config.generator) {
case HEJFOG:
return validate_HEJFOG_config(config.config);
case Sherpa2:
return validate_Sherpa2_config(config.config);
case Sherpa2NLO:
return validate_Sherpa2NLO_config(config.config);
}
}
void validate_config(Config const & config) {
validate_HEJ_config(clone(config.hej_config));
if(config.fo_configs.empty()) {
throw HEJ::missing_option{
"Need to specify at least one fixed-order generator"
};
}
for(auto const & config: config.fo_configs) {
validate_FO_config(config);
}
}
struct Sherpa2Format {
YAML::Node const & conf;
};
std::ostream & operator<<(std::ostream & out, Sherpa2Format f) {
// we need to convert all YAML nodes to `std::string`
// to avoid bad formatting of multiline strings
out << "(run){\n";
for(auto const & entry: f.conf["run"]) {
out << " " << entry.first.as<std::string>()
<< ' ' << entry.second.as<std::string>() << ";\n";
}
out << "}(run);\n\n"
"(processes){\n"
" Process " << f.conf["processes"].as<std::string>() << ";\n"
" End process\n"
"}(processes);\n\n"
"(selector){\n";
for(auto const & entry: f.conf["selector"]) {
out << " " << entry.as<std::string>() << ";\n";
}
out << "}(selector);\n";
if(f.conf["analysis"]) {
out << "\n(analysis){\n";
for(auto const & entry: f.conf["analysis"]) {
out << " " << entry.as<std::string>() << ";\n";
}
out << "}(analysis);\n";
}
return out;
}
std::string to_Sherpa_str(HEJ::ParticleID id) {
if(id == HEJ::pid::proton || id == HEJ::pid::antiproton) {
return "93";
}
return std::to_string(id);
}
}
HEJC::HEJC(fs::path const & config_file):
HEJC{YAML::LoadFile(config_file)}
{}
HEJC::HEJC(YAML::Node && config):
HEJC{into_Config(std::move(config))}
{}
HEJC::HEJC(Config const & config):
- runs_{config.runs},
process_{config.process},
pdf_{config.pdf},
beam_{config.beam},
hej_config_{config.hej_config},
fo_configs_{config.fo_configs}
{
validate_config(config);
}
- void HEJC::write_config(fs::path const & base_dir) const {
+ void HEJC::write_config(
+ fs::path const & target_dir,
+ Rng & rng
+ ) const {
// from this point on the only possible errors should be I/O failures,
// we don't want to produce half-broken output if we can avoid it
- Rng rng{};
- rng.seed(1);
- fs::create_directories(base_dir);
- for(std::uint64_t run = 0; run < runs_; ++run) {
- const fs::path subdir = "run_" + std::to_string(run);
- const fs::path target_dir = base_dir/subdir;
- fs::create_directories(target_dir);
write_HEJ(target_dir, rng);
for(auto const & fo_config: fo_configs_) {
write_FO_config(fo_config, target_dir, rng);
}
- }
}
void HEJC::write_HEJ(fs::path const & target_dir, Rng & rng) const {
const bool use_HEJFOG = std::any_of(
fo_configs_.begin(), fo_configs_.end(),
[](auto const & gen) { return gen.generator == Generator::HEJFOG; }
);
const bool need_lowpt = std::any_of(
fo_configs_.begin(), fo_configs_.end(),
[](auto const & gen) { return gen.generator != Generator::HEJFOG; }
);
if(use_HEJFOG) {
write_HEJ_with_HEJFOG(target_dir, rng);
}
if(need_lowpt) {
write_HEJ_default(target_dir, rng);
write_HEJ_lowpt(target_dir, rng);
}
std::vector<Range<std::uint32_t>> NLO_multiplicities;
for(auto const & fo_config: fo_configs_) {
if(is_NLO(fo_config.generator)) {
NLO_multiplicities.emplace_back(fo_config.multiplicities);
}
}
for(const auto multiplicities: NLO_multiplicities) {
for(const std::uint32_t njets: multiplicities) {
write_HEJ_NLO(target_dir, njets, rng);
write_HEJ_NLO_lowpt(target_dir, njets, rng);
}
}
}
void HEJC::write_HEJ_default(fs::path const & target_dir, Rng & rng) const {
YAML::Node conf = clone(hej_config_);
update_HEJ(conf, rng);
conf["NLO truncation"]["enabled"] = false;
conf["require low pt jet"] = false;
std::ofstream out{target_dir/"config_HEJ.yml"};
out << conf;
}
void HEJC::write_HEJ_lowpt(fs::path const & target_dir, Rng & rng) const {
YAML::Node conf = clone(hej_config_);
const auto min_jet_pt = conf["jets"]["min pt"].as<double>();
update_HEJ(conf, rng);
conf["fixed order jets"]["min pt"] = low_pt_frac * min_jet_pt;
conf["require low pt jet"] = true;
conf["NLO truncation"]["enabled"] = false;
if(conf["event treatment"]["non-resummable"].as<std::string>() == "keep") {
conf["event treatment"]["non-resummable"] = "discard";
}
std::ofstream out{target_dir/"config_HEJ_lowpt.yml"};
out << conf;
}
void HEJC::write_HEJ_with_HEJFOG(fs::path const & target_dir, Rng & rng) const {
YAML::Node conf = clone(hej_config_);
const auto min_jet_pt = conf["jets"]["min pt"].as<double>();
update_HEJ(conf, rng);
conf["fixed order jets"]["min pt"] = low_pt_frac * min_jet_pt;
conf["require low pt jet"] = false;
conf["NLO truncation"]["enabled"] = false;
std::ofstream out{target_dir/"config_HEJ_with_HEJFOG.yml"};
out << conf;
}
void HEJC::write_HEJ_NLO(
fs::path const & target_dir,
const std::uint32_t njets,
Rng & rng
) const {
YAML::Node conf = clone(hej_config_);
update_HEJ(conf, rng);
conf["NLO truncation"]["enabled"] = true;
conf["NLO truncation"]["nlo order"] = njets;
conf["require low pt jet"] = false;
const std::string filename =
"config_HEJ_NLO_" + std::to_string(njets) + "j.yml";
std::ofstream out{target_dir/filename};
out << conf;
}
void HEJC::write_HEJ_NLO_lowpt(
fs::path const & target_dir,
const std::uint32_t njets,
Rng & rng
) const {
YAML::Node conf = clone(hej_config_);
const auto min_jet_pt = conf["jets"]["min pt"].as<double>();
update_HEJ(conf, rng);
conf["fixed order jets"]["min pt"] = low_pt_frac * min_jet_pt;
conf["require low pt jet"] = true;
conf["NLO truncation"]["enabled"] = true;
conf["NLO truncation"]["nlo order"] = njets;
if(conf["event treatment"]["non-resummable"].as<std::string>() == "keep") {
conf["event treatment"]["non-resummable"] = "discard";
}
const std::string filename =
"config_HEJ_NLO_" + std::to_string(njets) + "j_lowpt.yml";
std::ofstream out{target_dir/filename};
out << conf;
}
void HEJC::write_FO_config(
GeneratorConfig const & conf,
fs::path const & target_dir,
Rng & rng
) const {
switch(conf.generator) {
case HEJFOG:
return write_HEJFOG(
conf.multiplicities,
conf.config,
target_dir,
rng
);
case Sherpa2:
return write_Sherpa2(
conf.multiplicities,
conf.config,
target_dir,
rng
);
case Sherpa2NLO:
return write_Sherpa2NLO(
conf.multiplicities,
conf.config,
target_dir,
rng
);
}
}
void HEJC::write_HEJFOG(
Range<std::uint32_t> multiplicities,
YAML::Node const & c,
std::filesystem::path const & target_dir,
Rng & rng
) const {
YAML::Node config = clone(c);
config["pdf"] = pdf_;
config["beam"]["energy"] = beam_.energy;
config["beam"]["particles"][0] = name(beam_.particles[0]);
config["beam"]["particles"][1] = name(beam_.particles[1]);
const auto min_jet_pt = hej_config_["jets"]["min pt"].as<double>();
config["jets"] = hej_config_["jets"];
config["jets"]["min pt"] = low_pt_frac * min_jet_pt;
config["jets"]["peak pt"] = min_jet_pt;
config["vev"] = hej_config_["vev"];
config["particle properties"] = hej_config_["particle properties"];
if(hej_config_["Higgs coupling"]) {
config["Higgs coupling"] = hej_config_["Higgs coupling"];
}
std::string proc_str =
name(process_.incoming[0]) + ' ' + name(process_.incoming[1])
+ " =>";
for(HEJ::ParticleID out: process_.outgoing) {
proc_str += ' ' + name(out);
}
proc_str += ' ';
for(const std::uint32_t njets: multiplicities) {
const std::string njet_str = std::to_string(njets);
config["process"] = proc_str + njet_str + 'j';
update_HEJ_seed(config, rng);
const std::string filename = "config_HEJFOG_" + njet_str + "j.yml";
std::ofstream out{target_dir/filename};
out << config;
}
}
void update_Sherpa2_seed(
YAML::Node & node,
Rng & rng
) {
std::uniform_int_distribution<std::uint32_t> dist{
0,
std::numeric_limits<std::uint32_t>::max()
};
node["run"]["RANDOM_SEED"] = dist(rng);
}
YAML::Node HEJC::gen_Sherpa2_config(YAML::Node const & base) const {
YAML::Node config = clone(base);
config["run"]["FRAGMENTATION"] = "Off";
config["run"]["YFS_MODE"] = 0;
config["run"]["MI_HANDLER"] = "None";
config["run"]["CSS_MAXEM"] = 0;
config["run"]["BEAM_REMNANTS"] = 0;
config["run"]["PDF_LIBRARY"] = "LHAPDFSherpa";
config["run"]["USE_PDF_ALPHAS"] = 1;
config["run"]["PDF_SET"] = pdf_;
config["run"]["MASSIVE[4]"] = 0;
config["run"]["MASS[4]"] = 0.;
config["run"]["MASSIVE[5]"] = 0;
config["run"]["MASS[5]"] = 0.;
const std::string E_beam = std::to_string(beam_.energy);
static constexpr std::array<std::string_view, 3> ew_boson_names = {
"Z", "W", "Higgs"
};
for(int id = 23; id <= 25; ++id) {
const std::string id_str = std::to_string(id);
const std::string mass_str = "MASS[" + id_str + "]";
const std::string width_str = "WIDTH[" + id_str + "]";
const auto name = std::string{ew_boson_names[id - 23]};
config["run"][mass_str] =
hej_config_["particle properties"][name]["mass"];
config["run"][width_str] =
hej_config_["particle properties"][name]["width"];
}
config["run"]["BEAM_1"] = std::to_string(beam_.particles[0]) + ' ' + E_beam;
config["run"]["BEAM_2"] = std::to_string(beam_.particles[1]) + ' ' + E_beam;
if(!config["run"]["ME_SIGNAL_GENERATOR"]) {
config["run"]["ME_SIGNAL_GENERATOR"] = "Comix,Amegic";
}
return config;
}
std::string HEJC::gen_Sherpa2_jet_def(
std::string njets,
const double min_jet_pt
) const {
return "FastjetFinder "
+ hej_config_["jets"]["algorithm"].as<std::string>()
+ ' ' + njets
+ ' ' + std::to_string(min_jet_pt)
+ " 0 " + hej_config_["jets"]["R"].as<std::string>()
+ " 0.75 " + hej_config_["jets"]["max rapidity"].as<std::string>();
}
void HEJC::write_Sherpa2(
Range<std::uint32_t> multiplicities,
YAML::Node const & c,
std::filesystem::path const & target_dir,
Rng & rng
) const {
YAML::Node config = gen_Sherpa2_config(c);
std::string proc_str =
to_Sherpa_str(process_.incoming[0]) + ' ' + to_Sherpa_str(process_.incoming[1])
+ " ->";
for(HEJ::ParticleID out: process_.outgoing) {
proc_str += ' ' + std::to_string(out);
}
for(const std::uint32_t njets: multiplicities) {
std::string proc_nj = proc_str;
for(std::uint32_t i = 0; i < njets; ++i) {
proc_nj += " 93";
}
config["processes"] = proc_nj;
const YAML::Node old_selector = clone(config["selector"]);
const std::string njet_str = std::to_string(njets);
const std::string jet_def = gen_Sherpa2_jet_def(
njet_str, hej_config_["jets"]["min pt"].as<double>()
);
config["selector"].push_back(jet_def);
{
const std::string filename = "Run_" + njet_str + "j.dat";
std::ofstream out{target_dir/filename};
update_Sherpa2_seed(config, rng);
out << Sherpa2Format{config};
}
config["selector"] = old_selector;
const std::string low_pt_jet_def = gen_Sherpa2_jet_def(
njet_str, low_pt_frac * hej_config_["jets"]["min pt"].as<double>()
);
config["selector"].push_back(low_pt_jet_def);
const std::string filename = "Run_" + njet_str + "j_lowpt.dat";
std::ofstream out{target_dir/filename};
update_Sherpa2_seed(config, rng);
out << Sherpa2Format{config};
config["selector"] = old_selector;
}
}
void HEJC::write_Sherpa2NLO(
Range<std::uint32_t> multiplicities,
YAML::Node const & c,
std::filesystem::path const & target_dir,
Rng & rng
) const {
// TODO: code duplication with `write_Sherpa2`
YAML::Node config = gen_Sherpa2_config(c);
assert(config["run"]["ME_SIGNAL_GENERATOR"]);
const auto me_gen = config["run"]["ME_SIGNAL_GENERATOR"].as<std::string>();
if(me_gen.find("OpenLoops") == me_gen.npos) {
config["run"]["ME_SIGNAL_GENERATOR"] = me_gen + ",OpenLoops";
}
std::string proc_str =
to_Sherpa_str(process_.incoming[0]) + ' ' + to_Sherpa_str(process_.incoming[1])
+ " ->";
for(HEJ::ParticleID out: process_.outgoing) {
proc_str += ' ' + std::to_string(out);
}
for(const std::uint32_t njets: multiplicities) {
std::string proc_nj = proc_str;
for(std::uint32_t i = 0; i < njets; ++i) {
proc_nj += " 93";
}
// evil hack: append mode & generator to process
config["processes"] = proc_nj + ";\n"
" NLO_QCD_Mode Fixed_Order;\n"
" Loop_Generator OpenLoops";
const YAML::Node old_selector = clone(config["selector"]);
const std::string njet_str = std::to_string(njets);
const std::string jet_def = gen_Sherpa2_jet_def(
njet_str, hej_config_["jets"]["min pt"].as<double>()
);
config["selector"].push_back(jet_def);
update_Sherpa2_seed(config, rng);
const std::string filename = "Run_NLO_" + njet_str + "j.dat";
std::ofstream out{target_dir/filename};
out << Sherpa2Format{config};
config["selector"] = old_selector;
}
}
}
diff --git a/config_generator/src/bin/main.cc b/config_generator/src/bin/main.cc
index 4cc0271..cff4e86 100644
--- a/config_generator/src/bin/main.cc
+++ b/config_generator/src/bin/main.cc
@@ -1,76 +1,59 @@
/**
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2023
* \copyright GPLv2 or later
*/
+#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <filesystem>
#include "HEJC/HEJC.hh"
+#include "HEJC/Options.hh"
#include <HEJ/exceptions.hh>
namespace fs = std::filesystem;
-bool can_write_to(fs::path out_dir) {
- fs::file_type type = fs::status(out_dir).type();
- // first follow symbolic links
- while(type == fs::file_type::symlink) {
- out_dir = fs::read_symlink(out_dir);
- type = fs::status(out_dir).type();
- }
- switch(type){
- case fs::file_type::directory:
- case fs::file_type::not_found:
- return true;
- default:
- return false;
- }
-}
-
-int main(int argc, char**argv) {
- if(argc < 3) {
- std::cerr << "\n# Usage:\n." << argv[0] << " CONFIG_FILES... OUTPUT_DIRECTORY\n";
- return EXIT_FAILURE;
- }
- char** begin_config_files = argv + 1;
- char** end_config_files = argv + argc - 1;
- char const * out_dir = argv[argc - 1];
- // We check if `out_dir` can be written to *before* checking the input files
- // This produces a nicer error message if the user forgot to specify the
- // output directory, in which case `out_dir` will be a regular (config) file
- if(!can_write_to(out_dir)){
- std::cerr << "Cannot write configuration to " << out_dir << ":\n"
- " " << out_dir << " exists and is not a directory\n";
- return EXIT_FAILURE;
+int main(int argc, char** argv) {
+ HEJC::Opt opt{argc, argv};
+ if(opt.help){
+ std::cout << opt.help_message << std::endl;
+ return EXIT_SUCCESS;
}
+ std::ranlux48 rng;
+ rng.seed(1);
try {
- HEJC::HEJC hejc(begin_config_files, end_config_files);
- try {
- hejc.write_config(out_dir);
- } catch (std::exception & exc) {
- std::cerr << "Error writing configuration to " << out_dir << ":\n"
- " " << exc.what() << "\n";
- return EXIT_FAILURE;
+ HEJC::HEJC hejc(opt.config_files.begin(), opt.config_files.end());
+ for(std::uint64_t run = 0; run < opt.runs; ++run) {
+ const fs::path subdir = "run_" + std::to_string(run);
+ const fs::path target_dir = opt.out_dir/subdir;
+ try {
+ fs::create_directories(target_dir);
+ hejc.write_config(target_dir, rng);
+ } catch (std::exception & exc) {
+ std::cerr << "Error writing configuration to " << target_dir << ":\n"
+ " " << exc.what() << "\n";
+ return EXIT_FAILURE;
+ }
}
} catch (HEJ::missing_option & exc) {
std::cerr << "Error reading configuration:\n"
" Missing option: " << exc.what() << "\n";
return EXIT_FAILURE;
} catch (HEJ::unknown_option & exc) {
std::cerr << "Error reading configuration:\n"
" Unknown option: " << exc.what() << "\n";
return EXIT_FAILURE;
} catch (HEJ::invalid_type & exc) {
std::cerr << "Error reading configuration:\n"
" Invalid setting type: " << exc.what() << "\n";
return EXIT_FAILURE;
} catch (std::exception & exc) {
std::cerr << "Error reading configuration:\n"
" " << exc.what() << "\n";
return EXIT_FAILURE;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, May 14, 11:52 AM (4 h, 33 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5084637
Default Alt Text
(102 KB)

Event Timeline