diff --git a/include/HEJ/utility.hh b/include/HEJ/utility.hh index b57d7df..6bbf2bf 100644 --- a/include/HEJ/utility.hh +++ b/include/HEJ/utility.hh @@ -1,104 +1,114 @@ /** * \file * \brief Contains various utilities * * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019-2020 * \copyright GPLv2 or later */ #pragma once #include #include #include #include "boost/core/demangle.hpp" #include "fastjet/PseudoJet.hh" namespace HEJ { inline std::string join( std::string const & /* delim */ ){ return ""; } inline std::string join( std::string const & /* delim */, std::string const & str ){ return str; } //! Join strings with a delimiter /** * @param delim Delimiter to be put between consecutive strings * @param first First string * @param second Second string * @param rest Remaining strings */ template std::string join( std::string const & delim, std::string const & first, std::string const & second, Strings&&... rest ){ return join(delim, first + delim + second, std::forward(rest)...); } //! Return the name of the argument's type template std::string type_string(T&& /*unused*/){ return boost::core::demangle(typeid(T).name()); } //! Eliminate compiler warnings for unused variables template constexpr void ignore(T&&... /*unused*/) {} //! Check whether two doubles are closer than ep >= 0 to each other inline constexpr bool nearby_ep(double a, double b, double ep){ assert(ep >= 0); return std::abs(a-b) < ep; } //! Check whether all components of two PseudoJets are closer than ep to each other inline bool nearby_ep( fastjet::PseudoJet const & pa, fastjet::PseudoJet const & pb, double ep ){ assert(ep >= 0); for(size_t i = 0; i < 4; ++i){ if(!nearby_ep(pa[i], pb[i], ep)) return false; } return true; } inline bool nearby( fastjet::PseudoJet const & pa, fastjet::PseudoJet const & pb, double const norm = 1. ){ return nearby_ep(pa, pb, 1e-7*norm); } namespace detail { template struct ArrayTag{ using type = typename ArrayTag, Ns...>::type; }; template struct ArrayTag { using type = std::array; }; } // helper for multidimensional std::array, for example // MultiArray = std::array, N2> template using MultiArray = typename detail::ArrayTag::type; + //! Check momentum conservation + template + bool momentum_conserved(Event const &ev, const double tolerance = 1e-7) { + fastjet::PseudoJet diff; + for (auto const &in : ev.incoming()) diff += in.p; + const double norm = diff.E(); + for (auto const &out : ev.outgoing()) diff -= out.p; + return nearby_ep(diff, fastjet::PseudoJet{}, tolerance*norm); + } + } // namespace HEJ diff --git a/src/PhaseSpacePoint.cc b/src/PhaseSpacePoint.cc index 450be43..8e1b0a5 100644 --- a/src/PhaseSpacePoint.cc +++ b/src/PhaseSpacePoint.cc @@ -1,987 +1,983 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019-2020 * \copyright GPLv2 or later */ #include "HEJ/PhaseSpacePoint.hh" #include #include #include #include #include #include #include #include #include #include #include "fastjet/ClusterSequence.hh" #include "fastjet/JetDefinition.hh" #include "HEJ/Constants.hh" #include "HEJ/Event.hh" #include "HEJ/JetSplitter.hh" #include "HEJ/PDG_codes.hh" #include "HEJ/RNG.hh" #include "HEJ/event_types.hh" #include "HEJ/kinematics.hh" #include "HEJ/resummation_jet.hh" #include "HEJ/utility.hh" namespace HEJ { namespace { constexpr int MAX_JET_USER_IDX = 1000; bool is_nonjet_parton(fastjet::PseudoJet const & parton){ assert(parton.user_index() != -1); return parton.user_index() > MAX_JET_USER_IDX; } bool is_jet_parton(fastjet::PseudoJet const & parton){ assert(parton.user_index() != -1); return parton.user_index() <= MAX_JET_USER_IDX; } namespace user_idx { //! user indices for partons with extremal rapidity enum ID: int { qqbar_mid1 = -9, qqbar_mid2 = -8, qqbarb = -7, qqbarf = -6, unob = -5, unof = -4, backward_fkl = -3, forward_fkl = -2, }; } // namespace user_idx using UID = user_idx::ID; double phase_space_normalisation( int num_Born_jets, int num_out_partons ){ return std::pow(16.*std::pow(M_PI,3), num_Born_jets - num_out_partons); } } // namespace Event::EventData to_EventData(PhaseSpacePoint psp){ Event::EventData result; result.incoming = std::move(psp).incoming_; // NOLINT(bugprone-use-after-move) result.outgoing = std::move(psp).outgoing_; // NOLINT(bugprone-use-after-move) // technically Event::EventData doesn't have to be sorted, // but PhaseSpacePoint should be anyway assert( std::is_sorted( begin(result.outgoing), end(result.outgoing), rapidity_less{} ) ); assert(result.outgoing.size() >= 2); static_assert( std::numeric_limits::has_quiet_NaN, "no quiet NaN for double" ); constexpr double nan = std::numeric_limits::quiet_NaN(); result.decays = std::move(psp).decays_; // NOLINT(bugprone-use-after-move) result.parameters.central = {nan, nan, psp.weight()}; // NOLINT(bugprone-use-after-move) return result; } std::vector PhaseSpacePoint::cluster_jets( std::vector const & partons ) const{ fastjet::ClusterSequence cs(partons, param_.jet_param.def); return sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt)); } bool PhaseSpacePoint::pass_resummation_cuts( std::vector const & jets ) const{ return cluster_jets(jets).size() == jets.size(); } namespace { // find iterators to central qqbar emission auto get_central_qqbar(Event const & ev) { // find born quarks (ignore extremal partons) auto const firstquark = std::find_if( std::next(ev.begin_partons()), std::prev(ev.end_partons(), 2), [](Particle const & s){ return (is_anyquark(s)); } ); // assert that it is a q-q_bar pair. assert(std::distance(firstquark, ev.end_partons()) != 2); assert( ( is_quark(*firstquark) && is_antiquark(*std::next(firstquark)) ) || ( is_antiquark(*firstquark) && is_quark(*std::next(firstquark)) ) ); return std::make_pair(firstquark, std::next(firstquark)); } //! returns index of most backward q-qbar jet template int get_back_quark_jet(Event const & ev, Iterator firstquark){ // find jets at FO corresponding to the quarks // technically this isn't necessary for LO std::vector const born_indices{ ev.particle_jet_indices() }; const auto firstquark_idx = std::distance(ev.begin_partons(), firstquark); int const firstjet_idx = born_indices[firstquark_idx]; assert(firstjet_idx>0); assert( born_indices[firstquark_idx+1] == firstjet_idx+1 ); return firstjet_idx; } //! returns index of most backward q-qbar jet int getBackQuarkJet(Event const & ev){ const auto firstquark = get_central_qqbar(ev).first; return get_back_quark_jet(ev, firstquark); } } // namespace double PhaseSpacePoint::estimate_emission_rapidity_range( Event const & ev ) const { assert(std::is_sorted(begin(ev.jets()), end(ev.jets()), rapidity_less{})); const double ymin = is_backward_g_to_h(ev)? ev.outgoing().front().rapidity(): most_backward_FKL(ev.jets()).rapidity(); const double ymax = is_forward_g_to_h(ev)? ev.outgoing().back().rapidity(): most_forward_FKL(ev.jets()).rapidity(); double delta_y = ymax - ymin; // neglect tiny probability for emission between central qqbar pair if(ev.type() == event_type::central_qqbar) { const int qjet = getBackQuarkJet(ev); delta_y -= ev.jets()[qjet+1].rapidity() - ev.jets()[qjet].rapidity(); } assert(delta_y >= 0); return delta_y; } double PhaseSpacePoint::estimate_ng_mean(Event const & ev) const { // Formula derived from fit in arXiv:1805.04446 (see Fig. 2) constexpr double GLUONS_PER_RAPIDITY = 0.975052; return GLUONS_PER_RAPIDITY*estimate_emission_rapidity_range(ev); } int PhaseSpacePoint::sample_ng(Event const & event, RNG & ran){ if (param_.nlo.enabled == true){ std::uniform_int_distribution<> dist(0, 1); const int ng = dist(ran); weight_ *= 2; assert(ng < 2); assert(ng >= 0); return ng; } else{ const double ng_mean = estimate_ng_mean(event); std::poisson_distribution dist(ng_mean); const int ng = dist(ran); assert(ng >= 0); assert(ng < MAX_JET_USER_IDX); weight_ *= std::tgamma(ng + 1)*std::exp(ng_mean)*std::pow(ng_mean, -ng); return ng; } } void PhaseSpacePoint::boost_AWZH_bosons_from( std::vector const & boosted_bosons, Event const & event ){ auto const & from = event.outgoing(); auto find_AWZH = [](Particle const & p){ return is_AWZH_boson(p); }; size_t boosted_idx = 0; for( auto original_boson = std::find_if(begin(from), end(from), find_AWZH); original_boson != end(from); original_boson = std::find_if(++original_boson, end(from), find_AWZH), ++boosted_idx ){ auto insertion_point = std::lower_bound( begin(outgoing_), end(outgoing_), *original_boson, rapidity_less{} ); // copy AWZH particle const int new_idx = std::distance(begin(outgoing_), insertion_point); assert(new_idx >= 0); // insert invalidates distance outgoing_.insert(insertion_point, {original_boson->type, boosted_bosons[boosted_idx], original_boson->colour} ); assert(outgoing_[new_idx].type == original_boson->type); assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{})); // copy & boost decay products const int idx = std::distance(begin(from), original_boson); assert(idx >= 0); const auto decay_it = event.decays().find(idx); if(decay_it != end(event.decays())){ auto decayparticles = decay_it->second; // change the momenta of the decay products. fastjet::PseudoJet sum; for(auto & particle: decayparticles){ auto & p = particle.p; // boost _to_ rest frame of input boson p.unboost(original_boson->p); // then boost _from_ rest frame of shuffled boson p.boost(boosted_bosons[boosted_idx]); if(p.E() < std::abs(p.pz())){ throw std::underflow_error("Reshuffled decay with E<|pz|"); } sum += p; } if(!nearby(boosted_bosons[boosted_idx], sum, boosted_bosons[boosted_idx].E())){ throw std::underflow_error("Boson and decays momenta do not match after reshuffling"); } decays_.emplace(new_idx, decayparticles); } } } namespace { template void label_extremal_qqbar( ConstIterator born_begin, ConstIterator born_end, Iterator first_out ){ // find born quarks const auto firstquark = std::find_if( born_begin, born_end-1, [](Particle const & s){ return (is_anyquark(s)); } ); assert(firstquark != born_end-1); const auto secondquark = std::find_if( firstquark+1, born_end, [](Particle const & s){ return (is_anyquark(s)); } ); assert(secondquark != born_end); assert( ( is_quark(*firstquark) && is_antiquark(*secondquark) ) || ( is_antiquark(*firstquark) && is_quark(*secondquark) )); assert(first_out->type == ParticleID::gluon); assert((first_out+1)->type == ParticleID::gluon); // copy type from born first_out->type = firstquark->type; (first_out+1)->type = secondquark->type; } } // namespace void PhaseSpacePoint::label_qqbar(Event const & event){ assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{})); assert(filter_partons(outgoing_).size() == outgoing_.size()); if(qqbarb_){ label_extremal_qqbar(event.outgoing().cbegin(), event.outgoing().cend(), outgoing_.begin() ); return; } if(qqbarf_){ // same as qqbarb with reversed order label_extremal_qqbar( event.outgoing().crbegin(), event.outgoing().crend(), outgoing_.rbegin() ); return; } // central qqbar const auto firstquark = get_central_qqbar(event).first; // find jets at FO corresponding to the quarks // technically this isn't necessary for LO const auto firstjet_idx = get_back_quark_jet(event, firstquark); // find corresponding jets after resummation fastjet::ClusterSequence cs{to_PseudoJet(outgoing_), param_.jet_param.def}; auto const jets = fastjet::sorted_by_rapidity( cs.inclusive_jets( param_.jet_param.min_pt )); std::vector const resum_indices{ cs.particle_jet_indices({jets}) }; // assert that jets didn't move assert(nearby_ep( ( event.jets().cbegin()+firstjet_idx )->rapidity(), jets[ firstjet_idx ].rapidity(), 1e-2) ); assert(nearby_ep( ( event.jets().cbegin()+firstjet_idx+1 )->rapidity(), jets[ firstjet_idx+1 ].rapidity(), 1e-2) ); // find last partons in first (central) jet size_t idx_out = 0; for(size_t i=resum_indices.size()-2; i>0; --i) if(resum_indices[i] == firstjet_idx){ idx_out = i; break; } assert(idx_out != 0); // check that there is sufficient pt in jets from the quarks const double minpartonjetpt = 1. - param_.soft_pt_regulator; if (outgoing_[idx_out].p.pt()pt()){ weight_=0.; status_ = StatusCode::wrong_jets; return; } if (outgoing_[idx_out+1].p.pt()pt()){ weight_=0.; status_ = StatusCode::wrong_jets; return; } // check that no additional emission between jets // such configurations are possible if we have an gluon gets generated // inside the rapidities of the qqbar chain, but clusted to a // differnet/outside jet. Changing this is non trivial if(resum_indices[idx_out+1] != resum_indices[idx_out]+1){ weight_=0.; status_ = StatusCode::gluon_in_qqbar; return; } outgoing_[idx_out].type = firstquark->type; outgoing_[idx_out+1].type = std::next(firstquark)->type; } void PhaseSpacePoint::label_quarks(Event const & ev){ const auto WZEmit = std::find_if( begin(ev.outgoing()), end(ev.outgoing()), [](Particle const & s){ return (std::abs(s.type) == pid::Wp || s.type == pid::Z_photon_mix); } ); if (WZEmit != end(ev.outgoing())){ if(!qqbarb_) { const size_t backward_FKL_idx = unob_?1:0; const auto backward_FKL = std::next(ev.begin_partons(), backward_FKL_idx); outgoing_[backward_FKL_idx].type = backward_FKL->type; } if(!qqbarf_) { const size_t forward_FKL_idx = unof_?1:0; const auto forward_FKL = std::prev(ev.end_partons(), 1+forward_FKL_idx); outgoing_.rbegin()[unof_].type = forward_FKL->type; // NOLINT } } else { if(!is_backward_g_to_h(ev)) { most_backward_FKL(outgoing_).type = ev.incoming().front().type; } if(!is_forward_g_to_h(ev)) { most_forward_FKL(outgoing_).type = ev.incoming().back().type; } } if(qqbar_mid_||qqbarb_||qqbarf_){ label_qqbar(ev); } } PhaseSpacePoint::PhaseSpacePoint( Event const & ev, PhaseSpacePointConfig conf, RNG & ran ): unob_{ev.type() == event_type::unob}, unof_{ev.type() == event_type::unof}, qqbarb_{ev.type() == event_type::qqbar_exb}, qqbarf_{ev.type() == event_type::qqbar_exf}, qqbar_mid_{ev.type() == event_type::qqbar_mid}, param_{std::move(conf)}, status_{unspecified} { // legacy code: override new variable with old if(param_.max_ext_soft_pt_fraction){ param_.soft_pt_regulator = *param_.max_ext_soft_pt_fraction; param_.max_ext_soft_pt_fraction = {}; } weight_ = 1; auto const & Born_jets = ev.jets(); const int ng = sample_ng(ev, ran); weight_ /= std::tgamma(ng + 1); const int ng_jets = sample_ng_jets(ev, ng, ran); std::vector out_partons = gen_non_jet( ng - ng_jets, CMINPT, param_.jet_param.min_pt, ran ); const auto qperp = std::accumulate( begin(out_partons), end(out_partons), fastjet::PseudoJet{} ); std::vector jets; std::vector bosons; std::tie(jets, bosons) = reshuffle(ev, qperp); if(weight_ == 0.) { status_ = failed_reshuffle; return; } if(! pass_resummation_cuts(jets)){ status_ = failed_resummation_cuts; weight_ = 0.; return; } // split jets in multiple partons std::vector jet_partons = split( ev, jets, ng_jets, ran ); if(weight_ == 0.) { status_ = StatusCode::failed_split; return; } const double ymin = is_backward_g_to_h(ev)? ev.outgoing().front().rapidity(): most_backward_FKL(jet_partons).rapidity() ; const double ymax = is_forward_g_to_h(ev)? ev.outgoing().back().rapidity(): most_forward_FKL(jet_partons).rapidity() ; if(qqbar_mid_){ const int qqbar_backjet = getBackQuarkJet(ev); rescale_qqbar_rapidities( out_partons, jets, ymin, ymax, qqbar_backjet ); } else{ rescale_rapidities(out_partons, ymin, ymax); } if(! cluster_jets(out_partons).empty()){ weight_ = 0.; status_ = StatusCode::empty_jets; return; } std::sort(begin(out_partons), end(out_partons), rapidity_less{}); assert( std::is_sorted(begin(jet_partons), end(jet_partons), rapidity_less{}) ); const auto first_jet_parton = out_partons.insert( end(out_partons), begin(jet_partons), end(jet_partons) ); std::inplace_merge( begin(out_partons), first_jet_parton, end(out_partons), rapidity_less{} ); if(! jets_ok(ev, out_partons)){ weight_ = 0.; status_ = StatusCode::wrong_jets; return; } weight_ *= phase_space_normalisation(Born_jets.size(), out_partons.size()); outgoing_.reserve(out_partons.size() + 2); // two slots for possible A, W, Z, H for( auto it = std::make_move_iterator(out_partons.begin()); it != std::make_move_iterator(out_partons.end()); ++it ){ outgoing_.emplace_back( Particle{pid::gluon, *it, {}}); } assert(!outgoing_.empty()); label_quarks(ev); if(weight_ == 0.) { //! @TODO optimise s.t. this is not possible // status is handled internally return; } // reattach bosons & decays if(!bosons.empty()){ try { boost_AWZH_bosons_from(bosons, ev); } catch (std::underflow_error const & e){ weight_ = 0.; status_ = StatusCode::failed_reshuffle; return; } } reconstruct_incoming(ev.incoming()); status_ = StatusCode::good; } std::vector PhaseSpacePoint::gen_non_jet( int const ng_non_jet, double const ptmin, double const ptmax, RNG & ran ){ // heuristic parameters for pt sampling const double ptpar = 1.3 + ng_non_jet/5.; const double temp1 = std::atan((ptmax - ptmin)/ptpar); std::vector partons(ng_non_jet); for(int i = 0; i < ng_non_jet; ++i){ const double r1 = ran.flat(); const double pt = ptmin + ptpar*std::tan(r1*temp1); const double temp2 = std::cos(r1*temp1); const double phi = 2*M_PI*ran.flat(); weight_ *= 2.0*M_PI*pt*ptpar*temp1/(temp2*temp2); // we don't know the allowed rapidity span yet, // set a random value to be rescaled later on const double y = ran.flat(); partons[i].reset_PtYPhiM(pt, y, phi); // Set user index higher than any jet-parton index // in order to assert that these are not inside jets partons[i].set_user_index(i + 1 + MAX_JET_USER_IDX); assert(ptmin-1e-5 <= partons[i].pt() && partons[i].pt() <= ptmax+1e-5); } assert(std::all_of(partons.cbegin(), partons.cend(), is_nonjet_parton)); return sorted_by_rapidity(partons); } void PhaseSpacePoint::rescale_qqbar_rapidities( std::vector & out_partons, std::vector const & jets, const double ymin1, const double ymax2, const int qqbar_backjet ){ const double ymax1 = jets[qqbar_backjet].rapidity(); const double ymin2 = jets[qqbar_backjet+1].rapidity(); constexpr double ep = 1e-7; const double tot_y = ymax1 - ymin1 + ymax2 - ymin2; std::vector> refpart( out_partons.begin(), out_partons.end()); double ratio = (ymax1 - ymin1)/tot_y; const auto gap{ std::find_if(refpart.begin(), refpart.end(), [ratio](fastjet::PseudoJet const & p){ return (p.rapidity()>=ratio);} ) }; double ymin = ymin1; double ymax = ymax1; double dy = ymax - ymin - 2*ep; double offset = 0.; for(auto it_part=refpart.begin(); it_part & partons, double ymin, double ymax ){ constexpr double ep = 1e-7; for(auto & parton: partons){ assert(0 <= parton.rapidity() && parton.rapidity() <= 1); const double dy = ymax - ymin - 2*ep; const double y = ymin + ep + dy*parton.rapidity(); parton.reset_momentum_PtYPhiM(parton.pt(), y, parton.phi()); weight_ *= dy; assert(ymin <= parton.rapidity() && parton.rapidity() <= ymax); } } namespace { template auto min(T const & a, T const & b, Rest&&... r) { using std::min; return min(a, min(b, std::forward(r)...)); } } double PhaseSpacePoint::probability_in_jet(Event const & ev) const{ const double dy = estimate_emission_rapidity_range(ev); const double R = param_.jet_param.def.R(); // jets into which we predominantly emit const int njets = ev.jets().size() - unof_ - unob_ - qqbarb_ - qqbarf_; //NOLINT assert(njets >= 1); const size_t nextremal_jets = std::min(njets, 2); const double p_J_y_large = (njets - nextremal_jets/2.)*R*R/(2.*dy); const double p_J_y0 = njets*R/M_PI; return min(p_J_y_large, p_J_y0, 1.); } int PhaseSpacePoint::sample_ng_jets(Event const & event, int ng, RNG & ran){ const double p_J = probability_in_jet(event); std::binomial_distribution<> bin_dist(ng, p_J); const int ng_J = bin_dist(ran); weight_ *= std::pow(p_J, -ng_J)*std::pow(1 - p_J, ng_J - ng); return ng_J; } std::pair< std::vector, std::vector > PhaseSpacePoint::reshuffle( Event const & ev, fastjet::PseudoJet const & q ){ // Create a copy of the outgoing momenta not containing decay products std::vector born_momenta; born_momenta.reserve(ev.jets().size()); std::transform(ev.jets().cbegin(), ev.jets().cend(), back_inserter(born_momenta), [](fastjet::PseudoJet const & t) { return &t; }); auto bosons = filter_AWZH_bosons(ev.outgoing()); std::vector p_boson_momenta; std::transform(bosons.cbegin(), bosons.cend(), back_inserter(p_boson_momenta), [](Particle const & t) { return &(t.p); }); std::vector boson_momenta; std::transform(bosons.cbegin(), bosons.cend(), back_inserter(boson_momenta), [](Particle const & t) { return t.p; }); // reshuffle all momenta if(q == fastjet::PseudoJet{0, 0, 0, 0}) return {ev.jets(), boson_momenta}; // add bosons to reshuffling if(!bosons.empty()) { born_momenta.insert( born_momenta.end(), p_boson_momenta.begin(), p_boson_momenta.end() ); } auto shuffle_momenta = resummation_jet_momenta(born_momenta, q); if(shuffle_momenta.empty()){ weight_ = 0; return {}; } // additional Jacobian to ensure Born integration over delta gives 1 weight_ *= resummation_jet_weight(born_momenta, q); // take out bosons again if(!bosons.empty()) { std::vector shuffle_bosons; for(size_t i = 0; i < bosons.size(); ++i) { shuffle_bosons.push_back(shuffle_momenta.back()); shuffle_momenta.pop_back(); } std::reverse(shuffle_bosons.begin(), shuffle_bosons.end()); return {shuffle_momenta, shuffle_bosons}; } return {shuffle_momenta, {}}; } std::vector PhaseSpacePoint::distribute_jet_partons( int ng_jets, std::vector const & jets, RNG & ran ){ size_t first_valid_jet = 0; size_t num_valid_jets = jets.size(); const double R_eff = 5./3.*param_.jet_param.def.R(); // if there is an unordered jet too far away from the FKL jets // then extra gluon constituents of the unordered jet would // violate the FKL rapidity ordering if((unob_||qqbarb_) && jets[0].delta_R(jets[1]) > R_eff){ ++first_valid_jet; --num_valid_jets; } else if((unof_||qqbarf_) && jets[jets.size()-1].delta_R(jets[jets.size()-2]) > R_eff){ --num_valid_jets; } std::vector np(jets.size(), 1); for(int i = 0; i < ng_jets; ++i){ ++np[first_valid_jet + ran.flat() * num_valid_jets]; } weight_ *= std::pow(num_valid_jets, ng_jets); return np; } #ifndef NDEBUG namespace { bool tagged_FKL_backward( std::vector const & jet_partons ){ return std::find_if( begin(jet_partons), end(jet_partons), [](fastjet::PseudoJet const & p){ return p.user_index() == UID::backward_fkl; } ) != end(jet_partons); } bool tagged_FKL_forward( std::vector const & jet_partons ){ // the most forward FKL parton is most likely near the end of jet_partons; // start search from there return std::find_if( jet_partons.rbegin(), jet_partons.rend(), [](fastjet::PseudoJet const & p){ return p.user_index() == UID::forward_fkl; } ) != jet_partons.rend(); } } // namespace #endif std::vector PhaseSpacePoint::split( Event const & Born_event, std::vector const & jets, int ng_jets , RNG & ran ){ return split( Born_event, jets, distribute_jet_partons(ng_jets, jets, ran), ran ); } bool PhaseSpacePoint::pass_extremal_cuts( fastjet::PseudoJet const & ext_parton, fastjet::PseudoJet const & jet ) const{ if(ext_parton.pt() < param_.min_extparton_pt) return false; return (ext_parton - jet).pt()/jet.pt() < param_.soft_pt_regulator; } std::vector PhaseSpacePoint::split( Event const & Born_event, std::vector const & jets, std::vector const & np, RNG & ran ){ assert(! jets.empty()); assert(jets.size() == np.size()); assert(pass_resummation_cuts(jets)); constexpr auto no_such_jet_idx = std::numeric_limits::max(); const size_t most_backward_FKL_idx = is_backward_g_to_h(Born_event)? no_such_jet_idx: // we have backward Higgs instead of FKL jet (0 + unob_ + qqbarb_); // NOLINT const size_t most_forward_FKL_idx = is_forward_g_to_h(Born_event)? no_such_jet_idx: // we have forward Higgs instead of FKL jet (jets.size() - 1 - unof_ - qqbarf_); // NOLINT const size_t qqbar_jet_idx = qqbar_mid_? getBackQuarkJet(Born_event): no_such_jet_idx; auto const & jet = param_.jet_param; const JetSplitter jet_splitter{jet.def, jet.min_pt}; std::vector jet_partons; // randomly distribute jet gluons among jets for(size_t i = 0; i < jets.size(); ++i){ auto split_res = jet_splitter.split(jets[i], np[i], ran); weight_ *= split_res.weight; if(weight_ == 0) return {}; assert( std::all_of( begin(split_res.constituents), end(split_res.constituents), is_jet_parton ) ); const auto first_new_parton = jet_partons.insert( end(jet_partons), begin(split_res.constituents), end(split_res.constituents) ); // mark uno and extremal FKL emissions here so we can check // their position once all emissions are generated // also mark qqbar_mid partons, and apply appropriate pt cut. auto extremal = end(jet_partons); if (i == most_backward_FKL_idx){ //FKL backward emission extremal = std::min_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index(UID::backward_fkl); } else if(((unob_ || qqbarb_) && i == 0)){ // unordered/qqbarb extremal = std::min_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index((unob_)?UID::unob:UID::qqbarb); } else if (i == most_forward_FKL_idx){ extremal = std::max_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index(UID::forward_fkl); } else if(((unof_ || qqbarf_) && i == jets.size() - 1)){ // unordered/qqbarf extremal = std::max_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index((unof_)?UID::unof:UID::qqbarf); } else if((qqbar_mid_ && i == qqbar_jet_idx)){ extremal = std::max_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index(UID::qqbar_mid1); } else if((qqbar_mid_ && i == qqbar_jet_idx+1)){ extremal = std::min_element( first_new_parton, end(jet_partons), rapidity_less{} ); extremal->set_user_index(UID::qqbar_mid2); } if( extremal != end(jet_partons) && !pass_extremal_cuts(*extremal, jets[i]) ){ weight_ = 0; return {}; } } assert(is_backward_g_to_h(Born_event) || tagged_FKL_backward(jet_partons)); assert(is_forward_g_to_h(Born_event) || tagged_FKL_forward(jet_partons)); std::sort(begin(jet_partons), end(jet_partons), rapidity_less{}); if( !extremal_ok(Born_event, jet_partons) || !split_preserved_jets(jets, jet_partons) ){ weight_ = 0.; return {}; } return jet_partons; } bool PhaseSpacePoint::extremal_ok( Event const & Born_event, std::vector const & partons ) const{ assert(std::is_sorted(begin(partons), end(partons), rapidity_less{})); if(unob_ && partons.front().user_index() != UID::unob) return false; if(unof_ && partons.back().user_index() != UID::unof) return false; if(qqbarb_ && partons.front().user_index() != UID::qqbarb) return false; if(qqbarf_ && partons.back().user_index() != UID::qqbarf) return false; if(is_backward_g_to_h(Born_event)) { if(partons.front().rapidity() < Born_event.outgoing().front().rapidity()){ return false; } } else if(most_backward_FKL(partons).user_index() != UID::backward_fkl) { return false; } if(is_forward_g_to_h(Born_event)) { return partons.back().rapidity() <= Born_event.outgoing().back().rapidity(); } return most_forward_FKL(partons).user_index() == UID::forward_fkl; } bool PhaseSpacePoint::split_preserved_jets( std::vector const & jets, std::vector const & jet_partons ) const{ assert(std::is_sorted(begin(jets), end(jets), rapidity_less{})); const auto split_jets = cluster_jets(jet_partons); // this can happen if two overlapping jets // are both split into more than one parton if(split_jets.size() != jets.size()) return false; for(size_t i = 0; i < split_jets.size(); ++i){ // this can happen if there are two overlapping jets // and a parton is assigned to the "wrong" jet if(!nearby_ep(jets[i].rapidity(), split_jets[i].rapidity(), 1e-2)){ return false; } } return true; } template Particle const & PhaseSpacePoint::most_backward_FKL( std::vector const & partons ) const{ return partons[0 + unob_ + qqbarb_]; } template Particle const & PhaseSpacePoint::most_forward_FKL( std::vector const & partons ) const{ const size_t idx = partons.size() - 1 - unof_ - qqbarf_; assert(idx < partons.size()); return partons[idx]; } template Particle & PhaseSpacePoint::most_backward_FKL( std::vector & partons ) const{ return partons[0 + unob_ + qqbarb_]; } template Particle & PhaseSpacePoint::most_forward_FKL( std::vector & partons ) const{ const size_t idx = partons.size() - 1 - unof_ - qqbarf_; assert(idx < partons.size()); return partons[idx]; } bool PhaseSpacePoint::contains_idx( fastjet::PseudoJet const & jet, fastjet::PseudoJet const & parton ) const { auto const & constituents = jet.constituents(); const int idx = parton.user_index(); const bool injet = std::find_if( begin(constituents), end(constituents), [idx](fastjet::PseudoJet const & con){return con.user_index() == idx;} ) != end(constituents); const double minpartonjetpt = 1. - param_.soft_pt_regulator; return ((parton.pt()>minpartonjetpt*jet.pt())&&injet); } bool PhaseSpacePoint::jets_ok( Event const & Born_event, std::vector const & partons ) const{ fastjet::ClusterSequence cs(partons, param_.jet_param.def); const auto jets = sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt)); if(jets.size() != Born_event.jets().size()) return false; int in_jet = 0; for(auto const & jet : jets){ assert(jet.has_constituents()); for(auto && parton: jet.constituents()){ if(is_nonjet_parton(parton)) return false; } in_jet += jet.constituents().size(); } const int expect_in_jet = std::count_if( partons.cbegin(), partons.cend(), is_jet_parton ); if(in_jet != expect_in_jet) return false; // note that PseudoJet::contains does not work here if( !is_backward_g_to_h(Born_event) && !contains_idx(most_backward_FKL(jets), most_backward_FKL(partons)) ) return false; if( !is_forward_g_to_h(Born_event) && !contains_idx(most_forward_FKL(jets), most_forward_FKL(partons)) ) return false; if(unob_ && !contains_idx(jets.front(), partons.front())) return false; if(qqbarb_ && !contains_idx(jets.front(), partons.front())) return false; if(unof_ && !contains_idx(jets.back(), partons.back())) return false; if(qqbarf_ && !contains_idx(jets.back(), partons.back())) return false; #ifndef NDEBUG for(size_t i = 0; i < jets.size(); ++i){ assert(nearby_ep(jets[i].rapidity(), Born_event.jets()[i].rapidity(), 1e-2)); } #endif return true; } void PhaseSpacePoint::reconstruct_incoming( std::array const & Born_incoming ){ std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_); for(size_t i = 0; i < incoming_.size(); ++i){ incoming_[i].type = Born_incoming[i].type; } assert(momentum_conserved()); } bool PhaseSpacePoint::momentum_conserved() const{ - fastjet::PseudoJet diff; - for(auto const & in: incoming()) diff += in.p; - const double norm = diff.E(); - for(auto const & out: outgoing()) diff -= out.p; - return nearby(diff, fastjet::PseudoJet{}, norm); + return HEJ::momentum_conserved(*this); } } //namespace HEJ