diff --git a/FixedOrderGen/src/PhaseSpacePoint.cc b/FixedOrderGen/src/PhaseSpacePoint.cc index a8381bd..31b00b3 100644 --- a/FixedOrderGen/src/PhaseSpacePoint.cc +++ b/FixedOrderGen/src/PhaseSpacePoint.cc @@ -1,456 +1,457 @@ #include "PhaseSpacePoint.hh" #include <random> #include "HEJ/kinematics.hh" #include "HEJ/utility.hh" #include "HEJ/exceptions.hh" #include "Process.hh" #include <CLHEP/Random/Randomize.h> #include <CLHEP/Random/RanluxEngine.h> using namespace HEJ; namespace HEJFOG{ static_assert( std::numeric_limits<double>::has_quiet_NaN, "no quiet NaN for double" ); constexpr double NaN = std::numeric_limits<double>::quiet_NaN(); HEJ::UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp){ HEJ::UnclusteredEvent result; result.incoming = psp.incoming(); std::sort( begin(result.incoming), end(result.incoming), [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} ); assert(result.incoming.size() == 2); result.outgoing = psp.outgoing(); assert( std::is_sorted( begin(result.outgoing), end(result.outgoing), HEJ::rapidity_less{} ) ); assert(result.outgoing.size() >= 2); result.decays = psp.decays(); result.central.mur = NaN; result.central.muf = NaN; result.central.weight = psp.weight(); return result; } namespace{ bool can_swap_to_uno( HEJ::Particle const & p1, HEJ::Particle const & p2 ){ return is_parton(p1) && p1.type != HEJ::pid::gluon && p2.type == HEJ::pid::gluon; } } void PhaseSpacePoint::maybe_turn_to_uno( double chance, HEJ::RNG & ran ){ assert(outgoing_.size() >= 2); 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] ); if(!can_be_uno_backward && !can_be_uno_forward) return; if(ran.flat() < chance){ weight_ /= chance; if(can_be_uno_backward && can_be_uno_forward){ if(ran.flat() < 0.5){ std::swap(outgoing_[0].type, outgoing_[1].type); } else{ std::swap(outgoing_[nout-1].type, outgoing_[nout-2].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); } } else weight_ /= 1 - chance; } template<class ParticleMomenta> fastjet::PseudoJet PhaseSpacePoint::gen_last_momentum( ParticleMomenta const & other_momenta, const double mass_square, const double y ) const { std::array<double,2> pt{0.,0.}; for (auto const & p: other_momenta) { pt[0]-= p.px(); pt[1]-= p.py(); } const double mperp = sqrt(pt[0]*pt[0]+pt[1]*pt[1]+mass_square); const double pz=mperp*sinh(y); const double E=mperp*cosh(y); return {pt[0], pt[1], pz, E}; } PhaseSpacePoint::PhaseSpacePoint( Process const & proc, JetParameters const & jet_param, HEJ::PDF & pdf, double E_beam, double uno_chance, ParticlesPropMap const & particles_properties, HEJ::RNG & ran ) { assert(proc.njets >= 2); if(proc.boson && particles_properties.find(*(proc.boson)) == particles_properties.end()) throw HEJ::missing_option("Boson " +std::to_string(*(proc.boson))+" can't be generated: missing properties"); status_ = good; weight_ = 1; const int nout = proc.njets + (proc.boson?1:0); outgoing_.reserve(nout); const bool is_pure_jets = !proc.boson; auto partons = gen_LO_partons( proc.njets, is_pure_jets, jet_param, E_beam, ran ); for(auto&& p_out: partons) { outgoing_.emplace_back(Particle{pid::gluon, std::move(p_out)}); } if(status_ != good) return; + // create boson if(proc.boson){ const auto & boson_prop = particles_properties.at(*proc.boson); - auto boson=gen_boson(*proc.boson, boson_prop.mass, boson_prop.width, ran); - auto pos=std::upper_bound( + auto boson(gen_boson(*proc.boson, boson_prop.mass, boson_prop.width, ran)); + const auto pos = std::upper_bound( begin(outgoing_),end(outgoing_),boson,rapidity_less{} ); - outgoing_.insert(pos, boson); + outgoing_.insert(pos, std::move(boson)); if(! boson_prop.decays.empty()){ const int boson_idx = std::distance(begin(outgoing_), pos); decays_.emplace( boson_idx, decay_boson(outgoing_[boson_idx], boson_prop.decays, ran) ); } } // normalisation of momentum-conserving delta function weight_ *= pow(2*M_PI, 4); reconstruct_incoming(proc.incoming, 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; if(proc.njets > 2) maybe_turn_to_uno(uno_chance, ran); } double PhaseSpacePoint::gen_hard_pt( int np , double ptmin, double ptmax, double y, HEJ::RNG & ran ) { // heuristic parameters for pt sampling const double ptpar = ptmin + np/5.; const double arg_small_y = atan((ptmax - ptmin)/ptpar); const double y_cut = 3.; const double r1 = ran.flat(); if(y < y_cut){ const double pt = ptmin + ptpar*tan(r1*arg_small_y); const double temp = cos(r1*arg_small_y); weight_ *= pt*ptpar*arg_small_y/(temp*temp); return pt; } const double ptpar2 = ptpar/(1 + 5*(y-y_cut)); const double temp = 1. - std::exp((ptmin-ptmax)/ptpar2); const double pt = ptmin - ptpar2*std::log(1-r1*temp); weight_ *= pt*ptpar2*temp/(1-r1*temp); return pt; } double PhaseSpacePoint::gen_soft_pt(int np, double max_pt, HEJ::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, HEJ::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<fastjet::PseudoJet> PhaseSpacePoint::gen_LO_partons( int np, bool is_pure_jets, JetParameters const & jet_param, double max_pt, HEJ::RNG & ran ){ if (np<2) throw std::invalid_argument{"Not enough partons in gen_LO_partons"}; weight_ /= pow(16.*pow(M_PI,3),np); weight_ /= std::tgamma(np+1); //remove rapidity ordering std::vector<fastjet::PseudoJet> 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( HEJ::ParticleID bosonid, double mass, double width, HEJ::RNG & ran ){ if(bosonid != HEJ::pid::Higgs) throw HEJ::not_implemented("Production of boson "+std::to_string(bosonid) +" not implemented yet."); // Usual phase space measure weight_ /= 16.*pow(M_PI, 3); // Generate a y Gaussian distributed around 0 // TODO: magic number only for Higgs const double y = random_normal(1.6, ran); const double r1 = ran.flat(); const double sH = mass*( mass + width*tan(M_PI/2.*r1 + (r1-1.)*atan(mass/width)) ); auto p = gen_last_momentum(outgoing_, sH, y); return Particle{bosonid, std::move(p)}; } Particle const & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> const & partons ) const{ if(!HEJ::is_parton(partons[0])) return partons[1]; return partons[0]; } Particle const & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> const & partons ) const{ const size_t last_idx = partons.size() - 1; if(!HEJ::is_parton(partons[last_idx])) return partons[last_idx-1]; return partons[last_idx]; } Particle & PhaseSpacePoint::most_backward_FKL( std::vector<Particle> & partons ) const{ if(!HEJ::is_parton(partons[0])) return partons[1]; return partons[0]; } Particle & PhaseSpacePoint::most_forward_FKL( std::vector<Particle> & partons ) const{ const size_t last_idx = partons.size() - 1; if(!HEJ::is_parton(partons[last_idx])) return partons[last_idx-1]; return partons[last_idx]; } void PhaseSpacePoint::reconstruct_incoming( std::array<HEJ::ParticleID, 2> const & ids, HEJ::PDF & pdf, double E_beam, double uf, HEJ::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].p.e()-incoming_[0].p.pz())/sqrts; const double xb=(incoming_[1].p.e()+incoming_[1].p.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; } // pick pdfs /** TODO: * ufa, ufb don'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 */ for(size_t i = 0; i < 2; ++i){ if(ids[i] == pid::proton || ids[i] == pid::p_bar){ incoming_[i].type = generate_incoming_id(i, i?xb:xa, uf, pdf, ran); } else { incoming_[i].type = ids[i]; } } assert(momentum_conserved(1e-7)); } namespace { /// particles are ordered, odd = anti ParticleID index_to_pid(size_t i){ if(!i) return pid::gluon; return static_cast<ParticleID>(i%2?(i+1)/2:-i/2); } } HEJ::ParticleID PhaseSpacePoint::generate_incoming_id( size_t const beam_idx, double const x, double const uf, HEJ::PDF & pdf, HEJ::RNG & ran ){ std::array<double,11> pdf_wt; pdf_wt[0] = fabs(pdf.pdfpt(beam_idx,x,uf,pid::gluon)); double pdftot = pdf_wt[0]; for(size_t i = 1; i < pdf_wt.size(); ++i){ pdf_wt[i] = 4./9.*fabs(pdf.pdfpt(beam_idx,x,uf,index_to_pid(i))); 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: "<<x<<" "<<uf<<" " <<sum<<" "<<pdftot<<" "<<r1<<std::endl; throw std::logic_error{"Failed to choose parton flavour"}; } double PhaseSpacePoint::random_normal( double stddev, HEJ::RNG & ran ){ const double r1 = ran.flat(); const double r2 = ran.flat(); const double lninvr1 = -log(r1); const double result = stddev*sqrt(2.*lninvr1)*cos(2.*M_PI*r2); weight_ *= exp(result*result/(2*stddev*stddev))*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<Decay> const & decays, HEJ::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; 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<Particle> PhaseSpacePoint::decay_boson( HEJ::Particle const & parent, std::vector<Decay> const & decays, HEJ::RNG & ran ){ const auto channel = select_decay_channel(decays, ran); if(channel.products.size() != 2){ throw HEJ::not_implemented{ "only decays into two particles are implemented" }; } std::vector<Particle> decay_products(channel.products.size()); for(size_t i = 0; i < channel.products.size(); ++i){ decay_products[i].type = channel.products[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.; const double sin_phi = sqrt(1. - cos_phi*cos_phi); // Know 0 < phi < pi const double px = E*cos(theta)*sin_phi; const double py = E*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/include/HEJ/PDG_codes.hh b/include/HEJ/PDG_codes.hh index 01dc521..be9a45f 100644 --- a/include/HEJ/PDG_codes.hh +++ b/include/HEJ/PDG_codes.hh @@ -1,97 +1,98 @@ /** \file PDG_codes.hh * \brief Contains the Particle IDs of all relevant SM particles. * * Large enumeration included which has multiple entries for potential * alternative names of different particles. There are also functions * which can be used to determine if a particle is a parton or if * it is a non-gluon boson. */ #pragma once #include <string> namespace HEJ { //! particle ids according to PDG namespace pid { //! The possible particle identities. We use PDG IDs as standard. enum ParticleID{ d = 1, /*!< Down Quark */ down = d, /*!< Down Quark */ u = 2, /*!< Up Quark */ up = u, /*!< Up Quark */ s = 3, /*!< Strange Quark */ strange = s, /*!< Strange Quark */ c = 4, /*!< Charm Quark */ charm = c, /*!< Charm Quark */ b = 5, /*!< Bottom Quark */ bottom = b, /*!< Bottom Quark */ t = 6, /*!< Top Quark */ top = t, /*!< Top Quark */ e = 11, /*!< Electron */ electron = e, /*!< Electron */ nu_e = 12, /*!< Electron Neutrino */ electron_neutrino = nu_e, /*!< Electron neutrino */ mu = 13, /*!< Muon */ muon = mu, /*!< Muon */ nu_mu = 14, /*!< Muon Neutrino */ muon_neutrino = nu_mu, /*!< Muon Neutrino */ tau = 15, /*!< Tau */ nu_tau = 16, /*!< Tau Neutrino */ tau_neutrino = nu_tau, /*!< Tau Neutrino */ d_bar = -d, /*!< Anti-Down Quark */ u_bar = -u, /*!< Anti-Up quark */ s_bar = -s, /*!< Anti-Strange Quark */ c_bar = -c, /*!< Anti-Charm Quark */ b_bar = -b, /*!< Anti-Bottom Quark */ t_bar = -t, /*!< Anti-Top Quark */ e_bar = -e, /*!< Positron */ + positron = e_bar, /*!< Positron */ nu_e_bar = -nu_e, /*!< Anti-Electron Neutrino */ mu_bar = -mu, /*!< Anti-Muon */ nu_mu_bar = -nu_mu, /*!< Anti-Muon Neutrino */ tau_bar = -tau, /*!< Anti-Tau */ nu_tau_bar = -nu_tau, /*!< Anti-Tau Neutrino */ gluon = 21, /*!< Gluon */ g = gluon, /*!< Gluon */ photon = 22, /*!< Photon */ gamma = photon, /*!< Photon */ Z = 23, /*!< Z Boson */ Wp = 24, /*!< W- Boson */ Wm = -Wp, /*!< W+ Boson */ h = 25, /*!< Higgs Boson */ Higgs = h, /*!< Higgs Boson */ higgs = h, /*!< Higgs Boson */ p = 2212, /*!< Proton */ proton = p, /*!< Proton */ p_bar = -p, /*!< Anti-Proton */ }; } using ParticleID = pid::ParticleID; //! Convert a particle name to the corresponding PDG particle ID ParticleID to_ParticleID(std::string const & name); /** * \brief Function to determine if particle is a parton * @param p PDG ID of particle * @returns true if the particle is a parton, false otherwise */ inline constexpr bool is_parton(ParticleID p){ return p == pid::gluon || std::abs(p) <= pid::top; } /** * \brief function to determine if the particle is a photon, W, Z, or Higgs boson * @param id PDG ID of particle * @returns true if the partice is a A,W,Z, or H, false otherwise */ inline constexpr bool is_AWZH_boson(ParticleID id){ return id == pid::Wm || (id >= pid::photon && id <= pid::Higgs); } } diff --git a/src/PDG_codes.cc b/src/PDG_codes.cc index 3dfc576..6dc9379 100644 --- a/src/PDG_codes.cc +++ b/src/PDG_codes.cc @@ -1,48 +1,48 @@ #include "HEJ/PDG_codes.hh" #include <map> #include <iostream> namespace HEJ{ ParticleID to_ParticleID(std::string const & name){ using namespace HEJ::pid; static const std::map<std::string, ParticleID> known = { {"d", d}, {"down", down}, {"1",static_cast<ParticleID>(1)}, {"u", u}, {"up", up}, {"2",static_cast<ParticleID>(2)}, {"s", s}, {"strange", strange}, {"3",static_cast<ParticleID>(3)}, {"c", c}, {"charm", charm}, {"4",static_cast<ParticleID>(4)}, {"b", b}, {"bottom", bottom}, {"5",static_cast<ParticleID>(5)}, {"t", t}, {"top", top}, {"6",static_cast<ParticleID>(6)}, - {"e", e}, {"electron", electron}, {"11",static_cast<ParticleID>(11)}, + {"e", e}, {"electron", electron}, {"e-", e}, {"11",static_cast<ParticleID>(11)}, {"nu_e", nu_e}, {"electron_neutrino", electron_neutrino}, {"12",static_cast<ParticleID>(12)}, - {"mu", mu}, {"muon", muon}, {"13",static_cast<ParticleID>(13)}, + {"mu", mu}, {"muon", muon}, {"mu-", mu}, {"13",static_cast<ParticleID>(13)}, {"nu_mu", nu_mu}, {"muon_neutrino", muon_neutrino}, {"14",static_cast<ParticleID>(14)}, - {"tau", tau}, {"15",static_cast<ParticleID>(15)}, + {"tau", tau}, {"tau-", tau}, {"15",static_cast<ParticleID>(15)}, {"nu_tau", nu_tau}, {"tau_neutrino", tau_neutrino}, {"16",static_cast<ParticleID>(16)}, {"d_bar", d_bar}, {"-1",static_cast<ParticleID>(-1)}, {"u_bar", u_bar}, {"-2",static_cast<ParticleID>(-2)}, {"s_bar", s_bar}, {"-3",static_cast<ParticleID>(-3)}, {"c_bar", c_bar}, {"-4",static_cast<ParticleID>(-4)}, {"b_bar", b_bar}, {"-5",static_cast<ParticleID>(-5)}, {"t_bar", t_bar}, {"-6",static_cast<ParticleID>(-6)}, - {"e_bar", e_bar}, {"-11",static_cast<ParticleID>(-11)}, + {"e_bar", e_bar}, {"positron", positron}, {"e+", e_bar}, {"-11",static_cast<ParticleID>(-11)}, {"nu_e_bar", nu_e_bar}, {"-12",static_cast<ParticleID>(-12)}, - {"mu_bar", mu_bar}, {"-13",static_cast<ParticleID>(-13)}, + {"mu_bar", mu_bar}, {"mu+", mu_bar}, {"-13",static_cast<ParticleID>(-13)}, {"nu_mu_bar", nu_mu_bar}, {"-14",static_cast<ParticleID>(-14)}, - {"tau_bar", tau_bar}, {"-15",static_cast<ParticleID>(-15)}, + {"tau_bar", tau_bar}, {"tau+", tau_bar}, {"-15",static_cast<ParticleID>(-15)}, {"nu_tau_bar", nu_tau_bar}, {"-16",static_cast<ParticleID>(-16)}, {"gluon", gluon}, {"g", g}, {"21",static_cast<ParticleID>(21)}, {"photon", photon}, {"gamma", gamma}, {"22",static_cast<ParticleID>(22)}, {"Z", Z}, {"23",static_cast<ParticleID>(23)}, {"Wp", Wp}, {"W+", Wp}, {"24",static_cast<ParticleID>(24)}, {"Wm", Wm}, {"W-", Wm}, {"-24",static_cast<ParticleID>(-24)}, {"h", h}, {"H", h}, {"Higgs", Higgs}, {"higgs", higgs}, {"25",static_cast<ParticleID>(25)}, {"p", p}, {"proton", proton}, {"p_bar", p_bar}, {"2212",static_cast<ParticleID>(2212)} }; const auto res = known.find(name); if(res == known.end()){ throw std::invalid_argument("Unknown particle " + name); } return res->second; } }