diff --git a/src/Event.cc b/src/Event.cc index be7cd2c..e650809 100644 --- a/src/Event.cc +++ b/src/Event.cc @@ -1,1485 +1,1496 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019-2023 * \copyright GPLv2 or later */ #include "HEJ/Event.hh" #include <algorithm> #include <boost/rational.hpp> #include <cassert> #include <cstdlib> #include <iomanip> #include <iterator> #include <memory> #include <numeric> #include <optional> #include <ostream> #include <sstream> #include <string> #include <utility> #include "HEJ/event_types.hh" #include "fastjet/ClusterSequence.hh" #include "fastjet/JetDefinition.hh" #include "fastjet/PseudoJet.hh" #include "LHEF/LHEF.h" #include "HEJ/Constants.hh" #include "HEJ/EWConstants.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/RNG.hh" #include "HEJ/exceptions.hh" #include "HEJ/LorentzVector.hh" #include "HEJ/utility.hh" namespace HEJ { /** * returns all EventTypes implemented in HEJ */ size_t implemented_types(std::vector<Particle> const & bosons){ using namespace event_type; // no bosons if(bosons.empty()) return FKL | UNO | QQBAR; // 1 boson if(bosons.size()== 1) { switch (bosons[0].type) { case ParticleID::Wp: case ParticleID::Wm: + case ParticleID::Z: case ParticleID::Z_photon_mix: return FKL | UNO | QQBAR; case ParticleID::h: return FKL | UNO; default: return non_resummable; } } // 2 bosons if(bosons.size() == 2) { // Resum only samesign W events if(bosons[0].type == ParticleID::Wp && bosons[1].type == ParticleID::Wp) { return FKL; } else if(bosons[0].type == ParticleID::Wm && bosons[1].type == ParticleID::Wm) { return FKL; } } return non_resummable; } namespace { using std::size_t; //! LHE status codes namespace lhe_status { enum Status: int { in = -1, decay = 2, out = 1, }; } using LHE_Status = lhe_status::Status; //! true if leptonic W decay bool valid_W_decay( int const w_charge, std::vector<Particle> const & decays ){ assert(std::abs(w_charge) == 1); if(decays.size() != 2) // no 1->2 decay return false; const int pidsum = decays[0].type + decays[1].type; if( std::abs(pidsum) != 1 || pidsum != w_charge ) // correct charge return false; // leptonic decay (only check first, second follows from pidsum) if( w_charge == 1 ) // W+ return is_charged_antilepton(decays[0]) || is_neutrino(decays[0]); // W- return is_charged_lepton(decays[0]) || is_antineutrino(decays[0]); } - //! true for Z decay to charged leptons + //! true for Z decay to leptons bool valid_Z_decay(std::vector<Particle> const & decays){ if(decays.size() != 2) // no 1->2 decay return false; if(decays[0].type != anti(decays[1].type)) { return false; } // leptonic decay (only check first, second follows from above) return is_anylepton(decays[0]); } + bool valid_Z_decay_to_neutrinos(std::vector<Particle> const & decays){ + return (decays.size() == 2) + && (decays[0].type == anti(decays[1].type)) + && is_anyneutrino(decays[0]); + } + //! true if supported decay bool valid_decay(std::vector<Particle> const & decays){ return valid_W_decay(+1, decays) || // Wp valid_W_decay(-1, decays) || // Wm valid_Z_decay( decays) // Z/gamma ; } /// @name helper functions to determine event type //@{ /** * \brief function which determines if type change is consistent with Wp emission. * @param in incoming Particle id * @param out outgoing Particle id * @param is_qqbar Current both incoming/both outgoing? * * \see is_Wm_Change */ bool is_Wp_Change(ParticleID in, ParticleID out, bool is_qqbar){ using namespace pid; if(!is_qqbar && (in==d_bar || in==u || in==s_bar || in==c)) return out == (in-1); if( is_qqbar && (in==d || in==u_bar || in==s || in==c_bar)) return out == -(in+1); return false; } /** * \brief function which determines if type change is consistent with Wm emission. * @param in incoming Particle id * @param out outgoing Particle id * @param is_qqbar Current both incoming/both outgoing? * * Ensures that change type of quark line is possible by a flavour changing * Wm emission. Allows checking of is_qqbar currents also. */ bool is_Wm_Change(ParticleID in, ParticleID out, bool is_qqbar){ using namespace pid; if(!is_qqbar && (in==d || in==u_bar || in==s || in==c_bar)) return out == (in+1); if( is_qqbar && (in==d_bar || in==u || in==s_bar || in==c)) return out == -(in-1); return false; } /** * \brief checks if particle type remains same from incoming to outgoing * @param in incoming Particle * @param out outgoing Particle * @param is_qqbar Current both incoming/outgoing? */ bool no_flavour_change(ParticleID in, ParticleID out, bool is_qqbar){ const int qqbarCurrent = is_qqbar?-1:1; if(std::abs(in)<=pid::top || in==pid::gluon) return (in==out*qqbarCurrent); return false; } bool is_gluon_to_Higgs(const ParticleID in, const ParticleID out) { return in == pid::gluon && out == pid::Higgs; } /** * \brief check if we have a valid Impact factor * @param in incoming Particle * @param out outgoing Particle * @param is_qqbar Current both incoming/outgoing? * @param W_change returns +1 if Wp, -1 if Wm, else 0 */ bool is_valid_impact_factor( ParticleID in, ParticleID out, bool is_qqbar, int & W_change ){ if( no_flavour_change(in, out, is_qqbar) || is_gluon_to_Higgs(in, out)) { return true; } if( is_Wp_Change(in, out, is_qqbar) ) { W_change+=1; return true; } if( is_Wm_Change(in, out, is_qqbar) ) { W_change-=1; return true; } return false; } bool is_extremal_higgs_off_quark( const ParticleID in, const ParticleID extremal_out, const ParticleID out ) { return in == out && extremal_out == pid::higgs && is_anyquark(in); } //! Returns all possible classifications from the impact factors // the beginning points are changed s.t. after the the classification they // point to the beginning of the (potential) FKL chain // sets W_change: + if Wp change // 0 if no change // - if Wm change // This function can be used with forward & backwards iterators template<class OutIterator> size_t possible_impact_factors( ParticleID incoming_id, // incoming OutIterator & begin_out, OutIterator const & end_out, // outgoing int & W_change, std::vector<Particle> const & boson, bool const backward // backward? ){ using namespace event_type; if(begin_out == end_out) return non_resummable; // keep track of all states that we don't test size_t not_tested = qqbar_mid; if(backward) not_tested |= unof | qqbar_exf; else not_tested |= unob | qqbar_exb; // Is this LL current? if( is_valid_impact_factor(incoming_id, begin_out->type, false, W_change) ){ ++begin_out; return not_tested | FKL; } // q -> H q and qbar -> H qbar are technically not LL, // but we treat them as such anyway const auto next = std::next(begin_out); if( // first ensure that the next particle is not part of the *other* impact factor next != end_out && is_extremal_higgs_off_quark(incoming_id, begin_out->type, next->type) ) { std::advance(begin_out, 2); return not_tested | FKL; } // or NLL current? // -> needs two partons in two different jets if( std::distance(begin_out, end_out)>=2 ){ auto next = std::next(begin_out); // Is this unordered emisson? if( incoming_id!=pid::gluon && begin_out->type==pid::gluon ){ if( is_valid_impact_factor( incoming_id, next->type, false, W_change ) ){ // veto Higgs inside uno assert(next!=end_out); if( !boson.empty() && boson.front().type == ParticleID::h ){ if( (backward && boson.front().rapidity() < next->rapidity()) ||(!backward && boson.front().rapidity() > next->rapidity())) return non_resummable; } begin_out = std::next(next); return not_tested | (backward?unob:unof); } } // Is this QQbar? else if( incoming_id==pid::gluon ){ if( is_valid_impact_factor( begin_out->type, next->type, true, W_change ) ){ // veto Higgs inside qqbar assert(next!=end_out); if( !boson.empty() && boson.front().type == ParticleID::h ){ if( (backward && boson.front().rapidity() < next->rapidity()) ||(!backward && boson.front().rapidity() > next->rapidity())) return non_resummable; } begin_out = std::next(next); return not_tested | (backward?qqbar_exb:qqbar_exf); } } } return non_resummable; } //! Returns all possible classifications from central emissions // the beginning points are changed s.t. after the the classification they // point to the end of the emission chain // sets W_change: + if Wp change // 0 if no change // - if Wm change template<class OutIterator> size_t possible_central( OutIterator & begin_out, OutIterator const & end_out, int & W_change, std::vector<Particle> const & boson ){ using namespace event_type; // if we already passed the central chain, // then it is not a valid all-order state if(std::distance(begin_out, end_out) < 0) return non_resummable; // keep track of all states that we don't test size_t possible = UNO | EXTREMAL_QQBAR; // Find the first quark or antiquark emission begin_out = std::find_if( begin_out, end_out, [](Particle const & p) { return is_anyquark(p); } ); // end of chain -> FKL if( begin_out==end_out ){ return possible | FKL; } // is this a qqbar-pair? // needs two partons in two separate jets auto next = std::next(begin_out); if( next != end_out && is_valid_impact_factor(begin_out->type, next->type, true, W_change) ){ // veto Higgs inside qqbar if( !boson.empty() && boson.front().type == ParticleID::h && boson.front().rapidity() > begin_out->rapidity() && boson.front().rapidity() < next->rapidity() ){ return non_resummable; } begin_out = std::next(next); // remaining chain should be pure FKL (gluon or higgs) if(std::any_of( begin_out, end_out, [](Particle const & p) { return is_anyquark(p); } )) { return non_resummable; } return possible | qqbar_mid; } return non_resummable; } namespace { bool is_parton_or_higgs(Particle const & p) { return is_parton(p) || p.type == pid::higgs; } bool decay_conserves_charge( Particle const &parent, std::vector<Particle> const &products ) { auto charge_diff = charge(parent); for (auto const &p : products) { charge_diff -= charge(p); } return charge_diff == 0; } bool charge_conserved(Event const &ev) { boost::rational<int> charge_diff{0}; for (auto const &in : ev.incoming()) { charge_diff += charge(in); } for (auto const &out : ev.outgoing()) { charge_diff -= charge(out); } if (charge_diff != 0) return false; return std::all_of( ev.decays().begin(), ev.decays().end(), [&ev](auto const &decay) { auto const &[parent, products] = decay; return decay_conserves_charge(ev.outgoing()[parent], products); }); } bool decay_conserves_momentum( Particle const &parent, std::vector<Particle> const &products, const double tolerance ) { fastjet::PseudoJet total_p; for (auto const &p : products) total_p += p.p; return nearby_ep(parent.p, total_p, tolerance); } bool event_momentum_conserved(Event const &ev, const double tolerance) { return momentum_conserved(ev, tolerance) && std::all_of( ev.decays().begin(), ev.decays().end(), [&ev, tolerance](auto const &decay) { auto const &[parent, products] = decay; return decay_conserves_momentum( ev.outgoing()[parent], products, tolerance); }); } template <class Container> bool massless_particles_onshell( Container const &c, const double tolerance ) { return std::all_of( c.begin(), c.end(), [tolerance](Particle const & p) { return is_massive(p) || p.m() < tolerance * std::max(p.E(), 1.0); } ); } bool all_massless_particles_onshell( Event const &ev, const double tolerance ) { return massless_particles_onshell(ev.incoming(), tolerance) && massless_particles_onshell(ev.outgoing(), tolerance) && std::all_of( ev.decays().begin(), ev.decays().end(), [tolerance](auto const &decay) { return massless_particles_onshell(decay.second, tolerance); }); } bool no_incoming_pt(Event const &ev, const double tolerance) { return std::all_of( ev.incoming().cbegin(), ev.incoming().end(), [tolerance](Particle const &p) { return std::abs(p.px()) < tolerance && std::abs(p.py()) < tolerance; }); } bool is_invalid(Event const &ev, const double tolerance) { return !( charge_conserved(ev) && event_momentum_conserved(ev, tolerance) && no_incoming_pt(ev, tolerance) && all_massless_particles_onshell(ev, tolerance) ); } // TODO: choose reasonable value or make configurable constexpr double TOLERANCE = 1e-3; bool incoming_are_partons(Event const &ev) { return std::all_of( ev.incoming().begin(), ev.incoming().end(), [](Particle const &p) { return is_parton(p); } ); } bool known_outgoing(Event const &ev) { return std::all_of( ev.outgoing().begin(), ev.outgoing().end(), [](Particle const &p) { return is_parton(p) || p.type == pid::Higgs || std::abs(p.type) == pid::Wp - || p.type == pid::Z_photon_mix; + || p.type == pid::Z_photon_mix + || p.type == pid::Z; }); } bool is_same_sign_WW(std::vector<Particle> const &particles) { return particles.size() == 2 && std::abs(particles.front().type) == pid::Wp && particles.front().type == particles.back().type; } bool all_W_Zphoton_decay(Event const &ev) { auto const &out = ev.outgoing(); for (std::size_t i = 0; i < out.size(); ++i) { - if ( - (std::abs(out[i].type) == pid::Wp || out[i].type == pid::Z_photon_mix) - && ev.decays().count(i) == 0 - ) { + const bool is_weak_boson = std::abs(out[i].type) == pid::Wp + || out[i].type == pid::Z_photon_mix + || out[i].type == pid::Z; + if (is_weak_boson && ev.decays().count(i) == 0) { return false; } } return true; } bool decay_known( Particle const &parent, std::vector<Particle> const &products ) { if (parent.type == pid::Higgs) return true; if (parent.type == pid::Z_photon_mix) return valid_Z_decay(products); + // If there is a Z we only allow decay to neutrinos, + // otherwise we are missing the Z/photon interference! + if (parent.type == pid::Z) return valid_Z_decay_to_neutrinos(products); if (std::abs(parent.type) == pid::Wp) { assert(charge(parent).denominator() == 1); return valid_W_decay(charge(parent).numerator(), products); } return false; } bool all_decays_known(Event const &ev) { return std::all_of( ev.decays().begin(), ev.decays().end(), [&ev](auto const &decay) { auto const &[parent, products] = decay; return decay_known(ev.outgoing()[parent], products); }); } bool is_known_process_type(Event const &ev) { if (!incoming_are_partons(ev)) return false; if (!known_outgoing(ev)) return false; if (!all_W_Zphoton_decay(ev)) return false; if (!all_decays_known(ev)) return false; auto const bosons = filter_AWZH_bosons(ev.outgoing()); if (bosons.size() > 2) return false; if (bosons.size() == 2 && !is_same_sign_WW(bosons)) { return false; } if (bosons.size() == 1 && bosons.front().type == pid::Higgs) { return !ev.jets().empty(); } return ev.jets().size() >= 2; } } /** * \brief Checks for all event types * @param ev Event * @returns Event Type * */ event_type::EventType classify(Event const & ev){ using namespace event_type; if(is_invalid(ev, TOLERANCE)) return invalid; if(! is_known_process_type(ev)) return unknown; // initialise variables auto const & in = ev.incoming(); // range for current checks auto begin_out = boost::make_filter_iterator( is_parton_or_higgs, cbegin(ev.outgoing()), cend(ev.outgoing()) ); auto rbegin_out = std::make_reverse_iterator( boost::make_filter_iterator( is_parton_or_higgs, cend(ev.outgoing()), cend(ev.outgoing()) ) ); assert(std::distance(begin(in), end(in)) == 2); assert(std::distance(begin_out, rbegin_out.base()) >= 2); assert(std::is_sorted(begin_out, rbegin_out.base(), rapidity_less{})); auto const bosons{ filter_AWZH_bosons(ev.outgoing()) }; // keep track of potential W couplings, at the end the sum should be 0 int remaining_Wp = 0; int remaining_Wm = 0; for(auto const & boson : bosons){ if(boson.type == ParticleID::Wp) ++remaining_Wp; else if(boson.type == ParticleID::Wm) ++remaining_Wm; } size_t final_type = VALID; // check forward impact factor int W_change = 0; final_type &= possible_impact_factors( in.front().type, begin_out, rbegin_out.base(), W_change, bosons, true ); if( final_type == non_resummable ) return non_resummable; if(W_change>0) remaining_Wp-=W_change; else if(W_change<0) remaining_Wm+=W_change; // check backward impact factor W_change = 0; final_type &= possible_impact_factors( in.back().type, rbegin_out, std::make_reverse_iterator(begin_out), W_change, bosons, false ); if( final_type == non_resummable ) return non_resummable; if(W_change>0) remaining_Wp-=W_change; else if(W_change<0) remaining_Wm+=W_change; // check central emissions W_change = 0; final_type &= possible_central( begin_out, rbegin_out.base(), W_change, bosons ); if( final_type == non_resummable ) return non_resummable; if(W_change>0) remaining_Wp-=W_change; else if(W_change<0) remaining_Wm+=W_change; // Check whether the right number of Ws are present if( remaining_Wp != 0 || remaining_Wm != 0 ) return non_resummable; // result has to be unique if( (final_type & (final_type-1)) != 0) return non_resummable; // check that each sub processes is implemented // (has to be done at the end) if( (final_type & ~implemented_types(bosons)) != 0 ) return non_resummable; return static_cast<EventType>(final_type); } //@} Particle extract_particle(LHEF::HEPEUP const & hepeup, size_t i){ auto id = static_cast<ParticleID>(hepeup.IDUP[i]); auto colour = is_parton(id)?hepeup.ICOLUP[i]:std::optional<Colour>(); return { id, { hepeup.PUP[i][0], hepeup.PUP[i][1], hepeup.PUP[i][2], hepeup.PUP[i][3] }, colour }; } 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 Event::EventData::EventData(LHEF::HEPEUP const & hepeup){ parameters.central = EventParameters{ hepeup.scales.mur, hepeup.scales.muf, hepeup.XWGTUP }; 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] == LHE_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)); } // 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)); } } void Event::EventData::sort(){ // sort particles std::sort( begin(incoming), end(incoming), [](Particle const & o1, Particle const & o2){return o1.p.pz()<o2.p.pz();} ); auto old_outgoing = std::move(outgoing); std::vector<size_t> idx(old_outgoing.size()); std::iota(idx.begin(), idx.end(), 0); std::sort(idx.begin(), idx.end(), [&old_outgoing](size_t i, size_t j){ return old_outgoing[i].rapidity() < old_outgoing[j].rapidity(); }); outgoing.clear(); outgoing.reserve(old_outgoing.size()); for(size_t i: idx) { outgoing.emplace_back(std::move(old_outgoing[i])); } // find decays again if(!decays.empty()){ auto old_decays = std::move(decays); decays.clear(); for(size_t i=0; i<idx.size(); ++i) { auto decay = old_decays.find(idx[i]); if(decay != old_decays.end()) decays.emplace(i, std::move(decay->second)); } assert(old_decays.size() == decays.size()); } } namespace { // use valid_X_decay to determine boson type ParticleID reconstruct_type(std::vector<Particle> const & progeny) { if(valid_W_decay(+1, progeny)) { return ParticleID::Wp; } if(valid_W_decay(-1, progeny)) { return ParticleID::Wm; } if(valid_Z_decay(progeny)) { return ParticleID::Z_photon_mix; } throw not_implemented{ "final state with decay X -> " + name(progeny[0].type) + " + " + name(progeny[1].type) }; } // reconstruct particle with explicit ParticleID Particle reconstruct_boson( std::vector<Particle> const & progeny, ParticleID const & type ) { Particle progenitor; progenitor.p = progeny[0].p + progeny[1].p; progenitor.type = type; return progenitor; } // reconstruct via call to reconstruct_type Particle reconstruct_boson(std::vector<Particle> const & progeny) { Particle progenitor {reconstruct_boson(progeny, reconstruct_type(progeny))}; assert(is_AWZH_boson(progenitor)); return progenitor; } using GroupedParticles = std::vector<std::vector<Particle> >; using Decay = std::pair<Particle, std::vector<Particle> >; using Decays = std::vector<Decay>; // return groups of reconstructable progeny std::vector<GroupedParticles> group_progeny(std::vector<Particle> & leptons) { /** Warning: The partition in to charged/neutral leptons is valid ONLY for WW. **/ assert(leptons.size() == 4); auto const begin_neutrino = std::partition( begin(leptons), end(leptons), [](Particle const & p) {return !is_anyneutrino(p);} ); std::vector<Particle> neutrinos (begin_neutrino, end(leptons)); leptons.erase(begin_neutrino, end(leptons)); if(leptons.size() != 2 || neutrinos.size() != 2) { return {}; } assert(leptons.size() == 2 && neutrinos.size() == 2); std::vector<GroupedParticles> valid_groupings; GroupedParticles candidate_grouping{{leptons[0], neutrinos[0]}, {leptons[1], neutrinos[1]}}; if(valid_decay(candidate_grouping.front()) && valid_decay(candidate_grouping.back())) { valid_groupings.emplace_back(std::move(candidate_grouping)); } candidate_grouping = {{leptons[1], neutrinos[0]}, {leptons[0], neutrinos[1]}}; if(valid_decay(candidate_grouping.front()) && valid_decay(candidate_grouping.back())) { valid_groupings.emplace_back(std::move(candidate_grouping)); } return valid_groupings; } // 'best' decay ordering measure double decay_measure(const Particle& reconstructed, EWConstants const & params) { ParticleProperties ref = params.prop(reconstructed.type); return std::abs(reconstructed.p.m() - ref.mass); } // decay_measure accumulated over decays double decay_measure(Decays const & decays, EWConstants const & params) { return std::accumulate( cbegin(decays), cend(decays), 0., [¶ms] (double dm, Decay const & decay) -> double { return dm + decay_measure(decay.first, params); } ); } // select best combination of decays for the event Decays select_decays ( std::vector<Particle> & leptons, EWConstants const & ew_parameters ) { std::vector<GroupedParticles> groupings = group_progeny(leptons); std::vector<Decays> valid_decays; valid_decays.reserve(groupings.size()); // Reconstruct all groupings for(GroupedParticles const & group : groupings) { Decays decays; for(auto const & progeny : group) { decays.emplace_back(make_pair(reconstruct_boson(progeny), progeny)); } valid_decays.emplace_back(decays); } if (valid_decays.empty()) { throw not_implemented{"No supported intermediate reconstruction available"}; } if (valid_decays.size() == 1) { return valid_decays[0]; } // select decay with smallest decay_measure auto selected = std::min_element(cbegin(valid_decays), cend(valid_decays), [&ew_parameters] (auto const & d1, auto const & d2) -> bool { return decay_measure(d1, ew_parameters) < decay_measure(d2, ew_parameters); } ); return *selected; } } // namespace void Event::EventData::reconstruct_intermediate(EWConstants const & ew_parameters) { auto const begin_leptons = std::partition( begin(outgoing), end(outgoing), [](Particle const & p) {return !is_anylepton(p);} ); std::vector<Particle> leptons(begin_leptons, end(outgoing)); outgoing.erase(begin_leptons, end(outgoing)); if(leptons.empty()) { return; } // nothing to do if(leptons.size() == 2) { outgoing.emplace_back(reconstruct_boson(leptons)); std::sort(begin(leptons), end(leptons), type_less{}); decays.emplace(outgoing.size()-1, std::move(leptons)); } else if(leptons.size() == 4) { Decays valid_decays = select_decays(leptons, ew_parameters); for(auto &decay : valid_decays) { outgoing.emplace_back(decay.first); std::sort(begin(decay.second), end(decay.second), type_less{}); decays.emplace(outgoing.size()-1, std::move(decay.second)); } } else { throw not_implemented { std::to_string(leptons.size()) + " leptons in the final state" }; } } namespace { void repair_momentum(fastjet::PseudoJet & p, const double tolerance) { if(p.e() > 0. && p.m2() != 0. && (p.m2() < tolerance * tolerance)) { const double rescale = std::sqrt(p.modp() / p.e()); const double e = p.e() * rescale; const double px = p.px() / rescale; const double py = p.py() / rescale; const double pz = p.pz() / rescale; p.reset(px, py, pz, e); } } } void Event::EventData::repair_momenta(const double tolerance) { for(auto & in: incoming) { if(is_massless(in)) { const double px = (std::abs(in.px()) < tolerance)?0.:in.px(); const double py = (std::abs(in.py()) < tolerance)?0.:in.py(); in.p.reset(px, py, in.p.pz(), in.p.e()); repair_momentum(in.p, tolerance); } } for(auto & out: outgoing) { if(is_massless(out)) repair_momentum(out.p, tolerance); } for(auto & decay: decays) { for(auto & out: decay.second) { if(is_massless(out)) repair_momentum(out.p, tolerance); } } } Event Event::EventData::cluster( fastjet::JetDefinition const & jet_def, double const min_jet_pt ){ sort(); return Event{ std::move(incoming), std::move(outgoing), std::move(decays), std::move(parameters), jet_def, min_jet_pt }; } Event::Event( std::array<Particle, 2> && incoming, std::vector<Particle> && outgoing, std::unordered_map<size_t, std::vector<Particle>> && decays, Parameters<EventParameters> && parameters, fastjet::JetDefinition const & jet_def, double const min_jet_pt ): incoming_{std::move(incoming)}, outgoing_{std::move(outgoing)}, decays_{std::move(decays)}, parameters_{std::move(parameters)}, cs_{ to_PseudoJet( filter_partons(outgoing_) ), jet_def }, min_jet_pt_{min_jet_pt} { jets_ = sorted_by_rapidity(cs_.inclusive_jets(min_jet_pt_)); assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{})); type_ = classify(*this); } namespace { //! check that Particles have a reasonable colour bool correct_colour(Particle const & part){ ParticleID id{ part.type }; if(!is_parton(id)) return !part.colour; if(!part.colour) return false; Colour const & col{ *part.colour }; if(is_quark(id)) return col.first != 0 && col.second == 0; if(is_antiquark(id)) return col.first == 0 && col.second != 0; assert(id==ParticleID::gluon); return col.first != 0 && col.second != 0 && col.first != col.second; } //! Connect parton to t-channel colour line & update the line //! returns false if connection not possible template<class OutIterator> bool try_connect_t(OutIterator const & it_part, Colour & line_colour){ if( line_colour.first == it_part->colour->second ){ line_colour.first = it_part->colour->first; return true; } if( line_colour.second == it_part->colour->first ){ line_colour.second = it_part->colour->second; return true; } return false; } //! Connect parton to u-channel colour line & update the line //! returns false if connection not possible template<class OutIterator> bool try_connect_u(OutIterator & it_part, Colour & line_colour){ auto it_next = std::next(it_part); if( try_connect_t(it_next, line_colour) && try_connect_t(it_part, line_colour) ){ it_part=it_next; return true; } return false; } } // namespace bool Event::is_leading_colour() const { if( !correct_colour(incoming()[0]) || !correct_colour(incoming()[1]) ) return false; Colour line_colour = *incoming()[0].colour; std::swap(line_colour.first, line_colour.second); // reasonable colour if(!std::all_of(outgoing().cbegin(), outgoing().cend(), correct_colour)) return false; for(auto it_part = cbegin_partons(); it_part!=cend_partons(); ++it_part){ switch (type()) { case event_type::FKL: if( !try_connect_t(it_part, line_colour) ) return false; break; case event_type::unob: case event_type::qqbar_exb: { if( !try_connect_t(it_part, line_colour) // u-channel only allowed at impact factor && (std::distance(cbegin_partons(), it_part)!=0 || !try_connect_u(it_part, line_colour))) return false; break; } case event_type::unof: case event_type::qqbar_exf: { if( !try_connect_t(it_part, line_colour) // u-channel only allowed at impact factor && (std::distance(it_part, cend_partons())!=2 || !try_connect_u(it_part, line_colour))) return false; break; } case event_type::qqbar_mid:{ auto it_next = std::next(it_part); if( !try_connect_t(it_part, line_colour) // u-channel only allowed at q-qbar/qbar-q pair && ( ( !(is_quark(*it_part) && is_antiquark(*it_next)) && !(is_antiquark(*it_part) && is_quark(*it_next))) || !try_connect_u(it_part, line_colour)) ) return false; break; } default: throw std::logic_error{"unreachable"}; } // no colour singlet exchange/disconnected diagram if(line_colour.first == line_colour.second) return false; } return (incoming()[1].colour->first == line_colour.first) && (incoming()[1].colour->second == line_colour.second); } namespace { //! connect incoming Particle to colour flow void connect_incoming(Particle & in, int & colour, int & anti_colour){ in.colour = std::make_pair(anti_colour, colour); // gluon if(in.type == pid::gluon) return; if(in.type > 0){ // quark assert(is_quark(in)); in.colour->second = 0; colour*=-1; return; } // anti-quark assert(is_antiquark(in)); in.colour->first = 0; anti_colour*=-1; } //! connect outgoing Particle to t-channel colour flow template<class OutIterator> void connect_tchannel( OutIterator & it_part, int & colour, int & anti_colour, RNG & ran ){ assert(colour>0 || anti_colour>0); if(it_part->type == ParticleID::gluon){ // gluon if(colour>0 && anti_colour>0){ // on g line => connect to colour OR anti-colour (random) if(ran.flat() < 0.5){ it_part->colour = std::make_pair(colour+2,colour); colour+=2; } else { it_part->colour = std::make_pair(anti_colour, anti_colour+2); anti_colour+=2; } } else if(colour > 0){ // on q line => connect to available colour it_part->colour = std::make_pair(colour+2, colour); colour+=2; } else { assert(colour<0 && anti_colour>0); // on qbar line => connect to available anti-colour it_part->colour = std::make_pair(anti_colour, anti_colour+2); anti_colour+=2; } } else if(is_quark(*it_part)) { // quark assert(anti_colour>0); if(colour>0){ // on g line => connect and remove anti-colour it_part->colour = std::make_pair(anti_colour, 0); anti_colour+=2; anti_colour*=-1; } else { // on qbar line => new colour colour*=-1; it_part->colour = std::make_pair(colour, 0); } } else if(is_antiquark(*it_part)) { // anti-quark assert(colour>0); if(anti_colour>0){ // on g line => connect and remove colour it_part->colour = std::make_pair(0, colour); colour+=2; colour*=-1; } else { // on q line => new anti-colour anti_colour*=-1; it_part->colour = std::make_pair(0, anti_colour); } } else { // not a parton assert(!is_parton(*it_part)); it_part->colour = {}; } } //! connect to t- or u-channel colour flow template<class OutIterator> void connect_utchannel( OutIterator & it_part, int & colour, int & anti_colour, RNG & ran ){ OutIterator it_first = it_part++; if(ran.flat()<.5) {// t-channel connect_tchannel(it_first, colour, anti_colour, ran); connect_tchannel(it_part, colour, anti_colour, ran); } else { // u-channel connect_tchannel(it_part, colour, anti_colour, ran); connect_tchannel(it_first, colour, anti_colour, ran); } } } // namespace bool Event::generate_colours(RNG & ran){ // generate only for HEJ events if(!event_type::is_resummable(type())) return false; assert(std::is_sorted( begin(outgoing()), end(outgoing()), rapidity_less{})); assert(incoming()[0].pz() < incoming()[1].pz()); // positive (anti-)colour -> can connect // negative (anti-)colour -> not available/used up by (anti-)quark int colour = COLOUR_OFFSET; int anti_colour = colour+1; // initialise first connect_incoming(incoming_[0], colour, anti_colour); // reset outgoing colours std::for_each(outgoing_.begin(), outgoing_.end(), [](Particle & part){ part.colour = {};}); for(auto it_part = begin_partons(); it_part!=end_partons(); ++it_part){ switch (type()) { // subleading can connect to t- or u-channel case event_type::unob: case event_type::qqbar_exb: { if( std::distance(begin_partons(), it_part)==0) connect_utchannel(it_part, colour, anti_colour, ran); else connect_tchannel(it_part, colour, anti_colour, ran); break; } case event_type::unof: case event_type::qqbar_exf: { if( std::distance(it_part, end_partons())==2) connect_utchannel(it_part, colour, anti_colour, ran); else connect_tchannel(it_part, colour, anti_colour, ran); break; } case event_type::qqbar_mid:{ auto it_next = std::next(it_part); if( std::distance(begin_partons(), it_part)>0 && std::distance(it_part, end_partons())>2 && ( (is_quark(*it_part) && is_antiquark(*it_next)) || (is_antiquark(*it_part) && is_quark(*it_next)) ) ) connect_utchannel(it_part, colour, anti_colour, ran); else connect_tchannel(it_part, colour, anti_colour, ran); break; } default: // rest has to be t-channel connect_tchannel(it_part, colour, anti_colour, ran); } } // Connect last connect_incoming(incoming_[1], anti_colour, colour); assert(is_leading_colour()); return true; } // generate_colours namespace { bool valid_parton( std::vector<fastjet::PseudoJet> const & jets, Particle const & parton, int const idx, double const soft_pt_regulator ){ // TODO code overlap with PhaseSpacePoint::pass_extremal_cuts if(idx<0) return false; assert(static_cast<int>(jets.size())>=idx); auto const & jet{ jets[idx] }; return (parton.p - jet).pt()/jet.pt() <= soft_pt_regulator; } } // namespace bool Event::valid_hej_state(double const soft_pt_regulator) const { using namespace event_type; const auto is_valid_parton = [&](Particle const & parton, int const jet_idx) { return valid_parton(jets(), parton, jet_idx, soft_pt_regulator); }; if(!is_resummable(type())) return false; auto const & jet_indices{ particle_jet_indices() }; auto jet_idx_begin{ jet_indices.cbegin() }; auto jet_idx_end{ jet_indices.crbegin() }; auto part_begin{ cbegin_partons() }; auto part_end{ crbegin_partons() }; if(!is_backward_g_to_h(*this)) { const int first_jet_idx = *jet_idx_begin; if(! is_valid_parton(*part_begin, first_jet_idx)) { return false; } ++part_begin; ++jet_idx_begin; // unob -> second parton in own jet if( type() & (unob | qqbar_exb) ){ if( (*jet_idx_begin == first_jet_idx) || !is_valid_parton(*part_begin, *jet_idx_begin) ) { return false; } ++part_begin; ++jet_idx_begin; } } if(!is_forward_g_to_h(*this)) { const int last_jet_idx = *jet_idx_end; if(!is_valid_parton(*part_end, last_jet_idx)) { return false; } ++part_end; ++jet_idx_end; if( type() & (unof | qqbar_exf) ){ if( (*jet_idx_end == last_jet_idx) || !is_valid_parton(*part_end, *jet_idx_end) ) { return false; } ++part_end; // ++jet_idx_end; // last check, we don't need idx_end afterwards } } if( type() & qqbar_mid ){ // find qqbar pair auto begin_qqbar{ std::find_if( part_begin, part_end.base(), [](Particle const & part) -> bool { return part.type != ParticleID::gluon; } )}; assert(begin_qqbar != part_end.base()); long int qqbar_pos{ std::distance(part_begin, begin_qqbar) }; assert(qqbar_pos >= 0); jet_idx_begin += qqbar_pos; const int next_jet_idx = *std::next(jet_idx_begin); if( (*jet_idx_begin == next_jet_idx) || ! is_valid_parton(*begin_qqbar, *jet_idx_begin) || ! is_valid_parton(*std::next(begin_qqbar), next_jet_idx) ) { return false; } } return true; } bool Event::valid_incoming() const{ for(std::size_t i=0; i < incoming_.size(); ++i){ if(!(HEJ::nearby_ep(std::abs(incoming_[i].pz()), incoming_[i].E(), TOL*incoming_[i].E()) && (incoming_[i].pt()==0.))) return false; } return true; } Event::ConstPartonIterator Event::begin_partons() const { return cbegin_partons(); } Event::ConstPartonIterator Event::cbegin_partons() const { return {HEJ::is_parton, cbegin(outgoing()), cend(outgoing())}; } Event::ConstPartonIterator Event::end_partons() const { return cend_partons(); } Event::ConstPartonIterator Event::cend_partons() const { return {HEJ::is_parton, cend(outgoing()), cend(outgoing())}; } Event::ConstReversePartonIterator Event::rbegin_partons() const { return crbegin_partons(); } Event::ConstReversePartonIterator Event::crbegin_partons() const { return std::reverse_iterator<ConstPartonIterator>( cend_partons() ); } Event::ConstReversePartonIterator Event::rend_partons() const { return crend_partons(); } Event::ConstReversePartonIterator Event::crend_partons() const { return std::reverse_iterator<ConstPartonIterator>( cbegin_partons() ); } Event::PartonIterator Event::begin_partons() { return {HEJ::is_parton, begin(outgoing_), end(outgoing_)}; } Event::PartonIterator Event::end_partons() { return {HEJ::is_parton, end(outgoing_), end(outgoing_)}; } Event::ReversePartonIterator Event::rbegin_partons() { return std::reverse_iterator<PartonIterator>( end_partons() ); } Event::ReversePartonIterator Event::rend_partons() { return std::reverse_iterator<PartonIterator>( begin_partons() ); } namespace { void print_momentum(std::ostream & os, fastjet::PseudoJet const & part){ constexpr int prec = 6; const std::streamsize orig_prec = os.precision(); os <<std::scientific<<std::setprecision(prec) << "[" <<std::setw(2*prec+1)<<std::right<< part.px() << ", " <<std::setw(2*prec+1)<<std::right<< part.py() << ", " <<std::setw(2*prec+1)<<std::right<< part.pz() << ", " <<std::setw(2*prec+1)<<std::right<< part.E() << "]"<< std::fixed; os.precision(orig_prec); } void print_colour(std::ostream & os, std::optional<Colour> const & col){ constexpr int width = 3; if(!col) os << "(no color)"; // American spelling for better alignment else os << "(" <<std::setw(width)<<std::right<< col->first << ", " <<std::setw(width)<<std::right<< col->second << ")"; } } // namespace std::ostream& operator<<(std::ostream & os, Event const & ev){ constexpr int prec = 4; constexpr int wtype = 3; // width for types const std::streamsize orig_prec = os.precision(); os <<std::setprecision(prec)<<std::fixed; os << "########## " << name(ev.type()) << " ##########" << std::endl; os << "Incoming particles:\n"; for(auto const & in: ev.incoming()){ os <<std::setw(wtype)<< in.type << ": "; print_colour(os, in.colour); os << " "; print_momentum(os, in.p); os << std::endl; } os << "\nOutgoing particles: " << ev.outgoing().size() << "\n"; for(auto const & out: ev.outgoing()){ os <<std::setw(wtype)<< out.type << ": "; print_colour(os, out.colour); os << " "; print_momentum(os, out.p); os << " => rapidity=" <<std::setw(2*prec-1)<<std::right<< out.rapidity() << std::endl; } os << "\nForming Jets: " << ev.jets().size() << "\n"; for(auto const & jet: ev.jets()){ print_momentum(os, jet); os << " => rapidity=" <<std::setw(2*prec-1)<<std::right<< jet.rapidity() << std::endl; } if(!ev.decays().empty() ){ os << "\nDecays: " << ev.decays().size() << "\n"; for(auto const & decay: ev.decays()){ os <<std::setw(wtype)<< ev.outgoing()[decay.first].type << " (" << decay.first << ") to:\n"; for(auto const & out: decay.second){ os <<" "<<std::setw(wtype)<< out.type << ": "; print_momentum(os, out.p); os << " => rapidity=" <<std::setw(2*prec-1)<<std::right<< out.rapidity() << std::endl; } } } os << std::defaultfloat; os.precision(orig_prec); return os; } std::string to_string(Event const & ev){ std::stringstream ss; ss << ev; return ss.str(); } double shat(Event const & ev){ return (ev.incoming()[0].p + ev.incoming()[1].p).m2(); } 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(); // event type 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; result.IDUP.reserve(num_particles); // PID result.ISTUP.reserve(num_particles); // status (in, out, decay) result.PUP.reserve(num_particles); // momentum result.MOTHUP.reserve(num_particles); // index mother particle result.ICOLUP.reserve(num_particles); // colour // incoming std::array<Particle, 2> incoming{ event.incoming() }; // First incoming should be positive pz according to LHE standard // (or at least most (everyone?) do it this way, and Pythia assumes it) if(incoming[0].pz() < incoming[1].pz()) std::swap(incoming[0], incoming[1]); for(Particle const & in: incoming){ result.IDUP.emplace_back(in.type); result.ISTUP.emplace_back(LHE_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); assert(in.colour); result.ICOLUP.emplace_back(*in.colour); } // outgoing 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) != 0u ?LHE_Status::decay :LHE_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); if(out.colour) result.ICOLUP.emplace_back(*out.colour); else{ result.ICOLUP.emplace_back(std::make_pair(0,0)); } } // decays for(auto const & decay: event.decays()){ for(auto const & out: decay.second){ result.IDUP.emplace_back(out.type); result.ISTUP.emplace_back(LHE_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; } } // namespace HEJ diff --git a/src/MatrixElement.cc b/src/MatrixElement.cc index 1044a74..b291815 100644 --- a/src/MatrixElement.cc +++ b/src/MatrixElement.cc @@ -1,2954 +1,2954 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019-2023 * \copyright GPLv2 or later */ #include "HEJ/MatrixElement.hh" #include <algorithm> #include <cassert> #include <cmath> #include <cstddef> #include <cstdlib> #include <iterator> #include <limits> #include <unordered_map> #include <utility> #include "fastjet/PseudoJet.hh" #include "HEJ/ConfigFlags.hh" #include "HEJ/Constants.hh" #include "HEJ/EWConstants.hh" #include "HEJ/Event.hh" #include "HEJ/HiggsCouplingSettings.hh" #include "HEJ/Hjets.hh" #include "HEJ/LorentzVector.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/Particle.hh" #include "HEJ/WWjets.hh" #include "HEJ/Wjets.hh" #include "HEJ/Zjets.hh" #include "HEJ/event_types.hh" #include "HEJ/exceptions.hh" #include "HEJ/jets.hh" #include "HEJ/utility.hh" namespace HEJ { namespace { // Colour acceleration multiplier for gluons // see eq:K_g in developer manual 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()); } // Colour acceleration multiplier for quarks // see eq:K_q in developer manual constexpr double K_q = C_F; // Colour acceleration multipliers double K( ParticleID type, CLHEP::HepLorentzVector const & pout, CLHEP::HepLorentzVector const & pin ){ if(type == pid::gluon) return K_g(pout, pin); return K_q; } } // namespace double MatrixElement::omega0( double alpha_s, double mur, fastjet::PseudoJet const & q_j ) const { const double lambda = param_.regulator_lambda; const double result = - alpha_s*N_C/M_PI*std::log(q_j.perp2()/(lambda*lambda)); if(! param_.log_correction) return result; return ( 1. + alpha_s/(4.*M_PI)*BETA0*std::log(mur*mur/(q_j.perp()*lambda)) )*result; } Weights MatrixElement::operator()(Event const & event) const { std::vector <double> tree_kin_part=tree_kin(event); std::vector <Weights> virtual_part=virtual_corrections(event); if(tree_kin_part.size() != virtual_part.size()) { throw std::logic_error("tree and virtuals have different sizes"); } Weights sum = Weights{0., std::vector<double>(event.variations().size(), 0.)}; for(size_t i=0; i<tree_kin_part.size(); ++i) { sum += tree_kin_part.at(i)*virtual_part.at(i); } return tree_param(event)*sum; } Weights MatrixElement::tree(Event const & event) const { std::vector <double> tree_kin_part=tree_kin(event); double sum = 0.; for(double i : tree_kin_part) { sum += i; } return tree_param(event)*sum; } Weights MatrixElement::tree_param(Event const & event) const { if(! is_resummable(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 = 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; } std::vector<Weights> MatrixElement::virtual_corrections(Event const & event) const { if(!event.valid_hej_state(param_.soft_pt_regulator)) { return {Weights{0., std::vector<double>(event.variations().size(), 0.)}}; } // only compute once for each renormalisation scale std::unordered_map<double, std::vector<double> > known_vec; std::vector<double> central_vec=virtual_corrections(event, event.central().mur); known_vec.emplace(event.central().mur, central_vec); for(auto const & var: event.variations()) { const auto ME_it = known_vec.find(var.mur); if(ME_it == end(known_vec)) { known_vec.emplace(var.mur, virtual_corrections(event, var.mur)); } } // At this stage known_vec contains one vector of virtual corrections for each mur value // Now put this into a vector of Weights std::vector<Weights> result_vec; for(size_t i=0; i<central_vec.size(); ++i) { Weights result; result.central = central_vec.at(i); for(auto const & var: event.variations()) { const auto ME_it = known_vec.find(var.mur); result.variations.emplace_back(ME_it->second.at(i)); } result_vec.emplace_back(result); } return result_vec; } template<class InputIterator> double MatrixElement::virtual_corrections_no_interference( InputIterator begin_parton, InputIterator end_parton, fastjet::PseudoJet & q, const double mur ) const{ const double alpha_s = alpha_s_(mur); double exponent = 0.; for(auto parton_it = begin_parton; parton_it != end_parton; ++parton_it){ exponent += omega0(alpha_s, mur, q)*( (parton_it+1)->rapidity() - parton_it->rapidity() ); q -= (parton_it+1)->p; } return exponent; } template<class InputIterator> std::vector <double> MatrixElement::virtual_corrections_interference( InputIterator begin_parton, InputIterator end_parton, fastjet::PseudoJet & q_t, fastjet::PseudoJet & q_b, const double mur ) const{ const double alpha_s = alpha_s_(mur); double sum_top = 0.; double sum_bot = 0.; double sum_mix = 0.; for(auto parton_it = begin_parton; parton_it != end_parton; ++parton_it){ const double dy = (parton_it+1)->rapidity() - parton_it->rapidity(); const double tmp_top = omega0(alpha_s, mur, q_t) * dy; const double tmp_bot = omega0(alpha_s, mur, q_b) * dy; sum_top += tmp_top; sum_bot += tmp_bot; sum_mix += (tmp_top + tmp_bot) / 2.; q_t -= (parton_it+1)->p; q_b -= (parton_it+1)->p; } return {sum_top, sum_bot, sum_mix}; } double MatrixElement::virtual_corrections_W( Event const & event, const double mur, Particle const & WBoson ) const{ auto const & in = event.incoming(); const auto partons = filter_partons(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(partons.begin(), partons.end(), rapidity_less{})); assert(partons.size() >= 2); assert(pa.pz() < pb.pz()); fastjet::PseudoJet q = pa - partons[0].p; auto first_idx = cbegin(partons); auto last_idx = cend(partons) - 1; #ifndef NDEBUG bool wc = true; #endif // With extremal qqbar or unordered gluon outside the extremal // partons then it is not part of the FKL ladder and does not // contribute to the virtual corrections. W emitted from the // most backward leg must be taken into account in t-channel if (event.type() == event_type::unob) { q -= partons[1].p; ++first_idx; if (in[0].type != partons[1].type ){ q -= WBoson.p; #ifndef NDEBUG wc=false; #endif } } else if (event.type() == event_type::qqbar_exb) { q -= partons[1].p; ++first_idx; if (std::abs(partons[0].type) != std::abs(partons[1].type)){ q -= WBoson.p; #ifndef NDEBUG wc=false; #endif } } else { if(event.type() == event_type::unof || event.type() == event_type::qqbar_exf){ --last_idx; } if (in[0].type != partons[0].type ){ q -= WBoson.p; #ifndef NDEBUG wc=false; #endif } } auto first_idx_qqbar = last_idx; auto last_idx_qqbar = last_idx; bool wqq = false; //if qqbarMid event, virtual correction do not occur between qqbar pair. if(event.type() == event_type::qqbar_mid){ const auto backquark = std::find_if( begin(partons) + 1, end(partons) - 1 , [](Particle const & s){ return (s.type != pid::gluon); } ); assert(backquark!=end(partons)-1 && (backquark+1)->type!=pid::gluon); if(std::abs(backquark->type) != std::abs((backquark+1)->type)) { wqq=true; #ifndef NDEBUG wc=false; #endif } last_idx = backquark; first_idx_qqbar = last_idx+1; } double exponent = virtual_corrections_no_interference(first_idx, last_idx, q, mur); if(last_idx != first_idx_qqbar) { q -= (last_idx+1)->p; if(wqq) q -= WBoson.p; exponent += virtual_corrections_no_interference(first_idx_qqbar, last_idx_qqbar, q, mur); } #ifndef NDEBUG if (wc) q -= WBoson.p; assert( nearby(q, -1*pb, norm) || is_AWZH_boson(partons.back().type) || event.type() == event_type::unof || event.type() == event_type::qqbar_exf ); #endif if(param_.nlo.enabled) { double nlo_virtual = 1.; // Only apply virtual corrections to a nlo order event. if(partons.size() == param_.nlo.nj) nlo_virtual += exponent; return nlo_virtual; } return std::exp(exponent); } std::vector <double> MatrixElement::virtual_corrections_WW( Event const & event, const double mur ) const{ auto const & in = event.incoming(); const auto partons = filter_partons(event.outgoing()); fastjet::PseudoJet const & pa = in.front().p; #ifndef NDEBUG fastjet::PseudoJet const & pb = in.back().p; #endif assert(std::is_sorted(partons.begin(), partons.end(), rapidity_less{})); assert(partons.size() >= 2); assert(pa.pz() < pb.pz()); assert(event.decays().size() == 2); std::vector<fastjet::PseudoJet> plbar; std::vector<fastjet::PseudoJet> pl; for(auto const & decay_pair : event.decays()) { auto const decay = decay_pair.second; if(decay.at(0).type < 0) { plbar.emplace_back(decay.at(0).p); pl .emplace_back(decay.at(1).p); } else { pl .emplace_back(decay.at(0).p); plbar.emplace_back(decay.at(1).p); } } fastjet::PseudoJet q_t = pa - partons[0].p - pl[0] - plbar[0]; fastjet::PseudoJet q_b = pa - partons[0].p - pl[1] - plbar[1]; auto const begin_parton = cbegin(partons); auto const end_parton = cend(partons) - 1; auto res = virtual_corrections_interference(begin_parton, end_parton, q_t, q_b, mur); if(param_.nlo.enabled) { if(partons.size() > param_.nlo.nj) return std::vector<double>(1., res.size()); assert(partons.size() == param_.nlo.nj); for(double & virt: res) virt += 1.; } else { for(double & virt: res) virt = exp(virt); } return res; } // Virtual corrections for Z processes without interference (only one contribution) double MatrixElement::virtual_corrections_Z_single( Event const & event, const double mur, Particle const & ZBoson, const bool emit_fwd ) const{ auto const & in = event.incoming(); const auto partons = filter_partons(event.outgoing()); fastjet::PseudoJet const & pa = in.front().p; #ifndef NDEBUG fastjet::PseudoJet const & pb = in.back().p; #endif assert(std::is_sorted(partons.begin(), partons.end(), rapidity_less{})); assert(partons.size() >= 2); assert(pa.pz() < pb.pz()); // If the Z is emitted from forward leg or central qqbar, don't subtract the Z momentum from first q fastjet::PseudoJet q; if(emit_fwd || event.type()==event_type::central_qqbar) { q = pa - partons[0].p; } else { q = pa - partons[0].p - ZBoson.p; } auto first_idx = cbegin(partons); auto last_idx = cend(partons) - 1; // for uno/exqqbar the two extremal partons do not contribute to the virtual corrections if (event.type() == event_type::unob || event.type() == event_type::qqbar_exb) { // unordered gluon or first quark is partons[0] and is already subtracted // partons[1] is the backward quark (uno case) or the second quark (exqqbar case) q -= partons[1].p; ++first_idx; } else if (event.type() == event_type::unof || event.type() == event_type::qqbar_exf) { // end sum at next to last parton --last_idx; } // if central qqbar event, virtual correction do not occur between qqbar pair auto first_idx_qqbar = last_idx; auto last_idx_qqbar = last_idx; if(event.type() == event_type::central_qqbar){ const auto backquark = std::find_if( begin(partons) + 1, end(partons) - 1 , [](Particle const & s){ return (s.type != pid::gluon); } ); assert(backquark!=end(partons)-1 && (backquark+1)->type!=pid::gluon); last_idx = backquark; first_idx_qqbar = last_idx+1; } double exponent = virtual_corrections_no_interference(first_idx, last_idx, q, mur); if(last_idx != first_idx_qqbar) { q -= (last_idx+1)->p; q -= ZBoson.p; exponent += virtual_corrections_no_interference(first_idx_qqbar, last_idx_qqbar, q, mur); } if(param_.nlo.enabled) { double nlo_virtual = 1.; // Only apply virtual corrections to a nlo order event. if(partons.size() == param_.nlo.nj) nlo_virtual += exponent; return nlo_virtual; } return exp(exponent); } // Virtual corrections for Z processes with 2 interfering contributions // Returns 3 values (2 squares + 1 interference term) std::vector<double> MatrixElement::virtual_corrections_Z_double( Event const & event, const double mur, Particle const & ZBoson ) const{ auto const & in = event.incoming(); const auto partons = filter_partons(event.outgoing()); fastjet::PseudoJet const & pa = in.front().p; #ifndef NDEBUG fastjet::PseudoJet const & pb = in.back().p; #endif assert(std::is_sorted(partons.begin(), partons.end(), rapidity_less{})); assert(partons.size() >= 2); assert(pa.pz() < pb.pz()); fastjet::PseudoJet q_t; if(event.type() == event_type::central_qqbar && is_gluon(in.front().type)) { // gq initiated central qqbar event -> "top" emission from qqbar q_t = pa - partons[0].p; } else { // top emission from top quark leg q_t = pa - partons[0].p - ZBoson.p; } fastjet::PseudoJet q_b = pa - partons[0].p; auto first_idx = cbegin(partons); auto last_idx = cend(partons) - 1; // for uno/exqqbar the two extremal partons do not contribute to the virtual corrections if (event.type() == event_type::unob || event.type() == event_type::qqbar_exb) { // unordered gluon or first quark is partons[0] and is already subtracted // partons[1] is the backward quark (uno case) or the second quark (exqqbar case) q_t -= partons[1].p; q_b -= partons[1].p; ++first_idx; } else if (event.type() == event_type::unof || event.type() == event_type::qqbar_exf) { // end sum at next to last parton --last_idx; } // if central qqbar event, virtual correction do not occur between qqbar pair auto first_idx_qqbar = last_idx; auto last_idx_qqbar = last_idx; if(event.type() == event_type::central_qqbar){ const auto backquark = std::find_if( begin(partons) + 1, end(partons) - 1 , [](Particle const & s){ return (s.type != pid::gluon); } ); assert(backquark!=end(partons)-1 && (backquark+1)->type!=pid::gluon); last_idx = backquark; first_idx_qqbar = last_idx+1; } auto res = virtual_corrections_interference(first_idx, last_idx, q_t, q_b, mur); if(last_idx != first_idx_qqbar) { q_t -= (last_idx+1)->p; q_b -= (last_idx+1)->p; if(is_gluon(in.front().type)) { // "top" emission from qqbar, "bot" emission from bottom quark leg q_t -= ZBoson.p; } else { // "top" emission from top quark leg, "bot" emission from qqbar q_b -= ZBoson.p; } auto res_2 = virtual_corrections_interference(first_idx_qqbar, last_idx_qqbar, q_t, q_b, mur); for(std::size_t i=0; i<res.size(); ++i){ res[i] += res_2[i]; } } if(param_.nlo.enabled) { if(partons.size() > param_.nlo.nj) return std::vector<double>(1., res.size()); assert(partons.size() == param_.nlo.nj); for(double & virt: res) virt += 1.; } else { for(double & virt: res) virt = exp(virt); } return res; } // Virtual corrections for Z processes with 3 interfering contributions // Returns 6 values (3 squares + 3 interference terms) std::vector <double> MatrixElement::virtual_corrections_Z_triple( Event const & event, const double mur, Particle const & ZBoson ) const{ assert(event.type() == event_type::central_qqbar); auto const & in = event.incoming(); const auto partons = filter_partons(event.outgoing()); fastjet::PseudoJet const & pa = in.front().p; #ifndef NDEBUG fastjet::PseudoJet const & pb = in.back().p; #endif assert(std::is_sorted(partons.begin(), partons.end(), rapidity_less{})); assert(partons.size() >= 2); assert(pa.pz() < pb.pz()); enum emission_site {top, mid, bot}; fastjet::PseudoJet q_t = pa - partons[0].p - ZBoson.p; fastjet::PseudoJet q_b = pa - partons[0].p; auto first_idx = cbegin(partons); auto last_idx = cend(partons) - 1; auto first_idx_qqbar = last_idx; auto last_idx_qqbar = last_idx; const auto backquark = std::find_if( begin(partons) + 1, end(partons) - 1 , [](Particle const & s){ return (s.type != pid::gluon); } ); assert(backquark!=end(partons)-1 && (backquark+1)->type!=pid::gluon); last_idx = backquark; first_idx_qqbar = last_idx+1; // here q[bot] = q[mid], thus we only need to evaluate virtual_corrections_interference[top][mid] const auto res_1_top_mid = virtual_corrections_interference(first_idx, last_idx, q_t, q_b, mur); double res_tmp[3][3]; res_tmp[top][top] = res_1_top_mid.at(0); res_tmp[mid][mid] = res_1_top_mid.at(1); res_tmp[bot][bot] = res_tmp[mid][mid]; res_tmp[top][mid] = res_1_top_mid.at(2); res_tmp[top][bot] = res_tmp[top][mid]; res_tmp[mid][bot] = res_tmp[mid][mid]; if(last_idx != first_idx_qqbar) { q_t -= (last_idx+1)->p; q_b -= (last_idx+1)->p; // here q[mid] = q[top], thus we only need to evaluate virtual_corrections_interference[top][bot] const auto res_2_top_bot = virtual_corrections_interference(first_idx_qqbar, last_idx_qqbar, q_t, q_b, mur); res_tmp[top][top] += res_2_top_bot.at(0); res_tmp[mid][mid] += res_2_top_bot.at(0); // same as [top][top] res_tmp[bot][bot] += res_2_top_bot.at(1); res_tmp[top][mid] += res_2_top_bot.at(0); // same as [top][top] res_tmp[top][bot] += res_2_top_bot.at(2); res_tmp[mid][bot] += res_2_top_bot.at(2); // same as [top][bot] } std::vector<double> res = {res_tmp[top][top], res_tmp[mid][mid], res_tmp[bot][bot], res_tmp[top][mid], res_tmp[top][bot], res_tmp[mid][bot]}; if(param_.nlo.enabled) { if(partons.size() > param_.nlo.nj) return std::vector<double>(1., res.size()); assert(partons.size() == param_.nlo.nj); for(double & virt: res) virt += 1.; } else { for(double & virt: res) virt = exp(virt); } return res; } std::vector<double> MatrixElement::virtual_corrections_Z( Event const & event, const double mur, Particle const & ZBoson ) const{ using namespace event_type; auto const & in = event.incoming(); if(event.type() == central_qqbar) { if(is_gluon(in.front().type)) { if(is_gluon(in.back().type)) { // gg event return {virtual_corrections_Z_single(event, mur, ZBoson, false)}; } else { // gq event return virtual_corrections_Z_double(event, mur, ZBoson); } } else { if(is_gluon(in.back().type)) { // qg event return virtual_corrections_Z_double(event, mur, ZBoson); } else { // qq event return virtual_corrections_Z_triple(event, mur, ZBoson); } } } if(event.type() == qqbar_exb && is_gluon(in.back().type)) { // gg event, emission from backward leg return {virtual_corrections_Z_single(event, mur, ZBoson, false)}; } if(event.type() == qqbar_exf && is_gluon(in.front().type)) { // gg event, emission from forward leg return {virtual_corrections_Z_single(event, mur, ZBoson, true)}; } if(event.type() == FKL || event.type() == unob || event.type() == unof) { if(is_gluon(in.back().type)) { // qg event, emission from backward leg return {virtual_corrections_Z_single(event, mur, ZBoson, false)}; } if(is_gluon(in.front().type)) { // gq event, emission from forward leg return {virtual_corrections_Z_single(event, mur, ZBoson, true)}; } } // qq initiated FKL/uno or qg/gq initiated exqqbar return virtual_corrections_Z_double(event, mur, ZBoson); } std::vector<double> MatrixElement::virtual_corrections( Event const & event, const 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 std::vector<Particle> bosons = filter_AWZH_bosons(out); if(event.jets().size() != param_.nlo.nj && param_.nlo.enabled) { throw std::logic_error{ "Input event has number of jets different to stated NLO " "input in config file: " + std::to_string(event.jets().size()) + " vs " +std::to_string(param_.nlo.nj) + "\n" }; } if(bosons.size() > 2) { throw not_implemented("Emission of >2 bosons is unsupported"); } if(bosons.size() == 2) { if(bosons[0].type == pid::Wp && bosons[1].type == pid::Wp) { return virtual_corrections_WW(event, mur); } else if(bosons[0].type == pid::Wm && bosons[1].type == pid::Wm) { return virtual_corrections_WW(event, mur); } throw not_implemented("Emission of bosons of unsupported type"); } if(bosons.size() == 1) { const auto AWZH_boson = bosons[0]; if(std::abs(AWZH_boson.type) == pid::Wp){ return {virtual_corrections_W(event, mur, AWZH_boson)}; } - if(AWZH_boson.type == pid::Z_photon_mix){ + if(AWZH_boson.type == pid::Z_photon_mix || AWZH_boson.type == pid::Z){ return virtual_corrections_Z(event, mur, AWZH_boson); } } 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; auto first_idx = cbegin(out); auto last_idx = cend(out) - 1; // if there is a Higgs boson _not_ emitted off an incoming gluon, // extremal qqbar 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 && in.front().type != pid::gluon) || event.type() == event_type::unob || event.type() == event_type::qqbar_exb){ q -= out[1].p; ++first_idx; } if((out.back().type == pid::Higgs && in.back().type != pid::gluon) || event.type() == event_type::unof || event.type() == event_type::qqbar_exf){ --last_idx; } auto first_idx_qqbar = last_idx; auto last_idx_qqbar = last_idx; //if central qqbar event, virtual correction do not occur between q-qbar. if(event.type() == event_type::qqbar_mid){ const auto backquark = std::find_if( begin(out) + 1, end(out) - 1 , [](Particle const & s){ return (s.type != pid::gluon && is_parton(s.type)); } ); assert(backquark!=end(out)-1 && (backquark+1)->type!=pid::gluon); last_idx = backquark; first_idx_qqbar = last_idx+1; } double exponent = virtual_corrections_no_interference(first_idx, last_idx, q, mur); if(last_idx != first_idx_qqbar) { q -= (last_idx+1)->p; exponent += virtual_corrections_no_interference(first_idx_qqbar, last_idx_qqbar, q, mur); } assert( nearby(q, -1*pb, norm) || (out.back().type == pid::Higgs && in.back().type != pid::gluon) || event.type() == event_type::unof || event.type() == event_type::qqbar_exf ); if (param_.nlo.enabled){ const auto partons = filter_partons(event.outgoing()); double nlo_virtual = 1.; // Only apply virtual corrections to a nlo order event. if(partons.size() == param_.nlo.nj) nlo_virtual += exponent; return {nlo_virtual}; } return {std::exp(exponent)}; } namespace { //! Lipatov vertex for partons emitted into extremal jets CLHEP::HepLorentzVector CLipatov( CLHEP::HepLorentzVector const & qav, CLHEP::HepLorentzVector const & qbv, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2 ) { const CLHEP::HepLorentzVector p5 = qav-qbv; const CLHEP::HepLorentzVector CL = -(qav+qbv) + 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)); return CL; } double C2Lipatov( CLHEP::HepLorentzVector const & qav, CLHEP::HepLorentzVector const & qbv, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2 ){ const CLHEP::HepLorentzVector CL = CLipatov(qav, qbv, p1, p2); return -CL.dot(CL); } //! Lipatov vertex with soft subtraction for partons emitted into extremal jets double C2Lipatovots( CLHEP::HepLorentzVector const & qav, CLHEP::HepLorentzVector const & qbv, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2, const double lambda ) { const double Cls=(C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2())); const double kperp=(qav-qbv).perp(); if (kperp>lambda) return Cls; return Cls-4./(kperp*kperp); } double C2Lipatov_Mix( CLHEP::HepLorentzVector const & qav_t, CLHEP::HepLorentzVector const & qbv_t, CLHEP::HepLorentzVector const & qav_b, CLHEP::HepLorentzVector const & qbv_b, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2 ) { const CLHEP::HepLorentzVector CL_t = CLipatov(qav_t, qbv_t, p1, p2); const CLHEP::HepLorentzVector CL_b = CLipatov(qav_b, qbv_b, p1, p2); return -CL_t.dot(CL_b); } double C2Lipatovots_Mix( CLHEP::HepLorentzVector const & qav_t, CLHEP::HepLorentzVector const & qbv_t, CLHEP::HepLorentzVector const & qav_b, CLHEP::HepLorentzVector const & qbv_b, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2, const double lambda ) { const double Cls = C2Lipatov_Mix(qav_t, qbv_t, qav_b, qbv_b, p1, p2) / sqrt(qav_t.m2() * qbv_t.m2() * qav_b.m2() * qbv_b.m2()); const double kperp = (qav_t - qbv_t).perp(); if (kperp > lambda){ return Cls; } return Cls - 4.0 / (kperp * kperp); } CLHEP::HepLorentzVector CLipatov( CLHEP::HepLorentzVector const & qav, CLHEP::HepLorentzVector const & qbv, CLHEP::HepLorentzVector const & pim, CLHEP::HepLorentzVector const & pip, CLHEP::HepLorentzVector const & pom, CLHEP::HepLorentzVector const & pop ){ const CLHEP::HepLorentzVector p5 = qav-qbv; const CLHEP::HepLorentzVector CL = -(qav+qbv) + 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; } //! Lipatov vertex double C2Lipatov( // B CLHEP::HepLorentzVector const & qav, CLHEP::HepLorentzVector const & qbv, CLHEP::HepLorentzVector const & pim, CLHEP::HepLorentzVector const & pip, CLHEP::HepLorentzVector const & pom, CLHEP::HepLorentzVector const & pop ){ const CLHEP::HepLorentzVector CL = CLipatov(qav, qbv, pim, pip, pom, pop); return -CL.dot(CL); } //! Lipatov vertex with soft subtraction double C2Lipatovots( CLHEP::HepLorentzVector const & qav, CLHEP::HepLorentzVector const & qbv, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2, const double lambda ) { const double Cls=(C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2())); const double kperp=(qav-qbv).perp(); if (kperp>lambda) return Cls; return Cls-4./(kperp*kperp); } double C2Lipatov_Mix( CLHEP::HepLorentzVector const & qav_t, CLHEP::HepLorentzVector const & qbv_t, CLHEP::HepLorentzVector const & qav_b, CLHEP::HepLorentzVector const & qbv_b, CLHEP::HepLorentzVector const & pim, CLHEP::HepLorentzVector const & pip, CLHEP::HepLorentzVector const & pom, CLHEP::HepLorentzVector const & pop ) { const CLHEP::HepLorentzVector CL_t = CLipatov(qav_t, qbv_t, pim, pip, pom, pop); const CLHEP::HepLorentzVector CL_b = CLipatov(qav_b, qbv_b, pim, pip, pom, pop); return -CL_t.dot(CL_b); } double C2Lipatovots_Mix( CLHEP::HepLorentzVector const & qav_t, CLHEP::HepLorentzVector const & qbv_t, CLHEP::HepLorentzVector const & qav_b, CLHEP::HepLorentzVector const & qbv_b, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2, const double lambda ) { const double Cls = C2Lipatov_Mix(qav_t, qbv_t, qav_b, qbv_b, pa, pb, p1, p2) / sqrt(qav_t.m2() * qbv_t.m2() * qav_b.m2() * qbv_b.m2()); const double kperp = (qav_t - qbv_t).perp(); if (kperp > lambda) { return Cls; } return Cls - 4.0 / (kperp * kperp); } /** Matrix element squared for tree-level current-current scattering * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param pg Unordered gluon momentum * @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 * * @note The unof contribution can be calculated by reversing the argument ordering. */ double ME_uno_current( [[maybe_unused]] ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pg, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa ){ assert(aptype!=pid::gluon); // aptype cannot be gluon const double t1 = (pa - p1 - pg).m2(); const double t2 = (pb - pn).m2(); return K_q * K(bptype, pn, pb)*currents::ME_unob_qq(pg, p1, pa, pn, pb) / (t1 * t2); } /** Matrix element squared for tree-level current-current scattering * @param bptype Particle b PDG ID * @param pgin Incoming gluon momentum * @param p1 More backward (anti-)quark from splitting Momentum * @param p2 Less backward (anti-)quark from splitting Momentum * @param pn Particle n Momentum * @param pb Particle b Momentum * @returns ME Squared for Tree-Level Current-Current Scattering * * @note The forward qqbar contribution can be calculated by reversing the * argument ordering. */ double ME_qqbar_current( ParticleID bptype, CLHEP::HepLorentzVector const & pgin, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & p2, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb ){ const double t1 = (pgin - p1 - p2).m2(); const double t2 = (pn - pb).m2(); return K_q * K(bptype, pn, pb)*currents::ME_qqbar_qg(pgin, pb, p1, p2, pn) / (t1 * t2); } /* \brief Matrix element squared for central qqbar tree-level current-current * scattering * * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param nabove Number of gluons emitted before central qqbarpair * @param nbelow Number of gluons emitted after central qqbarpair * @param pa Initial state a Momentum * @param pb Initial state b Momentum * @param partons Vector of all outgoing partons * @param qbar_first Ordering of the qqbar pair (true: qbar-q, false: q-qbar) * @returns ME Squared for qqbar_mid Tree-Level Current-Current Scattering */ double ME_qqbar_mid_current( ParticleID aptype, ParticleID bptype, int nabove, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<CLHEP::HepLorentzVector> const & partons, bool const qbar_first ){ using namespace currents; CLHEP::HepLorentzVector const & p1 = partons.front(); CLHEP::HepLorentzVector const & pn = partons.back(); const double t1 = (p1 - pa).m2(); const double t2 = (pb - pn).m2(); return K(aptype, p1, pa) *K(bptype, pn, pb) *ME_Cenqqbar_qq( pa, pb, partons, qbar_first, nabove ) / (t1 * t2 * (4.*(N_C*N_C - 1))); } /** 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( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa ){ const double t1 = (p1 - pa).m2(); const double t2 = (pb - pn).m2(); return K(aptype, p1, pa) * K(bptype, pn, pb) * currents::ME_qq(p1, pa, pn, pb)/(t1 * t2); } /** Matrix element squared for tree-level current-current scattering With W+Jets * @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 wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for Tree-Level Current-Current Scattering */ double ME_W_current( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wc, ParticleProperties const & Wprop ){ using namespace currents; assert(!(aptype==pid::gluon && bptype==pid::gluon)); if(aptype == pid::gluon || wc) { // swap currents to ensure that the W is emitted off the first one return ME_W_current(bptype, aptype, p1, pa, pn, pb, plbar, pl, false, Wprop); } // we assume that the W is emitted off a quark line // if this is not the case, we have to apply CP conjugation, // which is equivalent to swapping lepton and antilepton momenta const double current_contr = is_quark(aptype)? ME_W_qQ(p1,plbar,pl,pa,pn,pb,Wprop): ME_W_qQ(p1,pl,plbar,pa,pn,pb,Wprop); const double t1 = (pa - p1 - pl - plbar).m2(); const double tn = (pn - pb).m2(); return K(aptype, p1, pa) * K(bptype, pn, pb) * current_contr/(4.*(N_C*N_C - 1) * t1 * tn); } /** Matrix element squared for backwards uno tree-level current-current * scattering With W+Jets * * @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 pg Unordered gluon momentum * @param wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for unob Tree-Level Current-Current Scattering * * @note The unof contribution can be calculated by reversing the argument ordering. */ double ME_W_uno_current( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pg, CLHEP::HepLorentzVector plbar, CLHEP::HepLorentzVector pl, bool const wc, ParticleProperties const & Wprop ){ using namespace currents; assert(bptype != pid::gluon || aptype != pid::gluon); if(aptype == pid::gluon || wc) { // emission off pb -- pn line // we assume that the W is emitted off a quark line // if this is not the case, we have to apply CP conjugation, // which is equivalent to swapping lepton and antilepton momenta if(is_antiquark(bptype)) std::swap(plbar, pl); const double t1 = (pa - p1 - pg).m2(); const double tn = (pb - pn - plbar - pl).m2(); return K_q*K_q*ME_W_unob_qQ(p1,pa,pn,pb,pg,plbar,pl,Wprop)/(4.*(N_C*N_C - 1) * t1 * tn); } // emission off pa -- p1 line if(is_antiquark(aptype)) std::swap(plbar, pl); const double t1 = (pa - p1 - pg - plbar - pl).m2(); const double tn = (pb - pn).m2(); return K(bptype, pn, pb)/C_F*ME_Wuno_qQ(p1,pa,pn,pb,pg,plbar,pl,Wprop)/(t1 * tn); } /** \brief Matrix element squared for backward qqbar tree-level current-current * scattering With W+Jets * * @param bptype Particle b PDG ID * @param pa Initial state a Momentum * @param pb Initial state b Momentum * @param pq Final state q Momentum * @param pqbar Final state qbar Momentum * @param pn Final state n Momentum * @param plbar Final state anti-lepton momentum * @param pl Final state lepton momentum * @param wc Boolean. True->W Emitted from b. Else; emitted from leg a * @returns ME Squared for qqbarb Tree-Level Current-Current Scattering * * @note calculate forwards qqbar contribution by reversing argument ordering. */ double ME_W_qqbar_current( ParticleID bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & pq, CLHEP::HepLorentzVector const & pqbar, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const wc, ParticleProperties const & Wprop ){ using namespace currents; if(is_anyquark(bptype) && wc) { // W Must be emitted from forwards leg. const double t1 = (pa - pq - pqbar).m2(); const double tn = (pb - pn - pl - plbar).m2(); return K_q * K_q * ME_W_Exqqbar_QQq( pb, pa, pn, pq, pqbar, plbar, pl, is_antiquark(bptype), Wprop ) / (4.*(N_C*N_C - 1) * t1 * tn); } const double t1 = (pa - pl - plbar - pq - pqbar).m2(); const double tn = (pb - pn).m2(); return K(bptype, pn, pb)/C_F * ME_WExqqbar_qqbarQ(pa, pqbar, plbar, pl, pq, pn, pb, Wprop) / (t1 * tn); } /* \brief Matrix element squared for central qqbar tree-level current-current * scattering With W+Jets * * @param aptype Particle a PDG ID * @param bptype Particle b PDG ID * @param nabove Number of gluons emitted before central qqbarpair * @param nbelow Number of gluons emitted after central qqbarpair * @param pa Initial state a Momentum * @param pb Initial state b Momentum\ * @param partons Vector of all outgoing partons * @param plbar Final state anti-lepton momentum * @param pl Final state lepton momentum * @param qbar_first Ordering of the qqbar pair (true: qbar-q, false: q-qbar) * @param wqq Boolean. True siginfies W boson is emitted from Central qqbar * @param wc Boolean. wc=true signifies w boson emitted from leg b; if wqq=false. * @returns ME Squared for qqbar_mid Tree-Level Current-Current Scattering */ double ME_W_qqbar_mid_current( ParticleID aptype, ParticleID bptype, int nabove, int nbelow, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<CLHEP::HepLorentzVector> const & partons, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, bool const qbar_first, bool const wqq, bool const wc, ParticleProperties const & Wprop ){ using namespace currents; const double wt = K(aptype, partons.front(), pa) * K(bptype, partons.back(), pb) / (4.*(N_C*N_C - 1)); if(wqq) return wt*ME_WCenqqbar_qq(pa, pb, pl, plbar, partons, is_antiquark(aptype),is_antiquark(bptype), qbar_first, nabove, Wprop); return wt*ME_W_Cenqqbar_qq(pa, pb, pl, plbar, partons, is_antiquark(aptype), is_antiquark(bptype), qbar_first, nabove, nbelow, wc, Wprop); } /** Matrix element squared for tree-level current-current scattering With Z+Jets * @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 plbar Final state positron momentum * @param pl Final state electron momentum * @param Zprop Z properties * @param stw2 Value of sin(theta_w)^2 * @param ctw Value of cos(theta_w) * @returns ME Squared for Tree-Level Current-Current Scattering */ std::vector<double> ME_Z_current( const ParticleID aptype, const ParticleID bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, const ParticleID ltype, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, ParticleProperties const & Zprop, const double stw2, const double ctw ){ using namespace currents; const auto pZ = pl + plbar; const double pref = K(aptype, p1, pa) * K(bptype, pn, pb)/(4.*(N_C*N_C-1)); // we know they are not both gluons assert(!is_gluon(aptype) || !is_gluon(bptype)); if(is_anyquark(aptype) && is_gluon(bptype)){ // This is a qg event const double t1 = (pa-p1-pZ).m2(); const double tn = (pb-pn ).m2(); return { pref*ME_Z_qg(pa,pb,p1,pn,plbar,pl,aptype,bptype,ltype,Zprop,stw2,ctw)/(t1 * tn) }; } if(is_gluon(aptype) && is_anyquark(bptype)){ // This is a gq event const double t1 = (pa-p1 ).m2(); const double tn = (pb-pn-pZ).m2(); return { pref*ME_Z_qg(pb,pa,pn,p1,plbar,pl,bptype,aptype,ltype,Zprop,stw2,ctw)/(t1 * tn) }; } // This is a qq event assert(is_anyquark(aptype) && is_anyquark(bptype)); const double t1_top = (pa-p1-pZ).m2(); const double t2_top = (pb-pn ).m2(); const double t1_bot = (pa-p1 ).m2(); const double t2_bot = (pb-pn-pZ).m2(); std::vector<double> res = ME_Z_qQ(pa,pb,p1,pn,plbar,pl,aptype,bptype,ltype,Zprop,stw2,ctw); assert(res.size() == 3); res[0] *= pref/(t1_top * t2_top); res[1] *= pref/(t1_bot * t2_bot); res[2] *= pref/sqrt(t1_top * t2_top * t1_bot * t2_bot); return res; } /** Matrix element squared for backwards uno tree-level current-current * scattering With Z+Jets * * @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 pg Unordered gluon momentum * @param plbar Final state positron momentum * @param pl Final state electron momentum * @param Zprop Z properties * @param stw2 Value of sin(theta_w)^2 * @param ctw Value of cos(theta_w) * @returns ME Squared for unob Tree-Level Current-Current Scattering * * @note The unof contribution can be calculated by reversing the argument ordering. */ std::vector<double> ME_Z_uno_current( const ParticleID aptype, const ParticleID bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pg, const ParticleID ltype, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, ParticleProperties const & Zprop, const double stw2, const double ctw ){ using namespace currents; const auto pZ = pl + plbar; const double pref = K(aptype, p1, pa)/C_F * K(bptype, pn, pb)/C_F; // we only evaluate unordered backward -> first incoming particle must be a quark assert(is_anyquark(aptype)); const double t1_top = (pa-pg-p1-pZ).m2(); const double t2_top = (pb-pn ).m2(); if (is_gluon(bptype)) { // This is a qg event -> Z emission from top leg return { pref*ME_Zuno_qg(pa,pb,pg,p1,pn,plbar,pl,aptype,bptype,ltype,Zprop,stw2,ctw)/(t1_top * t2_top) }; } // This is a qq event assert(is_anyquark(bptype)); const double t1_bot = (pa-pg-p1).m2(); const double t2_bot = (pb-pn-pZ).m2(); std::vector<double> res = ME_Zuno_qQ(pa,pb,pg,p1,pn,plbar,pl,aptype,bptype,ltype,Zprop,stw2,ctw); assert(res.size() == 3); res[0] *= pref/(t1_top * t2_top); res[1] *= pref/(t1_bot * t2_bot); res[2] *= pref/sqrt(t1_top * t2_top * t1_bot * t2_bot); return res; } /** Matrix element squared for backwards extremal qqbar tree-level current-current * scattering With Z+Jets * * @param qptype PDG ID of final state quark in qqbar pair * @param bptype Incoming particle b PDG ID * @param pa Incoming particle a momentum * @param pb Incoming particle b momentum * @param pq Final state quark momentum * @param pqbar Final state anti-quark momentum * @param pn Final state particle n momentum * @param plbar Final state positron momentum * @param pl Final state electron momentum * @param Zprop Z properties * @param stw2 Value of sin(theta_w)^2 * @param ctw Value of cos(theta_w) * @returns ME Squared for qqbar_exb Tree-Level Current-Current Scattering * * @note The qqbar_exf contribution can be calculated by reversing the argument ordering. */ std::vector<double> ME_Z_exqqbar_current( const ParticleID qptype, const ParticleID bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & pq, CLHEP::HepLorentzVector const & pqbar, CLHEP::HepLorentzVector const & pn, const ParticleID ltype, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, ParticleProperties const & Zprop, const double stw2, const double ctw ){ using namespace currents; const auto pZ = pl + plbar; const double t1_top = (pa-pq-pqbar-pZ).m2(); const double t2_top = (pb-pn).m2(); if (is_gluon(bptype)) { // This is a gg event -> Z emission from top leg return { K(bptype, pn, pb)/C_F * ME_ZExqqbar_gg(pa,pb,pq,pqbar,pn,plbar,pl,qptype,ltype,Zprop,stw2,ctw) / (t1_top * t2_top) }; } // This is a gq event assert(is_anyquark(bptype)); const double t1_bot = (pa-pq-pqbar).m2(); const double t2_bot = (pb-pn-pZ).m2(); std::vector<double> res = ME_ZExqqbar_gq(pa,pb,pq,pqbar,pn,plbar,pl,qptype,bptype,ltype,Zprop,stw2,ctw); assert(res.size() == 3); res[0] /= (t1_top * t2_top); res[1] /= (t1_bot * t2_bot); res[2] /= sqrt(t1_top * t2_top * t1_bot * t2_bot); return res; } /** Matrix element squared for central qqbar tree-level current-current * scattering With Z+Jets * * @param aptype Incoming particle a PDG ID * @param bptype Incoming particle b PDG ID * @param qptype PDG ID of final state quark in qqbar pair * @param qbar_first Ordering of the qqbar pair (true: qbar-q, false: q-qbar) * @param nabove Number of gluons emitted before central qqbarpair * @param pa Incoming particle a momentum * @param pb Incoming particle b momentum * @param partons Vector of all outgoing partons * @param plbar Final state positron momentum * @param pl Final state electron momentum * @param Zprop Z properties * @param stw2 Value of sin(theta_w)^2 * @param ctw Value of cos(theta_w) * @returns ME Squared for central qqbar Tree-Level Current-Current Scattering */ std::vector<double> ME_Z_qqbar_mid_current( const ParticleID aptype, const ParticleID bptype, const ParticleID qptype, const bool qbar_first, const int nabove, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<CLHEP::HepLorentzVector> const & partons, const ParticleID ltype, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, ParticleProperties const & Zprop, const double stw2, const double ctw ){ using namespace currents; std::vector<double> res; if(is_gluon(aptype)) { if(is_gluon(bptype)) { // gg event res = { ME_ZCenqqbar_gg(pa,pb,plbar,pl,partons,qbar_first,nabove,qptype,ltype,Zprop,stw2,ctw) }; } else { // gq event res = ME_ZCenqqbar_gq(pa,pb,plbar,pl,partons,qbar_first,nabove,bptype,qptype,ltype,Zprop,stw2,ctw); } } else { assert(is_anyquark(bptype)); // qq event res = ME_ZCenqqbar_qq(pa,pb,plbar,pl,partons,qbar_first,nabove,aptype,bptype,qptype,ltype,Zprop,stw2,ctw); } const double wt = K(aptype, partons.front(), pa) * K(bptype, partons.back(), pb) / (4.*(N_C*N_C - 1)); for(double & me: res) me *= wt; return res; } /** \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( ParticleID aptype, ParticleID 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, double vev ){ const double t1 = (pa - p1).m2(); const double tn = (pb - pn).m2(); return K(aptype, p1, pa) *K(bptype, pn, pb) *currents::ME_H_qQ( pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb,vev ) / (t1 * tn * qH.m2() * qHp1.m2()); } /** \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 pg 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 * * @note This function assumes unordered gluon backwards from pa-p1 current. * For unof, reverse call order */ double ME_Higgs_current_uno( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pg, 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, double vev ){ const double t1 = (pa - p1 - pg).m2(); const double tn = (pn - pb).m2(); return K(aptype, p1, pa) *K(bptype, pn, pb) *currents::ME_H_unob_qQ( pg,p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev ) / (t1 * qH.m2() * qHp1.m2() * tn); } /** Matrix element squared for tree-level scattering with WW+Jets * @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 pl1bar Particle l1bar Momentum * @param pl1 Particle l1 Momentum * @param pl2bar Particle l2bar Momentum * @param pl2 Particle l2 Momentum * @returns ME Squared for Tree-Level Current-Current Scattering */ std::vector <double> ME_WW_current( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pn, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pl1bar, CLHEP::HepLorentzVector const & pl1, CLHEP::HepLorentzVector const & pl2bar, CLHEP::HepLorentzVector const & pl2, ParticleProperties const & Wprop ){ using namespace currents; if (aptype > 0 && bptype > 0) return ME_WW_qQ(p1, pl1bar, pl1, pa, pn, pl2bar, pl2, pb, Wprop); if (aptype < 0 && bptype > 0) return ME_WW_qbarQ(p1, pl1bar, pl1, pa, pn, pl2bar, pl2, pb, Wprop); if (aptype > 0 && bptype < 0) return ME_WW_qQbar(p1, pl1bar, pl1, pa, pn, pl2bar, pl2, pb, Wprop); if (aptype < 0 && bptype < 0) return ME_WW_qbarQbar(p1, pl1bar, pl1, pa, pn, pl2bar, pl2, pb, Wprop); throw std::logic_error("unreachable"); } void validate(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 MatrixElement::MatrixElement( std::function<double (double)> alpha_s, MatrixElementConfig conf ): alpha_s_{std::move(alpha_s)}, param_{std::move(conf)} { validate(param_); } std::vector<double> MatrixElement::tree_kin( Event const & ev ) const { if(!ev.valid_hej_state(param_.soft_pt_regulator)) return {0.}; if(!ev.valid_incoming()){ throw std::invalid_argument{ "Invalid momentum for one or more incoming particles. " "Incoming momenta must have vanishing mass and transverse momentum." }; } std::vector<Particle> bosons = filter_AWZH_bosons(ev.outgoing()); if(bosons.empty()) { return {tree_kin_jets(ev)}; } if(bosons.size() == 1) { switch(bosons[0].type){ case pid::Higgs: return {tree_kin_Higgs(ev)}; case pid::Wp: case pid::Wm: return {tree_kin_W(ev)}; + case pid::Z: case pid::Z_photon_mix: return tree_kin_Z(ev); // TODO case pid::photon: - case pid::Z: default: throw not_implemented("Emission of boson of unsupported type"); } } if(bosons.size() == 2) { if(bosons[0].type == pid::Wp && bosons[1].type == pid::Wp){ return tree_kin_WW(ev); } else if(bosons[0].type == pid::Wm && bosons[1].type == pid::Wm){ return tree_kin_WW(ev); } throw not_implemented("Emission of bosons of unsupported type"); } throw not_implemented("Emission of >2 bosons is unsupported"); } 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 lambda ){ 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, lambda)*C_A; } else{ wt *= C2Lipatovots(qip1, qi, pa, pb, p1, pn, lambda)*C_A; } qi = qip1; } return wt; } template<class InputIterator> std::vector <double> FKL_ladder_weight_mix( InputIterator begin_gluon, InputIterator end_gluon, CLHEP::HepLorentzVector const & q0_t, CLHEP::HepLorentzVector const & q0_b, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pn, const double lambda ){ double wt_top = 1; double wt_bot = 1; double wt_mix = 1; auto qi_t = q0_t; auto qi_b = q0_b; 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_t = qi_t - g; const auto qip1_b = qi_b - g; if(treat_as_extremal(*gluon_it)){ wt_top *= C2Lipatovots(qip1_t, qi_t, pa, pb, lambda)*C_A; wt_bot *= C2Lipatovots(qip1_b, qi_b, pa, pb, lambda)*C_A; wt_mix *= C2Lipatovots_Mix(qip1_t, qi_t, qip1_b, qi_b, pa, pb, lambda)*C_A; } else{ wt_top *= C2Lipatovots(qip1_t, qi_t, pa, pb, p1, pn, lambda)*C_A; wt_bot *= C2Lipatovots(qip1_b, qi_b, pa, pb, p1, pn, lambda)*C_A; wt_mix *= C2Lipatovots_Mix(qip1_t, qi_t, qip1_b, qi_b, pa, pb, p1, pn, lambda)*C_A; } qi_t = qip1_t; qi_b = qip1_b; } return {wt_top, wt_bot, wt_mix}; } std::vector<Particle> tag_extremal_jet_partons( Event const & ev ){ auto out_partons = filter_partons(ev.outgoing()); if(out_partons.size() == ev.jets().size()){ // no additional emissions in extremal jets, don't need to tag anything for(auto & parton: out_partons){ parton.p.set_user_index(NO_EXTREMAL_JET_IDX); } return out_partons; } auto const & jets = ev.jets(); std::vector<fastjet::PseudoJet> extremal_jets; if(! is_backward_g_to_h(ev)) { auto most_backward = begin(jets); // skip jets caused by unordered emission or qqbar if(ev.type() == event_type::unob || ev.type() == event_type::qqbar_exb){ assert(jets.size() >= 2); ++most_backward; } extremal_jets.emplace_back(*most_backward); } if(! is_forward_g_to_h(ev)) { auto most_forward = end(jets) - 1; if(ev.type() == event_type::unof || ev.type() == event_type::qqbar_exf){ assert(jets.size() >= 2); --most_forward; } extremal_jets.emplace_back(*most_forward); } const auto extremal_jet_indices = ev.particle_jet_indices( extremal_jets ); assert(extremal_jet_indices.size() == out_partons.size()); for(std::size_t i = 0; i < out_partons.size(); ++i){ assert(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 tree_kin_jets_qqbar_mid( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<Particle> const & partons, double lambda ){ const auto backmidquark = std::find_if( begin(partons)+1, end(partons)-1, [](Particle const & s){ return s.type != pid::gluon; } ); assert(backmidquark!=end(partons)-1); const bool qbar_first = is_antiquark(backmidquark->type); const auto pq = to_HepLorentzVector(*(backmidquark+(qbar_first?1:0))); const auto pqbar = to_HepLorentzVector(*(backmidquark+(qbar_first?0:1))); const auto p1 = to_HepLorentzVector(partons[0]); const auto pn = to_HepLorentzVector(partons[partons.size() - 1]); const auto begin_ladder_1 = cbegin(partons) + 1; const auto end_ladder_1 = (backmidquark); const auto begin_ladder_2 = (backmidquark+2); const auto end_ladder_2 = cend(partons) - 1; const int nabove = std::distance(begin_ladder_1, end_ladder_1); const auto q0 = pa - p1; // t-channel momentum after qqbar auto q3 = q0; for(auto parton_it = begin_ladder_1; parton_it != begin_ladder_2; ++parton_it){ q3 -= to_HepLorentzVector(*parton_it); } std::vector<CLHEP::HepLorentzVector> partonsHLV; partonsHLV.reserve(partons.size()); for(Particle const & parton: partons) { partonsHLV.push_back(to_HepLorentzVector(parton)); } const double current_factor = ME_qqbar_mid_current( aptype, bptype, nabove, pa, pb, partonsHLV, qbar_first ); const double ladder_factor = FKL_ladder_weight( begin_ladder_1, end_ladder_1, q0, pa, pb, p1, pn, lambda )*FKL_ladder_weight( begin_ladder_2, end_ladder_2, q3, pa, pb, p1, pn, lambda ); return current_factor*ladder_factor; } template<class InIter, class partIter> double tree_kin_jets_qqbar(InIter begin_in, InIter end_in, partIter begin_part, partIter end_part, double lambda){ const auto pgin = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(end_in-1)); const auto p1 = to_HepLorentzVector(*begin_part); const auto p2 = to_HepLorentzVector(*(begin_part+1)); const auto pn = to_HepLorentzVector(*(end_part-1)); assert((begin_in)->type==pid::gluon); // Incoming a must be gluon. const double current_factor = ME_qqbar_current( (end_in-1)->type, pgin, p1, p2, pn, pb )/(4.*(N_C*N_C - 1.)); const double ladder_factor = FKL_ladder_weight( (begin_part+2), (end_part-1), pgin-p1-p2, pgin, pb, p1, pn, lambda ); return current_factor*ladder_factor; } template<class InIter, class partIter> double tree_kin_jets_uno(InIter begin_in, InIter end_in, partIter begin_part, partIter end_part, double lambda ){ const auto pa = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(end_in-1)); const auto pg = to_HepLorentzVector(*begin_part); const auto p1 = to_HepLorentzVector(*(begin_part+1)); const auto pn = to_HepLorentzVector(*(end_part-1)); const double current_factor = ME_uno_current( (begin_in)->type, (end_in-1)->type, pg, pn, pb, p1, pa )/(4.*(N_C*N_C - 1.)); const double ladder_factor = FKL_ladder_weight( (begin_part+2), (end_part-1), pa-p1-pg, pa, pb, p1, pn, lambda ); return current_factor*ladder_factor; } } // namespace double MatrixElement::tree_kin_jets(Event const & ev) const { auto const & incoming = ev.incoming(); const auto partons = tag_extremal_jet_partons(ev); if (ev.type()==event_type::FKL){ 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, param_.regulator_lambda ); } if (ev.type()==event_type::unordered_backward){ return tree_kin_jets_uno(incoming.begin(), incoming.end(), partons.begin(), partons.end(), param_.regulator_lambda); } if (ev.type()==event_type::unordered_forward){ return tree_kin_jets_uno(incoming.rbegin(), incoming.rend(), partons.rbegin(), partons.rend(), param_.regulator_lambda); } if (ev.type()==event_type::extremal_qqbar_backward){ return tree_kin_jets_qqbar(incoming.begin(), incoming.end(), partons.begin(), partons.end(), param_.regulator_lambda); } if (ev.type()==event_type::extremal_qqbar_forward){ return tree_kin_jets_qqbar(incoming.rbegin(), incoming.rend(), partons.rbegin(), partons.rend(), param_.regulator_lambda); } if (ev.type()==event_type::central_qqbar){ return tree_kin_jets_qqbar_mid(incoming[0].type, incoming[1].type, to_HepLorentzVector(incoming[0]), to_HepLorentzVector(incoming[1]), partons, param_.regulator_lambda); } throw std::logic_error("Cannot reweight non-resummable processes in Pure Jets"); } namespace { double tree_kin_W_FKL( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<Particle> const & partons, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, double lambda, ParticleProperties const & Wprop ){ auto p1 = to_HepLorentzVector(partons[0]); auto pn = to_HepLorentzVector(partons[partons.size() - 1]); const auto begin_ladder = cbegin(partons) + 1; const auto end_ladder = cend(partons) - 1; bool wc = aptype==partons[0].type; //leg b emits w auto q0 = pa - p1; if(!wc) q0 -= pl + plbar; const double current_factor = ME_W_current( aptype, bptype, pn, pb, p1, pa, plbar, pl, wc, Wprop ); const double ladder_factor = FKL_ladder_weight( begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda ); return current_factor*ladder_factor; } template<class InIter, class partIter> double tree_kin_W_uno(InIter begin_in, partIter begin_part, partIter end_part, const CLHEP::HepLorentzVector & plbar, const CLHEP::HepLorentzVector & pl, double lambda, ParticleProperties const & Wprop ){ const auto pa = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(begin_in+1)); const auto pg = to_HepLorentzVector(*begin_part); const auto p1 = to_HepLorentzVector(*(begin_part+1)); const auto pn = to_HepLorentzVector(*(end_part-1)); bool wc = (begin_in)->type==(begin_part+1)->type; //leg b emits w auto q0 = pa - p1 - pg; if(!wc) q0 -= pl + plbar; const double current_factor = ME_W_uno_current( (begin_in)->type, (begin_in+1)->type, pn, pb, p1, pa, pg, plbar, pl, wc, Wprop ); const double ladder_factor = FKL_ladder_weight( begin_part+2, end_part-1, q0, pa, pb, p1, pn, lambda ); return current_factor*ladder_factor; } template<class InIter, class partIter> double tree_kin_W_qqbar(InIter begin_in, partIter begin_part, partIter end_part, const CLHEP::HepLorentzVector & plbar, const CLHEP::HepLorentzVector & pl, double lambda, ParticleProperties const & Wprop ){ const bool qbar_first=is_quark(*begin_part); const auto pa = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(begin_in+1)); const auto pq = to_HepLorentzVector(*(begin_part+(qbar_first?0:1))); const auto pqbar = to_HepLorentzVector(*(begin_part+(qbar_first?1:0))); const auto p1 = to_HepLorentzVector(*(begin_part)); const auto pn = to_HepLorentzVector(*(end_part-1)); const bool wc = (begin_in+1)->type!=(end_part-1)->type; //leg b emits w auto q0 = pa - pq - pqbar; if(!wc) q0 -= pl + plbar; const double current_factor = ME_W_qqbar_current( (begin_in+1)->type, pa, pb, pq, pqbar, pn, plbar, pl, wc, Wprop ); const double ladder_factor = FKL_ladder_weight( begin_part+2, end_part-1, q0, pa, pb, p1, pn, lambda ); return current_factor*ladder_factor; } double tree_kin_W_qqbar_mid( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<Particle> const & partons, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, double lambda, ParticleProperties const & Wprop ){ const auto backmidquark = std::find_if( begin(partons)+1, end(partons)-1, [](Particle const & s){ return s.type != pid::gluon; } ); assert(backmidquark!=end(partons)-1); const bool qbar_first = is_antiquark(backmidquark->type); const auto pq = to_HepLorentzVector(*(backmidquark+(qbar_first?1:0))); const auto pqbar = to_HepLorentzVector(*(backmidquark+(qbar_first?0:1))); const auto p1 = to_HepLorentzVector(partons.front()); const auto pn = to_HepLorentzVector(partons.back()); const auto begin_ladder_1 = cbegin(partons) + 1; const auto end_ladder_1 = (backmidquark); const auto begin_ladder_2 = (backmidquark+2); const auto end_ladder_2 = cend(partons) - 1; const int nabove = std::distance(begin_ladder_1, end_ladder_1); const int nbelow = std::distance(begin_ladder_2, end_ladder_2); const bool wqq = backmidquark->type != -(backmidquark+1)->type; // qqbar emit W const bool wc = !wqq && (aptype==partons.front().type); //leg b emits w assert(!wqq || !wc); auto q0 = pa - p1; // t-channel momentum after qqbar auto q3 = q0; if(wqq){ // emission from qqbar q3 -= pl + plbar; } else if(!wc) { // emission from leg a q0 -= pl + plbar; q3 -= pl + plbar; } for(auto parton_it = begin_ladder_1; parton_it != begin_ladder_2; ++parton_it){ q3 -= to_HepLorentzVector(*parton_it); } std::vector<CLHEP::HepLorentzVector> partonsHLV; partonsHLV.reserve(partons.size()); for(Particle const & parton: partons) { partonsHLV.push_back(to_HepLorentzVector(parton)); } const double current_factor = ME_W_qqbar_mid_current( aptype, bptype, nabove, nbelow, pa, pb, partonsHLV, plbar, pl, qbar_first, wqq, wc, Wprop ); const double ladder_factor = FKL_ladder_weight( begin_ladder_1, end_ladder_1, q0, pa, pb, p1, pn, lambda )*FKL_ladder_weight( begin_ladder_2, end_ladder_2, q3, pa, pb, p1, pn, lambda ); return current_factor*ladder_factor; } } // namespace double MatrixElement::tree_kin_W(Event const & ev) const { using namespace event_type; auto const & incoming(ev.incoming()); #ifndef NDEBUG // assert that there is exactly one decay corresponding to the W assert(ev.decays().size() == 1); auto const & w_boson{ std::find_if(ev.outgoing().cbegin(), ev.outgoing().cend(), [] (Particle const & p) -> bool { return std::abs(p.type) == ParticleID::Wp; }) }; assert(w_boson != ev.outgoing().cend()); assert( static_cast<long int>(ev.decays().cbegin()->first) == std::distance(ev.outgoing().cbegin(), w_boson) ); #endif // find decay products of W auto const & decay{ ev.decays().cbegin()->second }; assert(decay.size() == 2); assert( ( is_anylepton(decay.at(0)) && is_anyneutrino(decay.at(1)) ) || ( is_anylepton(decay.at(1)) && is_anyneutrino(decay.at(0)) ) ); // get lepton & neutrino CLHEP::HepLorentzVector plbar; CLHEP::HepLorentzVector pl; if (decay.at(0).type < 0){ plbar = to_HepLorentzVector(decay.at(0)); pl = to_HepLorentzVector(decay.at(1)); } else{ pl = to_HepLorentzVector(decay.at(0)); plbar = to_HepLorentzVector(decay.at(1)); } const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); const auto partons = tag_extremal_jet_partons(ev); if(ev.type() == FKL){ return tree_kin_W_FKL(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Wprop()); } if(ev.type() == unordered_backward){ return tree_kin_W_uno(cbegin(incoming), cbegin(partons), cend(partons), plbar, pl, param_.regulator_lambda, param_.ew_parameters.Wprop()); } if(ev.type() == unordered_forward){ return tree_kin_W_uno(crbegin(incoming), crbegin(partons), crend(partons), plbar, pl, param_.regulator_lambda, param_.ew_parameters.Wprop()); } if(ev.type() == extremal_qqbar_backward){ return tree_kin_W_qqbar(cbegin(incoming), cbegin(partons), cend(partons), plbar, pl, param_.regulator_lambda, param_.ew_parameters.Wprop()); } if(ev.type() == extremal_qqbar_forward){ return tree_kin_W_qqbar(crbegin(incoming), crbegin(partons), crend(partons), plbar, pl, param_.regulator_lambda, param_.ew_parameters.Wprop()); } assert(ev.type() == central_qqbar); return tree_kin_W_qqbar_mid(incoming[0].type, incoming[1].type, pa, pb, partons, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Wprop()); } namespace /* WW */ { std::vector <double> tree_kin_WW_FKL( ParticleID aptype, ParticleID bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<Particle> const & partons, CLHEP::HepLorentzVector const & pl1bar, CLHEP::HepLorentzVector const & pl1, CLHEP::HepLorentzVector const & pl2bar, CLHEP::HepLorentzVector const & pl2, double lambda, ParticleProperties const & Wprop ){ assert(is_anyquark(aptype)); assert(is_anyquark(bptype)); auto p1 = to_HepLorentzVector(partons[0]); auto pn = to_HepLorentzVector(partons[partons.size() - 1]); const std::vector <double> current_factor = ME_WW_current( aptype, bptype, pn, pb, p1, pa, pl1bar, pl1, pl2bar, pl2, Wprop ); auto const begin_ladder = cbegin(partons) + 1; auto const end_ladder = cend(partons) - 1; // pa -> W1 p1, pb -> W2 + pn const auto q0 = pa - p1 - (pl1 + pl1bar); // pa -> W2 p1, pb -> W1 + pn const auto q1 = pa - p1 - (pl2 + pl2bar); const std::vector <double> ladder_factor = FKL_ladder_weight_mix( begin_ladder, end_ladder, q0, q1, pa, pb, p1, pn, lambda ); assert(current_factor.size() == 3); assert(current_factor.size() == ladder_factor.size()); std::vector<double> result(current_factor.size()); for(size_t i=0; i<current_factor.size(); ++i){ result[i] = K_q*K_q/(4.*(N_C*N_C - 1.)) *current_factor.at(i) *ladder_factor.at(i); } const double t1_top = q0.m2(); const double t2_top = (pb-pn-pl2bar-pl2).m2(); const double t1_bot = q1.m2(); const double t2_bot = (pb-pn-pl1bar-pl1).m2(); result[0] /= t1_top * t2_top; result[1] /= t1_bot * t2_bot; result[2] /= sqrt(t1_top * t2_top * t1_bot * t2_bot); return result; } } // namespace std::vector <double> MatrixElement::tree_kin_WW(Event const & ev) const { using namespace event_type; auto const & incoming(ev.incoming()); auto const pa = to_HepLorentzVector(incoming[0]); auto const pb = to_HepLorentzVector(incoming[1]); auto const partons = tag_extremal_jet_partons(ev); // W1 & W2 assert(ev.decays().size() == 2); std::vector<CLHEP::HepLorentzVector> plbar; std::vector<CLHEP::HepLorentzVector> pl; for(auto const & decay_pair : ev.decays()) { auto const decay = decay_pair.second; // TODO: how to label W1, W2 if(decay.at(0).type < 0) { plbar.emplace_back(to_HepLorentzVector(decay.at(0))); pl .emplace_back(to_HepLorentzVector(decay.at(1))); } else { pl .emplace_back(to_HepLorentzVector(decay.at(0))); plbar.emplace_back(to_HepLorentzVector(decay.at(1))); } } if(ev.type() == FKL) { return tree_kin_WW_FKL( incoming[0].type, incoming[1].type, pa, pb, partons, plbar[0], pl[0], plbar[1], pl[1], param_.regulator_lambda, param_.ew_parameters.Wprop() ); } throw std::logic_error("Can only reweight FKL events in WW"); } namespace{ std::vector <double> tree_kin_Z_FKL( const ParticleID aptype, const ParticleID bptype, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, std::vector<Particle> const & partons, const ParticleID ltype, CLHEP::HepLorentzVector const & plbar, CLHEP::HepLorentzVector const & pl, const double lambda, ParticleProperties const & Zprop, const double stw2, const double ctw ){ const auto p1 = to_HepLorentzVector(partons[0]); const auto pn = to_HepLorentzVector(partons[partons.size() - 1]); const auto begin_ladder = cbegin(partons) + 1; const auto end_ladder = cend(partons) - 1; const std::vector <double> current_factor = ME_Z_current( aptype, bptype, pn, pb, p1, pa, ltype, plbar, pl, Zprop, stw2, ctw ); std::vector <double> ladder_factor; if(is_gluon(bptype)){ // This is a qg event const auto q0 = pa-p1-plbar-pl; ladder_factor.push_back(FKL_ladder_weight(begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda)); } else if(is_gluon(aptype)){ // This is a gq event const auto q0 = pa-p1; ladder_factor.push_back(FKL_ladder_weight(begin_ladder, end_ladder, q0, pa, pb, p1, pn, lambda)); } else { // This is a qq event const auto q0 = pa-p1-plbar-pl; const auto q1 = pa-p1; ladder_factor=FKL_ladder_weight_mix(begin_ladder, end_ladder, q0, q1, pa, pb, p1, pn, lambda); } std::vector <double> result; for(size_t i=0; i<current_factor.size(); ++i){ result.push_back(current_factor.at(i)*ladder_factor.at(i)); } return result; } template<class InIter, class partIter> std::vector <double> tree_kin_Z_uno( InIter begin_in, partIter begin_part, partIter end_part, const ParticleID ltype, const CLHEP::HepLorentzVector & plbar, const CLHEP::HepLorentzVector & pl, const double lambda, ParticleProperties const & Zprop, const double stw2, const double ctw ){ const auto pa = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(begin_in+1)); const auto pg = to_HepLorentzVector(*begin_part); const auto p1 = to_HepLorentzVector(*(begin_part+1)); const auto pn = to_HepLorentzVector(*(end_part-1)); const ParticleID aptype = (begin_in)->type; const ParticleID bptype = (begin_in+1)->type; // we only evaluate unordered backward -> first incoming particle must be a quark assert(is_anyquark(aptype)); const std::vector <double> current_factor = ME_Z_uno_current( aptype, bptype, pn, pb, p1, pa, pg, ltype, plbar, pl, Zprop, stw2, ctw ); std::vector <double> ladder_factor; const auto q0 = pa-pg-p1-plbar-pl; if(is_gluon(bptype)){ // This is a qg event ladder_factor.push_back(FKL_ladder_weight(begin_part+2, end_part-1, q0, pa, pb, p1, pn, lambda)); }else{ // This is a qq event const auto q1 = pa-pg-p1; ladder_factor=FKL_ladder_weight_mix(begin_part+2, end_part-1, q0, q1, pa, pb, p1, pn, lambda); } std::vector <double> result; for(size_t i=0; i<current_factor.size(); ++i){ result.push_back(current_factor.at(i)*ladder_factor.at(i)); } return result; } template<class InIter, class partIter> std::vector <double> tree_kin_Z_qqbar( InIter begin_in, partIter begin_part, partIter end_part, const ParticleID ltype, const CLHEP::HepLorentzVector & plbar, const CLHEP::HepLorentzVector & pl, const double lambda, ParticleProperties const & Zprop, const double stw2, const double ctw ){ const bool qbar_first = is_antiquark(*begin_part); const auto pa = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(begin_in+1)); const auto pq = to_HepLorentzVector(*(begin_part+(qbar_first?1:0))); const auto pqbar = to_HepLorentzVector(*(begin_part+(qbar_first?0:1))); const auto p1 = to_HepLorentzVector(*(begin_part)); const auto pn = to_HepLorentzVector(*(end_part-1)); const ParticleID qptype = (begin_part+(qbar_first?1:0))->type; const ParticleID bptype = (begin_in+1)->type; const std::vector <double> current_factor = ME_Z_exqqbar_current( qptype, bptype, pa, pb, pq, pqbar, pn, ltype, plbar, pl, Zprop, stw2, ctw ); std::vector <double> ladder_factor; const auto q0 = pa-pq-pqbar-plbar-pl; if(is_gluon(bptype)){ // This is a gg event ladder_factor.push_back(FKL_ladder_weight(begin_part+2, end_part-1, q0, pa, pb, p1, pn, lambda)); }else{ // This is a gq event const auto q1 = pa-pq-pqbar; ladder_factor=FKL_ladder_weight_mix(begin_part+2, end_part-1, q0, q1, pa, pb, p1, pn, lambda); } std::vector <double> result; for(size_t i=0; i<current_factor.size(); ++i){ result.push_back(current_factor.at(i)*ladder_factor.at(i)); } return result; } template<class InIter, class partIter> std::vector<double> tree_kin_Z_qqbar_mid( InIter begin_in, partIter begin_part, partIter end_part, const ParticleID ltype, const CLHEP::HepLorentzVector & plbar, const CLHEP::HepLorentzVector & pl, const double lambda, ParticleProperties const & Zprop, const double stw2, const double ctw ){ const ParticleID aptype = (begin_in)->type; const ParticleID bptype = (begin_in+1)->type; const auto pa = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(begin_in+1)); const auto backmidquark = std::find_if( begin_part+1, end_part-1, [](Particle const & s){ return s.type != pid::gluon; } ); assert(backmidquark!=end_part-1); const bool qbar_first = is_antiquark(backmidquark->type); const auto pq = to_HepLorentzVector(*(backmidquark+(qbar_first?1:0))); const auto pqbar = to_HepLorentzVector(*(backmidquark+(qbar_first?0:1))); const ParticleID qptype = (backmidquark+(qbar_first?1:0))->type; const auto p1 = to_HepLorentzVector(*(begin_part)); const auto pn = to_HepLorentzVector(*(end_part-1)); const auto begin_ladder_1 = begin_part + 1; const auto end_ladder_1 = (backmidquark); const auto begin_ladder_2 = (backmidquark+2); const auto end_ladder_2 = end_part - 1; const int nabove = std::distance(begin_ladder_1, end_ladder_1); std::vector<CLHEP::HepLorentzVector> partonsHLV; partonsHLV.reserve(std::distance(begin_part, end_part)); for(auto parton_it = begin_part; parton_it != end_part; ++parton_it){ partonsHLV.push_back(to_HepLorentzVector(*parton_it)); } const std::vector<double> current_factor = ME_Z_qqbar_mid_current( aptype, bptype, qptype, qbar_first, nabove, pa, pb, partonsHLV, ltype, plbar, pl, Zprop, stw2, ctw ); std::vector<double> ladder_factor; if(is_gluon(aptype)) { if(is_gluon(bptype)) { // gg event -> Z emitted from central qqbar // first t-channel momentum const auto q0 = pa - p1; // t-channel momentum after qqbar auto q3 = q0 - pl - plbar; for(auto parton_it = begin_ladder_1; parton_it != begin_ladder_2; ++parton_it){ q3 -= to_HepLorentzVector(*parton_it); } ladder_factor.push_back( FKL_ladder_weight(begin_ladder_1, end_ladder_1, q0, pa, pb, p1, pn, lambda) * FKL_ladder_weight(begin_ladder_2, end_ladder_2, q3, pa, pb, p1, pn, lambda) ); } else { // gq event -> Z emitted from central qqbar or bottom leg auto q_bot = pa - p1; // q0_mid = q0_bot -> no need to evaluate FKL_ladder_weight_mix for the first ladder const double ladder_1 = FKL_ladder_weight(begin_ladder_1, end_ladder_1, q_bot, pa, pb, p1, pn, lambda); for(auto parton_it = begin_ladder_1; parton_it != begin_ladder_2; ++parton_it) { q_bot -= to_HepLorentzVector(*parton_it); } auto q_mid = q_bot - pl - plbar; const auto ladder_2 = FKL_ladder_weight_mix(begin_ladder_2, end_ladder_2, q_mid, q_bot, pa, pb, p1, pn, lambda); for(size_t i=0; i<ladder_2.size(); ++i){ ladder_factor.push_back(ladder_1 * ladder_2.at(i)); } } } else { assert(is_anyquark(bptype)); // qq event -> 3 contributions auto q_t = pa - p1 - pl - plbar; auto q_b = pa - p1; // q0_bot = q0_mid -> only need to evaluate FKL_ladder_weight_mix[top][mid] const auto ladder_1_top_mid = FKL_ladder_weight_mix(begin_ladder_1, end_ladder_1, q_t, q_b, pa, pb, p1, pn, lambda); enum emission_site {top, mid, bot}; double ladder[3][3]; ladder[top][top] = ladder_1_top_mid.at(0); ladder[mid][mid] = ladder_1_top_mid.at(1); ladder[bot][bot] = ladder[mid][mid]; ladder[top][mid] = ladder_1_top_mid.at(2); ladder[top][bot] = ladder[top][mid]; ladder[mid][bot] = ladder[mid][mid]; for(auto parton_it = begin_ladder_1; parton_it != begin_ladder_2; ++parton_it) { q_b -= to_HepLorentzVector(*parton_it); } q_t = q_b - pl - plbar; // q3_mid = q3_top -> only need to evaluate FKL_ladder_weight_mix[top][bot] const auto ladder_2_top_bot = FKL_ladder_weight_mix(begin_ladder_2, end_ladder_2, q_t, q_b, pa, pb, p1, pn, lambda); ladder[top][top] *= ladder_2_top_bot.at(0); ladder[mid][mid] *= ladder_2_top_bot.at(0); // same as [top][top] ladder[bot][bot] *= ladder_2_top_bot.at(1); ladder[top][mid] *= ladder_2_top_bot.at(0); // same as [top][top] ladder[top][bot] *= ladder_2_top_bot.at(2); ladder[mid][bot] *= ladder_2_top_bot.at(2); // same as [top][bot] ladder_factor = {ladder[top][top], ladder[mid][mid], ladder[bot][bot], ladder[top][mid], ladder[top][bot], ladder[mid][bot]}; } assert(current_factor.size() == ladder_factor.size()); std::vector<double> res; for(size_t i=0; i<current_factor.size(); ++i) { res.push_back(current_factor.at(i)*ladder_factor.at(i)); } return res; } } // namespace std::vector<double> MatrixElement::tree_kin_Z(Event const & ev) const { using namespace event_type; auto const & incoming(ev.incoming()); // find decay products of Z auto const & decay{ ev.decays().cbegin()->second }; assert(decay.size() == 2); assert(is_anylepton(decay.at(0)) && decay.at(0).type==-decay.at(1).type); // get leptons CLHEP::HepLorentzVector plbar; CLHEP::HepLorentzVector pl; ParticleID ltype; if (decay.at(0).type < 0){ plbar = to_HepLorentzVector(decay.at(0)); pl = to_HepLorentzVector(decay.at(1)); ltype = decay.at(1).type; } else{ pl = to_HepLorentzVector(decay.at(0)); ltype = decay.at(0).type; plbar = to_HepLorentzVector(decay.at(1)); } const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); const auto partons = tag_extremal_jet_partons(ev); const double stw2 = param_.ew_parameters.sin2_tw(); const double ctw = param_.ew_parameters.cos_tw(); if(ev.type() == FKL){ return tree_kin_Z_FKL(incoming[0].type, incoming[1].type, pa, pb, partons, ltype, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Zprop(), stw2, ctw); } if(ev.type() == unordered_backward){ return tree_kin_Z_uno(cbegin(incoming), cbegin(partons), cend(partons), ltype, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Zprop(), stw2, ctw); } if(ev.type() == unordered_forward){ auto kin_rev = tree_kin_Z_uno(crbegin(incoming), crbegin(partons), crend(partons), ltype, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Zprop(), stw2, ctw); if(!is_gluon(incoming[0].type)){ // qq unordered forward: reorder contributions such that first/second // value corresponds to Z emission from top/bottom leg respectively std::swap(kin_rev[0], kin_rev[1]); } return kin_rev; } if(ev.type() == extremal_qqbar_backward){ return tree_kin_Z_qqbar(cbegin(incoming), cbegin(partons), cend(partons), ltype, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Zprop(), stw2, ctw); } if(ev.type() == extremal_qqbar_forward){ auto kin_rev = tree_kin_Z_qqbar(crbegin(incoming), crbegin(partons), crend(partons), ltype, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Zprop(), stw2, ctw); if(!is_gluon(incoming[0].type)){ // qqbar forward with incoming qg: reorder contributions such that first/second // value corresponds to Z emission from top/bottom leg respectively std::swap(kin_rev[0], kin_rev[1]); } return kin_rev; } assert(ev.type() == central_qqbar); if(!is_gluon(incoming[0].type) && is_gluon(incoming[1].type)){ // qg initiated central qqbar: reverse event to evaluate as gq auto kin_rev = tree_kin_Z_qqbar_mid(crbegin(incoming), crbegin(partons), crend(partons), ltype, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Zprop(), stw2, ctw); std::swap(kin_rev[0], kin_rev[1]); return kin_rev; } return tree_kin_Z_qqbar_mid(cbegin(incoming), cbegin(partons), cend(partons), ltype, plbar, pl, param_.regulator_lambda, param_.ew_parameters.Zprop(), stw2, ctw); } double MatrixElement::tree_kin_Higgs(Event const & ev) const { if(ev.outgoing().front().type == pid::Higgs){ return tree_kin_Higgs_first(ev); } if(ev.outgoing().back().type == pid::Higgs){ return tree_kin_Higgs_last(ev); } return tree_kin_Higgs_between(ev); } // kinetic matrix element square for backward Higgs emission // cf. eq:ME_h_jets_peripheral in developer manual, // but without factors \alpha_s and the FKL ladder double MatrixElement::MH2_backwardH( const ParticleID type_forward, CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & pH, CLHEP::HepLorentzVector const & pn ) const { using namespace currents; const double vev = param_.ew_parameters.vev(); return K(type_forward, pn, pb)*ME_H_gq( pH, pa, pn, pb, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb, vev )/(4.*(N_C*N_C - 1)*(pa-pH).m2()*(pb-pn).m2()); } // kinetic matrix element square for backward unordered emission // and forward g -> Higgs double MatrixElement::MH2_unob_forwardH( CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb, CLHEP::HepLorentzVector const & pg, CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pH ) const { using namespace currents; const double vev = param_.ew_parameters.vev(); constexpr double K_f1 = K_q; constexpr double nhel = 4.; return K_f1*ME_juno_jgH( pg, p1, pa, pH, pb, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb, vev )/(nhel*(N_C*N_C - 1)*(pa - p1 - pg).m2()*(pb - pH).m2()); } double MatrixElement::tree_kin_Higgs_first(Event const & ev) const { auto const & incoming = ev.incoming(); auto const & outgoing = ev.outgoing(); assert(outgoing.front().type == pid::Higgs); if(is_anyquark(incoming.front())) { assert(incoming.front().type == outgoing[1].type); return tree_kin_Higgs_between(ev); } const auto partons = tag_extremal_jet_partons(ev); const auto pa = to_HepLorentzVector(incoming.front()); const auto pb = to_HepLorentzVector(incoming.back()); const auto pH = to_HepLorentzVector(outgoing.front()); const auto end_ladder = end(partons) - ((ev.type() == event_type::unof)?2:1); const auto pn = to_HepLorentzVector(*end_ladder); const double ladder = FKL_ladder_weight( begin(partons), end_ladder, pa - pH, pa, pb, pa, pn, param_.regulator_lambda ); if(ev.type() == event_type::unof) { const auto pg = to_HepLorentzVector(outgoing.back()); return MH2_unob_forwardH( pb, pa, pg, pn, pH )*ladder; } return MH2_backwardH( incoming.back().type, pa, pb, pH, pn )*ladder; } double MatrixElement::tree_kin_Higgs_last(Event const & ev) const { auto const & incoming = ev.incoming(); auto const & outgoing = ev.outgoing(); assert(outgoing.back().type == pid::Higgs); if(is_anyquark(incoming.back())) { assert(incoming.back().type == outgoing[outgoing.size()-2].type); return tree_kin_Higgs_between(ev); } const auto partons = tag_extremal_jet_partons(ev); const auto pa = to_HepLorentzVector(incoming.front()); const auto pb = to_HepLorentzVector(incoming.back()); const auto pH = to_HepLorentzVector(outgoing.back()); auto begin_ladder = begin(partons) + 1; auto q0 = pa - to_HepLorentzVector(partons.front()); if(ev.type() == event_type::unob) { q0 -= to_HepLorentzVector(*begin_ladder); ++begin_ladder; } const auto p1 = to_HepLorentzVector(*(begin_ladder - 1)); const double ladder = FKL_ladder_weight( begin_ladder, end(partons), q0, pa, pb, p1, pb, param_.regulator_lambda ); if(ev.type() == event_type::unob) { const auto pg = to_HepLorentzVector(outgoing.front()); return MH2_unob_forwardH( pa, pb, pg, p1, pH )*ladder; } return MH2_backwardH( incoming.front().type, pb, pa, pH, p1 )*ladder; } namespace { template<class InIter, class partIter> double tree_kin_Higgs_uno(InIter begin_in, InIter end_in, partIter begin_part, partIter end_part, CLHEP::HepLorentzVector const & qH, CLHEP::HepLorentzVector const & qHp1, double mt, bool inc_bot, double mb, double vev ){ const auto pa = to_HepLorentzVector(*begin_in); const auto pb = to_HepLorentzVector(*(end_in-1)); const auto pg = to_HepLorentzVector(*begin_part); const auto p1 = to_HepLorentzVector(*(begin_part+1)); const auto pn = to_HepLorentzVector(*(end_part-1)); return ME_Higgs_current_uno( (begin_in)->type, (end_in-1)->type, pg, pn, pb, p1, pa, qH, qHp1, mt, inc_bot, mb, vev ); } } // namespace double MatrixElement::tree_kin_Higgs_between(Event const & ev) 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); const auto partons = tag_extremal_jet_partons(ev); const auto pa = to_HepLorentzVector(incoming[0]); const auto pb = to_HepLorentzVector(incoming[1]); auto p1 = to_HepLorentzVector( partons[(ev.type() == unob)?1:0] ); auto pn = to_HepLorentzVector( partons[partons.size() - ((ev.type() == unof)?2:1)] ); auto first_after_Higgs = begin(partons) + (the_Higgs-begin(outgoing)); assert( (first_after_Higgs == end(partons) && ( (ev.type() == unob) || partons.back().type != pid::gluon )) || first_after_Higgs->rapidity() >= the_Higgs->rapidity() ); assert( (first_after_Higgs == begin(partons) && ( (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 = NAN; if(ev.type() == FKL){ 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, param_.ew_parameters.vev() ); } else if(ev.type() == unob){ current_factor = tree_kin_Higgs_uno( begin(incoming), end(incoming), begin(partons), end(partons), qH, qH-pH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb, param_.ew_parameters.vev() ); const auto p_unob = to_HepLorentzVector(partons.front()); q0 -= p_unob; p1 += p_unob; ++begin_ladder; } else if(ev.type() == unof){ current_factor = tree_kin_Higgs_uno( rbegin(incoming), rend(incoming), rbegin(partons), rend(partons), qH-pH, qH, param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb, param_.ew_parameters.vev() ); pn += to_HepLorentzVector(partons.back()); --end_ladder; } else{ throw std::logic_error("Can only reweight FKL or uno processes in H+Jets"); } const double ladder_factor = FKL_ladder_weight( begin_ladder, first_after_Higgs, q0, pa, pb, p1, pn, param_.regulator_lambda )*FKL_ladder_weight( first_after_Higgs, end_ladder, qH - pH, pa, pb, p1, pn, param_.regulator_lambda ); return current_factor/(4.*(N_C*N_C-1.))*ladder_factor; } namespace { double get_AWZH_coupling(Event const & ev, double alpha_s, double alpha_w) { std::vector<Particle> bosons = filter_AWZH_bosons(ev.outgoing()); if(bosons.empty()) { return 1.; } if(bosons.size() == 1) { switch(bosons[0].type){ case pid::Higgs: return alpha_s*alpha_s; case pid::Wp: case pid::Wm: return alpha_w*alpha_w; + case pid::Z: case pid::Z_photon_mix: return alpha_w*alpha_w; // TODO case pid::photon: - case pid::Z: default: throw not_implemented("Emission of boson of unsupported type"); } } if(bosons.size() == 2) { if(bosons[0].type == pid::Wp && bosons[1].type == pid::Wp) { return alpha_w*alpha_w*alpha_w*alpha_w; } else if(bosons[0].type == pid::Wm && bosons[1].type == pid::Wm) { return alpha_w*alpha_w*alpha_w*alpha_w; } throw not_implemented("Emission of bosons of unsupported type"); } throw not_implemented("Emission of >2 bosons is unsupported"); } } // namespace double MatrixElement::tree_param(Event const & ev, double mur) const { assert(is_resummable(ev.type())); const auto begin_partons = ev.begin_partons(); const auto end_partons = ev.end_partons(); const auto num_partons = std::distance(begin_partons, end_partons); const double alpha_s = alpha_s_(mur); const double gs2 = 4.*M_PI*alpha_s; double res = std::pow(gs2, num_partons); if(param_.log_correction){ // use alpha_s(q_perp), evolved to mur assert(num_partons >= 2); const auto first_emission = std::next(begin_partons); const auto last_emission = std::prev(end_partons); for(auto parton = first_emission; parton != last_emission; ++parton){ res *= 1. + alpha_s/(2.*M_PI)*BETA0*std::log(mur/parton->perp()); } } return get_AWZH_coupling(ev, alpha_s, param_.ew_parameters.alpha_w())*res; } } // namespace HEJ diff --git a/t/ME_data/ME_Z_neutrinos.dat b/t/ME_data/ME_Z_neutrinos.dat index 421e467..a07a1d7 100644 --- a/t/ME_data/ME_Z_neutrinos.dat +++ b/t/ME_data/ME_Z_neutrinos.dat @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:996c87f8837c224735955c624ab956fe9ba6d77c925d94f0f6157d593813bfba +oid sha256:9bfce3b3ed8fc682317e5691615bc8c1735ab11e3a88016aa1246fdf21e64db8 size 160000