diff --git a/FixedOrderGen/src/EventGenerator.cc b/FixedOrderGen/src/EventGenerator.cc index 4380eac..2fe6def 100644 --- a/FixedOrderGen/src/EventGenerator.cc +++ b/FixedOrderGen/src/EventGenerator.cc @@ -1,81 +1,78 @@ #include "EventGenerator.hh" #include "Process.hh" #include "Beam.hh" #include "JetParameters.hh" #include "PhaseSpacePoint.hh" #include "HEJ/Event.hh" #include "HEJ/config.hh" namespace HEJFOG{ EventGenerator::EventGenerator( Process process, Beam beam, HEJ::ScaleGenerator scale_gen, JetParameters jets, int pdf_id, double uno_chance, ParticlesPropMap particles_properties, HEJ::HiggsCouplingSettings Higgs_coupling, HEJ::RNG & ran ): pdf_{pdf_id, beam.particles[0], beam.particles[1]}, ME_{ [this](double mu){ return pdf_.Halphas(mu); }, HEJ::MatrixElementConfig{ - HEJ::JetParameters{jets.def, jets.min_pt}, false, std::move(Higgs_coupling) } }, scale_gen_{std::move(scale_gen)}, process_{std::move(process)}, jets_{std::move(jets)}, beam_{std::move(beam)}, uno_chance_{uno_chance}, particles_properties_{std::move(particles_properties)}, ran_{ran} { } HEJ::Event EventGenerator::gen_event(){ HEJFOG::PhaseSpacePoint psp{ process_, jets_, pdf_, beam_.energy, uno_chance_, particles_properties_, ran_ }; status_ = psp.status(); if(status_ != good) return {}; HEJ::Event ev = scale_gen_( HEJ::Event{ to_UnclusteredEvent(std::move(psp)), jets_.def, jets_.min_pt } ); const double shat = HEJ::shat(ev); const double xa = (ev.incoming()[0].E()-ev.incoming()[0].pz())/(2.*beam_.energy); const double xb = (ev.incoming()[1].E()+ev.incoming()[1].pz())/(2.*beam_.energy); // evaluate matrix element on this point - ev.central().weight *= ME_.tree( - ev.central().mur, ev.incoming(), ev.outgoing(), false - )/(shat*shat); + const auto ME_weights = ME_.tree(ev, false); + ev.central().weight *= ME_weights.central/(shat*shat); ev.central().weight *= pdf_.pdfpt(0,xa,ev.central().muf, ev.incoming()[0].type); ev.central().weight *= pdf_.pdfpt(0,xb,ev.central().muf, ev.incoming()[1].type); - for(auto & var: ev.variations()){ - var.weight *= ME_.tree( - var.mur, ev.incoming(), ev.outgoing(), false - )/(shat*shat); + for(size_t i = 0; i < ev.variations().size(); ++i){ + auto & var = ev.variations(i); + var.weight *= ME_weights.variations[i]/(shat*shat); var.weight *= pdf_.pdfpt(0,xa,var.muf, ev.incoming()[0].type); var.weight *= pdf_.pdfpt(0,xb,var.muf, ev.incoming()[1].type); } return ev; } } diff --git a/include/HEJ/Event.hh b/include/HEJ/Event.hh index 6ad19e5..51f9f50 100644 --- a/include/HEJ/Event.hh +++ b/include/HEJ/Event.hh @@ -1,186 +1,186 @@ /** \file * \brief Declares the Event class and helpers * */ #pragma once #include <string> #include <memory> #include <unordered_map> #include "HEJ/utility.hh" #include "HEJ/event_types.hh" #include "LHEF/LHEF.h" #include "fastjet/JetDefinition.hh" #include "fastjet/ClusterSequence.hh" namespace HEJ{ struct ParameterDescription; //! Event parameters struct EventParameters{ double mur; /**< Value of the Renormalisation Scale */ double muf; /**< Value of the Factorisation Scale */ double weight; /**< Event Weight */ //! Optional description std::shared_ptr<ParameterDescription> description = nullptr; }; //! Description of event parameters struct ParameterDescription { //! Name of central scale choice (e.g. "H_T/2") std::string scale_name; //! Actual renormalisation scale divided by central scale double mur_factor; //! Actual factorisation scale divided by central scale double muf_factor; ParameterDescription() = default; ParameterDescription( std::string scale_name, double mur_factor, double muf_factor ): scale_name{scale_name}, mur_factor{mur_factor}, muf_factor{muf_factor} {}; }; //! An event before jet clustering struct UnclusteredEvent{ //! Default Constructor UnclusteredEvent() = default; //! Constructor from LesHouches event information UnclusteredEvent(LHEF::HEPEUP const & hepeup); std::array<Particle, 2> incoming; /**< Incoming Particles */ std::vector<Particle> outgoing; /**< Outgoing Particles */ //! Particle decays in the format {outgoing index, decay products} std::unordered_map<size_t, std::vector<Particle>> decays; //! Central parameter (e.g. scale) choice EventParameters central; std::vector<EventParameters> variations; /**< For parameter variation */ }; /** An event with clustered jets * * This is the main HEJ 2 event class. * It contains kinematic information including jet clustering, * parameter (e.g. scale) settings and the event weight. */ class Event{ public: //! Default Event Constructor Event() = default; //! Event Constructor adding jet clustering to an unclustered event Event( UnclusteredEvent ev, fastjet::JetDefinition const & jet_def, double min_jet_pt ); //! The jets formed by the outgoing partons std::vector<fastjet::PseudoJet> jets() const; //! The corresponding event before jet clustering UnclusteredEvent const & unclustered() const { return ev_; } //! Central parameter choice EventParameters const & central() const{ return ev_.central; } //! Central parameter choice EventParameters & central(){ return ev_.central; } //! Incoming particles std::array<Particle, 2> const & incoming() const{ return ev_.incoming; } //! Outgoing particles std::vector<Particle> const & outgoing() const{ return ev_.outgoing; } //! Particle decays /** * The key in the returned map corresponds to the index in the * vector returned by outgoing() */ std::unordered_map<size_t, std::vector<Particle>> const & decays() const{ return ev_.decays; } //! Parameter (scale) variations std::vector<EventParameters> const & variations() const{ return ev_.variations; } //! Parameter (scale) variations std::vector<EventParameters> & variations(){ return ev_.variations; } //! Parameter (scale) variation /** * @param i Index of the requested variation */ EventParameters const & variations(size_t i) const{ return ev_.variations[i]; } //! Parameter (scale) variation /** * @param i Index of the requested variation */ EventParameters & variations(size_t i){ return ev_.variations[i]; } //! Indices of the jets the outgoing partons belong to /** * @param jets Jets to be tested * @returns A vector containing, for each outgoing parton, * the index in the vector of jets the considered parton * belongs to. If the parton is not inside any of the * passed jets, the corresponding index is set to -1. */ std::vector<int> particle_jet_indices( std::vector<fastjet::PseudoJet> const & jets ) const{ return cs_.particle_jet_indices(jets); } //! Jet definition used for clustering fastjet::JetDefinition const & jet_def() const{ return cs_.jet_def(); } //! Minimum jet transverse momentum double min_jet_pt() const{ return min_jet_pt_; } //! Event type event_type::EventType type() const{ return type_; } private: UnclusteredEvent ev_; fastjet::ClusterSequence cs_; double min_jet_pt_; event_type::EventType type_; }; - //! Square of the partonic centre-of-mass energy + //! Square of the partonic centre-of-mass energy \f$\hat{s}\f$ double shat(Event const & ev); //! Convert an event to a LHEF::HEPEUP LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP *); } diff --git a/include/HEJ/EventReweighter.hh b/include/HEJ/EventReweighter.hh index a3f3279..8e33d2d 100644 --- a/include/HEJ/EventReweighter.hh +++ b/include/HEJ/EventReweighter.hh @@ -1,136 +1,181 @@ /** \file * \brief Declares the EventReweighter class * * EventReweighter is the main class used within HEJ 2. It reweights the * resummation events. */ #pragma once #include "fastjet/PseudoJet.hh" #include "fastjet/ClusterSequence.hh" #include "LHEF/LHEF.h" #include "HEJ/config.hh" #include "HEJ/PDF.hh" #include "HEJ/utility.hh" #include "HEJ/Event.hh" #include "HEJ/event_types.hh" #include "HEJ/PhaseSpacePoint.hh" #include "HEJ/MatrixElement.hh" #include "HEJ/RNG.hh" namespace HEJ{ //! Beam parameters /** * Currently, only symmetric beams are supported, * so there is a single beam energy. */ struct Beam{ double E; /**< Beam energy */ std::array<ParticleID, 2> type; /**< Beam particles */ }; //! Main class for reweighting events in HEJ. class EventReweighter{ using EventType = event_type::EventType; public: EventReweighter( Beam beam, /**< Beam Energy */ int pdf_id, /**< PDF ID */ ScaleGenerator scale_gen, /**< Scale settings */ EventReweighterConfig conf, /**< Configuration parameters */ HEJ::RNG & ran /**< Random number generator */ ); EventReweighter( LHEF::HEPRUP const & heprup, /**< LHEF event header */ ScaleGenerator scale_gen, /**< Scale settings */ EventReweighterConfig conf, /**< Configuration parameters */ HEJ::RNG & ran /**< Random number generator */ ); //! Get the used pdf PDF const & pdf() const; //! Generate resummation events for a given fixed-order event /** * @param ev Fixed-order event corresponding * to the resummation events * @param num_events Number of trial resummation configurations. * @returns A vector of resummation events. * * The result vector depends on the type of the input event and the * treatment of different types as specified in the constructor: * * \ref reweight The result vector contains between * 0 and num_events resummation events. * * \ref keep If the input event passes the resummation jet cuts * the result vector contains one event. Otherwise it is empty. * * \ref discard The result vector is empty */ std::vector<Event> reweight( Event const & ev, int num_events ); private: - struct EventFactors{ - double central; - std::vector<double> variations; - }; - template<typename... T> PDF const & pdf(T&& ...); + /** \internal + * \brief main generation/reweighting function: + * generate phase space points and divide out Born factors + */ std::vector<Event> gen_res_events( Event const & ev, int num_events ); std::vector<Event> rescale( Event const & Born_ev, std::vector<Event> events ) const; + /** \internal + * \brief Do the Jets pass the resummation Cuts? + * + * @param ev Event in Question + * @returns 0 or 1 depending on if ev passes Jet Cuts + */ bool jets_pass_resummation_cuts(Event const & ev) const; - EventFactors pdf_factors(Event const & ev) const; - - EventFactors matrix_elements(Event const & ev) const; + /** \internal + * \brief pdf_factors Function + * + * @param ev Event in Question + * @returns EventFactor due to PDFs + * + * Calculates the Central value and the variation due + * to the PDF choice made. + */ + Weights pdf_factors(Event const & ev) const; + + /** \internal + * \brief matrix_elements Function + * + * @param ev Event in question + * @returns EventFactor due to MatrixElements + * + * Calculates the Central value and the variation due + * to the Matrix Element. + */ + Weights matrix_elements(Event const & ev) const; - EventFactors fixed_order_scale_ME(Event const & ev) const; + /** \internal + * \brief Scale-dependent part of fixed-order matrix element + * + * @param ev Event in question + * @returns EventFactor scale variation due to FO-ME. + * + * This is only called to compute the scale variation for events where + * we don't do resummation (e.g. non-FKL). + * Since at tree level the scale dependence is just due to alpha_s, + * it is enough to return the alpha_s(mur) factors in the matrix element. + * The rest drops out in the ratio of (output event ME)/(input event ME), + * so we never have to compute it. + */ + Weights fixed_order_scale_ME(Event const & ev) const; + /** \internal + * \brief Computes the tree level matrix element + * + * @param ev Event in Question + * @returns HEJ approximation to Tree level Matrix Element + * + * This computes the HEJ approximation to the tree level FO + * Matrix element which is used within the LO weighting process. + */ double tree_matrix_element(Event const & ev) const; //! \internal General parameters EventReweighterConfig param_; //! \internal Beam energy double E_beam_; //! \internal PDF PDF pdf_; //! \internal Object to calculate the square of the matrix element MatrixElement MEt2_; //! \internal Object to calculate event renormalisation and factorisation scales ScaleGenerator scale_gen_; - //! \internal random number generator - /** - * \internal We use a reference_wrapper so that EventReweighter objects can - * still be copied (which would be impossible with a reference). + /** \internal random number generator + * + * \note We use a reference_wrapper so that EventReweighter objects can + * still be copied (which would be impossible with a reference). */ std::reference_wrapper<HEJ::RNG> ran_; }; template<typename... T> PDF const & EventReweighter::pdf(T&&... t){ return pdf_ = PDF{std::forward<T>(t)...}; } } diff --git a/include/HEJ/MatrixElement.hh b/include/HEJ/MatrixElement.hh index 429b648..ebbc7f4 100644 --- a/include/HEJ/MatrixElement.hh +++ b/include/HEJ/MatrixElement.hh @@ -1,208 +1,163 @@ /** \file * \brief Contains the MatrixElement Class */ #pragma once #include <functional> #include "HEJ/config.hh" #include "HEJ/utility.hh" #include "HEJ/HiggsCouplingSettings.hh" +#include "HEJ/Weights.hh" #include "CLHEP/Vector/LorentzVector.h" namespace HEJ{ + class Event; //! Class to calculate the squares of matrix elements class MatrixElement{ public: /** \brief MatrixElement Constructor * @param alpha_s Function taking the renormalisation scale * and returning the strong coupling constant * @param conf General matrix element settings */ MatrixElement( std::function<double (double)> alpha_s, MatrixElementConfig conf ); /** - * \brief regulated HEJ matrix element - * @param mur Value of the renormalisation scale - * @param incoming Incoming particles - * @param outgoing Outgoing particles + * \brief squares of regulated HEJ matrix elements + * @param event The event for which to calculate matrix elements * @param check_momenta Special treatment for partons inside extremal jets - * @returns The HEJ matrix element including virtual corrections + * @returns The squares of HEJ matrix elements including virtual corrections * * cf. eq. (22) in \cite Andersen:2011hs - * Incoming particles should be ordered by ascending z momentum. - * Outgoing particles should be ordered by ascending rapidity. * * \internal Relation to standard HEJ Met2: MatrixElement = Met2*shat^2/(pdfta*pdftb) */ - double operator()( - double mur, - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Weights operator()( + Event const & event, bool check_momenta ) const; - //! HEJ tree-level matrix element + //! Squares of HEJ tree-level matrix elements /** - * @param mur Value of the renormalisation scale - * @param incoming Incoming particles - * @param outgoing Outgoing particles + * @param event The event for which to calculate matrix elements * @param check_momenta Special treatment for partons inside extremal jets - * @returns The HEJ matrix element without virtual corrections + * @returns The squares of HEJ matrix elements without virtual corrections * * cf. eq. (22) in \cite Andersen:2011hs - * Incoming particles should be ordered by ascending z momentum. - * Outgoing particles should be ordered by ascending rapidity. */ - double tree( - double mur, - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, - bool check_momenta - ) const; - - //! HEJ tree-level matrix element - parametric part - /** - * @param mur Value of the renormalisation scale - * @param incoming Incoming particles - * @param outgoing Outgoing particles - * @returns The parametric part of the tree matrix element - * - * cf. eq. (22) in \cite Andersen:2011hs - * - * The tree level matrix element factorises into a parametric part - * which depends on the theory parameters (alpha_s and scale) - * and a kinematic part comprising the dependence on the particle momenta - * and colour factors. This function returns the former. - */ - double tree_param( - double mur, - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing - ) const; - - //! HEJ tree-level matrix element - kinematic part - /** - * @param incoming Incoming particles - * @param outgoing Outgoing particles - * @param check_momenta Special treatment for partons inside extremal jets - * @returns The kinematic part of the tree matrix element - * - * cf. eq. (22) in \cite Andersen:2011hs - * Incoming particles should be ordered by ascending z momentum. - * Outgoing particles should be ordered by ascending rapidity. - * - * The tree level matrix element factorises into a parametric part - * which depends on the theory parameters (alpha_s and scale) - * and a kinematic part comprising the dependence on the particle momenta - * and colour factors. This function returns the latter. - */ - double tree_kin( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Weights tree( + Event const & event, bool check_momenta ) const; /** - * \brief Calculates the Virtual Corrections - * @param mur Value of the renormalisation scale - * @param in Incoming particles - * @param out Outgoing particles - * @returns The Virtual Corrections of the Matrix Element - * - * Incoming particles should be ordered by ascending z momentum. - * Outgoing particles should be ordered by ascending rapidity. + * \brief Virtual corrections to matrix element squares + * @param event The event for which to calculate matrix elements + * @returns The virtual corrections to the squares of the matrix elements * * The all order virtual corrections to LL in the MRK limit is * given by replacing 1/t in the scattering amplitude according to the * lipatov ansatz. * * cf. second-to-last line of eq. (22) in \cite Andersen:2011hs * note that indices are off by one, i.e. out[0].p corresponds to p_1 */ - double virtual_corrections( - double mur, - std::array<Particle, 2> const & in, - std::vector<Particle> const & out + Weights virtual_corrections( + Event const & event ) const; private: + + Weights tree_param( + Event const & event + ) const; + + double tree_kin( + Event const & event, + bool check_momenta + ) const; + + double tree_param( + Event const & event, + double mur + ) const; + + double virtual_corrections( + Event const & event, + double mur + ) const; + //! \internal cf. last line of eq. (22) in \cite Andersen:2011hs double omega0( double alpha_s, double mur, fastjet::PseudoJet const & q_j, double lambda ) const; double tree_kin_jets( - std::array<Particle, 2> const & incoming, - std::vector<Particle> partons, + Event const & ev, bool check_momenta ) const; double tree_kin_Higgs( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const; double tree_kin_Higgs_first( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const; double tree_kin_Higgs_last( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const; /** * \internal * \brief Higgs inbetween extremal partons. * * Note that in the case of unordered emission, the Higgs is *always* * treated as if in between the extremal (FKL) partons, even if its * rapidity is outside the extremal parton rapidities */ double tree_kin_Higgs_between( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const; double tree_param_partons( double alpha_s, double mur, std::vector<Particle> const & partons ) const; std::vector<int> in_extremal_jet_indices( std::vector<fastjet::PseudoJet> const & partons ) const; std::vector<Particle> tag_extremal_jet_partons( - std::array<Particle, 2> const & incoming, - std::vector<Particle> out_partons, bool check_momenta + Event const & ev, bool check_momenta ) const; double MH2_forwardH( CLHEP::HepLorentzVector p1out, CLHEP::HepLorentzVector p1in, pid::ParticleID type2, CLHEP::HepLorentzVector p2out, CLHEP::HepLorentzVector p2in, CLHEP::HepLorentzVector pH, double t1, double t2 ) const; std::function<double (double)> alpha_s_; MatrixElementConfig param_; }; } diff --git a/include/HEJ/Weights.hh b/include/HEJ/Weights.hh new file mode 100644 index 0000000..fc92237 --- /dev/null +++ b/include/HEJ/Weights.hh @@ -0,0 +1,45 @@ +/** \file + * \brief Container for event weights + */ +#pragma once + +#include <vector> + +namespace HEJ{ + //! Collection of weights assigned to a single event + /** + * A number of member functions of the MatrixElement class return Weights + * objects containing the squares of the matrix elements for the various + * scale choices. + */ + struct Weights { + double central; + std::vector<double> variations; + + Weights& operator*=(Weights const & other); + Weights& operator*=(double factor); + Weights& operator/=(Weights const & other); + Weights& operator/=(double factor); + }; + + inline + Weights operator*(Weights a, Weights const & b) { + return a*=b; + } + inline + Weights operator*(Weights a, double b) { + return a*=b; + } + inline + Weights operator*(double b, Weights a) { + return a*=b; + } + inline + Weights operator/(Weights a, Weights const & b) { + return a/=b; + } + inline + Weights operator/(Weights a, double b) { + return a/=b; + } +} diff --git a/include/HEJ/config.hh b/include/HEJ/config.hh index a7fe397..7c51c05 100644 --- a/include/HEJ/config.hh +++ b/include/HEJ/config.hh @@ -1,175 +1,173 @@ /** \file * \brief HEJ 2 configuration parameters */ #pragma once #include <string> #include <memory> #include "fastjet/JetDefinition.hh" #include "yaml-cpp/yaml.h" #include "HEJ/event_types.hh" #include "HEJ/Event.hh" #include "HEJ/HiggsCouplingSettings.hh" #include "HEJ/optional.hh" #include "HEJ/output_formats.hh" #include "HEJ/ScaleFunction.hh" #include "HEJ/utility.hh" namespace HEJ{ //! Jet parameters struct JetParameters{ fastjet::JetDefinition def; /**< Jet Definition */ double min_pt; /**< Minimum Jet Transverse Momentum */ }; //! Settings for scale variation struct ScaleConfig{ //! Base scale choices std::vector<ScaleFunction> base; //! Factors for multiplicative scale variation std::vector<double> factors; //! Maximum ratio between renormalisation and factorisation scale double max_ratio; }; //! Settings for random number generator struct RNGConfig { //! Random number generator name std::string name; //! Optional initial seed optional<std::string> seed; }; /**! Possible treatments for fixed-order input events. * * The program will decide on how to treat an event based on * the value of this enumeration. */ enum class EventTreatment{ reweight, /**< Perform resummation */ keep, /**< Keep the event */ discard, /**< Discard the event */ }; //! Container to store the treatments for various event types using EventTreatMap = std::map<event_type::EventType, EventTreatment>; /**! Input parameters. * * This struct handles stores all configuration parameters * needed in a HEJ 2 run. * * \internal To add a new option: * 1. Add a member to the Config struct. * 2. Inside "src/YAMLreader.cc": * - Add the option name to the "supported" Node in * get_supported_options. * - Initialise the new Config member in to_Config. * The functions set_from_yaml (for mandatory options) and * set_from_yaml_if_defined (non-mandatory) may be helpful. * 3. Add a new entry (with short description) to config.yaml * 4. Update the user documentation in "doc/Sphinx/" */ struct Config { //! Parameters for scale variation ScaleConfig scales; //! Resummation jet properties JetParameters resummation_jets; //! Fixed-order jet properties JetParameters fixed_order_jets; //! Minimum transverse momentum for extremal partons double min_extparton_pt; //! Maximum transverse momentum fraction from soft radiation in extremal jets double max_ext_soft_pt_fraction; //! Number of resummation configurations to generate per fixed-order event int trials; //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running bool log_correction; //! Whether to perform unweighting bool unweight; //! Event output files names and formats std::vector<OutputFile> output; //! Parameters for random number generation RNGConfig rng; //! Map to decide what to do for different event types EventTreatMap treat; //! Parameters for custom analyses YAML::Node analysis_parameters; //! Settings for effective Higgs-gluon coupling HiggsCouplingSettings Higgs_coupling; }; //! Configuration options for the PhaseSpacePoint class struct PhaseSpacePointConfig { //! Properties of resummation jets JetParameters jet_param; //! Minimum transverse momentum for extremal partons double min_extparton_pt; //! Maximum transverse momentum fraction from soft radiation in extremal jets double max_ext_soft_pt_fraction; }; //! Configuration options for the MatrixElement class struct MatrixElementConfig { - //! Jet properties - JetParameters jet_param; //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running bool log_correction; //! Settings for effective Higgs-gluon coupling HiggsCouplingSettings Higgs_coupling; }; //! Configuration options for the EventReweighter class struct EventReweighterConfig { //! Settings for phase space point generation PhaseSpacePointConfig psp_config; //! Settings for matrix element calculation MatrixElementConfig ME_config; //! Properties of resummation jets JetParameters jet_param; //! Treatment of the various event types EventTreatMap treat; }; /**! Extract PhaseSpacePointConfig from Config * * \internal We do not provide a PhaseSpacePointConfig constructor from Config * so that PhaseSpacePointConfig remains an aggregate. * This faciliates writing client code (e.g. the HEJ fixed-order generator) * that creates a PhaseSpacePointConfig *without* a Config object. * * @see to_MatrixElementConfig, to_EventReweighterConfig */ inline PhaseSpacePointConfig to_PhaseSpacePointConfig(Config const & conf) { return {conf.resummation_jets, conf.min_extparton_pt, conf.max_ext_soft_pt_fraction}; } /**! Extract MatrixElementConfig from Config * * @see to_PhaseSpacePointConfig, to_EventReweighterConfig */ inline MatrixElementConfig to_MatrixElementConfig(Config const & conf) { - return {conf.resummation_jets, conf.log_correction, conf.Higgs_coupling}; + return {conf.log_correction, conf.Higgs_coupling}; } /**! Extract EventReweighterConfig from Config * * @see to_PhaseSpacePointConfig, to_MatrixElementConfig */ inline EventReweighterConfig to_EventReweighterConfig(Config const & conf) { return { to_PhaseSpacePointConfig(conf), to_MatrixElementConfig(conf), conf.resummation_jets, conf.treat }; } } // namespace HEJ diff --git a/include/HEJ/event_types.hh b/include/HEJ/event_types.hh index 16f1416..6cf3d82 100644 --- a/include/HEJ/event_types.hh +++ b/include/HEJ/event_types.hh @@ -1,42 +1,60 @@ /** \file * \brief Define different types of events. * */ #pragma once #include "HEJ/utility.hh" namespace HEJ{ //! Namespace for event types namespace event_type{ //! Possible event types enum EventType: size_t{ FKL, /**< FKL-type event */ unordered_backward, /**< event with unordered backward emission */ unordered_forward, /**< event with unordered forward emission */ nonHEJ, /**< event configuration not covered by HEJ */ no_2_jets, /**< event with less than two jets */ bad_final_state, /**< event with an unsupported final state */ unob = unordered_backward, unof = unordered_forward, first_type = FKL, last_type = bad_final_state }; //! Event type names /** * For example, names[FKL] is the string "FKL" */ static constexpr auto names = make_array( "FKL", "unordered backward", "unordered forward", "nonHEJ", "no 2 jets", "bad final state" ); + + inline + bool is_HEJ(EventType type) { + switch(type) { + case FKL: + case unordered_backward: + case unordered_forward: + return true; + default: + return false; + } + } + + inline + bool is_uno(EventType type) { + return type == unordered_backward || type == unordered_forward; + } + } } diff --git a/include/HEJ/uno.hh b/include/HEJ/uno.hh deleted file mode 100644 index 289a556..0000000 --- a/include/HEJ/uno.hh +++ /dev/null @@ -1,65 +0,0 @@ -/** \file uno.hh - * \brief Functions for determining if event is unordered. - */ - -#pragma once - -#include "HEJ/utility.hh" -#include "HEJ/PDG_codes.hh" - -namespace HEJ{ - //! Check if an event has an unordered backward gluon emission - /** - * @param incoming Incoming particles in ascending pz - * @param outgoing Outgoing particles in ascending rapidity - * @returns true iff the most backward outgoing particle is a - * gluon and the first incoming particle is not a gluon. - * - * If the incoming and outgoing particles are ordered such that HEJ resummation - * is possible, this function can help to distinguish between FKL and unordered - * events. - */ - inline - bool has_unob_gluon( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing - ){ - return incoming.front().type != pid::gluon - && outgoing.front().type == pid::gluon; - } - - - //! Check if an event has an unordered backward gluon emission - /** - * @param incoming Incoming particles in ascending pz - * @param outgoing Outgoing particles in ascending rapidity - * @returns true iff the most forward outgoing particle is a - * gluon and the last incoming particle is not a gluon. - * - * If the incoming and outgoing particles are ordered such that HEJ resummation - * is possible, this function can help to distinguish between FKL and unordered - * events. - */ - inline - bool has_unof_gluon( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing - ){ - return incoming.back().type != pid::gluon - && outgoing.back().type == pid::gluon; - } - - //! Check if an event has an gluon emission - /** - * @see has_unob_gluon, has_unof_gluon - */ - inline - bool has_uno_gluon( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing - ){ - return - has_unob_gluon(incoming, outgoing) || has_unof_gluon(incoming, outgoing); - } - -} diff --git a/src/Event.cc b/src/Event.cc index f098930..6dc344f 100644 --- a/src/Event.cc +++ b/src/Event.cc @@ -1,343 +1,341 @@ #include "HEJ/Event.hh" #include "HEJ/utility.hh" namespace HEJ{ namespace{ constexpr int status_in = -1; constexpr int status_decayed = 2; constexpr int status_out = 1; - // helper functions to determine event type + /// @name helper functions to determine event type + //@{ - // check if there is at most one photon, W, H, Z in the final state - // and all the rest are quarks or gluons + /** + * \brief check if final state valid for HEJ + * + * check if there is at most one photon, W, H, Z in the final state + * and all the rest are quarks or gluons + */ bool final_state_ok(std::vector<Particle> const & outgoing){ bool has_AWZH_boson = false; for(auto const & out: outgoing){ if(is_AWZH_boson(out.type)){ if(has_AWZH_boson) return false; has_AWZH_boson = true; } else if(! is_parton(out.type)) return false; } return true; } template<class Iterator> Iterator remove_AWZH(Iterator begin, Iterator end){ return std::remove_if( begin, end, [](Particle const & p){return is_AWZH_boson(p);} ); } template<class Iterator> bool valid_outgoing(Iterator begin, Iterator end){ return std::distance(begin, end) >= 2 && std::is_sorted(begin, end, rapidity_less{}) && std::count_if( begin, end, [](Particle const & s){return is_AWZH_boson(s);} ) < 2; } - // Note that this changes the outgoing range! + /// @note that this changes the outgoing range! template<class ConstIterator, class Iterator> bool is_FKL( ConstIterator begin_incoming, ConstIterator end_incoming, Iterator begin_outgoing, Iterator end_outgoing ){ assert(std::distance(begin_incoming, end_incoming) == 2); assert(std::distance(begin_outgoing, end_outgoing) >= 2); // One photon, W, H, Z in the final state is allowed. // Remove it for remaining tests, end_outgoing = remove_AWZH(begin_outgoing, end_outgoing); // Test if this is a standard FKL configuration. return (begin_incoming->type == begin_outgoing->type) && ((end_incoming-1)->type == (end_outgoing-1)->type) && std::all_of( begin_outgoing + 1, end_outgoing - 1, [](Particle const & p){ return p.type == pid::gluon; } ); } bool is_FKL( std::array<Particle, 2> const & incoming, std::vector<Particle> outgoing ){ assert(std::is_sorted(begin(incoming), end(incoming), pz_less{})); assert(valid_outgoing(begin(outgoing), end(outgoing))); return is_FKL( begin(incoming), end(incoming), begin(outgoing), end(outgoing) ); } bool has_2_jets(Event const & event){ return event.jets().size() >= 2; } /** * \brief Checks whether event is unordered backwards * @param ev Event * @returns Is Event Unordered Backwards * - * Checks there is more than 3 constuents in the final state - * Checks there is more than 3 jets - * Checks the most backwards parton is a gluon - * Checks the most forwards jet is not a gluon - * Checks the rest of the event is FKL - * Checks the second most backwards is not a different boson - * Checks the unordered gluon actually forms a jet + * - Checks there is more than 3 constuents in the final state + * - Checks there is more than 3 jets + * - Checks the most backwards parton is a gluon + * - Checks the most forwards jet is not a gluon + * - Checks the rest of the event is FKL + * - Checks the second most backwards is not a different boson + * - Checks the unordered gluon actually forms a jet */ bool is_unordered_backward(Event const & ev){ auto const & in = ev.incoming(); auto const & out = ev.outgoing(); assert(std::is_sorted(begin(in), end(in), pz_less{})); assert(valid_outgoing(begin(out), end(out))); if(out.size() < 3) return false; if(ev.jets().size() < 3) return false; if(in.front().type == pid::gluon) return false; if(out.front().type != pid::gluon) return false; // When skipping the unordered emission // the remainder should be a regular FKL event, // except that the (new) first outgoing particle must not be a A,W,Z,H. const auto FKL_begin = next(begin(out)); if(is_AWZH_boson(*FKL_begin)) return false; if(!is_FKL(in, {FKL_begin, end(out)})) return false; // check that the unordered gluon forms an extra jet const auto jets = sorted_by_rapidity(ev.jets()); const auto indices = ev.particle_jet_indices({jets.front()}); return indices[0] >= 0 && indices[1] == -1; } /** * \brief Checks for a forward unordered gluon emission * @param ev Event * @returns Is the event a forward unordered emission * * \see is_unordered_backward */ bool is_unordered_forward(Event const & ev){ auto const & in = ev.incoming(); auto const & out = ev.outgoing(); assert(std::is_sorted(begin(in), end(in), pz_less{})); assert(valid_outgoing(begin(out), end(out))); if(out.size() < 3) return false; if(ev.jets().size() < 3) return false; if(in.back().type == pid::gluon) return false; if(out.back().type != pid::gluon) return false; // When skipping the unordered emission // the remainder should be a regular FKL event, // except that the (new) last outgoing particle must not be a A,W,Z,H. const auto FKL_end = prev(end(out)); if(is_AWZH_boson(*prev(FKL_end))) return false; if(!is_FKL(in, {begin(out), FKL_end})) return false; // check that the unordered gluon forms an extra jet const auto jets = sorted_by_rapidity(ev.jets()); const auto indices = ev.particle_jet_indices({jets.back()}); return indices.back() >= 0 && indices[indices.size()-2] == -1; } using event_type::EventType; EventType classify(Event const & ev){ if(! final_state_ok(ev.outgoing())) return EventType::bad_final_state; if(! has_2_jets(ev)) return EventType::no_2_jets; if(is_FKL(ev.incoming(), ev.outgoing())) return EventType::FKL; if(is_unordered_backward(ev)){ return EventType::unordered_backward; } if(is_unordered_forward(ev)){ return EventType::unordered_forward; } return EventType::nonHEJ; } + //@} Particle extract_particle(LHEF::HEPEUP const & hepeup, int i){ return Particle{ static_cast<ParticleID>(hepeup.IDUP[i]), fastjet::PseudoJet{ hepeup.PUP[i][0], hepeup.PUP[i][1], hepeup.PUP[i][2], hepeup.PUP[i][3] } }; } bool is_decay_product(std::pair<int, int> const & mothers){ if(mothers.first == 0) return false; return mothers.second == 0 || mothers.first == mothers.second; } - } + } // namespace anonymous UnclusteredEvent::UnclusteredEvent(LHEF::HEPEUP const & hepeup): central(EventParameters{ hepeup.scales.mur, hepeup.scales.muf, hepeup.weight() }) { size_t in_idx = 0; for (int i = 0; i < hepeup.NUP; ++i) { // skip decay products // we will add them later on, but we have to ensure that // the decayed particle is added before if(is_decay_product(hepeup.MOTHUP[i])) continue; auto particle = extract_particle(hepeup, i); // needed to identify mother particles for decay products particle.p.set_user_index(i+1); if(hepeup.ISTUP[i] == status_in){ if(in_idx > incoming.size()) { throw std::invalid_argument{ "Event has too many incoming particles" }; } incoming[in_idx++] = std::move(particle); } else outgoing.emplace_back(std::move(particle)); } std::sort( begin(incoming), end(incoming), [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} ); std::sort(begin(outgoing), end(outgoing), rapidity_less{}); // add decay products for (int i = 0; i < hepeup.NUP; ++i) { if(!is_decay_product(hepeup.MOTHUP[i])) continue; const int mother_id = hepeup.MOTHUP[i].first; const auto mother = std::find_if( begin(outgoing), end(outgoing), [mother_id](Particle const & particle){ return particle.p.user_index() == mother_id; } ); if(mother == end(outgoing)){ throw std::invalid_argument{"invalid decay product parent"}; } const int mother_idx = std::distance(begin(outgoing), mother); assert(mother_idx >= 0); decays[mother_idx].emplace_back(extract_particle(hepeup, i)); } } Event::Event( UnclusteredEvent ev, fastjet::JetDefinition const & jet_def, double min_jet_pt ): ev_{std::move(ev)}, cs_{to_PseudoJet(filter_partons(ev_.outgoing)), jet_def}, min_jet_pt_{min_jet_pt} { type_ = classify(*this); } std::vector<fastjet::PseudoJet> Event::jets() const{ return cs_.inclusive_jets(min_jet_pt_); } - /** - * \brief Returns the invarient mass of the event - * @param ev Event - * @returns s hat - * - * Makes use of the FastJet PseudoJet function m2(). - * Applies this function to the sum of the incoming partons. - */ double shat(Event const & ev){ return (ev.incoming()[0].p + ev.incoming()[1].p).m2(); } namespace{ // colour flow according to Les Houches standard // TODO: stub std::vector<std::pair<int, int>> colour_flow( std::array<Particle, 2> const & incoming, std::vector<Particle> const & outgoing ){ std::vector<std::pair<int, int>> result( incoming.size() + outgoing.size() ); for(auto & col: result){ col = std::make_pair(-1, -1); } return result; } } LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP * heprup){ LHEF::HEPEUP result; result.heprup = heprup; result.weights = {{event.central().weight, nullptr}}; for(auto const & var: event.variations()){ result.weights.emplace_back(var.weight, nullptr); } size_t num_particles = event.incoming().size() + event.outgoing().size(); for(auto const & decay: event.decays()) num_particles += decay.second.size(); result.NUP = num_particles; // the following entries are pretty much meaningless result.IDPRUP = event.type()+1; // event ID result.AQEDUP = 1./128.; // alpha_EW //result.AQCDUP = 0.118 // alpha_QCD // end meaningless part result.XWGTUP = event.central().weight; result.SCALUP = event.central().muf; result.scales.muf = event.central().muf; result.scales.mur = event.central().mur; result.scales.SCALUP = event.central().muf; result.pdfinfo.p1 = event.incoming().front().type; result.pdfinfo.p2 = event.incoming().back().type; result.pdfinfo.scale = event.central().muf; for(Particle const & in: event.incoming()){ result.IDUP.emplace_back(in.type); result.ISTUP.emplace_back(status_in); result.PUP.push_back({in.p[0], in.p[1], in.p[2], in.p[3], in.p.m()}); result.MOTHUP.emplace_back(0, 0); } for(size_t i = 0; i < event.outgoing().size(); ++i){ Particle const & out = event.outgoing()[i]; result.IDUP.emplace_back(out.type); const int status = event.decays().count(i)?status_decayed:status_out; result.ISTUP.emplace_back(status); result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()}); result.MOTHUP.emplace_back(1, 2); } result.ICOLUP = colour_flow( event.incoming(), filter_partons(event.outgoing()) ); if(result.ICOLUP.size() < num_particles){ const size_t AWZH_boson_idx = std::find_if( begin(event.outgoing()), end(event.outgoing()), [](Particle const & s){ return is_AWZH_boson(s); } ) - begin(event.outgoing()) + event.incoming().size(); assert(AWZH_boson_idx <= result.ICOLUP.size()); result.ICOLUP.insert( begin(result.ICOLUP) + AWZH_boson_idx, std::make_pair(0,0) ); } for(auto const & decay: event.decays()){ for(auto const out: decay.second){ result.IDUP.emplace_back(out.type); result.ISTUP.emplace_back(status_out); result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()}); const size_t mother_idx = 1 + event.incoming().size() + decay.first; result.MOTHUP.emplace_back(mother_idx, mother_idx); result.ICOLUP.emplace_back(0,0); } } assert(result.ICOLUP.size() == num_particles); static constexpr double unknown_spin = 9.; //per Les Houches accord result.VTIMUP = std::vector<double>(num_particles, unknown_spin); result.SPINUP = result.VTIMUP; return result; } } diff --git a/src/EventReweighter.cc b/src/EventReweighter.cc index 7687fb2..72e1f6d 100644 --- a/src/EventReweighter.cc +++ b/src/EventReweighter.cc @@ -1,343 +1,259 @@ #include "HEJ/EventReweighter.hh" #include <string> #include <unordered_map> +#include "HEJ/exceptions.hh" #include "HEJ/PhaseSpacePoint.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/utility.hh" namespace HEJ{ using EventType = event_type::EventType; namespace { static_assert( std::numeric_limits<double>::has_quiet_NaN, "no quiet NaN for double" ); constexpr double NaN = std::numeric_limits<double>::quiet_NaN(); UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp){ UnclusteredEvent result; result.incoming = psp.incoming(); std::sort( begin(result.incoming), end(result.incoming), [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} ); assert(result.incoming.size() == 2); result.outgoing = psp.outgoing(); assert( std::is_sorted( begin(result.outgoing), end(result.outgoing), rapidity_less{} ) ); assert(result.outgoing.size() >= 2); result.decays = psp.decays(); result.central.mur = NaN; result.central.muf = NaN; result.central.weight = psp.weight(); return result; } } // namespace anonymous EventReweighter::EventReweighter( LHEF::HEPRUP const & heprup, ScaleGenerator scale_gen, EventReweighterConfig conf, HEJ::RNG & ran ): EventReweighter{ HEJ::Beam{ heprup.EBMUP.first, {{ static_cast<HEJ::ParticleID>(heprup.IDBMUP.first), static_cast<HEJ::ParticleID>(heprup.IDBMUP.second) }} }, heprup.PDFSUP.first, std::move(scale_gen), std::move(conf), ran } { if(heprup.EBMUP.second != E_beam_){ throw std::invalid_argument( "asymmetric beam: " + std::to_string(E_beam_) + " ---> <--- " + std::to_string(heprup.EBMUP.second) ); }; if(heprup.PDFSUP.second != pdf_.id()){ throw std::invalid_argument( "conflicting PDF ids: " + std::to_string(pdf_.id()) + " vs. " + std::to_string(heprup.PDFSUP.second) ); } } EventReweighter::EventReweighter( Beam beam, int pdf_id, ScaleGenerator scale_gen, EventReweighterConfig conf, HEJ::RNG & ran ): param_{std::move(conf)}, E_beam_{beam.E}, pdf_{pdf_id, beam.type.front(), beam.type.back()}, MEt2_{ [this](double mu){ return pdf_.Halphas(mu); }, param_.ME_config }, scale_gen_(std::move(scale_gen)), ran_{ran} {} PDF const & EventReweighter::pdf() const{ return pdf_; } std::vector<Event> EventReweighter::reweight( Event const & input_ev, int num_events ){ auto res_events = gen_res_events(input_ev, num_events); if(res_events.empty()) return {}; for(auto & event: res_events) event = scale_gen_(event); return rescale(input_ev, std::move(res_events)); } - /** - * \brief main generation/reweighting function: - * generate phase space points and divide out Born factors - */ std::vector<Event> EventReweighter::gen_res_events( Event const & ev, int phase_space_points ){ assert(ev.variations().empty()); switch(param_.treat.at(ev.type())){ case EventTreatment::discard: return {}; case EventTreatment::keep: if(! jets_pass_resummation_cuts(ev)) return {}; else return {ev}; default:; } const double Born_shat = shat(ev); std::vector<Event> resummation_events; for(int psp_number = 0; psp_number < phase_space_points; ++psp_number){ PhaseSpacePoint psp{ev, param_.psp_config, ran_}; if(psp.weight() == 0.) continue; if(psp.incoming()[0].E() > E_beam_ || psp.incoming()[1].E() > E_beam_) continue; resummation_events.emplace_back( to_UnclusteredEvent(std::move(psp)), param_.jet_param.def, param_.jet_param.min_pt ); auto & new_event = resummation_events.back(); assert(new_event.variations().empty()); new_event.central().mur = ev.central().mur; new_event.central().muf = ev.central().muf; const double resum_shat = shat(new_event); new_event.central().weight *= ev.central().weight*Born_shat*Born_shat/ (phase_space_points*resum_shat*resum_shat); } return resummation_events; } std::vector<Event> EventReweighter::rescale( Event const & Born_ev, std::vector<Event> events ) const{ const double Born_pdf = pdf_factors(Born_ev).central; const double Born_ME = tree_matrix_element(Born_ev); for(auto & cur_event: events){ const auto pdf = pdf_factors(cur_event); assert(pdf.variations.size() == cur_event.variations().size()); const auto ME = matrix_elements(cur_event); assert(ME.variations.size() == cur_event.variations().size()); cur_event.central().weight *= pdf.central*ME.central/(Born_pdf*Born_ME); for(size_t i = 0; i < cur_event.variations().size(); ++i){ cur_event.variations(i).weight *= pdf.variations[i]*ME.variations[i]/(Born_pdf*Born_ME); } } return events; }; - /** - * \brief Do the Jets pass the resummation Cuts? - * - * @param ev Event in Question - * @returns 0 or 1 depending on if ev passes Jet Cuts - */ bool EventReweighter::jets_pass_resummation_cuts( Event const & ev ) const{ const auto out_as_PseudoJet = to_PseudoJet(filter_partons(ev.outgoing())); fastjet::ClusterSequence cs{out_as_PseudoJet, param_.jet_param.def}; return cs.inclusive_jets(param_.jet_param.min_pt).size() == ev.jets().size(); } - - - /** - * \brief pdf_factors Function - * - * @param ev Event in Question - * @returns EventFactor due to PDFs - * - * Calculates the Central value and the variation due - * to the PDF choice made. - */ - EventReweighter::EventFactors + Weights EventReweighter::pdf_factors(Event const & ev) const{ auto const & a = ev.incoming().front(); auto const & b = ev.incoming().back(); const double xa = a.p.e()/E_beam_; const double xb = b.p.e()/E_beam_; - EventFactors result; + Weights result; std::unordered_map<double, double> known_pdf; result.central = pdf_.pdfpt(0,xa,ev.central().muf,a.type)* pdf_.pdfpt(1,xb,ev.central().muf,b.type); known_pdf.emplace(ev.central().muf, result.central); result.variations.reserve(ev.variations().size()); for(auto const & ev_param: ev.variations()){ const double muf = ev_param.muf; auto cur_pdf = known_pdf.find(muf); if(cur_pdf == known_pdf.end()){ cur_pdf = known_pdf.emplace( muf, pdf_.pdfpt(0,xa,muf,a.type)*pdf_.pdfpt(1,xb,muf,b.type) ).first; } result.variations.emplace_back(cur_pdf->second); } assert(result.variations.size() == ev.variations().size()); return result; } - - - /** - * \brief matrix_elements Function - * - * @param ev Event in question - * @returns EventFactor due to MatrixElements - * - * Calculates the Central value and the variation due - * to the Matrix Element. - */ - EventReweighter::EventFactors + Weights EventReweighter::matrix_elements(Event const & ev) const{ assert(param_.treat.count(ev.type()) > 0); if(param_.treat.find(ev.type())->second == EventTreatment::keep){ return fixed_order_scale_ME(ev); } - // precompute overall kinematic factor - const double ME_kin = MEt2_.tree_kin(ev.incoming(), ev.outgoing(), true); - - EventFactors result; - std::unordered_map<double, double> known_ME; - result.central = MEt2_( - ev.central().mur, - ev.incoming(), ev.outgoing(), - true - ); - known_ME.emplace(ev.central().mur, result.central); - - result.variations.reserve(ev.variations().size()); - for(auto const & param: ev.variations()){ - const double mur = param.mur; - auto cur_ME = known_ME.find(mur); - if(cur_ME == known_ME.end()){ - const double ME = MEt2_.tree_param( - mur, ev.incoming(), ev.outgoing() - )*ME_kin*MEt2_.virtual_corrections( - mur, ev.incoming(), ev.outgoing() - ); - cur_ME = known_ME.emplace(mur, ME).first; - } - result.variations.emplace_back(cur_ME->second); - } - assert(result.variations.size() == ev.variations().size()); - return result; + return MEt2_(ev, true); } - /** - * \brief Computes the tree level matrix element - * - * @param ev Event in Question - * @returns HEJ approximation to Tree level Matrix Element - * - * This computes the HEJ approximation to the tree level FO - * Matrix element which is used within the LO weighting process. - */ double EventReweighter::tree_matrix_element(Event const & ev) const{ assert(ev.variations().empty()); assert(param_.treat.count(ev.type()) > 0); if(param_.treat.find(ev.type())->second == EventTreatment::keep){ return fixed_order_scale_ME(ev).central; } - return MEt2_.tree( - ev.central().mur, - ev.incoming(), ev.outgoing(), - false - ); + return MEt2_.tree(ev, false).central; } - /** - * \brief Scale-dependent part of fixed-order matrix element - * - * @param ev Event in question - * @returns EventFactor scale variation due to FO-ME. - * - * This is only called to compute the scale variation for events where - * we don't do resummation (e.g. non-FKL). - * Since at tree level the scale dependence is just due to alpha_s, - * it is enough to return the alpha_s(mur) factors in the matrix element. - * The rest drops out in the ratio of (output event ME)/(input event ME), - * so we never have to compute it. - */ - EventReweighter::EventFactors + Weights EventReweighter::fixed_order_scale_ME(Event const & ev) const{ int alpha_s_power = 0; for(auto const & part: ev.outgoing()){ if(is_parton(part)) ++alpha_s_power; else { switch(part.type){ case pid::Higgs: { alpha_s_power += 2; break; } // TODO case pid::Wp: case pid::Wm: case pid::photon: case pid::Z: default: - throw std::logic_error("Emission of boson of unsupported type"); + throw not_implemented("Emission of boson of unsupported type"); } } } - EventFactors result; + Weights result; result.central = pow(pdf_.Halphas(ev.central().mur), alpha_s_power); for(auto const & var: ev.variations()){ result.variations.emplace_back( pow(pdf_.Halphas(var.mur), alpha_s_power) ); } return result; } } // namespace HEJ diff --git a/src/MatrixElement.cc b/src/MatrixElement.cc index e9c644d..5d31d30 100644 --- a/src/MatrixElement.cc +++ b/src/MatrixElement.cc @@ -1,826 +1,860 @@ #include "HEJ/MatrixElement.hh" +#include <unordered_map> + #include <CLHEP/Random/Randomize.h> #include <CLHEP/Random/RanluxEngine.h> #include "HEJ/Constants.hh" #include "HEJ/currents.hh" #include "HEJ/exceptions.hh" #include "HEJ/PDG_codes.hh" -#include "HEJ/uno.hh" +#include "HEJ/event_types.hh" #include "HEJ/utility.hh" namespace HEJ{ //cf. last line of eq. (22) in \ref Andersen:2011hs double MatrixElement::omega0( double alpha_s, double mur, fastjet::PseudoJet const & q_j, double lambda ) const { const double result = - alpha_s*N_C/M_PI*log(q_j.perp2()/(lambda*lambda)); if(! param_.log_correction) return result; // use alpha_s(sqrt(q_j*lambda)), evolved to mur return ( 1. + alpha_s/(4.*M_PI)*beta0*log(mur*mur/(q_j.perp()*lambda)) )*result; } + Weights MatrixElement::operator()( + Event const & event, + bool check_momenta + ) const { + return tree(event, check_momenta)*virtual_corrections(event); + } + + Weights MatrixElement::tree( + Event const & event, + bool check_momenta + ) const { + if(! is_HEJ(event.type())) { + return Weights{0., std::vector<double>(event.variations().size(), 0.)}; + } + return tree_param(event)*tree_kin(event, check_momenta); + } + + Weights MatrixElement::tree_param( + Event const & event + ) const { + Weights result; + // only compute once for each renormalisation scale + std::unordered_map<double, double> known; + result.central = tree_param(event, event.central().mur); + known.emplace(event.central().mur, result.central); + for(auto const & var: event.variations()) { + const auto ME_it = known.find(var.mur); + if(ME_it == end(known)) { + const double wt = tree_param(event, var.mur); + result.variations.emplace_back(wt); + known.emplace(var.mur, wt); + } + else { + result.variations.emplace_back(ME_it->second); + } + } + return result; + } + + Weights MatrixElement::virtual_corrections( + Event const & event + ) const { + if(! is_HEJ(event.type())) { + return Weights{0., std::vector<double>(event.variations().size(), 0.)}; + } + Weights result; + // only compute once for each renormalisation scale + std::unordered_map<double, double> known; + result.central = virtual_corrections(event, event.central().mur); + known.emplace(event.central().mur, result.central); + for(auto const & var: event.variations()) { + const auto ME_it = known.find(var.mur); + if(ME_it == end(known)) { + const double wt = virtual_corrections(event, var.mur); + result.variations.emplace_back(wt); + known.emplace(var.mur, wt); + } + else { + result.variations.emplace_back(ME_it->second); + } + } + return result; + } + double MatrixElement::virtual_corrections( - double mur, - std::array<Particle, 2> const & in, - std::vector<Particle> const & out + Event const & event, + double mur ) const{ + auto const & in = event.incoming(); + auto const & out = event.outgoing(); fastjet::PseudoJet const & pa = in.front().p; #ifndef NDEBUG fastjet::PseudoJet const & pb = in.back().p; double const norm = (in.front().p + in.back().p).E(); #endif assert(std::is_sorted(out.begin(), out.end(), rapidity_less{})); assert(out.size() >= 2); assert(pa.pz() < pb.pz()); fastjet::PseudoJet q = pa - out[0].p; size_t first_idx = 0; size_t last_idx = out.size() - 1; // if there is a Higgs or unordered gluon outside the extremal partons // then it is not part of the FKL ladder and does not contribute // to the virtual corrections - if(out.front().type == pid::Higgs || has_unob_gluon(in, out)){ + if(out.front().type == pid::Higgs || event.type() == event_type::unob){ q -= out[1].p; ++first_idx; } - if(out.back().type == pid::Higgs || has_unof_gluon(in, out)){ + if(out.back().type == pid::Higgs || event.type() == event_type::unof){ --last_idx; } double exponent = 0; const double alpha_s = alpha_s_(mur); for(size_t j = first_idx; j < last_idx; ++j){ exponent += omega0(alpha_s, mur, q, CLAMBDA)*( out[j+1].rapidity() - out[j].rapidity() ); q -= out[j+1].p; } assert( nearby(q, -1*pb, norm) || out.back().type == pid::Higgs - || has_unof_gluon(in, out) + || event.type() == event_type::unof ); return exp(exponent); } } // namespace HEJ namespace { //! Lipatov vertex for partons emitted into extremal jets double C2Lipatov(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2) { CLHEP::HepLorentzVector temptrans=-(qav+qbv); CLHEP::HepLorentzVector p5=qav-qbv; CLHEP::HepLorentzVector CL=temptrans + p1*(qav.m2()/p5.dot(p1) + 2.*p5.dot(p2)/p1.dot(p2)) - p2*(qbv.m2()/p5.dot(p2) + 2.*p5.dot(p1)/p1.dot(p2)); // cout << "#Fadin qa : "<<qav<<endl; // cout << "#Fadin qb : "<<qbv<<endl; // cout << "#Fadin p1 : "<<p1<<endl; // cout << "#Fadin p2 : "<<p2<<endl; // cout << "#Fadin p5 : "<<p5<<endl; // cout << "#Fadin Gauge Check : "<< CL.dot(p5)<<endl; // cout << "#Fadin C2L : "<< -CL.dot(CL)<<" "<<-CL.dot(CL)/(qav.m2()*qbv.m2())/(4./p5.perp2())<<endl; // TODO can this dead test go? // if (-CL.dot(CL)<0.) // if (fabs(CL.dot(p5))>fabs(CL.dot(CL))) // not sufficient! // return 0.; // else return -CL.dot(CL); } //! Lipatov vertex with soft subtraction for partons emitted into extremal jets double C2Lipatovots(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2) { double kperp=(qav-qbv).perp(); if (kperp>HEJ::CLAMBDA) return C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2()); else { double Cls=(C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2())); return Cls-4./(kperp*kperp); } } //! Lipatov vertex double C2Lipatov(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector pim, CLHEP::HepLorentzVector pip, CLHEP::HepLorentzVector pom, CLHEP::HepLorentzVector pop) // B { CLHEP::HepLorentzVector temptrans=-(qav+qbv); CLHEP::HepLorentzVector p5=qav-qbv; CLHEP::HepLorentzVector CL=temptrans + qav.m2()*(1./p5.dot(pip)*pip + 1./p5.dot(pop)*pop)/2. - qbv.m2()*(1./p5.dot(pim)*pim + 1./p5.dot(pom)*pom)/2. + ( pip*(p5.dot(pim)/pip.dot(pim) + p5.dot(pom)/pip.dot(pom)) + pop*(p5.dot(pim)/pop.dot(pim) + p5.dot(pom)/pop.dot(pom)) - pim*(p5.dot(pip)/pip.dot(pim) + p5.dot(pop)/pop.dot(pim)) - pom*(p5.dot(pip)/pip.dot(pom) + p5.dot(pop)/pop.dot(pom)) )/2.; return -CL.dot(CL); } //! Lipatov vertex with soft subtraction double C2Lipatovots(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv, CLHEP::HepLorentzVector pa, CLHEP::HepLorentzVector pb, CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2) { double kperp=(qav-qbv).perp(); if (kperp>HEJ::CLAMBDA) return C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2()); else { double Cls=(C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2())); double temp=Cls-4./(kperp*kperp); return temp; } } /** Matrix element squared for tree-level current-current scattering * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @returns ME Squared for Tree-Level Current-Current Scattering */ double ME_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa ){ if (aptype==21&&bptype==21) { return jM2gg(pn,pb,p1,pa); } else if (aptype==21&&bptype!=21) { if (bptype > 0) return jM2qg(pn,pb,p1,pa); else return jM2qbarg(pn,pb,p1,pa); } else if (bptype==21&&aptype!=21) { // ----- || ----- if (aptype > 0) return jM2qg(p1,pa,pn,pb); else return jM2qbarg(p1,pa,pn,pb); } else { // they are both quark if (bptype>0) { if (aptype>0) return jM2qQ(pn,pb,p1,pa); else return jM2qQbar(pn,pb,p1,pa); } else { if (aptype>0) return jM2qQbar(p1,pa,pn,pb); else return jM2qbarQbar(pn,pb,p1,pa); } } throw std::logic_error("unknown particle types"); } /** \brief Matrix element squared for tree-level current-current scattering with Higgs * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param qH t-channel momentum before Higgs * @param qHp1 t-channel momentum after Higgs * @returns ME Squared for Tree-Level Current-Current Scattering with Higgs */ double ME_Higgs_current( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & qH, // t-channel momentum before Higgs CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs double mt, bool include_bottom, double mb ){ if (aptype==21&&bptype==21) // gg initial state return MH2gg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else if (aptype==21&&bptype!=21) { if (bptype > 0) return MH2qg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4./9.; else return MH2qbarg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4./9.; } else if (bptype==21&&aptype!=21) { if (aptype > 0) return MH2qg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4./9.; else return MH2qbarg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4./9.; } else { // they are both quark if (bptype>0) { if (aptype>0) return MH2qQ(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.); else return MH2qQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.); } else { if (aptype>0) return MH2qQbar(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4.*4./(9.*9.); else return MH2qbarQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.); } } throw std::logic_error("unknown particle types"); } /** \brief Current matrix element squared with Higgs and unordered forward emission * @param aptype Particle A PDG ID * @param bptype Particle B PDG ID * @param punof Unordered Particle Momentum * @param pn Particle n Momentum * @param pb Particle b Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param qH t-channel momentum before Higgs * @param qHp1 t-channel momentum after Higgs * @returns ME Squared with Higgs and unordered forward emission */ double ME_Higgs_current_unof( int aptype, int bptype, CLHEP::HepLorentzVector const & punof, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & qH, // t-channel momentum before Higgs CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs double mt, bool include_bottom, double mb ){ if (aptype==21&&bptype!=21) { if (bptype > 0) return jM2unogqHg(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unogqbarHg(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { // they are both quark if (bptype>0) { if (aptype>0) return jM2unogqHQ(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unogqHQbar(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { if (aptype>0) return jM2unogqbarHQ(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unogqbarHQbar(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } } throw std::logic_error("unknown particle types"); } /** \brief Current matrix element squared with Higgs and unordered backward emission * @param aptype Particle A PDG ID * @param bptype Particle B PDG ID * @param pn Particle n Momentum * @param pb Particle b Momentum * @param punob Unordered back Particle Momentum * @param p1 Particle 1 Momentum * @param pa Particle a Momentum * @param qH t-channel momentum before Higgs * @param qHp1 t-channel momentum after Higgs * @returns ME Squared with Higgs and unordered backward emission */ double ME_Higgs_current_unob( int aptype, int bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & punob, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & qH, // t-channel momentum before Higgs CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs double mt, bool include_bottom, double mb ){ if (bptype==21&&aptype!=21) { if (aptype > 0) return jM2unobgHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unobgHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { // they are both quark if (aptype>0) { if (bptype>0) return jM2unobqHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unobqbarHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } else { if (bptype>0) return jM2unobqHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); else return jM2unobqbarHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb); } } throw std::logic_error("unknown particle types"); } CLHEP::HepLorentzVector to_HepLorentzVector(HEJ::Particle const & particle){ return {particle.p.px(), particle.p.py(), particle.p.pz(), particle.p.E()}; } void validate(HEJ::MatrixElementConfig const & config) { #ifndef HEJ_BUILD_WITH_QCDLOOP if(!config.Higgs_coupling.use_impact_factors) { throw std::invalid_argument{ "Invalid Higgs coupling settings.\n" "HEJ without QCDloop support can only use impact factors.\n" "Set use_impact_factors to true or recompile HEJ.\n" }; } #endif if(config.Higgs_coupling.use_impact_factors && config.Higgs_coupling.mt != std::numeric_limits<double>::infinity()) { throw std::invalid_argument{ "Conflicting settings: " "impact factors may only be used in the infinite top mass limit" }; } } } // namespace anonymous namespace HEJ{ MatrixElement::MatrixElement( std::function<double (double)> alpha_s, MatrixElementConfig conf ): alpha_s_{std::move(alpha_s)}, param_{std::move(conf)} { validate(param_); } - double MatrixElement::operator()( - double mur, - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, - bool check_momenta - ) const { - return tree( - mur, - incoming, outgoing, - check_momenta - )*virtual_corrections( - mur, - incoming, outgoing - ); - } - double MatrixElement::tree_kin( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const { - assert( - std::is_sorted( - incoming.begin(), incoming.end(), - [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} - ) - ); - assert(std::is_sorted(outgoing.begin(), outgoing.end(), rapidity_less{})); auto AWZH_boson = std::find_if( - begin(outgoing), end(outgoing), + begin(ev.outgoing()), end(ev.outgoing()), [](Particle const & p){return is_AWZH_boson(p);} ); - if(AWZH_boson == end(outgoing)){ - return tree_kin_jets(incoming, outgoing, check_momenta); + if(AWZH_boson == end(ev.outgoing())){ + return tree_kin_jets(ev, check_momenta); } switch(AWZH_boson->type){ case pid::Higgs: { - return tree_kin_Higgs(incoming, outgoing, check_momenta); + return tree_kin_Higgs(ev, check_momenta); } // TODO case pid::Wp: case pid::Wm: case pid::photon: case pid::Z: default: - throw std::logic_error("Emission of boson of unsupported type"); + throw not_implemented("Emission of boson of unsupported type"); } } namespace{ constexpr int extremal_jet_idx = 1; constexpr int no_extremal_jet_idx = 0; bool treat_as_extremal(Particle const & parton){ return parton.p.user_index() == extremal_jet_idx; } template<class InputIterator> double FKL_ladder_weight( InputIterator begin_gluon, InputIterator end_gluon, CLHEP::HepLorentzVector const & q0, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pn ){ double wt = 1; auto qi = q0; for(auto gluon_it = begin_gluon; gluon_it != end_gluon; ++gluon_it){ assert(gluon_it->type == pid::gluon); const auto g = to_HepLorentzVector(*gluon_it); const auto qip1 = qi - g; if(treat_as_extremal(*gluon_it)){ wt *= C2Lipatovots(qip1, qi, pa, pb)*C_A; } else{ wt *= C2Lipatovots(qip1, qi, pa, pb, p1, pn)*C_A; } qi = qip1; } return wt; } } // namespace anonymous std::vector<Particle> MatrixElement::tag_extremal_jet_partons( - std::array<Particle, 2> const & incoming, - std::vector<Particle> out_partons, bool check_momenta + Event const & ev, bool check_momenta ) const{ + auto out_partons = filter_partons(ev.outgoing()); if(!check_momenta){ for(auto & parton: out_partons){ parton.p.set_user_index(no_extremal_jet_idx); } return out_partons; } - fastjet::ClusterSequence cs(to_PseudoJet(out_partons), param_.jet_param.def); - const auto jets = sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt)); + // TODO: avoid reclustering + fastjet::ClusterSequence cs(to_PseudoJet(out_partons), ev.jet_def()); + const auto jets = sorted_by_rapidity(cs.inclusive_jets(ev.min_jet_pt())); assert(jets.size() >= 2); auto most_backward = begin(jets); auto most_forward = end(jets) - 1; // skip jets caused by unordered emission - if(has_unob_gluon(incoming, out_partons)){ + if(ev.type() == event_type::unob){ assert(jets.size() >= 3); ++most_backward; } - else if(has_unof_gluon(incoming, out_partons)){ + else if(ev.type() == event_type::unof){ assert(jets.size() >= 3); --most_forward; } const auto extremal_jet_indices = cs.particle_jet_indices( {*most_backward, *most_forward} ); assert(extremal_jet_indices.size() == out_partons.size()); for(size_t i = 0; i < out_partons.size(); ++i){ assert(HEJ::is_parton(out_partons[i])); const int idx = (extremal_jet_indices[i]>=0)? extremal_jet_idx: no_extremal_jet_idx; out_partons[i].p.set_user_index(idx); } return out_partons; } double MatrixElement::tree_kin_jets( - std::array<Particle, 2> const & incoming, - std::vector<Particle> partons, + Event const & ev, bool check_momenta ) const { - partons = tag_extremal_jet_partons(incoming, partons, check_momenta); - if(has_unob_gluon(incoming, partons) || has_unof_gluon(incoming, partons)){ + auto const & incoming = ev.incoming(); + const auto partons = tag_extremal_jet_partons(ev, check_momenta); + if(is_uno(ev.type())){ throw not_implemented("unordered emission not implemented for pure jets"); } const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); const auto p1 = to_HepLorentzVector(partons.front()); const auto pn = to_HepLorentzVector(partons.back()); return ME_current( incoming[0].type, incoming[1].type, pn, pb, p1, pa )/(4*(N_C*N_C - 1))*FKL_ladder_weight( begin(partons) + 1, end(partons) - 1, pa - p1, pa, pb, p1, pn ); } double MatrixElement::tree_kin_Higgs( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const { - if(has_uno_gluon(incoming, outgoing)){ - return tree_kin_Higgs_between(incoming, outgoing, check_momenta); + if(is_uno(ev.type())){ + return tree_kin_Higgs_between(ev, check_momenta); } - if(outgoing.front().type == pid::Higgs){ - return tree_kin_Higgs_first(incoming, outgoing, check_momenta); + if(ev.outgoing().front().type == pid::Higgs){ + return tree_kin_Higgs_first(ev, check_momenta); } - if(outgoing.back().type == pid::Higgs){ - return tree_kin_Higgs_last(incoming, outgoing, check_momenta); + if(ev.outgoing().back().type == pid::Higgs){ + return tree_kin_Higgs_last(ev, check_momenta); } - return tree_kin_Higgs_between(incoming, outgoing, check_momenta); + return tree_kin_Higgs_between(ev, check_momenta); } namespace { // Colour acceleration multipliers, for gluons see eq. (7) in arXiv:0910.5113 // TODO: code duplication with currents.cc double K_g(double p1minus, double paminus) { return 1./2.*(p1minus/paminus + paminus/p1minus)*(C_A - 1./C_A) + 1./C_A; } double K_g( CLHEP::HepLorentzVector const & pout, CLHEP::HepLorentzVector const & pin ) { if(pin.z() > 0) return K_g(pout.plus(), pin.plus()); return K_g(pout.minus(), pin.minus()); } double K( ParticleID type, CLHEP::HepLorentzVector const & pout, CLHEP::HepLorentzVector const & pin ) { if(type == ParticleID::gluon) return K_g(pout, pin); return C_F; } // Colour factor in strict MRK limit double K_MRK(ParticleID type) { return (type == ParticleID::gluon)?C_A:C_F; } } double MatrixElement::MH2_forwardH( CLHEP::HepLorentzVector p1out, CLHEP::HepLorentzVector p1in, ParticleID type2, CLHEP::HepLorentzVector p2out, CLHEP::HepLorentzVector p2in, CLHEP::HepLorentzVector pH, double t1, double t2 ) const{ ignore(p2out, p2in); const double shat = p1in.invariantMass2(p2in); // gluon case #ifdef HEJ_BUILD_WITH_QCDLOOP if(!param_.Higgs_coupling.use_impact_factors){ return K(type2, p2out, p2in)*C_A*1./(16*M_PI*M_PI)*t1/t2*MH2gq_outsideH( p1out, p1in, p2out, p2in, pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb )/(4*(N_C*N_C - 1)); } #endif return K_MRK(type2)/C_A*9./2.*shat*shat*( C2gHgp(p1in,p1out,pH) + C2gHgm(p1in,p1out,pH) )/(t1*t2); } double MatrixElement::tree_kin_Higgs_first( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const { + auto const & incoming = ev.incoming(); + auto const & outgoing = ev.outgoing(); assert(outgoing.front().type == pid::Higgs); if(outgoing[1].type != pid::gluon) { assert(incoming.front().type == outgoing[1].type); - return tree_kin_Higgs_between(incoming, outgoing, check_momenta); + return tree_kin_Higgs_between(ev, check_momenta); } const auto pH = to_HepLorentzVector(outgoing.front()); const auto partons = tag_extremal_jet_partons( - incoming, - std::vector<Particle>(begin(outgoing) + 1, end(outgoing)), - check_momenta + ev, check_momenta ); const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); const auto p1 = to_HepLorentzVector(partons.front()); const auto pn = to_HepLorentzVector(partons.back()); const auto q0 = pa - p1 - pH; const double t1 = q0.m2(); const double t2 = (pn - pb).m2(); return MH2_forwardH( p1, pa, incoming[1].type, pn, pb, pH, t1, t2 )*FKL_ladder_weight( begin(partons) + 1, end(partons) - 1, q0, pa, pb, p1, pn ); } double MatrixElement::tree_kin_Higgs_last( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const { + auto const & incoming = ev.incoming(); + auto const & outgoing = ev.outgoing(); assert(outgoing.back().type == pid::Higgs); if(outgoing[outgoing.size()-2].type != pid::gluon) { assert(incoming.back().type == outgoing[outgoing.size()-2].type); - return tree_kin_Higgs_between(incoming, outgoing, check_momenta); + return tree_kin_Higgs_between(ev, check_momenta); } const auto pH = to_HepLorentzVector(outgoing.back()); const auto partons = tag_extremal_jet_partons( - incoming, - std::vector<Particle>(begin(outgoing), end(outgoing) - 1), - check_momenta + ev, check_momenta ); const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); auto p1 = to_HepLorentzVector(partons.front()); const auto pn = to_HepLorentzVector(partons.back()); auto q0 = pa - p1; const double t1 = q0.m2(); const double t2 = (pn + pH - pb).m2(); return MH2_forwardH( pn, pb, incoming[0].type, p1, pa, pH, t2, t1 )*FKL_ladder_weight( begin(partons) + 1, end(partons) - 1, q0, pa, pb, p1, pn ); } double MatrixElement::tree_kin_Higgs_between( - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, + Event const & ev, bool check_momenta ) const { + using namespace event_type; + auto const & incoming = ev.incoming(); + auto const & outgoing = ev.outgoing(); + const auto the_Higgs = std::find_if( begin(outgoing), end(outgoing), [](Particle const & s){ return s.type == pid::Higgs; } ); assert(the_Higgs != end(outgoing)); const auto pH = to_HepLorentzVector(*the_Higgs); - std::vector<Particle> partons(begin(outgoing), the_Higgs); - partons.insert(end(partons), the_Higgs + 1, end(outgoing)); - partons = tag_extremal_jet_partons(incoming, partons, check_momenta); + const auto partons = tag_extremal_jet_partons(ev, check_momenta); const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); auto p1 = to_HepLorentzVector( - partons[has_unob_gluon(incoming, outgoing)?1:0] + partons[(ev.type() == unob)?1:0] ); auto pn = to_HepLorentzVector( - partons[partons.size() - (has_unof_gluon(incoming, outgoing)?2:1)] + partons[partons.size() - ((ev.type() == unof)?2:1)] ); auto first_after_Higgs = begin(partons) + (the_Higgs-begin(outgoing)); assert( (first_after_Higgs == end(partons) && ( - has_unob_gluon(incoming, outgoing) + (ev.type() == unob) || partons.back().type != pid::gluon )) || first_after_Higgs->rapidity() >= the_Higgs->rapidity() ); assert( (first_after_Higgs == begin(partons) && ( - has_unof_gluon(incoming, outgoing) + (ev.type() == unof) || partons.front().type != pid::gluon )) || (first_after_Higgs-1)->rapidity() <= the_Higgs->rapidity() ); // always treat the Higgs as if it were in between the extremal FKL partons if(first_after_Higgs == begin(partons)) ++first_after_Higgs; else if(first_after_Higgs == end(partons)) --first_after_Higgs; // t-channel momentum before Higgs auto qH = pa; for(auto parton_it = begin(partons); parton_it != first_after_Higgs; ++parton_it){ qH -= to_HepLorentzVector(*parton_it); } auto q0 = pa - p1; auto begin_ladder = begin(partons) + 1; auto end_ladder = end(partons) - 1; double current_factor; - if(has_unob_gluon(incoming, outgoing)){ + if(ev.type() == unob){ current_factor = C_A*C_A/2.*ME_Higgs_current_unob( // 1/2 = "K_uno" incoming[0].type, incoming[1].type, pn, pb, to_HepLorentzVector(partons.front()), p1, pa, qH, qH - pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb ); const auto p_unob = to_HepLorentzVector(partons.front()); q0 -= p_unob; p1 += p_unob; ++begin_ladder; } - else if(has_unof_gluon(incoming, outgoing)){ + else if(ev.type() == unof){ current_factor = C_A*C_A/2.*ME_Higgs_current_unof( // 1/2 = "K_uno" incoming[0].type, incoming[1].type, to_HepLorentzVector(partons.back()), pn, pb, p1, pa, qH, qH - pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb ); pn += to_HepLorentzVector(partons.back()); --end_ladder; } else{ current_factor = ME_Higgs_current( incoming[0].type, incoming[1].type, pn, pb, p1, pa, qH, qH - pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb ); } const double ladder_factor = FKL_ladder_weight( begin_ladder, first_after_Higgs, q0, pa, pb, p1, pn )*FKL_ladder_weight( first_after_Higgs, end_ladder, qH - pH, pa, pb, p1, pn ); return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor; } double MatrixElement::tree_param_partons( double alpha_s, double mur, std::vector<Particle> const & partons ) const{ const double gs2 = 4.*M_PI*alpha_s; double wt = std::pow(gs2, partons.size()); if(param_.log_correction){ // use alpha_s(q_perp), evolved to mur assert(partons.size() >= 2); for(size_t i = 1; i < partons.size()-1; ++i){ wt *= 1 + alpha_s/(2*M_PI)*beta0*log(mur/partons[i].p.perp()); } } return wt; } double MatrixElement::tree_param( - double mur, - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing + Event const & ev, + double mur ) const{ + assert(is_HEJ(ev.type())); + + const auto & out = ev.outgoing(); const double alpha_s = alpha_s_(mur); auto AWZH_boson = std::find_if( - begin(outgoing), end(outgoing), + begin(out), end(out), [](auto const & p){return is_AWZH_boson(p);} ); double AWZH_coupling = 1.; - if(AWZH_boson != end(outgoing)){ + if(AWZH_boson != end(out)){ switch(AWZH_boson->type){ case pid::Higgs: { AWZH_coupling = alpha_s*alpha_s; break; } // TODO case pid::Wp: case pid::Wm: case pid::photon: case pid::Z: default: - throw std::logic_error("Emission of boson of unsupported type"); + throw not_implemented("Emission of boson of unsupported type"); } } - if(has_unob_gluon(incoming, outgoing)){ + if(ev.type() == event_type::unob){ return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons( - alpha_s, mur, filter_partons({begin(outgoing) + 1, end(outgoing)}) + alpha_s, mur, filter_partons({begin(out) + 1, end(out)}) ); } - if(has_unof_gluon(incoming, outgoing)){ + if(ev.type() == event_type::unof){ return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons( - alpha_s, mur, filter_partons({begin(outgoing), end(outgoing) - 1}) + alpha_s, mur, filter_partons({begin(out), end(out) - 1}) ); } - return AWZH_coupling*tree_param_partons(alpha_s, mur, filter_partons(outgoing)); + return AWZH_coupling*tree_param_partons(alpha_s, mur, filter_partons(out)); } - double MatrixElement::tree( - double mur, - std::array<Particle, 2> const & incoming, - std::vector<Particle> const & outgoing, - bool check_momenta - ) const { - return tree_param(mur, incoming, outgoing)*tree_kin( - incoming, outgoing, check_momenta - ); - } } // namespace HEJ diff --git a/src/PhaseSpacePoint.cc b/src/PhaseSpacePoint.cc index d3f1850..ff32eb3 100644 --- a/src/PhaseSpacePoint.cc +++ b/src/PhaseSpacePoint.cc @@ -1,538 +1,537 @@ #include "HEJ/PhaseSpacePoint.hh" #include <random> #include <CLHEP/Random/Randomize.h> #include <CLHEP/Random/RanluxEngine.h> #include "HEJ/Constants.hh" #include "HEJ/resummation_jet.hh" -#include "HEJ/uno.hh" #include "HEJ/utility.hh" #include "HEJ/kinematics.hh" namespace HEJ{ namespace { constexpr int max_jet_user_idx = PhaseSpacePoint::ng_max; bool is_nonjet_parton(fastjet::PseudoJet const & parton){ assert(parton.user_index() != -1); return parton.user_index() > max_jet_user_idx; } bool is_jet_parton(fastjet::PseudoJet const & parton){ assert(parton.user_index() != -1); return parton.user_index() <= max_jet_user_idx; } // user indices for partons with extremal rapidity constexpr int unob_idx = -5; constexpr int unof_idx = -4; constexpr int backward_FKL_idx = -3; constexpr int forward_FKL_idx = -2; } namespace { double estimate_ng_mean(std::vector<fastjet::PseudoJet> const & Born_jets){ const double delta_y = Born_jets.back().rapidity() - Born_jets.front().rapidity(); assert(delta_y > 0); // Formula derived from fit in arXiv:1805.04446 (see Fig. 2) return 0.975052*delta_y; } } std::vector<fastjet::PseudoJet> PhaseSpacePoint::cluster_jets( std::vector<fastjet::PseudoJet> const & partons ) const{ fastjet::ClusterSequence cs(partons, param_.jet_param.def); return cs.inclusive_jets(param_.jet_param.min_pt); } bool PhaseSpacePoint::pass_resummation_cuts( std::vector<fastjet::PseudoJet> const & jets ) const{ return cluster_jets(jets).size() == jets.size(); } int PhaseSpacePoint::sample_ng(std::vector<fastjet::PseudoJet> const & Born_jets){ const double ng_mean = estimate_ng_mean(Born_jets); std::poisson_distribution<int> dist(ng_mean); const int ng = dist(ran_.get()); assert(ng >= 0); assert(ng < ng_max); weight_ *= std::tgamma(ng + 1)*std::exp(ng_mean)*std::pow(ng_mean, -ng); return ng; } void PhaseSpacePoint::copy_AWZH_boson_from(Event const & event){ auto const & from = event.outgoing(); const auto AWZH_boson = std::find_if( begin(from), end(from), [](Particle const & p){ return is_AWZH_boson(p); } ); if(AWZH_boson == end(from)) return; auto insertion_point = std::lower_bound( begin(outgoing_), end(outgoing_), *AWZH_boson, rapidity_less{} ); outgoing_.insert(insertion_point, *AWZH_boson); // copy decay products const int idx = std::distance(begin(from), AWZH_boson); assert(idx >= 0); const auto decay_it = event.decays().find(idx); if(decay_it != end(event.decays())){ const int new_idx = std::distance(begin(outgoing_), insertion_point); assert(new_idx >= 0); assert(outgoing_[new_idx].type == AWZH_boson->type); decays_.emplace(new_idx, decay_it->second); } assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{})); } PhaseSpacePoint::PhaseSpacePoint( Event const & ev, PhaseSpacePointConfig conf, HEJ::RNG & ran ): - unob_{has_unob_gluon(ev.incoming(), ev.outgoing())}, - unof_{!unob_ && has_unof_gluon(ev.incoming(), ev.outgoing())}, + unob_{ev.type() == event_type::unob}, + unof_{ev.type() == event_type::unof}, param_{std::move(conf)}, ran_{ran} { weight_ = 1; const auto Born_jets = sorted_by_rapidity(ev.jets()); const int ng = sample_ng(Born_jets); weight_ /= std::tgamma(ng + 1); const int ng_jets = sample_ng_jets(ng, Born_jets); std::vector<fastjet::PseudoJet> out_partons = gen_non_jet( ng - ng_jets, CMINPT, param_.jet_param.min_pt ); { const auto qperp = std::accumulate( begin(out_partons), end(out_partons), fastjet::PseudoJet{} ); const auto jets = reshuffle(Born_jets, qperp); if(weight_ == 0.) return; if(! pass_resummation_cuts(jets)){ weight_ = 0.; return; } std::vector<fastjet::PseudoJet> jet_partons = split(jets, ng_jets); if(weight_ == 0.) return; rescale_rapidities( out_partons, most_backward_FKL(jet_partons).rapidity(), most_forward_FKL(jet_partons).rapidity() ); if(! cluster_jets(out_partons).empty()){ weight_ = 0.; return; } std::sort(begin(out_partons), end(out_partons), rapidity_less{}); assert( std::is_sorted(begin(jet_partons), end(jet_partons), rapidity_less{}) ); const auto first_jet_parton = out_partons.insert( end(out_partons), begin(jet_partons), end(jet_partons) ); std::inplace_merge( begin(out_partons), first_jet_parton, end(out_partons), rapidity_less{} ); } if(! jets_ok(Born_jets, out_partons)){ weight_ = 0.; return; } weight_ *= phase_space_normalisation(Born_jets.size(), out_partons.size()); outgoing_.reserve(out_partons.size() + 1); // one slot for possible A, W, Z, H for(auto & p: out_partons){ outgoing_.emplace_back(Particle{pid::gluon, std::move(p)}); } most_backward_FKL(outgoing_).type = ev.incoming().front().type; most_forward_FKL(outgoing_).type = ev.incoming().back().type; copy_AWZH_boson_from(ev); assert(!outgoing_.empty()); reconstruct_incoming(ev.incoming()); } std::vector<fastjet::PseudoJet> PhaseSpacePoint::gen_non_jet( int count, double ptmin, double ptmax ){ // heuristic parameters for pt sampling const double ptpar = 1.3 + count/5.; const double temp1 = atan((ptmax - ptmin)/ptpar); std::vector<fastjet::PseudoJet> partons(count); for(size_t i = 0; i < (size_t) count; ++i){ const double r1 = ran_.get().flat(); const double pt = ptmin + ptpar*tan(r1*temp1); const double temp2 = cos(r1*temp1); const double phi = 2*M_PI*ran_.get().flat(); weight_ *= 2.0*M_PI*pt*ptpar*temp1/(temp2*temp2); // we don't know the allowed rapidity span yet, // set a random value to be rescaled later on const double y = ran_.get().flat(); partons[i].reset_PtYPhiM(pt, y, phi); // Set user index higher than any jet-parton index // in order to assert that these are not inside jets partons[i].set_user_index(i + 1 + ng_max); assert(ptmin-1e-5 <= partons[i].pt() && partons[i].pt() <= ptmax+1e-5); } assert(std::all_of(partons.cbegin(), partons.cend(), is_nonjet_parton)); return partons; } void PhaseSpacePoint::rescale_rapidities( std::vector<fastjet::PseudoJet> & partons, double ymin, double ymax ){ constexpr double ep = 1e-7; for(auto & parton: partons){ assert(0 <= parton.rapidity() && parton.rapidity() <= 1); const double dy = ymax - ymin - 2*ep; const double y = ymin + ep + dy*parton.rapidity(); parton.reset_momentum_PtYPhiM(parton.pt(), y, parton.phi()); weight_ *= dy; assert(ymin <= parton.rapidity() && parton.rapidity() <= ymax); } } namespace { template<typename T, typename... Rest> auto min(T const & a, T const & b, Rest&&... r) { using std::min; return min(a, min(b, std::forward<Rest>(r)...)); } } double PhaseSpacePoint::probability_in_jet( std::vector<fastjet::PseudoJet> const & Born_jets ) const{ assert(std::is_sorted(begin(Born_jets), end(Born_jets), rapidity_less{})); assert(Born_jets.size() >= 2); const double dy = Born_jets.back().rapidity() - Born_jets.front().rapidity(); const double R = param_.jet_param.def.R(); const int njets = Born_jets.size(); const double p_J_y_large = (njets-1)*R*R/(2.*dy); const double p_J_y0 = njets*R/M_PI; return min(p_J_y_large, p_J_y0, 1.); } int PhaseSpacePoint::sample_ng_jets( int ng, std::vector<fastjet::PseudoJet> const & Born_jets ){ const double p_J = probability_in_jet(Born_jets); std::binomial_distribution<> bin_dist(ng, p_J); const int ng_J = bin_dist(ran_.get()); weight_ *= std::pow(p_J, -ng_J)*std::pow(1 - p_J, ng_J - ng); return ng_J; } std::vector<fastjet::PseudoJet> PhaseSpacePoint::reshuffle( std::vector<fastjet::PseudoJet> const & Born_jets, fastjet::PseudoJet const & q ){ if(q == fastjet::PseudoJet{0, 0, 0, 0}) return Born_jets; const auto jets = resummation_jet_momenta(Born_jets, q); if(jets.empty()){ weight_ = 0; return {}; } // additional Jacobian to ensure Born integration over delta gives 1 weight_ *= resummation_jet_weight(Born_jets, q); return jets; } std::vector<int> PhaseSpacePoint::distribute_jet_partons( int ng_jets, std::vector<fastjet::PseudoJet> const & jets ){ size_t first_valid_jet = 0; size_t num_valid_jets = jets.size(); const double R_eff = 5./3.*param_.jet_param.def.R(); // if there is an unordered jet too far away from the FKL jets // then extra gluon constituents of the unordered jet would // violate the FKL rapidity ordering if(unob_ && jets[0].delta_R(jets[1]) > R_eff){ ++first_valid_jet; --num_valid_jets; } else if(unof_ && jets[jets.size()-1].delta_R(jets[jets.size()-2]) > R_eff){ --num_valid_jets; } std::vector<int> np(jets.size(), 1); for(int i = 0; i < ng_jets; ++i){ ++np[first_valid_jet + ran_.get().flat() * num_valid_jets]; } weight_ *= std::pow(num_valid_jets, ng_jets); return np; } #ifndef NDEBUG namespace{ bool tagged_FKL_backward( std::vector<fastjet::PseudoJet> const & jet_partons ){ return std::find_if( begin(jet_partons), end(jet_partons), [](fastjet::PseudoJet const & p){ return p.user_index() == backward_FKL_idx; } ) != end(jet_partons); } bool tagged_FKL_forward( std::vector<fastjet::PseudoJet> const & jet_partons ){ // the most forward FKL parton is most likely near the end of jet_partons; // start search from there return std::find_if( jet_partons.rbegin(), jet_partons.rend(), [](fastjet::PseudoJet const & p){ return p.user_index() == forward_FKL_idx; } ) != jet_partons.rend(); } bool tagged_FKL_extremal( std::vector<fastjet::PseudoJet> const & jet_partons ){ return tagged_FKL_backward(jet_partons) && tagged_FKL_forward(jet_partons); } } // namespace anonymous #endif std::vector<fastjet::PseudoJet> PhaseSpacePoint::split( std::vector<fastjet::PseudoJet> const & jets, int ng_jets ){ return split(jets, distribute_jet_partons(ng_jets, jets)); } bool PhaseSpacePoint::pass_extremal_cuts( fastjet::PseudoJet const & ext_parton, fastjet::PseudoJet const & jet ) const{ if(ext_parton.pt() < param_.min_extparton_pt) return false; return (ext_parton - jet).pt()/jet.pt() < param_.max_ext_soft_pt_fraction; } std::vector<fastjet::PseudoJet> PhaseSpacePoint::split( std::vector<fastjet::PseudoJet> const & jets, std::vector<int> const & np ){ assert(! jets.empty()); assert(jets.size() == np.size()); assert(pass_resummation_cuts(jets)); const size_t most_backward_FKL_idx = 0 + unob_; const size_t most_forward_FKL_idx = jets.size() - 1 - unof_; const auto & jet = param_.jet_param; const JetSplitter jet_splitter{jet.def, jet.min_pt, ran_}; std::vector<fastjet::PseudoJet> jet_partons; // randomly distribute jet gluons among jets for(size_t i = 0; i < jets.size(); ++i){ auto split_res = jet_splitter.split(jets[i], np[i]); weight_ *= split_res.weight; if(weight_ == 0) return {}; assert( std::all_of( begin(split_res.constituents), end(split_res.constituents), is_jet_parton ) ); const auto first_new_parton = jet_partons.insert( end(jet_partons), begin(split_res.constituents), end(split_res.constituents) ); // mark uno and extremal FKL emissions here so we can check // their position once all emissions are generated auto extremal = end(jet_partons); if((unob_ && i == 0) || i == most_backward_FKL_idx){ // unordered or FKL backward emission extremal = std::min_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index( (i == most_backward_FKL_idx)?backward_FKL_idx:unob_idx ); } else if((unof_ && i == jets.size() - 1) || i == most_forward_FKL_idx){ // unordered or FKL forward emission extremal = std::max_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index( (i == most_forward_FKL_idx)?forward_FKL_idx:unof_idx ); } if( extremal != end(jet_partons) && !pass_extremal_cuts(*extremal, jets[i]) ){ weight_ = 0; return {}; } } assert(tagged_FKL_extremal(jet_partons)); std::sort(begin(jet_partons), end(jet_partons), rapidity_less{}); if( !extremal_ok(jet_partons) || !split_preserved_jets(jets, jet_partons) ){ weight_ = 0.; return {}; } return jet_partons; } bool PhaseSpacePoint::extremal_ok( std::vector<fastjet::PseudoJet> const & partons ) const{ assert(std::is_sorted(begin(partons), end(partons), rapidity_less{})); if(unob_ && partons.front().user_index() != unob_idx) return false; if(unof_ && partons.back().user_index() != unof_idx) return false; return most_backward_FKL(partons).user_index() == backward_FKL_idx && most_forward_FKL(partons).user_index() == forward_FKL_idx; } bool PhaseSpacePoint::split_preserved_jets( std::vector<fastjet::PseudoJet> const & jets, std::vector<fastjet::PseudoJet> const & jet_partons ) const{ assert(std::is_sorted(begin(jets), end(jets), rapidity_less{})); const auto split_jets = sorted_by_rapidity(cluster_jets(jet_partons)); // this can happen if two overlapping jets // are both split into more than one parton if(split_jets.size() != jets.size()) return false; for(size_t i = 0; i < split_jets.size(); ++i){ // this can happen if there are two overlapping jets // and a parton is assigned to the "wrong" jet if(!nearby_ep(jets[i].rapidity(), split_jets[i].rapidity(), 1e-2)){ return false; } } return true; } template<class Particle> Particle const & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> const & partons ) const{ return partons[0 + unob_]; } template<class Particle> Particle const & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> const & partons ) const{ const size_t idx = partons.size() - 1 - unof_; assert(idx < partons.size()); return partons[idx]; } template<class Particle> Particle & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> & partons ) const{ return partons[0 + unob_]; } template<class Particle> Particle & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> & partons ) const{ const size_t idx = partons.size() - 1 - unof_; assert(idx < partons.size()); return partons[idx]; } namespace { bool contains_idx( fastjet::PseudoJet const & jet, fastjet::PseudoJet const & parton ){ auto const & constituents = jet.constituents(); const int idx = parton.user_index(); return std::find_if( begin(constituents), end(constituents), [idx](fastjet::PseudoJet const & con){return con.user_index() == idx;} ) != end(constituents); } } /** * final jet test: * - number of jets must match Born kinematics * - no partons designated as nonjet may end up inside jets * - all other outgoing partons *must* end up inside jets * - the extremal (in rapidity) partons must be inside the extremal jets * - rapidities must be the same (by construction) */ bool PhaseSpacePoint::jets_ok( std::vector<fastjet::PseudoJet> const & Born_jets, std::vector<fastjet::PseudoJet> const & partons ) const{ fastjet::ClusterSequence cs(partons, param_.jet_param.def); const auto jets = sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt)); if(jets.size() != Born_jets.size()) return false; int in_jet = 0; for(size_t i = 0; i < jets.size(); ++i){ assert(jets[i].has_constituents()); for(auto && parton: jets[i].constituents()){ if(is_nonjet_parton(parton)) return false; } in_jet += jets[i].constituents().size(); } const int expect_in_jet = std::count_if( partons.cbegin(), partons.cend(), is_jet_parton ); if(in_jet != expect_in_jet) return false; // note that PseudoJet::contains does not work here if(! ( contains_idx(most_backward_FKL(jets), most_backward_FKL(partons)) && contains_idx(most_forward_FKL(jets), most_forward_FKL(partons)) )) return false; if(unob_ && !contains_idx(jets.front(), partons.front())) return false; if(unof_ && !contains_idx(jets.back(), partons.back())) return false; for(size_t i = 0; i < jets.size(); ++i){ assert(nearby_ep(jets[i].rapidity(), Born_jets[i].rapidity(), 1e-2)); } return true; } void PhaseSpacePoint::reconstruct_incoming( std::array<Particle, 2> const & Born_incoming ){ std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_); for(size_t i = 0; i < incoming_.size(); ++i){ incoming_[i].type = Born_incoming[i].type; } assert(momentum_conserved()); } double PhaseSpacePoint::phase_space_normalisation( int num_Born_jets, int num_out_partons ) const{ return pow(16*pow(M_PI,3), num_Born_jets - num_out_partons); } bool PhaseSpacePoint::momentum_conserved() const{ fastjet::PseudoJet diff; for(auto const & in: incoming()) diff += in.p; const double norm = diff.E(); for(auto const & out: outgoing()) diff -= out.p; return nearby(diff, fastjet::PseudoJet{}, norm); } } //namespace HEJ diff --git a/src/Weights.cc b/src/Weights.cc new file mode 100644 index 0000000..9e0ee9c --- /dev/null +++ b/src/Weights.cc @@ -0,0 +1,41 @@ +#include "HEJ/Weights.hh" + +#include <stdexcept> + +namespace HEJ { + + Weights& Weights::operator*=(Weights const & other) { + if(other.variations.size() != variations.size()) { + throw std::invalid_argument{"Wrong number of weights"}; + } + central *= other.central; + for(std::size_t i = 0; i < variations.size(); ++i) { + variations[i] *= other.variations[i]; + } + return *this; + } + + Weights& Weights::operator*=(double factor) { + central *= factor; + for(auto & wt: variations) wt *= factor; + return *this; + } + + Weights& Weights::operator/=(Weights const & other) { + if(other.variations.size() != variations.size()) { + throw std::invalid_argument{"Wrong number of weights"}; + } + central /= other.central; + for(std::size_t i = 0; i < variations.size(); ++i) { + variations[i] /= other.variations[i]; + } + return *this; + } + + Weights& Weights::operator/=(double factor) { + central /= factor; + for(auto & wt: variations) wt /= factor; + return *this; + } + +} diff --git a/t/check_res.cc b/t/check_res.cc index 50b4325..9ef2ffd 100644 --- a/t/check_res.cc +++ b/t/check_res.cc @@ -1,97 +1,96 @@ #include <iostream> #include "LHEF/LHEF.h" #include "HEJ/stream.hh" #include "HEJ/EventReweighter.hh" #include "HEJ/Mixmax.hh" namespace{ const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4}; const fastjet::JetDefinition Born_jet_def{jet_def}; constexpr double Born_jetptmin = 30; constexpr double extpartonptmin = 30; constexpr double max_ext_soft_pt_fraction = std::numeric_limits<double>::infinity(); constexpr double jetptmin = 35; constexpr bool log_corr = false; using EventTreatment = HEJ::EventTreatment; using namespace HEJ::event_type; HEJ::EventTreatMap treat{ {no_2_jets, EventTreatment::discard}, {bad_final_state, EventTreatment::discard}, {nonHEJ, EventTreatment::discard}, {unof, EventTreatment::discard}, {unob, EventTreatment::discard}, {FKL, EventTreatment::reweight} }; }; int main(int argn, char** argv) { if(argn == 5 && std::string(argv[4]) == "uno"){ --argn; treat[unof] = EventTreatment::reweight; treat[unob] = EventTreatment::reweight; treat[FKL] = EventTreatment::discard; } if(argn != 4){ std::cerr << "Usage: check_res eventfile xsection tolerance [uno]"; return EXIT_FAILURE; } const double xsec_ref = std::stod(argv[2]); const double tolerance = std::stod(argv[3]); HEJ::istream in{argv[1]}; LHEF::Reader reader{in}; HEJ::PhaseSpacePointConfig psp_conf; psp_conf.jet_param = HEJ::JetParameters{jet_def, jetptmin}; psp_conf.min_extparton_pt = extpartonptmin; psp_conf.max_ext_soft_pt_fraction = max_ext_soft_pt_fraction; HEJ::MatrixElementConfig ME_conf; - ME_conf.jet_param = psp_conf.jet_param; ME_conf.log_correction = log_corr; ME_conf.Higgs_coupling = HEJ::HiggsCouplingSettings{}; HEJ::EventReweighterConfig conf; conf.psp_config = std::move(psp_conf); conf.ME_config = std::move(ME_conf); conf.jet_param = psp_conf.jet_param; conf.treat = treat; reader.readEvent(); const bool has_Higgs = std::find( begin(reader.hepeup.IDUP), end(reader.hepeup.IDUP), 25 ) != end(reader.hepeup.IDUP); const double mu = has_Higgs?125.:91.188; HEJ::ScaleGenerator scale_gen{ {{std::to_string(mu), HEJ::FixedScale{mu}}}, {}, 1. }; HEJ::Mixmax ran{}; HEJ::EventReweighter hej{reader.heprup, std::move(scale_gen), conf, ran}; double xsec = 0.; double xsec_err = 0.; do{ HEJ::Event ev{ HEJ::UnclusteredEvent{reader.hepeup}, Born_jet_def, Born_jetptmin }; auto resummed_events = hej.reweight(ev, 20); for(auto const & ev: resummed_events) { xsec += ev.central().weight; xsec_err += ev.central().weight*ev.central().weight; } } while(reader.readEvent()); xsec_err = std::sqrt(xsec_err); const double significance = std::abs(xsec - xsec_ref) / std::sqrt( xsec_err*xsec_err + tolerance*tolerance ); std::cout << xsec_ref << " +/- " << tolerance << " ~ " << xsec << " +- " << xsec_err << " => " << significance << " sigma\n"; if(significance > 3.){ std::cerr << "Cross section is off by over 3 sigma!\n"; return EXIT_FAILURE; } } diff --git a/t/test_ME_generic.cc b/t/test_ME_generic.cc index 8563223..33a9077 100644 --- a/t/test_ME_generic.cc +++ b/t/test_ME_generic.cc @@ -1,104 +1,104 @@ // Generic tester for the ME for a given set of PSP // reference weights and PSP (as LHE file) have to be given as _individual_ files #include <fstream> #include "LHEF/LHEF.h" #include "HEJ/MatrixElement.hh" #include "HEJ/Event.hh" #include "HEJ/YAMLreader.hh" #include "HEJ/stream.hh" constexpr double alpha_s = 0.118; -constexpr double mu = 1234.; // coupling fixed, mu doesn't matter constexpr double ep = 1e-6; -void dump(HEJ::UnclusteredEvent const & ev, HEJ::Config config){ +void dump(HEJ::Event const & ev){ { LHEF::Writer writer{std::cout}; std::cout << std::setprecision(6); - HEJ::Event out_ev{ev, config.resummation_jets.def, config.resummation_jets.min_pt}; - writer.hepeup = to_HEPEUP(std::move(out_ev), nullptr); + writer.hepeup = to_HEPEUP(std::move(ev), nullptr); writer.writeEvent(); } std::cout << "Rapidity ordering:\n"; - for(const auto & part: ev.outgoing){ + for(const auto & part: ev.outgoing()){ std::cout << std::setw(2) << part.type << ": "<< std::setw(7) << part.rapidity() << std::endl; } } int main(int argn, char** argv){ if(argn != 4 && argn != 5){ std::cerr << "\n# Usage:\n."<< argv[0] <<" config.yml ME_weights input_file.lhe\n\n"; return EXIT_FAILURE; } bool OUTPUT_MODE = false; if(argn == 5 && std::string("OUTPUT")==std::string(argv[4])) OUTPUT_MODE = true; const HEJ::Config config = HEJ::load_config(argv[1]); std::fstream wgt_file; if ( OUTPUT_MODE ) { std::cout << "_______________________USING OUTPUT MODE!_______________________" << std::endl; wgt_file.open(argv[2], std::fstream::out); wgt_file.precision(10); } else { wgt_file.open(argv[2], std::fstream::in); } HEJ::istream in{argv[3]}; LHEF::Reader reader{in}; HEJ::MatrixElement ME{ [](double){ return alpha_s; }, HEJ::to_MatrixElementConfig(config) }; double max_ratio = 0.; size_t idx_max_ratio = 0; - HEJ::UnclusteredEvent ev_max_ratio; + HEJ::Event ev_max_ratio; double av_ratio = 0; size_t i = 0; while(reader.readEvent()){ ++i; - HEJ::UnclusteredEvent event{reader.hepeup}; - const double our_ME = ME.tree( - mu, event.incoming, event.outgoing, true - ); + HEJ::Event event{ + HEJ::UnclusteredEvent{reader.hepeup}, + config.resummation_jets.def, + config.resummation_jets.min_pt + }; + const double our_ME = ME.tree(event, true).central; if ( OUTPUT_MODE ) { wgt_file << our_ME << std::endl; } else { std::string line; if(!std::getline(wgt_file,line)) break; const double ref_ME = std::stod(line); const double diff = std::abs(our_ME/ref_ME-1.); av_ratio+=diff; if( diff > max_ratio ) { max_ratio = diff; idx_max_ratio = i; ev_max_ratio = event; } if( diff > ep ){ size_t precision(std::cout.precision()); std::cout.precision(16); std::cout<< "Large difference in PSP " << i << "\nis: "<<our_ME << " should: " << ref_ME << " => difference: " << diff << std::endl; std::cout.precision(precision); - dump(event, config); + dump(event); return EXIT_FAILURE; } } } wgt_file.close(); if ( !OUTPUT_MODE ) { size_t precision(std::cout.precision()); std::cout.precision(16); std::cout << "Avg ratio after " << i << " PSP: " << av_ratio/i << std::endl; std::cout << "maximal ratio at " << idx_max_ratio << ": " << max_ratio << std::endl; std::cout.precision(precision); } return EXIT_SUCCESS; }