diff --git a/FixedOrderGen/include/PhaseSpacePoint.hh b/FixedOrderGen/include/PhaseSpacePoint.hh index fe0266f..04699ee 100644 --- a/FixedOrderGen/include/PhaseSpacePoint.hh +++ b/FixedOrderGen/include/PhaseSpacePoint.hh @@ -1,231 +1,272 @@ /** \file PhaseSpacePoint.hh * \brief Contains the PhaseSpacePoint Class * * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019-2020 * \copyright GPLv2 or later */ #pragma once #include #include #include #include #include +#include "boost/iterator/filter_iterator.hpp" + #include "HEJ/Event.hh" #include "HEJ/Particle.hh" #include "HEJ/PDG_codes.hh" #include "fastjet/PseudoJet.hh" #include "Decay.hh" #include "Status.hh" #include "Subleading.hh" namespace HEJ { class EWConstants; class PDF; class RNG; } namespace HEJFOG { class JetParameters; class Process; //! A point in resummation phase space class PhaseSpacePoint{ public: + + //! Iterator over partons + using ConstPartonIterator = boost::filter_iterator< + bool (*)(HEJ::Particle const &), + std::vector::const_iterator + >; + //! Reverse Iterator over partons + using ConstReversePartonIterator = std::reverse_iterator< + ConstPartonIterator>; + //! Default PhaseSpacePoint Constructor PhaseSpacePoint() = delete; //! PhaseSpacePoint Constructor /** * @param proc The process to generate * @param jet_properties Jet defintion & cuts * @param pdf The pdf set (used for sampling) * @param E_beam Energie of the beam * @param subl_chance Chance to turn a potentially unordered * emission into an actual one * @param subl_channels Possible subleading channels. * see HEJFOG::Subleading * @param particle_properties Properties of producted boson * * Initially, only FKL phase space points are generated. subl_chance gives * the change of turning one emissions into a subleading configuration, * i.e. either unordered or central quark/anti-quark pair. Unordered * emissions require that the most extremal emission in any direction is * a quark or anti-quark and the next emission is a gluon. Quark/anti-quark * pairs are only generated for W processes. At most one subleading * emission will be generated in this way. */ PhaseSpacePoint( Process const & proc, JetParameters const & jet_properties, HEJ::PDF & pdf, double E_beam, double subl_chance, Subleading subl_channels, ParticlesDecayMap const & particle_decays, HEJ::EWConstants const & ew_parameters, HEJ::RNG & ran ); //! Get Weight Function /** * @returns Weight of Event */ double weight() const{ return weight_; } Status status() const{ return status_; } //! Get Incoming Function /** * @returns Incoming Particles */ std::array const & incoming() const{ return incoming_; } //! Get Outgoing Function /** * @returns Outgoing Particles */ std::vector const & outgoing() const{ return outgoing_; } std::unordered_map> const & decays() const{ return decays_; } + //! Iterator to the first outgoing parton + ConstPartonIterator begin_partons() const; + //! Iterator to the first outgoing parton + ConstPartonIterator cbegin_partons() const; + + //! Iterator to the end of the outgoing partons + ConstPartonIterator end_partons() const; + //! Iterator to the end of the outgoing partons + ConstPartonIterator cend_partons() const; + + //! Reverse Iterator to the first outgoing parton + ConstReversePartonIterator rbegin_partons() const; + //! Reverse Iterator to the first outgoing parton + ConstReversePartonIterator crbegin_partons() const; + //! Reverse Iterator to the first outgoing parton + ConstReversePartonIterator rend_partons() const; + //! Reverse Iterator to the first outgoing parton + ConstReversePartonIterator crend_partons() const; + private: friend HEJ::Event::EventData to_EventData(PhaseSpacePoint psp); /** * @internal * @brief Generate LO parton momentum * * @param count Number of partons to generate * @param is_pure_jets If true ensures momentum conservation in x and y * @param jet_param Jet properties to fulfil * @param max_pt max allowed pt for a parton (typically E_CMS) * @param ran Random Number Generator * * @returns Momentum of partons * * Ensures that each parton is in its own jet. * Generation is independent of parton flavour. Output is sorted in rapidity. */ std::vector gen_LO_partons( int count, bool is_pure_jets, JetParameters const & jet_param, double max_pt, HEJ::RNG & ran ); HEJ::Particle gen_boson( HEJ::ParticleID bosonid, double mass, double width, HEJ::RNG & ran ); template fastjet::PseudoJet gen_last_momentum( ParticleMomenta const & other_momenta, double mass_square, double y ) const; bool jets_ok( std::vector const & Born_jets, std::vector const & partons ) const; /** * @internal * @brief Generate incoming partons according to the PDF * * @param uf Scale used in the PDF */ void reconstruct_incoming( Process const & proc, Subleading subl_channels, HEJ::PDF & pdf, double E_beam, double uf, HEJ::RNG & ran ); HEJ::ParticleID generate_incoming_id( std::size_t beam_idx, double x, double uf, HEJ::PDF & pdf, std::bitset<11> allowed_partons, HEJ::RNG & ran ); bool momentum_conserved(double ep) const; - HEJ::Particle const & most_backward_FKL( - std::vector const & partons - ) const; - HEJ::Particle const & most_forward_FKL( - std::vector const & partons - ) const; - HEJ::Particle & most_backward_FKL(std::vector & partons) const; - HEJ::Particle & most_forward_FKL(std::vector & partons) const; bool extremal_FKL_ok( std::vector const & partons ) const; double random_normal(double stddev, HEJ::RNG & ran); /** * @internal * @brief Turns a FKL configuration into a subleading one * * @param chance Change to switch to subleading configuration * @param channels Allowed channels for subleading process * @param proc Process to decide which subleading * configurations are allowed * * With a chance of "chance" the FKL configuration is either turned into * a unordered configuration or, for A/W/Z bosons, a configuration with * a central quark/anti-quark pair. */ void maybe_turn_to_subl(double chance, Subleading channels, Process const & proc, HEJ::RNG & ran); void turn_to_uno(bool can_be_uno_backward, bool can_be_uno_forward, HEJ::RNG & ran); void turn_to_qqx(bool allow_strange, HEJ::RNG & ran); //! decay where we select the decay channel std::vector decay_boson( HEJ::Particle const & parent, std::vector const & decays, HEJ::RNG & ran ); //! generate decay products of a boson std::vector decay_boson( HEJ::Particle const & parent, std::vector const & decays, HEJ::RNG & ran ); /// @brief setup outgoing partons to ensure correct coupling to boson void couple_boson(HEJ::ParticleID boson, HEJ::RNG & ran); Decay select_decay_channel( std::vector const & decays, HEJ::RNG & ran ); double gen_hard_pt( int np, double ptmin, double ptmax, double y, HEJ::RNG & ran ); double gen_soft_pt(int np, double ptmax, HEJ::RNG & ran); double gen_parton_pt( int count, JetParameters const & jet_param, double ptmax, double y, HEJ::RNG & ran ); + //! Iterator over partons (non-const) + using PartonIterator = boost::filter_iterator< + bool (*)(HEJ::Particle const &), + std::vector::iterator + >; + //! Reverse Iterator over partons (non-const) + using ReversePartonIterator = std::reverse_iterator; + + //! Iterator to the first outgoing parton (non-const) + PartonIterator begin_partons(); + //! Iterator to the end of the outgoing partons (non-const) + PartonIterator end_partons(); + + //! Reverse Iterator to the first outgoing parton (non-const) + ReversePartonIterator rbegin_partons(); + //! Reverse Iterator to the first outgoing parton (non-const) + ReversePartonIterator rend_partons(); + double weight_; Status status_; std::array incoming_; std::vector outgoing_; //! Particle decays in the format {outgoing index, decay products} std::unordered_map> decays_; }; //! Extract HEJ::Event::EventData from PhaseSpacePoint HEJ::Event::EventData to_EventData(PhaseSpacePoint psp); } diff --git a/FixedOrderGen/src/EventGenerator.cc b/FixedOrderGen/src/EventGenerator.cc index c956a97..5c9c779 100644 --- a/FixedOrderGen/src/EventGenerator.cc +++ b/FixedOrderGen/src/EventGenerator.cc @@ -1,99 +1,101 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019-2020 * \copyright GPLv2 or later */ #include "EventGenerator.hh" #include #include #include "HEJ/Config.hh" #include "HEJ/Event.hh" #include "HEJ/event_types.hh" #include "HEJ/EWConstants.hh" #include "HEJ/exceptions.hh" #include "HEJ/HiggsCouplingSettings.hh" #include "Process.hh" #include "Beam.hh" #include "JetParameters.hh" #include "PhaseSpacePoint.hh" namespace HEJFOG { EventGenerator::EventGenerator( Process process, Beam beam, HEJ::ScaleGenerator scale_gen, JetParameters jets, int pdf_id, double subl_change, Subleading subl_channels, ParticlesDecayMap particle_decays, HEJ::HiggsCouplingSettings Higgs_coupling, HEJ::EWConstants ew_parameters, std::shared_ptr ran ): pdf_{pdf_id, beam.particles[0], beam.particles[1]}, ME_{ [this](double mu){ return pdf_.Halphas(mu); }, HEJ::MatrixElementConfig{ false, std::move(Higgs_coupling), ew_parameters } }, scale_gen_{std::move(scale_gen)}, process_{std::move(process)}, jets_{std::move(jets)}, beam_{std::move(beam)}, subl_change_{subl_change}, subl_channels_{subl_channels}, particle_decays_{std::move(particle_decays)}, ew_parameters_{ew_parameters}, ran_{std::move(ran)} { } HEJ::optional EventGenerator::gen_event(){ HEJFOG::PhaseSpacePoint psp{ process_, jets_, pdf_, beam_.energy, subl_change_, subl_channels_, particle_decays_, ew_parameters_, *ran_ }; status_ = psp.status(); if(status_ != good) return {}; HEJ::Event ev = scale_gen_( HEJ::Event{ to_EventData( std::move(psp) ).cluster( jets_.def, jets_.min_pt) } ); - if(!is_resummable(ev.type())) + if(!is_resummable(ev.type())){ + std::cerr << ev << std::endl; throw HEJ::not_implemented("Tried to generate a event type, " "which is not yet implemented in HEJ."); + } ev.generate_colours(*ran_); const double shat = HEJ::shat(ev); const double xa = (ev.incoming()[0].E()-ev.incoming()[0].pz())/(2.*beam_.energy); const double xb = (ev.incoming()[1].E()+ev.incoming()[1].pz())/(2.*beam_.energy); // evaluate matrix element ev.parameters() *= ME_.tree(ev)/(shat*shat); // and PDFs ev.central().weight *= pdf_.pdfpt(0,xa,ev.central().muf, ev.incoming()[0].type); ev.central().weight *= pdf_.pdfpt(0,xb,ev.central().muf, ev.incoming()[1].type); for(std::size_t i = 0; i < ev.variations().size(); ++i){ auto & var = ev.variations(i); var.weight *= pdf_.pdfpt(0,xa,var.muf, ev.incoming()[0].type); var.weight *= pdf_.pdfpt(0,xb,var.muf, ev.incoming()[1].type); } return ev; } } diff --git a/FixedOrderGen/src/PhaseSpacePoint.cc b/FixedOrderGen/src/PhaseSpacePoint.cc index 38831f1..4ecb68c 100644 --- a/FixedOrderGen/src/PhaseSpacePoint.cc +++ b/FixedOrderGen/src/PhaseSpacePoint.cc @@ -1,735 +1,776 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019-2020 * \copyright GPLv2 or later */ #include "PhaseSpacePoint.hh" #include #include #include #include #include #include #include #include #include #include #include "fastjet/ClusterSequence.hh" #include "HEJ/Constants.hh" #include "HEJ/EWConstants.hh" #include "HEJ/exceptions.hh" #include "HEJ/kinematics.hh" #include "HEJ/Particle.hh" #include "HEJ/PDF.hh" #include "HEJ/RNG.hh" #include "HEJ/utility.hh" #include "JetParameters.hh" #include "Process.hh" namespace { using namespace HEJ; static_assert( std::numeric_limits::has_quiet_NaN, "no quiet NaN for double" ); constexpr double NaN = std::numeric_limits::quiet_NaN(); } // namespace anonymous namespace HEJFOG { Event::EventData to_EventData(PhaseSpacePoint psp){ //! @TODO Same function already in HEJ Event::EventData result; result.incoming = std::move(psp).incoming_; assert(result.incoming.size() == 2); result.outgoing = std::move(psp).outgoing_; // 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); result.decays = std::move(psp).decays_; result.parameters.central = {NaN, NaN, psp.weight()}; return result; } + PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::begin_partons() const { + return cbegin_partons(); + } + PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::cbegin_partons() const { + return boost::make_filter_iterator( + static_cast(HEJ::is_parton), + cbegin(outgoing()), + cend(outgoing()) + ); + } + + PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::end_partons() const { + return cend_partons(); + } + PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::cend_partons() const { + return boost::make_filter_iterator( + static_cast(HEJ::is_parton), + cend(outgoing()), + cend(outgoing()) + ); + } + + PhaseSpacePoint::ConstReversePartonIterator PhaseSpacePoint::rbegin_partons() const { + return crbegin_partons(); + } + PhaseSpacePoint::ConstReversePartonIterator PhaseSpacePoint::crbegin_partons() const { + return std::reverse_iterator( cend_partons() ); + } + + PhaseSpacePoint::ConstReversePartonIterator PhaseSpacePoint::rend_partons() const { + return crend_partons(); + } + PhaseSpacePoint::ConstReversePartonIterator PhaseSpacePoint::crend_partons() const { + return std::reverse_iterator( cbegin_partons() ); + } + + PhaseSpacePoint::PartonIterator PhaseSpacePoint::begin_partons() { + return boost::make_filter_iterator( + static_cast(HEJ::is_parton), + begin(outgoing_), + end(outgoing_) + ); + } + + PhaseSpacePoint::PartonIterator PhaseSpacePoint::end_partons() { + return boost::make_filter_iterator( + static_cast(HEJ::is_parton), + end(outgoing_), + end(outgoing_) + ); + } + + PhaseSpacePoint::ReversePartonIterator PhaseSpacePoint::rbegin_partons() { + return std::reverse_iterator( end_partons() ); + } + + PhaseSpacePoint::ReversePartonIterator PhaseSpacePoint::rend_partons() { + return std::reverse_iterator( begin_partons() ); + } + namespace { bool can_swap_to_uno( Particle const & p1, Particle const & p2 ){ - return is_parton(p1) - && p1.type != pid::gluon - && p2.type == pid::gluon; + assert(is_parton(p1) && is_parton(p2)); + return p1.type != pid::gluon + && p2.type == pid::gluon; } - size_t count_gluons(std::vector::const_iterator first, - std::vector::const_iterator last + size_t count_gluons(PhaseSpacePoint::ConstPartonIterator first, + PhaseSpacePoint::ConstPartonIterator last ){ return std::count_if(first, last, [](Particle const & p) {return p.type == pid::gluon;}); } /** assumes FKL configurations between first and last, * else there can be a quark in a non-extreme position * e.g. uno configuration gqg would pass */ Subleading possible_qqx( - std::vector::const_iterator first, - std::vector::const_iterator last + PhaseSpacePoint::ConstPartonIterator first, + PhaseSpacePoint::ConstPartonIterator last ){ using namespace subleading; + assert(std::distance(first,last)>2); Subleading channels(~0l); channels.reset(qqx); channels.reset(eqqx); channels.reset(cqqx); auto const ngluon = count_gluons(first,last); if(ngluon < 2) return channels; return channels.set(qqx); //! @TODO stub if(ngluon == 2 && first->type==pid::gluon){ //! backwards qqx? return channels.set(eqqx); } channels.set(cqqx); return channels.set(eqqx); } bool is_AWZ_proccess(Process const & proc){ return proc.boson && is_AWZ_boson(*proc.boson); } bool is_up_type(Particle const & part){ return is_anyquark(part) && !(std::abs(part.type)%2); } bool is_down_type(Particle const & part){ return is_anyquark(part) && std::abs(part.type)%2; } bool can_couple_to_W(Particle const & part, pid::ParticleID const W_id){ const int W_charge = W_id>0?1:-1; return std::abs(part.type)<5 && ( (W_charge*part.type > 0 && is_up_type(part)) || (W_charge*part.type < 0 && is_down_type(part)) ); } } void PhaseSpacePoint::maybe_turn_to_subl( double chance, Subleading channels, Process const & proc, RNG & ran ){ if(proc.njets <= 2) return; assert(outgoing_.size() >= 2); // decide what kind of subleading process is allowed - const size_t nout = outgoing_.size(); - const bool can_be_uno_backward = can_swap_to_uno(outgoing_[0], outgoing_[1]); - const bool can_be_uno_forward = can_swap_to_uno(outgoing_[nout-1], outgoing_[nout-2]); + bool can_be_uno_backward = can_swap_to_uno( + *cbegin_partons(), *(++cbegin_partons()) ); + bool can_be_uno_forward = can_swap_to_uno( + *crbegin_partons(), *(++crbegin_partons()) ); + // Special case: Higgs can not be outside of uno + if(proc.boson && *proc.boson==pid::Higgs){ + if(outgoing_.begin()->type == pid::Higgs + || (++outgoing_.begin())->type==pid::Higgs){ + can_be_uno_backward = false; + } + if(outgoing_.rbegin()->type == pid::Higgs + || (++outgoing_.rbegin())->type==pid::Higgs){ + can_be_uno_forward = false; + } + } if(channels[subleading::uno]){ channels.set(subleading::uno, can_be_uno_backward || can_be_uno_forward); } - channels &= possible_qqx(outgoing_.cbegin(), outgoing_.cend()); + channels &= possible_qqx(cbegin_partons(), cend_partons()); bool allow_strange = true; if(is_AWZ_proccess(proc)) { - if(std::none_of(outgoing_.cbegin(), outgoing_.cend(), + if(std::none_of(cbegin_partons(), cend_partons(), [&proc](Particle const & p){ return can_couple_to_W(p, *proc.boson);})) { // enforce qqx if A/W/Z can't couple somewhere else // this is ensured to work through filter_partons in reconstruct_incoming channels.reset(subleading::uno); assert(channels.any()); chance = 1.; // strange not allowed for W if(std::abs(*proc.boson)== pid::Wp) allow_strange = false; } } // no subleading std::size_t const nchannels = channels.count(); if(nchannels==0) return; if(ran.flat() >= chance){ weight_ /= 1 - chance; return; } // select channel weight_ /= chance; double const step = 1./nchannels; weight_*=nchannels; unsigned selected = subleading::first; double rnd = nchannels>1?ran.flat():0.; for(; selected<=subleading::last; ++selected){ assert(rnd>=0); if(channels[selected]){ if(rndtype, (++begin_partons())->type); } - weight_ *= 2.; - } else if(can_be_uno_backward){ - std::swap(outgoing_[0].type, outgoing_[1].type); - } else { - assert(can_be_uno_forward); - std::swap(outgoing_[nout-1].type, outgoing_[nout-2].type); + return std::swap(rbegin_partons()->type, (++rbegin_partons())->type); } + if(can_be_uno_backward){ + return std::swap(begin_partons()->type, (++begin_partons())->type); + } + assert(can_be_uno_forward); + std::swap(rbegin_partons()->type, (++rbegin_partons())->type); } void PhaseSpacePoint::turn_to_qqx(const bool allow_strange, RNG & ran){ /// find first and last gluon in FKL chain auto first = std::find_if(outgoing_.begin(), outgoing_.end(), [](Particle const & p){return p.type == pid::gluon;}); - std::vector FKL_gluons; + std::vector::iterator> FKL_gluons; for(auto p = first; p!=outgoing_.end(); ++p){ - if(p->type == pid::gluon) FKL_gluons.push_back(&*p); + if(p->type == pid::gluon) FKL_gluons.push_back(p); else if(is_anyquark(*p)) break; } const size_t ng = FKL_gluons.size(); if(ng < 2) throw std::logic_error("not enough gluons to create qqx"); // select flavour of quark const double r1 = 2.*ran.flat()-1.; const double max_flavour = allow_strange?N_F:N_F-1; weight_ *= max_flavour*2; int flavour = pid::down + std::floor(std::abs(r1)*max_flavour); flavour*=r1<0.?-1:1; // select gluon for switch const size_t idx = std::floor((ng-1) * ran.flat()); weight_ *= (ng-1); FKL_gluons[idx]->type = ParticleID(flavour); FKL_gluons[idx+1]->type = ParticleID(-flavour); } template fastjet::PseudoJet PhaseSpacePoint::gen_last_momentum( ParticleMomenta const & other_momenta, const double mass_square, const double y ) const { std::array pt{0.,0.}; for (auto const & p: other_momenta) { pt[0]-= p.px(); pt[1]-= p.py(); } const double mperp = std::sqrt(pt[0]*pt[0]+pt[1]*pt[1]+mass_square); const double pz=mperp*std::sinh(y); const double E=mperp*std::cosh(y); return {pt[0], pt[1], pz, E}; } namespace { //! adds a particle to target (in correct rapidity ordering) //! @returns positon of insertion auto insert_particle(std::vector & target, Particle && particle ){ const auto pos = std::upper_bound( begin(target),end(target),particle,rapidity_less{} ); target.insert(pos, std::move(particle)); return pos; } } PhaseSpacePoint::PhaseSpacePoint( Process const & proc, JetParameters const & jet_param, PDF & pdf, double E_beam, double const subl_chance, Subleading const subl_channels, ParticlesDecayMap const & particle_decays, EWConstants const & ew_parameters, RNG & ran ){ assert(proc.njets >= 2); status_ = good; weight_ = 1; const int nout = proc.njets + (proc.boson?1:0) + proc.boson_decay.size(); outgoing_.reserve(nout); // generate parton momenta const bool is_pure_jets = (nout == proc.njets); auto partons = gen_LO_partons( proc.njets, is_pure_jets, jet_param, E_beam, ran ); // pre fill flavour with gluons for(auto&& p_out: partons) { outgoing_.emplace_back(Particle{pid::gluon, std::move(p_out), {}}); } if(status_ != good) return; if(proc.boson){ // decay boson auto const & boson_prop = ew_parameters.prop(*proc.boson) ; auto boson{ gen_boson(*proc.boson, boson_prop.mass, boson_prop.width, ran) }; const auto pos{insert_particle(outgoing_, std::move(boson))}; const size_t boson_idx = std::distance(begin(outgoing_), pos); auto const & boson_decay = particle_decays.find(*proc.boson); if( !proc.boson_decay.empty() ){ // decay given in proc decays_.emplace( boson_idx, decay_boson(outgoing_[boson_idx], proc.boson_decay, ran) ); } else if( boson_decay != particle_decays.end() && !boson_decay->second.empty() ){ // decay given explicitly decays_.emplace( boson_idx, decay_boson(outgoing_[boson_idx], boson_decay->second, ran) ); } } // normalisation of momentum-conserving delta function weight_ *= std::pow(2*M_PI, 4); /** @TODO * uf (jet_param.min_pt) doesn't correspond to our final scale choice. * The HEJ scale generators currently expect a full event as input, * so fixing this is not completely trivial */ reconstruct_incoming(proc, subl_channels, pdf, E_beam, jet_param.min_pt, ran); if(status_ != good) return; // set outgoing states - most_backward_FKL(outgoing_).type = incoming_[0].type; - most_forward_FKL(outgoing_).type = incoming_[1].type; + begin_partons()->type = incoming_[0].type; + rbegin_partons()->type = incoming_[1].type; maybe_turn_to_subl(subl_chance, subl_channels, proc, ran); if(proc.boson) couple_boson(*proc.boson, ran); } // pt generation, see eq:pt_sampling in developer manual double PhaseSpacePoint::gen_hard_pt( const int np , const double ptmin, const double ptmax, const double /* y */, RNG & ran ) { // heuristic parameter for pt sampling, see eq:pt_par in developer manual const double ptpar = ptmin + np/5.; const double arctan = std::atan((ptmax - ptmin)/ptpar); const double xpt = ran.flat(); const double pt = ptmin + ptpar*std::tan(xpt*arctan); const double cosine = std::cos(xpt*arctan); weight_ *= pt*ptpar*arctan/(cosine*cosine); return pt; } double PhaseSpacePoint::gen_soft_pt(int np, double max_pt, RNG & ran) { constexpr double ptpar = 4.; const double r = ran.flat(); const double pt = max_pt + ptpar/np*std::log(r); weight_ *= pt*ptpar/(np*r); return pt; } double PhaseSpacePoint::gen_parton_pt( int count, JetParameters const & jet_param, double max_pt, double y, RNG & ran ) { constexpr double p_small_pt = 0.02; if(! jet_param.peak_pt) { return gen_hard_pt(count, jet_param.min_pt, max_pt, y, ran); } const double r = ran.flat(); if(r > p_small_pt) { weight_ /= 1. - p_small_pt; return gen_hard_pt(count, *jet_param.peak_pt, max_pt, y, ran); } weight_ /= p_small_pt; const double pt = gen_soft_pt(count, *jet_param.peak_pt, ran); if(pt < jet_param.min_pt) { weight_=0.0; status_ = not_enough_jets; return jet_param.min_pt; } return pt; } std::vector PhaseSpacePoint::gen_LO_partons( int np, bool is_pure_jets, JetParameters const & jet_param, double max_pt, RNG & ran ){ if (np<2) throw std::invalid_argument{"Not enough partons in gen_LO_partons"}; weight_ /= std::pow(16.*std::pow(M_PI,3),np); weight_ /= std::tgamma(np+1); //remove rapidity ordering std::vector partons; partons.reserve(np); for(int i = 0; i < np; ++i){ const double y = -jet_param.max_y + 2*jet_param.max_y*ran.flat(); weight_ *= 2*jet_param.max_y; const bool is_last_parton = i+1 == np; if(is_pure_jets && is_last_parton) { constexpr double parton_mass_sq = 0.; partons.emplace_back(gen_last_momentum(partons, parton_mass_sq, y)); break; } const double phi = 2*M_PI*ran.flat(); weight_ *= 2.0*M_PI; const double pt = gen_parton_pt(np, jet_param, max_pt, y, ran); if(weight_ == 0.0) return {}; partons.emplace_back(fastjet::PtYPhiM(pt, y, phi)); assert(jet_param.min_pt <= partons[i].pt()); assert(partons[i].pt() <= max_pt+1e-5); } // Need to check that at LO, the number of jets = number of partons; fastjet::ClusterSequence cs(partons, jet_param.def); auto cluster_jets=cs.inclusive_jets(jet_param.min_pt); if (cluster_jets.size()!=unsigned(np)){ weight_=0.0; status_ = not_enough_jets; return {}; } std::sort(begin(partons), end(partons), rapidity_less{}); return partons; } Particle PhaseSpacePoint::gen_boson( ParticleID bosonid, double mass, double width, RNG & ran ){ // Usual phase space measure weight_ /= 16.*std::pow(M_PI, 3); // Generate a y Gaussian distributed around 0 /// @TODO check magic numbers for different boson Higgs /// @TODO better sampling for W const double stddev_y = 1.6; const double y = random_normal(stddev_y, ran); const double r1 = ran.flat(); const double s_boson = mass*( mass + width*std::tan(M_PI/2.*r1 + (r1-1.)*std::atan(mass/width)) ); // off-shell s_boson sampling, compensates for Breit-Wigner /// @TODO use a flag instead if(std::abs(bosonid) == pid::Wp){ weight_/=M_PI*M_PI*16.; //Corrects B-W factors, see git issue 132 weight_*= mass*width*( M_PI+2.*std::atan(mass/width) ) / ( 1. + std::cos( M_PI*r1 + 2.*(r1-1.)*std::atan(mass/width) ) ); } auto p = gen_last_momentum(outgoing_, s_boson, y); return Particle{bosonid, std::move(p), {}}; } - Particle const & PhaseSpacePoint::most_backward_FKL( - std::vector const & partons - ) const{ - if(!is_parton(partons[0])) return partons[1]; - return partons[0]; - } - - Particle const & PhaseSpacePoint::most_forward_FKL( - std::vector const & partons - ) const{ - const size_t last_idx = partons.size() - 1; - if(!is_parton(partons[last_idx])) return partons[last_idx-1]; - return partons[last_idx]; - } - - Particle & PhaseSpacePoint::most_backward_FKL( - std::vector & partons - ) const{ - if(!is_parton(partons[0])) return partons[1]; - return partons[0]; - } - - Particle & PhaseSpacePoint::most_forward_FKL( - std::vector & partons - ) const{ - const size_t last_idx = partons.size() - 1; - if(!is_parton(partons[last_idx])) return partons[last_idx-1]; - return partons[last_idx]; - } - namespace { /// partons are ordered: even = anti, 0 = gluon ParticleID index_to_pid(size_t i){ if(!i) return pid::gluon; return static_cast( i%2 ? (i+1)/2 : -i/2 ); } /// partons are ordered: even = anti, 0 = gluon size_t pid_to_index(ParticleID id){ if(id==pid::gluon) return 0; return id>0 ? id*2-1 : std::abs(id)*2; } using part_mask = std::bitset<11>; //!< Selection mask for partons part_mask init_allowed(ParticleID const id){ if(std::abs(id) == pid::proton) return ~0; part_mask out{0}; if(is_parton(id)) out[pid_to_index(id)] = 1; return out; } /// decides which "index" (see index_to_pid) are allowed for process part_mask allowed_quarks(ParticleID const boson){ part_mask allowed = ~0; if(std::abs(boson) == pid::Wp){ // special case W: // Wp: anti-down or up-type quark, no b/t // Wm: down or anti-up-type quark, no b/t allowed = boson>0? 0b00011001101 :0b00100110011; } return allowed; } /** * @brief Returns list of all allowed initial states partons * * checks which partons are allowed as initial state: * 1. only allow what is given in the Runcard (p -> all) * 2. A/W/Z require something to couple to * a) no qqx => no incoming gluon * b) 2j => no incoming gluon * c) 3j => can couple OR is gluon => 2 gluons become qqx later */ std::array filter_partons( Process const & proc, Subleading const subl_channels, RNG & ran ){ std::array allowed_partons{ init_allowed(proc.incoming[0]), init_allowed(proc.incoming[1]) }; bool const allow_qqx = subl_channels[subleading::qqx]; // special case A/W/Z if(is_AWZ_proccess(proc) && ((proc.njets < 4) || !allow_qqx)){ // all possible incoming states auto allowed = allowed_quarks(*proc.boson); if(proc.njets == 2 || !allow_qqx) allowed.reset(0); // possible states per leg std::array const maybe_partons{ allowed_partons[0]&allowed, allowed_partons[1]&allowed}; if(maybe_partons[0].any() && maybe_partons[1].any()){ // two options to get allowed initial state => choose one at random const size_t idx = ran.flat() < 0.5; allowed_partons[idx] = maybe_partons[idx]; // else choose the possible } else if(maybe_partons[0].any()) { allowed_partons[0] = maybe_partons[0]; } else if(maybe_partons[1].any()) { allowed_partons[1] = maybe_partons[1]; } else{ throw std::invalid_argument{"Incoming state not allowed."}; } } return allowed_partons; } } void PhaseSpacePoint::reconstruct_incoming( Process const & proc, Subleading const subl_channels, PDF & pdf, double E_beam, double uf, RNG & ran ){ std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_); // calculate xa, xb const double sqrts=2*E_beam; const double xa=(incoming_[0].E()-incoming_[0].pz())/sqrts; const double xb=(incoming_[1].E()+incoming_[1].pz())/sqrts; // abort if phase space point is outside of collider energy reach if (xa>1. || xb>1.){ weight_=0; status_ = too_much_energy; return; } auto const & ids = proc.incoming; std::array allowed_partons = filter_partons(proc, subl_channels, ran); for(size_t i = 0; i < 2; ++i){ if(ids[i] == pid::proton || ids[i] == pid::p_bar){ // pick ids according to pdfs incoming_[i].type = generate_incoming_id(i, i?xb:xa, uf, pdf, allowed_partons[i], ran); } else { assert(allowed_partons[i][pid_to_index(ids[i])]); incoming_[i].type = ids[i]; } } assert(momentum_conserved(1e-7)); } ParticleID PhaseSpacePoint::generate_incoming_id( size_t const beam_idx, double const x, double const uf, PDF & pdf, part_mask allowed_partons, RNG & ran ){ std::array pdf_wt; pdf_wt[0] = allowed_partons[0]? std::fabs(pdf.pdfpt(beam_idx,x,uf,pid::gluon)):0.; double pdftot = pdf_wt[0]; for(size_t i = 1; i < pdf_wt.size(); ++i){ pdf_wt[i] = allowed_partons[i]? 4./9.*std::fabs(pdf.pdfpt(beam_idx,x,uf,index_to_pid(i))):0; pdftot += pdf_wt[i]; } const double r1 = pdftot * ran.flat(); double sum = 0; for(size_t i=0; i < pdf_wt.size(); ++i){ if (r1 < (sum+=pdf_wt[i])){ weight_*= pdftot/pdf_wt[i]; return index_to_pid(i); } } std::cerr << "Error in choosing incoming parton: "< allowed_parts; - for(auto & part: outgoing_){ + std::vector allowed_parts; + for(auto part_it=begin_partons(); part_it!=end_partons(); ++part_it){ // Wp -> up OR anti-down, Wm -> anti-up OR down, no bottom - if ( can_couple_to_W(part, boson) ) - allowed_parts.push_back(&part); + if ( can_couple_to_W(*part_it, boson) ) + allowed_parts.push_back(part_it); } if(allowed_parts.size() == 0){ throw std::logic_error{"Found no parton for coupling with boson"}; } // select one and flip it size_t idx = 0; if(allowed_parts.size() > 1){ /// @TODO more efficient sampling /// old code: probability[i] = exp(parton[i].y - W.y) idx = std::floor(ran.flat()*allowed_parts.size()); weight_ *= allowed_parts.size(); } const int W_charge = boson>0?1:-1; allowed_parts[idx]->type = static_cast( allowed_parts[idx]->type - W_charge ); } double PhaseSpacePoint::random_normal( double stddev, RNG & ran ){ const double r1 = ran.flat(); const double r2 = ran.flat(); const double lninvr1 = -std::log(r1); const double result = stddev*std::sqrt(2.*lninvr1)*std::cos(2.*M_PI*r2); weight_ *= exp(result*result/(2*stddev*stddev))*std::sqrt(2.*M_PI)*stddev; return result; } bool PhaseSpacePoint::momentum_conserved(double ep) const{ fastjet::PseudoJet diff; for(auto const & in: incoming()) diff += in.p; for(auto const & out: outgoing()) diff -= out.p; return nearby_ep(diff, fastjet::PseudoJet{}, ep); } Decay PhaseSpacePoint::select_decay_channel( std::vector const & decays, RNG & ran ){ double br_total = 0.; for(auto const & decay: decays) br_total += decay.branching_ratio; // adjust weight // this is given by (channel branching ratio)/(chance to pick channel) // where (chance to pick channel) = // (channel branching ratio)/(total branching ratio) weight_ *= br_total; if(decays.size()==1) return decays.front(); const double r1 = br_total*ran.flat(); double br_sum = 0.; for(auto const & decay: decays){ br_sum += decay.branching_ratio; if(r1 < br_sum) return decay; } throw std::logic_error{"unreachable"}; } std::vector PhaseSpacePoint::decay_boson( Particle const & parent, std::vector const & decays, RNG & ran ){ const auto channel = select_decay_channel(decays, ran); return decay_boson(parent, channel.products, ran); } std::vector PhaseSpacePoint::decay_boson( Particle const & parent, std::vector const & decays, RNG & ran ){ if(decays.size() != 2){ throw not_implemented{ "only decays into two particles are implemented" }; } std::vector decay_products(decays.size()); for(size_t i = 0; i < decays.size(); ++i){ decay_products[i].type = decays[i]; } // choose polar and azimuth angle in parent rest frame const double E = parent.m()/2; const double theta = 2.*M_PI*ran.flat(); const double cos_phi = 2.*ran.flat()-1.; // Jacobian Factors for W in line 418 const double sin_phi = std::sqrt(1. - cos_phi*cos_phi); // Know 0 < phi < pi const double px = E*std::cos(theta)*sin_phi; const double py = E*std::sin(theta)*sin_phi; const double pz = E*cos_phi; decay_products[0].p.reset(px, py, pz, E); decay_products[1].p.reset(-px, -py, -pz, E); for(auto & particle: decay_products) particle.p.boost(parent.p); return decay_products; } } diff --git a/FixedOrderGen/t/CMakeLists.txt b/FixedOrderGen/t/CMakeLists.txt index b16fb1a..241ceaf 100644 --- a/FixedOrderGen/t/CMakeLists.txt +++ b/FixedOrderGen/t/CMakeLists.txt @@ -1,108 +1,108 @@ set(tst_dir "${CMAKE_CURRENT_SOURCE_DIR}") foreach(tst W_reconstruct_enu W_2j_classify W_nj_classify) add_executable(test_${tst} ${tst_dir}/${tst}.cc) target_link_libraries(test_${tst} hejfog_lib) add_test(NAME ${tst} COMMAND test_${tst} WORKING_DIRECTORY ${tst_dir}) endforeach() # this only tests if the runcard actually works, not if the result is correct add_test( NAME main_2j COMMAND HEJFOG ${tst_dir}/config_2j.yml ) add_test( NAME main_h2j COMMAND HEJFOG ${tst_dir}/config_h2j.yml ) add_test( NAME main_h2j_decay COMMAND HEJFOG ${tst_dir}/config_h2j_decay.yml ) add_test( NAME peakpt COMMAND HEJFOG ${tst_dir}/config_2j_peak.yml ) # check that uno emission doesn't change FKL xs add_executable(FKL_uno FKL_uno.cc) target_link_libraries(FKL_uno hejfog_lib) add_test( NAME FKL_uno # calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 COMMAND FKL_uno ${tst_dir}/config_h3j_uno.yml 0.0243548 0.000119862 ) # xs tests add_executable(xs_gen xs_gen.cc) target_link_libraries(xs_gen hejfog_lib) ## Higgs add_test( NAME xs_h2j # calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 COMMAND xs_gen ${tst_dir}/config_h2j.yml 2.04928 0.00956022 ) add_test( NAME xs_h3j # calculated with HEJ revision bd4388fe55cbc3f5a7b6139096456c551294aa31 COMMAND xs_gen ${tst_dir}/config_h3j.yml 1.07807 0.0132409 ) add_test( NAME xs_h5j # calculated with HEJ revision dbde2ffbb3b383ae6709b2424d8f0f9d5658270b COMMAND xs_gen ${tst_dir}/config_h5j.yml 0.0112504 0.000199633 ) add_test( NAME xs_h3j_uno # calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 COMMAND xs_gen ${tst_dir}/config_h3j_uno.yml 0.00347538 3.85875e-05 ) add_test( NAME xs_h2j_decay # calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20 COMMAND xs_gen ${tst_dir}/config_h2j_decay.yml 0.00466994 2.20995e-05 ) ## pure jets add_test( NAME xs_2j # calculated with "combined" HEJ svn r3480 COMMAND xs_gen ${tst_dir}/config_2j.yml 86.42031848e06 590570 ) add_test( NAME xs_3j_uno # calculated with HEJ revision b40e0212b144cdd267df19333b574cc7b91847f1 COMMAND xs_gen ${tst_dir}/config_3j_uno.yml 520697 8948.18 ) add_test( NAME xs_3j_qqx # calculated with HEJ revision b40e0212b144cdd267df19333b574cc7b91847f1 COMMAND xs_gen ${tst_dir}/config_3j_qqx.yml 122233 1613.04 ) add_test( NAME xs_4j_qqx # calculated with HEJ revision b40e0212b144cdd267df19333b574cc7b91847f1 COMMAND xs_gen ${tst_dir}/config_4j_qqx.yml 29805.4 581.473 ) add_test( NAME xs_4j # calculated with HEJ revision 13207b5f67a5f40a2141aa7ee515b022bd4efb65 COMMAND xs_gen ${tst_dir}/config_4j.yml 915072 15402.4 ) ## W add_test( NAME xs_W2j # calculated with HEJ revision 987bb30a7985a24a7961f98cfbbc74d3a7992970 COMMAND xs_gen ${tst_dir}/config_Wm2j.yml 231.404 3.43798 ) add_test( NAME xs_W3j_uno - # calculated with HEJ revision 987bb30a7985a24a7961f98cfbbc74d3a7992970 - COMMAND xs_gen ${tst_dir}/config_Wp3j_uno.yml 0.139372 0.00248345 + # calculated with HEJ revision 996f728a56ed67b980fccbe79948fe56914aaccd+1 + COMMAND xs_gen ${tst_dir}/config_Wp3j_uno.yml 0.295275 0.0056667 ) add_test( NAME xs_W3j_eqqx # calculated with HEJ revision 987bb30a7985a24a7961f98cfbbc74d3a7992970 COMMAND xs_gen ${tst_dir}/config_Wp3j_qqx.yml 8.45323 0.103449 ) add_test( NAME xs_W4j_qqx # calculated with HEJ revision 987bb30a7985a24a7961f98cfbbc74d3a7992970 COMMAND xs_gen ${tst_dir}/config_Wp4j_qqx.yml 0.0851908 0.00300447 )