Page Menu
Home
HEPForge
Search
Configure Global Search
Log In
Files
F9501155
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
45 KB
Subscribers
None
View Options
diff --git a/doc/sphinx/analyses.rst b/doc/sphinx/analyses.rst
index 57fc41f..4a43b7e 100644
--- a/doc/sphinx/analyses.rst
+++ b/doc/sphinx/analyses.rst
@@ -1,91 +1,94 @@
.. _`Writing custom analyses`:
Writing custom analyses
=======================
HEJ 2 and the HEJ fixed-order generator can generate HepMC files, so you
can always run a `Rivet <https://rivet.hepforge.org/>`_ analysis on
these. However if you compiled HEJ 2 with Rivet you can use the native
Rivet interface. For example
.. code-block:: YAML
analyses:
- rivet: [MC_XS, MC_JETS]
output: HEJ
would call the generic
`MC_XS <https://rivet.hepforge.org/analyses/MC_XS.html>`_ and
`MC_JETS <https://rivet.hepforge.org/analyses/MC_JETS.html>`_ analysis
and write the result into :code:`HEJ[.Scalename].yoda`.
HEJ 2 will then run Rivet over all different scales seperatly and
write out each into a different yoda file. Alternatively instead
of using Rivet, you can provide a custom analysis inside a C++ library.
An analysis is a class that derives from the abstract :code:`Analysis`
-base class provided by HEJ 2. It has to implement three public
+base class provided by HEJ 2. It has to implement four public
functions:
* The :code:`pass_cuts` member function return true if and only if the
given event (first argument) passes the analysis cuts
* The :code:`fill` member function adds an event to the analysis, which
for example can be used to fill histograms. HEJ 2 will only
pass events for which :code:`pass_cuts` has returned true.
+* The :code:`set_xs_scale` member function updates the ratio between
+ the total cross section and the sum of event weights.
+
* The :code:`finalise` member function is called after all events have
been processed. It can be used, for example, to print out or save the
analysis results.
The :code:`pass_cuts` and :code:`fill` functions take two arguments: the
resummation event generated by HEJ 2 and the original fixed-order
input event. Usually, the second argument can be ignored. It can be
used, for example, for implementing cuts that depend on the ratio of the
weights between the fixed-order and the resummation event.
In addition to the three member functions, there has to be a global
:code:`make_analysis` function that takes the analysis parameters in the form of
a YAML :code:`Node` and returns a :code:`std::unique_ptr` to the Analysis.
The following code creates the simplest conceivable analysis.
.. literalinclude:: ../../examples/AnalysisTemplate.cc
You can save this code to a file, for example :code:`myanalysis.cc`, and
compile it into a shared library. Using the :code:`g++` compiler, the
library can be built with
.. code-block:: sh
g++ $(HEJ-config --cxxflags) -fPIC -shared -O2 -fvisibility=hidden -Wl,-soname,libmyanalysis.so -o libmyanalysis.so myanalysis.cc
It is also good practice to add :code:`__attribute__((visibility("default")))`
after :code:`extern "C"` in the above code snippet and then compile with the
additional flag :code:`-fvisibility=hidden` to prevent name clashes.
You can use the analysis in HEJ 2 or the HEJ fixed-order
generator by adding
.. code-block:: YAML
analyses:
- plugin: /path/to/libmyanalysis.so
to the .yml configuration file.
As a more interesting example, here is the code for an analysis that
sums up the total cross section and prints the result to both standard
output and a file specified in the .yml config with
.. code-block:: YAML
analyses:
- plugin: analysis/build/directory/src/libmy_analysis.so
output: outfile
To access the configuration at run time, HEJ 2 uses the yaml-cpp
library; for more details see the `yaml-cpp tutorial
<https://github.com/jbeder/yaml-cpp/wiki/Tutorial>`_. The analysis code
itself is
.. literalinclude:: ../../examples/AnalysisPrint.cc
diff --git a/examples/AnalysisPrint.cc b/examples/AnalysisPrint.cc
index 522fe2b..02dbc56 100644
--- a/examples/AnalysisPrint.cc
+++ b/examples/AnalysisPrint.cc
@@ -1,78 +1,80 @@
//! HEJ analysis to output the cross section to a file
#include <cmath>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include "HEJ/Analysis.hh"
#include "HEJ/Event.hh"
#include "yaml-cpp/yaml.h"
#include "LHEF/LHEF.h"
namespace {
class AnalysisPrint: public HEJ::Analysis {
public:
AnalysisPrint(YAML::Node const & config, LHEF::HEPRUP const & heprup):
- xsection_{0.}, xsection_error_{0.},
+ wt_sum_{0.}, wt2_sum_{0.},
outfile_{config["output"].as<std::string>()},
generators_{heprup.generators}
{}
void fill(
HEJ::Event const & event,
HEJ::Event const & /* FO_event */
) override {
const double wt = event.central().weight;
- xsection_ += wt;
- xsection_error_ += wt*wt; // this error estimate is too small
+ wt_sum_ += wt;
+ wt2_sum_ += wt*wt; // this error estimate is too small
}
bool pass_cuts(
HEJ::Event const & /* event */,
HEJ::Event const & /* FO_event */
) override {
return true;
}
- void scale(double xsscale) override {
- // used to scale the cross sections and histograms before .finalise in case the
- // normalisation is unknown until the end of the run
- xsection_*=xsscale;
- xsection_error_*=xsscale*xsscale;
+ void set_xs_scale(double xsscale) override {
+ xs_scale_ = xsscale;
}
void finalise() override {
+ // compute cross section from sum of weights and scaling factor
+ const double xsection = wt_sum_ * xs_scale_;
+ const double xsection_error = std::sqrt(wt2_sum_) * xs_scale_;
+
// print to screen
std::cout << "Generated with:\n";
for(auto const & generator: generators_)
std::cout << generator.name << " " << generator.version << "\n";
- std::cout << "cross section: " << xsection_ << " +- "
- << std::sqrt(xsection_error_) << std::endl;
+ std::cout << "cross section: " << xsection << " +- "
+ << xsection_error << std::endl;
// print to file
std::ofstream fout{outfile_};
fout << "Generated with\n";
for(auto const & generator: generators_)
fout << generator.name << " " << generator.version << "\n";
- fout << "cross section: " << xsection_ << " +- "
- << std::sqrt(xsection_error_) << std::endl;
+ fout << "cross section: " << xsection << " +- "
+ << xsection_error << std::endl;
}
private:
- double xsection_, xsection_error_;
+ double wt_sum_, wt2_sum_;
+ double xs_scale_{1.0};
std::string outfile_;
std::vector<LHEF::Generator> generators_;
};
}
extern "C"
__attribute__((visibility("default")))
std::unique_ptr<HEJ::Analysis> make_analysis(
YAML::Node const & config, LHEF::HEPRUP const & heprup
){
return std::make_unique<AnalysisPrint>(config, heprup);
}
diff --git a/examples/AnalysisROOT.cc b/examples/AnalysisROOT.cc
index 2419823..f73ab03 100644
--- a/examples/AnalysisROOT.cc
+++ b/examples/AnalysisROOT.cc
@@ -1,112 +1,118 @@
//! Simple HEJ analysis to plot Delta y_{fb} with ROOT
#include <memory> // for std::unique_ptr
#include <iostream> // for std::cout
#include <stdexcept>
#include "TFile.h"
#include "TKey.h"
#include "TH1.h"
#include "HEJ/Analysis.hh"
#include "HEJ/Event.hh"
#include "HEJ/Version.hh"
namespace YAML {
class Node;
}
namespace LHEF {
class HEPRUP;
}
namespace {
void Fill2DHistogram(TH1D & h,double x,double wt)
{
const int binnumber=h.GetXaxis()->FindBin(x);
const double binwidth=h.GetXaxis()->GetBinWidth(binnumber);
h.Fill(x,wt/binwidth);
}
class AnalysisROOT: public HEJ::Analysis {
private:
+ double xs_scale_{1.0};
TFile hfile_;
TH1D htotal_,hydif_,hyf_,hyb_;
public:
AnalysisROOT(
YAML::Node const & /* config */, LHEF::HEPRUP const & /* heprup */
):hfile_{"HEJ.root","RECREATE",
("Output from "+HEJ::Version::package_name_full()).c_str()},
htotal_{"total","total",1,-0.5,.5},
hydif_{"ydif","ydif",40,0.,10.},
hyf_{"yf","yf",40,-5.,5.},
hyb_{"yb","yb",40,-5.,5.}
{
// Setup histograms
htotal_.Sumw2();
hydif_.Sumw2();
hyf_.Sumw2();
hyb_.Sumw2();
}
void fill(
HEJ::Event const & event,
HEJ::Event const & /* FO_event */
) override {
const double weight=event.central().weight;
htotal_.Fill(0.0,weight);
auto const & jets=event.jets(); // jets are sorted in rapidity
Fill2DHistogram(hydif_,
fabs(jets.back().rapidity()-jets.front().rapidity()), weight);
Fill2DHistogram(hyf_, jets.front().rapidity(), weight);
Fill2DHistogram(hyb_, jets.back().rapidity(), weight);
}
bool pass_cuts(
HEJ::Event const & event,
HEJ::Event const & /* FO_event */
) override {
if (event.jets().size()<2)
return false;
return true;
}
- void scale(const double xsscale) override {
+ void set_xs_scale(double xsscale) override {
+ xs_scale_ = xsscale;
+ }
+
+ void finalise() override {
+ scale();
+ hfile_.Write();
+ hfile_.Close();
+ std::cout << "AnalysisROOT bids you farewell\n"; // be polite
+ }
+
+ private:
+ void scale() {
std::cout << "AnalysisROOT scales the histograms\n";
TList* list = hfile_.GetListOfKeys() ;
if(!list) throw std::logic_error{"<E> No keys defined\n"};
TIter next(list) ;
TKey* key ;
TObject* obj ;
while ( (key = (TKey*)next()) ) {
obj = key->ReadObj() ;
auto hist = dynamic_cast<TH1*>(obj);
if(hist) {
std::cerr << "Histo name: " << hist->GetName()
<< " title: " << hist->GetTitle()
<< "will be scaled\n";
- hist->Scale(xsscale);
+ hist->Scale(xs_scale_);
} else {
- std::cerr << "<W> Object " << obj->GetName()
- << " is not 1D or 2D histogram : will not be scaled\n";
+ std::cerr << "<W> Object " << obj->GetName()
+ << " is not 1D or 2D histogram : will not be scaled\n";
}
}
}
-
- void finalise() override {
- hfile_.Write();
- hfile_.Close();
- std::cout << "AnalysisROOT bids you farewell\n"; // be polite
- }
-
};
}
extern "C"
__attribute__((visibility("default")))
std::unique_ptr<HEJ::Analysis> make_analysis(
YAML::Node const & config, LHEF::HEPRUP const & heprup
){
return std::make_unique<AnalysisROOT>(config, heprup);
}
diff --git a/examples/AnalysisTemplate.cc b/examples/AnalysisTemplate.cc
index 0269c93..96bb5c6 100644
--- a/examples/AnalysisTemplate.cc
+++ b/examples/AnalysisTemplate.cc
@@ -1,52 +1,52 @@
//! simple HEJ analysis that doesn't do anything
#include <memory> // for std::unique_ptr
#include <iostream>
#include "HEJ/Analysis.hh"
namespace YAML {
class Node;
}
namespace LHEF {
class HEPRUP;
}
namespace {
class AnalysisTemplate: public HEJ::Analysis {
public:
AnalysisTemplate(
YAML::Node const & /* config */, LHEF::HEPRUP const & /* heprup */
) {}
void fill(
HEJ::Event const & /* event */,
HEJ::Event const & /* FO_event */
) override {
}
bool pass_cuts(
HEJ::Event const & /* event */,
HEJ::Event const & /* FO_event */
) override {
return true;
}
- void scale(double /* xsscale */) override {
- std::cerr <<"AnalysisTemplate Warning:: Function scale does nothing\n";
+ void set_xs_scale(double /* xsscale */) override {
+ // typically store this information for later
}
void finalise() override {
std::cout << "bye" << std::endl; // be polite
}
};
}
extern "C"
__attribute__((visibility("default")))
std::unique_ptr<HEJ::Analysis> make_analysis(
YAML::Node const & config, LHEF::HEPRUP const & heprup
){
return std::make_unique<AnalysisTemplate>(config, heprup);
}
diff --git a/examples/WRapidityAnalysisROOT.cc b/examples/WRapidityAnalysisROOT.cc
index 43ee6e2..1ebdcd0 100644
--- a/examples/WRapidityAnalysisROOT.cc
+++ b/examples/WRapidityAnalysisROOT.cc
@@ -1,157 +1,164 @@
//! Simple HEJ analysis to plot y_W with ROOT
//!
//! See README.md for installation and running instructions.
//!
#include <cassert>
#include <cmath>
#include <iterator>
#include <memory> // for std::unique_ptr
#include <iostream> // for std::cout
#include "HEJ/PDG_codes.hh"
#include "TFile.h"
#include "TKey.h"
#include "TH1.h"
#include "HEJ/Analysis.hh"
#include "HEJ/Event.hh"
#include "HEJ/Version.hh"
namespace YAML {
class Node;
}
namespace LHEF {
class HEPRUP;
}
namespace {
void Fill2DHistogram(TH1D & h,double x,double wt)
{
const int binnumber=h.GetXaxis()->FindBin(x);
const double binwidth=h.GetXaxis()->GetBinWidth(binnumber);
h.Fill(x,wt/binwidth);
}
class WRapidityAnalysisROOT: public HEJ::Analysis {
private:
+ double xs_scale_{1.0};
TFile hfile_;
TH1D htotal_,hydif_,hyf_,hyb_,hyW_,hyWsampling_;
public:
WRapidityAnalysisROOT(
YAML::Node const & /* config */, LHEF::HEPRUP const & /* heprup */
):hfile_{"HEJ.root","RECREATE",
("Output from "+HEJ::Version::package_name_full()).c_str()},
htotal_{"total","total",1,-0.5,.5},
hydif_{"ydif","ydif",40,0.,10.},
hyf_{"yf","yf",40,-5.,5.},
hyb_{"yb","yb",40,-5.,5.},
hyW_{"yW","yW",40,-5.,5.},
hyWsampling_{"yWsampling","yWsampling",40,-5.,5.}
{
// Setup histograms
htotal_.Sumw2();
hydif_.Sumw2();
hyf_.Sumw2();
hyb_.Sumw2();
hyW_.Sumw2();
}
void fill(
HEJ::Event const & event,
HEJ::Event const & /* FO_event */
) override {
const double weight = event.central().weight;
htotal_.Fill(0.0,weight);
auto const & jets = event.jets(); // jets are sorted in rapidity
// find the W momentum
auto const & outgoing = event.outgoing();
auto the_boson = std::find_if(
outgoing.begin(), outgoing.end(),
[](auto const & particle) { return std::abs(particle.type) == HEJ::pid::Wp; }
);
// we already checked in `pass_cuts` that there is a boson,
// so the following assert cannot fail
assert(the_boson != outgoing.end());
Fill2DHistogram(hydif_,
std::abs(jets.back().rapidity()-jets.front().rapidity()), weight);
Fill2DHistogram(hyf_, jets.front().rapidity(), weight);
Fill2DHistogram(hyb_, jets.back().rapidity(), weight);
Fill2DHistogram(hyW_, the_boson->rapidity(), weight);
Fill2DHistogram(hyWsampling_, the_boson->rapidity(),1);
}
bool pass_cuts(
HEJ::Event const & event,
HEJ::Event const & /* FO_event */
) override {
if(event.jets().size() < 2)
return false;
auto const & outgoing = event.outgoing();
// Find the W boson
auto the_W_boson = std::find_if(
outgoing.begin(), outgoing.end(),
[](auto const & particle) { return std::abs(particle.type) == HEJ::pid::Wp; }
);
if(the_W_boson == outgoing.end())
return false;
const auto W_pos = std::distance(outgoing.begin(), the_W_boson);
// Find the decay products
const auto the_decay = event.decays().find(W_pos);
if(the_decay == event.decays().end())
return false;
// Find the charged lepton
const auto the_charged_lepton = std::find_if(
the_decay->second.begin(), the_decay->second.end(),
[](HEJ::Particle const & p) {
return HEJ::is_anylepton(p) && !HEJ::is_anyneutrino(p);
}
);
if(the_charged_lepton == the_decay->second.end())
return false;
return std::abs(the_charged_lepton->rapidity()) > 2.5;
}
- void scale(const double xsscale) override {
+ void set_xs_scale(double xsscale) override {
+ xs_scale_ = xsscale;
+ }
+
+ void finalise() override {
+ scale();
+ hfile_.Write();
+ hfile_.Close();
+ std::cout << "WRapidityAnalysisROOT bids you farewell\n"; // be polite
+ }
+
+ private:
+ void scale() {
std::cout << "AnalysisROOT scales the histograms\n";
TList* list = hfile_.GetListOfKeys() ;
if(!list) throw std::logic_error{"<E> No keys defined\n"};
TIter next(list) ;
TKey* key ;
TObject* obj ;
while ( (key = (TKey*)next()) ) {
obj = key->ReadObj() ;
auto hist = dynamic_cast<TH1*>(obj);
if(hist) {
std::cerr << "Histo name: " << hist->GetName()
<< " title: " << hist->GetTitle()
<< "will be scaled\n";
- hist->Scale(xsscale);
+ hist->Scale(xs_scale_);
} else {
std::cerr << "<W> Object " << obj->GetName()
<< " is not 1D or 2D histogram : will not be scaled\n";
}
}
}
- void finalise() override {
- hfile_.Write();
- hfile_.Close();
- std::cout << "WRapidityAnalysisROOT bids you farewell\n"; // be polite
- }
-
};
}
extern "C"
__attribute__((visibility("default")))
std::unique_ptr<HEJ::Analysis> make_analysis(
YAML::Node const & config, LHEF::HEPRUP const & heprup
){
return std::make_unique<WRapidityAnalysisROOT>(config, heprup);
}
diff --git a/include/HEJ/Analysis.hh b/include/HEJ/Analysis.hh
index 3391569..3f6b45c 100644
--- a/include/HEJ/Analysis.hh
+++ b/include/HEJ/Analysis.hh
@@ -1,57 +1,53 @@
/** \file
* \brief Header file for the Analysis interface
*
* This header contains declarations that facilitate creating custom analyses
* to be used with HEJ 2.
* \todo link to user documentation
*
* \authors The HEJ collaboration (see AUTHORS for details)
- * \date 2022
+ * \date 2022-2023
* \copyright GPLv2 or later
*/
#pragma once
namespace LHEF {
class HEPRUP;
}
//! Main HEJ 2 Namespace
namespace HEJ {
class Event;
//! Analysis base class
/**
* This is the interface that all analyses should implement,
* i.e. all custom analyses have to be derived from this struct.
*/
struct Analysis {
/**
* @param res_event The event in resummation phase space
* @param FO_event The original fixed-order event
*/
virtual void fill(Event const & res_event, Event const & FO_event) = 0;
//! Decide whether an event passes the cuts
/**
* @param res_event The event in resummation phase space
* @param FO_event The original fixed-order event
* @returns Whether the event passes all cuts
*/
virtual bool pass_cuts(Event const & res_event, Event const & FO_event) = 0;
//! Finalise analysis
/**
* This function is called after all events have been processed and
* can be used for example to print out or save the results.
*/
virtual void finalise() = 0;
- /**
- * This function is called after all events have been processed and
- * can be used to impose the correct normalisation of weights, in case
- this is unknown until the very end (e.g. division by number of events).
- */
- virtual void scale(double xsscale) = 0;
+ //! Set the ratio (cross section) / (sum of event weights)
+ virtual void set_xs_scale(double scale) = 0;
virtual ~Analysis() = default;
};
} // namespace HEJ
diff --git a/include/HEJ/RivetAnalysis.hh b/include/HEJ/RivetAnalysis.hh
index 674e39e..64c3955 100644
--- a/include/HEJ/RivetAnalysis.hh
+++ b/include/HEJ/RivetAnalysis.hh
@@ -1,71 +1,71 @@
/** \file
* \brief HEJ 2 interface to rivet analyses
*
* \authors The HEJ collaboration (see AUTHORS for details)
- * \date 2019-2022
+ * \date 2019-2023
* \copyright GPLv2 or later
*/
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "LHEF/LHEF.h"
#include "HEJ/Analysis.hh"
namespace YAML {
class Node;
}
namespace HEJ {
/**
* @brief Class representing a Rivet analysis
*
* This class inherits from Analysis and can therefore be used
* like any other HEJ 2 analysis.
*/
class RivetAnalysis: public Analysis {
public:
//! Create RivetAnalysis
static std::unique_ptr<Analysis> create(
YAML::Node const & config, LHEF::HEPRUP const & heprup);
//! Constructor
/**
* @param config Configuration parameters
* @param heprup General run informations
*
* config["rivet"] should be the name of a single Rivet analysis or
* a list of Rivet analyses. config["output"] is the prefix for
* the .yoda output files.
*/
RivetAnalysis(YAML::Node const & config, LHEF::HEPRUP heprup);
~RivetAnalysis() override;
//! Pass an event to the underlying Rivet analysis
void fill(Event const & event, Event const & /*unused*/) override;
//! no additional cuts are applied
bool pass_cuts(Event const & /*unused*/, Event const & /*unused*/
) override { return true; }
- void scale(double scale) override;
+ void set_xs_scale(double scale) override;
void finalise() override;
private:
std::vector<std::string> analyses_names_;
std::string output_name_;
LHEF::HEPRUP heprup_;
/// struct to organise the infos per rivet run/scale setting
struct rivet_info;
std::vector<rivet_info> rivet_runs_;
/**
* \internal
* @brief Calculates the scale variation from the first event for the output
* file
*/
void init(Event const & event);
bool first_event_;
};
} // namespace HEJ
diff --git a/include/HEJ/detail/EmptyAnalysis.hh b/include/HEJ/detail/EmptyAnalysis.hh
index ddc83b6..08563cc 100644
--- a/include/HEJ/detail/EmptyAnalysis.hh
+++ b/include/HEJ/detail/EmptyAnalysis.hh
@@ -1,52 +1,52 @@
/** \file
* \brief Internal header for empty analysis, to be removed in HEJ 2.3
*
* \authors The HEJ collaboration (see AUTHORS for details)
- * \date 2022
+ * \date 2022-2023
* \copyright GPLv2 or later
*/
#pragma once
#include <memory>
#include "HEJ/Analysis.hh"
namespace YAML {
class Node;
}
namespace LHEF {
class HEPRUP;
}
namespace HEJ {
class Event;
namespace detail {
struct EmptyAnalysis: Analysis{
//! Create EmptyAnalysis
static std::unique_ptr<Analysis> create(
YAML::Node const & parameters, LHEF::HEPRUP const & /*unused*/);
//! Fill event into analysis (e.g. to histograms)
/**
* This function does nothing
*/
void fill(Event const & /*res_event*/, Event const & /*FO_event*/) override;
//! Whether a resummation event passes all cuts
/**
* There are no cuts, so all events pass
*/
bool pass_cuts(Event const & /*res_event*/, Event const & /*FO_event*/) override;
+ //! Set the ratio (cross section) / (sum of event weights)
+ void set_xs_scale(double scale) override;
//! Finalise analysis
/**
* This function does nothing
*/
void finalise() override;
- void scale(double /*xsscale*/) override;
-
~EmptyAnalysis() override = default;
};
}
} // namespace HEJ
diff --git a/src/EmptyAnalysis.cc b/src/EmptyAnalysis.cc
index 1d5806a..a95c26c 100644
--- a/src/EmptyAnalysis.cc
+++ b/src/EmptyAnalysis.cc
@@ -1,86 +1,86 @@
/**
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2019-2020
* \copyright GPLv2 or later
*/
#include "HEJ/EmptyAnalysis.hh"
#include <string>
#include <vector>
#include <iostream>
#include "yaml-cpp/yaml.h"
#include "HEJ/exceptions.hh"
namespace HEJ::detail {
namespace {
std::vector<std::string> param_as_strings(YAML::Node const & parameters){
using YAML::NodeType;
switch(parameters.Type()){
case NodeType::Null:
case NodeType::Undefined:
return {};
case NodeType::Scalar:
return {parameters.as<std::string>()};
case NodeType::Sequence: {
std::vector<std::string> param_strings;
for(auto && param: parameters){
param_strings.emplace_back(param.as<std::string>());
}
return param_strings;
}
case NodeType::Map: {
std::vector<std::string> param_strings;
for(auto && param: parameters){
param_strings.emplace_back(param.first.as<std::string>());
}
return param_strings;
}
default:;
}
throw std::logic_error{"unreachable"};
}
} // namespace
std::unique_ptr<Analysis> EmptyAnalysis::create(
YAML::Node const & parameters, LHEF::HEPRUP const & /*unused*/
){
const auto param_strings = param_as_strings(parameters);
if(! param_strings.empty()){
std::string error{"Unknown analysis parameter(s):"};
for(auto && p: param_strings) error += " " + p;
throw unknown_option{error};
}
return std::make_unique<EmptyAnalysis>();
}
void EmptyAnalysis::fill(
Event const & /*res_event*/, Event const & /*FO_event*/
){
// do nothing
}
bool EmptyAnalysis::pass_cuts(
Event const & /*res_event*/, Event const & /*FO_event*/
){
return true;
}
- void EmptyAnalysis::scale(double /*xsscale*/){
+ void EmptyAnalysis::set_xs_scale(double /*xsscale*/){
// do nothing
}
void EmptyAnalysis::finalise(){
// do nothing
}
} // namespace HEJ::detail
namespace HEJ {
std::unique_ptr<Analysis> EmptyAnalysis::create(
YAML::Node const & parameters, LHEF::HEPRUP const & heprup
){
return detail::EmptyAnalysis::create(parameters, heprup);
}
}
diff --git a/src/RivetAnalysis.cc b/src/RivetAnalysis.cc
index 13e7ad3..6e148ec 100644
--- a/src/RivetAnalysis.cc
+++ b/src/RivetAnalysis.cc
@@ -1,208 +1,199 @@
/**
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2019-2020
* \copyright GPLv2 or later
*/
#include "HEJ/RivetAnalysis.hh"
#include "HEJ/ConfigFlags.hh"
#include "HEJ/utility.hh"
#ifdef HEJ_BUILD_WITH_RIVET
#include <cstddef>
#include <ostream>
#include <utility>
#include "yaml-cpp/yaml.h"
#include "Rivet/AnalysisHandler.hh"
#include "Rivet/Config/RivetConfig.hh"
#ifdef RIVET_ENABLE_HEPMC_3
#include "HepMC3/GenEvent.h"
#include "HEJ/HepMC3Interface.hh"
#else // rivet with HepMC 2
#include "HEJ/HepMC2Interface.hh"
#include "HepMC/GenEvent.h"
#endif // RIVET_ENABLE_HEPMC_3
#include "HEJ/Event.hh"
#include "HEJ/exceptions.hh"
#endif
namespace HEJ {
std::unique_ptr<Analysis> RivetAnalysis::create(
YAML::Node const & config, LHEF::HEPRUP const & heprup
){
return std::make_unique<RivetAnalysis>(config, heprup);
}
}
#ifdef HEJ_BUILD_WITH_RIVET
namespace HEJ {
#ifdef RIVET_ENABLE_HEPMC_3
using HepMCInterface=HepMC3Interface;
#else
using HepMCInterface=HepMC2Interface;
#endif
struct RivetAnalysis::rivet_info {
rivet_info(std::unique_ptr<Rivet::AnalysisHandler> && h,
std::string && s, HepMCInterface && m):
handler{std::move(h)}, name{std::move(s)}, hepmc{std::move(m)}
{}
// AnalysisHandler doesn't allow a copy constructor -> use ptr
std::unique_ptr<Rivet::AnalysisHandler> handler;
std::string name;
HepMCInterface hepmc;
};
RivetAnalysis::RivetAnalysis(YAML::Node const & config,
LHEF::HEPRUP heprup
):
heprup_{std::move(heprup)},
first_event_(true)
{
// assert that only supported options are provided
if(!config.IsMap()) throw invalid_type{"Rivet config has to be a map!"};
for(auto const & entry: config){
const auto name = entry.first.as<std::string>();
if(name != "output" && name != "rivet")
throw unknown_option{"Unknown option \'"+name+"\' provided to rivet."};
}
// get output name
if(!config["output"])
throw std::invalid_argument{
"No output was provided to rivet. "
"Either give an output or deactivate rivet."
};
if(!config["output"].IsScalar())
throw invalid_type{
"Output for rivet must be a scalar."
};
output_name_ = config["output"].as<std::string>();
// read in analyses
auto const & name_node = config["rivet"];
switch(name_node.Type()){
case YAML::NodeType::Scalar:
analyses_names_.push_back(name_node.as<std::string>());
break;
case YAML::NodeType::Sequence:
for(YAML::const_iterator it = name_node.begin(); it != name_node.end(); ++it){
analyses_names_.push_back(it->as<std::string>());
}
break;
default:
throw std::invalid_argument{
"No analysis was provided to rivet. "
"Either give an analysis or deactivate rivet."
};
}
}
// it is a bit ugly that we can't do this directly in `initialise`
void RivetAnalysis::init(Event const & event){
#ifdef HEJ_USE_RIVET2
rivet_runs_.reserve(event.variations().size()+1);
#else
ignore(event); // shut up compiler
#endif
rivet_runs_.emplace_back( std::make_unique<Rivet::AnalysisHandler>(),
std::string(""), HepMCInterface(heprup_)
);
rivet_runs_.back().handler->addAnalyses(analyses_names_);
#ifdef HEJ_USE_RIVET2
//! scale variation in rivet 2 through multiple analyses
if( !event.variations().empty() ){
for(auto const & vari : event.variations()){
rivet_runs_.emplace_back( std::make_unique<Rivet::AnalysisHandler>(),
"."+to_simple_string(*vari.description), HepMCInterface(heprup_)
);
rivet_runs_.back().handler->addAnalyses(analyses_names_);
}
}
#endif
}
void RivetAnalysis::fill(Event const & event, Event const & /*unused*/){
if(first_event_){
first_event_=false;
init(event);
}
auto hepmc_event(rivet_runs_.front().hepmc(event));
rivet_runs_.front().handler->analyze(hepmc_event);
#ifdef HEJ_USE_RIVET2
for(std::size_t i = 1; i < rivet_runs_.size(); ++i){
auto & run = rivet_runs_[i];
run.hepmc.set_central(hepmc_event, event, i-1);
run.handler->analyze(hepmc_event);
}
#endif
}
- void RivetAnalysis::scale(double xsscale){
- // used to scale the cross sections and histograms before .finalise in case the
- // normalisation is unknown until the end of the run
- for(auto & run: rivet_runs_){
- // get yoda analysis objects:
- std::vector<YODA::AnalysisObjectPtr> yodaAOS=run.handler->getYodaAOs(true);
- for (auto& t : yodaAOS) {
- auto * obj = dynamic_cast<YODA::Fillable*>(t.get());
- if(obj) {
- obj->scaleW(xsscale);
- }
- }
+ void RivetAnalysis::set_xs_scale(double scale) {
+ for(auto & run: rivet_runs_) {
+ run.hepmc.set_xs_scale(scale);
}
}
void RivetAnalysis::finalise(){
for(auto & run: rivet_runs_){
run.handler->finalize();
run.handler->writeData(output_name_+run.name+std::string(".yoda"));
}
}
} // namespace HEJ
#else // no rivet => create empty analysis
namespace Rivet {
class AnalysisHandler {};
}
namespace HEJ {
struct RivetAnalysis::rivet_info{};
RivetAnalysis::RivetAnalysis(
YAML::Node const & /*config*/, LHEF::HEPRUP /*heprup*/
){
ignore(first_event_);
throw std::invalid_argument(
"Failed to create RivetAnalysis: "
"HEJ 2 was built without rivet support"
);
}
void RivetAnalysis::init(Event const & /*event*/){}
void RivetAnalysis::fill(Event const & /*event*/, Event const & /*unused*/){}
- void RivetAnalysis::scale(double /*xsscale*/){}
+ void RivetAnalysis::set_xs_scale(double /*xsscale*/){}
void RivetAnalysis::finalise(){}
} // namespace HEJ
#endif
namespace HEJ {
RivetAnalysis::~RivetAnalysis() = default;
}
diff --git a/src/bin/HEJ.cc b/src/bin/HEJ.cc
index 383320f..365816d 100644
--- a/src/bin/HEJ.cc
+++ b/src/bin/HEJ.cc
@@ -1,448 +1,444 @@
/**
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2019-2022
* \copyright GPLv2 or later
*/
#include <array>
#include <chrono>
#include <iostream>
#include <limits>
#include <memory>
#include <numeric>
#include <optional>
#include "yaml-cpp/yaml.h"
#include "fastjet/ClusterSequence.hh"
#include "HEJ/CombinedEventWriter.hh"
#include "HEJ/Config.hh"
#include "HEJ/CrossSectionAccumulator.hh"
#include "HEJ/Event.hh"
#include "HEJ/EventReader.hh"
#include "HEJ/BufferedEventReader.hh"
#include "HEJ/EventReweighter.hh"
#include "HEJ/filesystem.hh"
#include "HEJ/get_analysis.hh"
#include "HEJ/make_RNG.hh"
#include "HEJ/ProgressBar.hh"
#include "HEJ/stream.hh"
#include "HEJ/Unweighter.hh"
#include "HEJ/Version.hh"
#include "HEJ/YAMLreader.hh"
HEJ::Config load_config(char const * filename){
try{
return HEJ::load_config(filename);
}
catch(std::exception const & exc){
std::cerr << "Error: " << exc.what() << '\n';
std::exit(EXIT_FAILURE);
}
}
std::vector<std::unique_ptr<HEJ::Analysis>> get_analyses(
std::vector<YAML::Node> const & parameters, LHEF::HEPRUP const & heprup
){
try{
return HEJ::get_analyses(parameters, heprup);
}
catch(std::exception const & exc){
std::cerr << "Failed to load analysis: " << exc.what() << '\n';
std::exit(EXIT_FAILURE);
}
}
std::string time_to_string(const time_t time){
char s[30];
struct tm * p = localtime(&time);
strftime(s, 30, "%a %b %d %Y %H:%M:%S", p);
return s;
}
void check_one_parton_per_jet(HEJ::Event const & ev) {
const std::size_t npartons = std::count_if(
ev.outgoing().begin(), ev.outgoing().end(),
[](HEJ::Particle const & p) { return is_parton(p); }
);
if(ev.jets().size() < npartons) {
throw std::invalid_argument{
"Number of partons (" + std::to_string(npartons) + ") in input event"
" does not match number of jets (" + std::to_string(ev.jets().size())+ ")"
};
}
}
HEJ::Event to_event(
LHEF::HEPEUP const & hepeup,
HEJ::JetParameters const & fixed_order_jets,
HEJ::EWConstants const & ew_parameters,
const double off_shell_tolerance
) {
HEJ::Event::EventData event_data{hepeup};
event_data.reconstruct_intermediate(ew_parameters);
event_data.repair_momenta(off_shell_tolerance);
HEJ::Event ev{
std::move(event_data).cluster(
fixed_order_jets.def, fixed_order_jets.min_pt
)
};
check_one_parton_per_jet(ev);
return ev;
}
void unweight(
HEJ::Unweighter & unweighter,
HEJ::WeightType weight_type,
std::vector<HEJ::Event> & events,
HEJ::RNG & ran
) {
if(weight_type == HEJ::WeightType::unweighted_resum){
unweighter.set_cut_to_maxwt(events);
}
events.erase(
unweighter.unweight(begin(events), end(events), ran),
end(events)
);
}
// peek up to nevents events from reader
std::vector<LHEF::HEPEUP> peek_events(
HEJ::BufferedEventReader & reader,
const int nevents
) {
std::vector<LHEF::HEPEUP> events;
while(
static_cast<int>(events.size()) < nevents
&& reader.read_event()
) {
events.emplace_back(reader.hepeup());
}
// put everything back into the reader
for(auto it = rbegin(events); it != rend(events); ++it) {
reader.emplace(*it);
}
return events;
}
void append_resummed_events(
std::vector<HEJ::Event> & resummation_events,
HEJ::EventReweighter & reweighter,
LHEF::HEPEUP const & hepeup,
const size_t trials,
HEJ::JetParameters const & fixed_order_jets,
HEJ::EWConstants const & ew_parameters,
const double off_shell_tolerance
) {
const HEJ::Event FO_event = to_event(
hepeup,
fixed_order_jets,
ew_parameters,
off_shell_tolerance
);
if(reweighter.treatment(FO_event.type()) != HEJ::EventTreatment::reweight) {
return;
}
const auto resummed = reweighter.reweight(FO_event, trials);
resummation_events.insert(
end(resummation_events),
begin(resummed), end(resummed)
);
}
std::optional<HEJ::ProgressBar<std::size_t>> make_progress_bar(
std::optional<std::size_t> n_input_events,
char const * event_file
) {
if(n_input_events) {
return HEJ::ProgressBar{std::cout, *n_input_events};
}
if(HEJ::is_regular(event_file)) {
auto t_reader = HEJ::make_reader(event_file);
std::size_t n_input_events = 0;
while(t_reader->read_event()) ++n_input_events;
return HEJ::ProgressBar{std::cout, n_input_events};
}
return {};
}
void train(
HEJ::Unweighter & unweighter,
HEJ::BufferedEventReader & reader,
HEJ::EventReweighter & reweighter,
const size_t total_trials,
const double max_dev,
double reweight_factor,
HEJ::JetParameters const & fixed_order_jets,
HEJ::EWConstants const & ew_parameters,
const double off_shell_tolerance
) {
std::cout << "Reading up to " << total_trials << " training events...\n";
auto FO_events = peek_events(reader, total_trials);
if(FO_events.empty()) {
throw std::runtime_error{
"No events generated to calibrate the unweighting weight!"
"Please increase the number \"trials\" or deactivate the unweighting."
};
}
const size_t trials = total_trials/FO_events.size();
// adjust reweight factor so that the overall normalisation
// is the same as in the full run
reweight_factor *= trials;
for(auto & hepeup: FO_events) {
hepeup.XWGTUP *= reweight_factor;
}
std::cout << "Training unweighter with "
<< trials << '*' << FO_events.size() << " events\n";
auto progress = HEJ::ProgressBar<int>{
std::cout, static_cast<int>(FO_events.size())
};
std::vector<HEJ::Event> resummation_events;
for(auto const & hepeup: FO_events) {
append_resummed_events(
resummation_events,
reweighter, hepeup, trials, fixed_order_jets,
ew_parameters,
off_shell_tolerance
);
++progress;
}
unweighter.set_cut_to_peakwt(resummation_events, max_dev);
std::cout << "\nUnweighting events with weight up to "
<< unweighter.get_cut() << '\n';
}
int main(int argn, char** argv) {
using clock = std::chrono::system_clock;
if (argn != 3) {
std::cerr << "\n# Usage:\n."<< argv[0] <<" config_file input_file\n\n";
return EXIT_FAILURE;
}
const auto start_time = clock::now();
{
std::cout << "Starting " << HEJ::Version::package_name_full()
<< ", revision " << HEJ::Version::revision() << " ("
<< time_to_string(clock::to_time_t(start_time)) << ")" << std::endl;
}
fastjet::ClusterSequence::print_banner();
// read configuration
const HEJ::Config config = load_config(argv[1]);
auto reader = HEJ::make_reader(argv[2]);
assert(reader);
auto heprup{ reader->heprup() };
heprup.generators.emplace_back(LHEF::XMLTag{});
heprup.generators.back().name = HEJ::Version::package_name();
heprup.generators.back().version = HEJ::Version::String();
auto analyses = get_analyses( config.analyses_parameters, heprup );
assert(analyses.empty() || analyses.front() != nullptr);
HEJ::CombinedEventWriter writer{config.output, std::move(heprup)};
double global_reweight = 1.;
auto const & max_events = config.max_events;
// if we need the event number:
if(std::abs(heprup.IDWTUP) == 4 || std::abs(heprup.IDWTUP) == 1 || max_events){
// try to read from LHE head
auto input_events{reader->number_events()};
if(!input_events) {
// else count manually
auto t_reader = HEJ::make_reader(argv[2]);
input_events = 0;
while(t_reader->read_event()) ++(*input_events);
}
if(std::abs(heprup.IDWTUP) == 4 || std::abs(heprup.IDWTUP) == 1){
// IDWTUP 4 or 1 assume average(weight)=xs, but we need sum(weights)=xs
std::cout << "Found IDWTUP " << heprup.IDWTUP << ": "
<< "assuming \"cross section = average weight\".\n"
<< "converting to \"cross section = sum of weights\" ";
global_reweight /= *input_events;
}
if(max_events && (*input_events > *max_events)){
// maximal number of events given
global_reweight *= *input_events/static_cast<double>(*max_events);
std::cout << "Processing " << *max_events
<< " out of " << *input_events << " events\n";
}
}
HEJ::ScaleGenerator scale_gen{
config.scales.base,
config.scales.factors,
config.scales.max_ratio
};
std::shared_ptr<HEJ::RNG> ran{
HEJ::make_RNG(config.rng.name, config.rng.seed)};
assert(ran != nullptr);
HEJ::EventReweighter hej{
reader->heprup(),
std::move(scale_gen),
to_EventReweighterConfig(config),
ran
};
std::optional<HEJ::Unweighter> unweighter{};
if(config.weight_type != HEJ::WeightType::weighted) {
unweighter = HEJ::Unweighter{};
}
if(config.weight_type == HEJ::WeightType::partially_unweighted) {
HEJ::BufferedEventReader buffered_reader{std::move(reader)};
assert(config.unweight_config);
train(
*unweighter,
buffered_reader,
hej,
config.unweight_config->trials,
config.unweight_config->max_dev,
global_reweight/config.trials,
config.fixed_order_jets,
config.ew_parameters,
config.off_shell_tolerance
);
reader = std::make_unique<HEJ::BufferedEventReader>(
std::move(buffered_reader)
);
}
// status infos & eye candy
if (config.nlo.enabled){
std::cout << "HEJ@NLO Truncation Enabled for NLO order: "
<< config.nlo.nj << std::endl;
}
size_t nevent = 0;
std::array<int, HEJ::event_type::last_type + 1>
nevent_type{0}, nfailed_type{0};
auto progress = make_progress_bar(reader->number_events(), argv[2]);
if(!progress) {
std::cout
<< "Cannot determine number of input events. "
"Disabling progress bar"
<< std::endl;
}
HEJ::CrossSectionAccumulator xs;
std::map<HEJ::StatusCode, int> status_counter;
size_t total_trials = 0;
size_t total_resum = 0;
// Loop over the events in the input file
while(reader->read_event() && (!max_events || nevent < *max_events) ){
++nevent;
// reweight events so that the total cross section is conserved
auto hepeup = reader->hepeup();
hepeup.XWGTUP *= global_reweight;
const auto FO_event = to_event(
hepeup,
config.fixed_order_jets,
config.ew_parameters,
config.off_shell_tolerance
);
if(FO_event.central().weight == 0) {
static const bool warned_once = [argv,nevent](){
std::cerr
<< "WARNING: event number " << nevent
<< " in " << argv[2] << " has zero weight. "
"Ignoring this and all further events with vanishing weight.\n";
return true;
}();
(void) warned_once; // shut up compiler warnings
continue;
}
auto resummed_events{ hej.reweight(FO_event, config.trials) };
// some bookkeeping
for(auto const & s: hej.status())
++status_counter[s];
total_trials+=hej.status().size();
++nevent_type[FO_event.type()];
if(resummed_events.empty()) ++nfailed_type[FO_event.type()];
if(unweighter) {
unweight(*unweighter, config.weight_type, resummed_events, *ran);
}
// analysis
for(auto & ev: resummed_events){
//TODO: move pass_cuts to after phase space point generation
bool passed = analyses.empty();
for(auto const & analysis: analyses){
+ analysis->set_xs_scale(reader->scalefactor());
if(analysis->pass_cuts(ev, FO_event)){
passed = true;
analysis->fill(ev, FO_event);
};
}
if(passed){
writer.set_xs_scale(reader->scalefactor());
writer.write(ev);
} else {
ev.parameters()*=0; // do not use discarded events afterwards
}
}
xs.fill_correlated(resummed_events);
total_resum += resummed_events.size();
if(progress) ++*progress;
} // main event loop
std::cout << '\n';
// scale to account for num_trials of sherpa
- if(const double scalefactor = reader->scalefactor(); scalefactor != 1.) {
- for(auto const & analysis: analyses){
- analysis->scale(scalefactor);
- }
- xs.scale(scalefactor);
- }
+ xs.scale(reader->scalefactor());
for(auto const & analysis: analyses){
analysis->finalise();
}
writer.finish();
using namespace HEJ::event_type;
std::cout<< "Events processed: " << nevent << " (" << total_resum << " resummed)"<< '\n';
std::cout << '\t' << name(EventType::first_type) << ": "
<< nevent_type[EventType::first_type]
<< ", failed to reconstruct " << nfailed_type[EventType::first_type]
<< '\n';
for(auto i=EventType::first_type+1; i<=EventType::last_type; i*=2){
std::cout << '\t' << name(static_cast<EventType>(i)) << ": "
<< nevent_type[i]
<< ", failed to reconstruct " << nfailed_type[i]
<< '\n';
}
std::cout << '\n' << xs << '\n';
std::cout << "Generation statistic: "
<< status_counter[HEJ::StatusCode::good] << "/" << total_trials
<< " trials successful.\n";
for(auto && entry: status_counter){
const double fraction = static_cast<double>(entry.second)/total_trials;
const int percent = std::round(100*fraction);
std::cout << std::left << std::setw(17) << (to_string(entry.first) + ":")
<< " [";
for(int i = 0; i < percent/2; ++i) std::cout << '#';
for(int i = percent/2; i < 50; ++i) std::cout << ' ';
std::cout << "] " <<std::setw(2)<<std::right<< percent << "%\n";
}
std::chrono::duration<double> run_time = (clock::now() - start_time);
std::cout << "\nFinished " << HEJ::Version::package_name() << " at "
<< time_to_string(clock::to_time_t(clock::now()))
<< "\n=> Runtime: " << run_time.count() << " sec ("
<< nevent/run_time.count() << " Events/sec).\n";
return EXIT_SUCCESS;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Feb 23, 1:59 PM (2 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4474985
Default Alt Text
(45 KB)
Attached To
rHEJ HEJ
Event Timeline
Log In to Comment