Page Menu
Home
HEPForge
Search
Configure Global Search
Log In
Files
F7876981
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
69 KB
Subscribers
None
View Options
diff --git a/FixedOrderGen/include/PhaseSpacePoint.hh b/FixedOrderGen/include/PhaseSpacePoint.hh
index f89ab03..4399492 100644
--- a/FixedOrderGen/include/PhaseSpacePoint.hh
+++ b/FixedOrderGen/include/PhaseSpacePoint.hh
@@ -1,298 +1,322 @@
/** \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 <array>
#include <bitset>
#include <cstddef>
#include <unordered_map>
+#include <optional>
#include <vector>
#include "boost/iterator/filter_iterator.hpp"
#include "HEJ/Event.hh"
#include "HEJ/PDG_codes.hh"
#include "HEJ/Particle.hh"
#include "fastjet/PseudoJet.hh"
#include "Decay.hh"
#include "Status.hh"
#include "Subleading.hh"
namespace HEJ {
class EWConstants;
class PDF;
class RNG;
}
namespace HEJFOG {
struct JetParameters;
struct Process;
//! A point in resummation phase space
class PhaseSpacePoint{
public:
//! Iterator over partons
using ConstPartonIterator = boost::filter_iterator<
bool (*)(HEJ::Particle const &),
std::vector<HEJ::Particle>::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_param 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 chance 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_param,
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<HEJ::Particle, 2> const & incoming() const{
return incoming_;
}
//! Get Outgoing Function
/**
* @returns Outgoing Particles
*/
std::vector<HEJ::Particle> const & outgoing() const{
return outgoing_;
}
std::unordered_map<std::size_t, std::vector<HEJ::Particle>> 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:
static constexpr std::size_t NUM_PARTONS = 11; //!< Number of partons
public:
using part_mask = std::bitset<NUM_PARTONS>; //!< Selection mask for partons
private:
friend HEJ::Event::EventData to_EventData(PhaseSpacePoint psp);
/**
* @internal
* @brief Generate LO parton momentum
*
* @param np 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<fastjet::PseudoJet> gen_LO_partons(
int np, 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,
+ Process const & proc,
+ std::optional<subleading::Channels> channel,
HEJ::RNG & ran
);
double gen_V_boson_rapidity(HEJ::RNG & ran);
- double gen_Higgs_rapidity(HEJ::RNG & ran);
+ double gen_Higgs_rapidity(
+ Process const & proc,
+ std::optional<subleading::Channels> channel,
+ HEJ::RNG & ran
+ );
template<class ParticleMomenta>
fastjet::PseudoJet gen_last_momentum(
ParticleMomenta const & other_momenta,
double mass_square, double y
) const;
bool jets_ok(
std::vector<fastjet::PseudoJet> const & Born_jets,
std::vector<fastjet::PseudoJet> 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,
- double subl_chance, Subleading subl_channels,
+ std::optional<subleading::Channels> channel,
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,
part_mask allowed_partons, HEJ::RNG & ran
);
//! @brief Returns list of all allowed initial states partons
std::array<part_mask,2> allowed_incoming(
Process const & proc,
- double subl_chance, Subleading subl_channels,
+ std::optional<subleading::Channels> channel,
HEJ::RNG & ran
);
//! Ensure that incoming state compatible with A/W/Z production
+ //! Should not be called for final-state qqbar configurations,
+ //! since A/W/Z emission doesn't constrain the initial state there
std::array<part_mask,2> incoming_AWZ(
- Process const & proc, Subleading subl_channels,
+ Process const & proc,
std::array<part_mask,2> allowed_partons,
HEJ::RNG & ran
);
//! Ensure that incoming state compatible with extremal qqbar
std::array<part_mask,2> incoming_eqqbar(
std::array<part_mask,2> allowed_partons, HEJ::RNG & ran);
//! Ensure that incoming state compatible with unordered
std::array<part_mask,2> incoming_uno(
std::array<part_mask,2> allowed_partons, HEJ::RNG & ran);
bool momentum_conserved(double ep) const;
bool extremal_FKL_ok(
std::vector<fastjet::PseudoJet> const & partons
) const;
double random_normal(double stddev, HEJ::RNG & ran);
+ double random_normal_trunc(
+ double stddev,
+ HEJ::RNG & ran,
+ double min,
+ double max
+ );
/**
* @internal
* @brief Turns a FKL configuration into a subleading one
*
* @param chance Chance 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_subl(
+ subleading::Channels channel,
+ Process const & proc,
+ HEJ::RNG & ran
+ );
void turn_to_subl(
Subleading channels,
bool can_be_uno_backward, bool can_be_uno_forward, bool allow_strange,
HEJ::RNG & ran);
void turn_to_uno(bool can_be_uno_backward, bool can_be_uno_forward,
HEJ::RNG & ran);
HEJ::ParticleID select_qqbar_flavour(bool allow_strange, HEJ::RNG & ran);
void turn_to_cqqbar(bool allow_strange, HEJ::RNG & ran);
void turn_to_eqqbar(bool allow_strange, HEJ::RNG & ran);
//! decay where we select the decay channel
std::vector<HEJ::Particle> decay_channel(
HEJ::Particle const & parent,
std::vector<Decay> 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<Decay> 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 max_pt, HEJ::RNG & ran);
double gen_parton_pt(
int count, JetParameters const & jet_param, double max_pt, double y,
HEJ::RNG & ran
);
//! Iterator over partons (non-const)
using PartonIterator = boost::filter_iterator<
bool (*)(HEJ::Particle const &),
std::vector<HEJ::Particle>::iterator
>;
//! Reverse Iterator over partons (non-const)
using ReversePartonIterator = std::reverse_iterator<PartonIterator>;
//! 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();
+ std::optional<subleading::Channels> select_channel(
+ Subleading subl_channels,
+ double chance,
+ HEJ::RNG & ran
+ );
+
double weight_;
Status status_;
std::array<HEJ::Particle, 2> incoming_;
std::vector<HEJ::Particle> outgoing_;
//! Particle decays in the format {outgoing index, decay products}
std::unordered_map<std::size_t, std::vector<HEJ::Particle>> decays_;
};
//! Extract HEJ::Event::EventData from PhaseSpacePoint
HEJ::Event::EventData to_EventData(PhaseSpacePoint psp);
} // namespace HEJFOG
diff --git a/FixedOrderGen/src/PhaseSpacePoint.cc b/FixedOrderGen/src/PhaseSpacePoint.cc
index 459fb1f..7ba2e7b 100644
--- a/FixedOrderGen/src/PhaseSpacePoint.cc
+++ b/FixedOrderGen/src/PhaseSpacePoint.cc
@@ -1,1003 +1,1032 @@
/**
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2019-2020
* \copyright GPLv2 or later
*/
#include "PhaseSpacePoint.hh"
#include <algorithm>
#include <cassert>
#include <cmath>
+#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <iterator>
#include <limits>
#include <tuple>
#include <type_traits>
#include <utility>
+#include "Subleading.hh"
#include "fastjet/ClusterSequence.hh"
#include "HEJ/Constants.hh"
#include "HEJ/EWConstants.hh"
#include "HEJ/PDF.hh"
#include "HEJ/PDG_codes.hh"
#include "HEJ/Particle.hh"
#include "HEJ/RNG.hh"
#include "HEJ/exceptions.hh"
#include "HEJ/kinematics.hh"
#include "HEJ/utility.hh"
#include "JetParameters.hh"
#include "Process.hh"
namespace HEJFOG {
HEJ::Event::EventData to_EventData(PhaseSpacePoint psp){
//! @TODO Same function already in HEJ
HEJ::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),
HEJ::rapidity_less{}
)
);
assert(result.outgoing.size() >= 2);
result.decays = std::move(psp).decays_; // NOLINT(bugprone-use-after-move)
static_assert(
std::numeric_limits<double>::has_quiet_NaN,
"no quiet NaN for double"
);
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
result.parameters.central = {nan, nan, psp.weight()}; // NOLINT(bugprone-use-after-move)
return result;
}
PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::begin_partons() const {
return cbegin_partons();
}
PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::cbegin_partons() const {
return {HEJ::is_parton, cbegin(outgoing()), cend(outgoing())};
}
PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::end_partons() const {
return cend_partons();
}
PhaseSpacePoint::ConstPartonIterator PhaseSpacePoint::cend_partons() const {
return {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<ConstPartonIterator>( cend_partons() );
}
PhaseSpacePoint::ConstReversePartonIterator PhaseSpacePoint::rend_partons() const {
return crend_partons();
}
PhaseSpacePoint::ConstReversePartonIterator PhaseSpacePoint::crend_partons() const {
return std::reverse_iterator<ConstPartonIterator>( cbegin_partons() );
}
PhaseSpacePoint::PartonIterator PhaseSpacePoint::begin_partons() {
return {HEJ::is_parton, begin(outgoing_), end(outgoing_)};
}
PhaseSpacePoint::PartonIterator PhaseSpacePoint::end_partons() {
return {HEJ::is_parton, end(outgoing_), end(outgoing_)};
}
PhaseSpacePoint::ReversePartonIterator PhaseSpacePoint::rbegin_partons() {
return std::reverse_iterator<PartonIterator>( end_partons() );
}
PhaseSpacePoint::ReversePartonIterator PhaseSpacePoint::rend_partons() {
return std::reverse_iterator<PartonIterator>( begin_partons() );
}
namespace {
bool can_swap_to_uno(
HEJ::Particle const & p1, HEJ::Particle const & p2
){
assert(is_parton(p1) && is_parton(p2));
return p1.type != HEJ::pid::gluon
&& p2.type == HEJ::pid::gluon;
}
- size_t count_gluons(PhaseSpacePoint::ConstPartonIterator first,
- PhaseSpacePoint::ConstPartonIterator last
- ){
- return std::count_if(first, last, [](HEJ::Particle const & p)
- {return p.type == HEJ::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_qqbar(
- PhaseSpacePoint::ConstPartonIterator first,
- PhaseSpacePoint::ConstReversePartonIterator const & last
- ){
- using namespace subleading;
- assert( std::distance( first,last.base() )>2 );
- Subleading channels = ALL;
- channels.reset(eqqbar);
- channels.reset(cqqbar);
- auto const ngluon = count_gluons(first,last.base());
- if(ngluon < 2) return channels;
- if(first->type==HEJ::pid::gluon || last->type==HEJ::pid::gluon){
- channels.set(eqqbar);
- }
- if(std::distance(first,last.base())>=4){
- channels.set(cqqbar);
- }
- return channels;
- }
-
template<class PartonIt, class OutIt>
bool uno_possible(
PartonIt first_parton, OutIt first_out
){
using namespace HEJ;
// Special case: Higgs can not be outside of uno
if(first_out->type == pid::Higgs
|| std::next(first_out)->type==pid::Higgs){
return false;
}
// decide what kind of subleading process is allowed
return can_swap_to_uno( *first_parton, *std::next(first_parton) );
}
bool is_AWZ_proccess(Process const & proc){
return proc.boson && HEJ::is_AWZ_boson(*proc.boson);
}
bool is_up_type(HEJ::Particle const & part){
return is_anyquark(part) && ((std::abs(part.type)%2) == 0);
}
bool is_down_type(HEJ::Particle const & part){
return is_anyquark(part) && ((std::abs(part.type)%2) != 0);
}
bool can_couple_to_W(
HEJ::Particle const & part, HEJ::pid::ParticleID const W_id
){
const int W_charge = W_id>0?1:-1;
return std::abs(part.type)<HEJ::pid::b
&& ( (W_charge*part.type > 0 && is_up_type(part))
|| (W_charge*part.type < 0 && is_down_type(part)) );
}
-
- Subleading ensure_AWZ(
- double & subl_chance, bool & allow_strange,
- HEJ::ParticleID const boson,
- PhaseSpacePoint::ConstPartonIterator first_parton,
- PhaseSpacePoint::ConstPartonIterator last_parton
- ){
- auto channels = subleading::ALL;
- if(std::none_of(first_parton, last_parton,
- [&boson](HEJ::Particle const & p){
- return can_couple_to_W(p, boson);})) {
- // enforce qqbar 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());
- subl_chance = 1.;
- // strange not allowed for W
- if(std::abs(boson)== HEJ::pid::Wp) allow_strange = false;
- }
- return channels;
- }
} // namespace
- void PhaseSpacePoint::turn_to_subl(
- Subleading const channels,
- bool const can_be_uno_backward, bool const can_be_uno_forward,
- bool const allow_strange,
- HEJ::RNG & ran
- ){
- double const nchannels = channels.count();
- double const step = 1./nchannels;
- weight_*=nchannels;
- unsigned selected = subleading::first;
- double rnd = nchannels>1?ran.flat():0.;
- // @TODO optimise this sampling
- for(; selected<=subleading::last; ++selected){
- assert(rnd>=0);
- if(channels[selected]){
- if(rnd<step) break;
- rnd-=step;
- }
- }
- switch(selected){
- case subleading::uno:
- return turn_to_uno(can_be_uno_backward, can_be_uno_forward, ran);
- case subleading::cqqbar:
- return turn_to_cqqbar(allow_strange, ran);
- case subleading::eqqbar:
- return turn_to_eqqbar(allow_strange, ran);
- default:
- throw std::logic_error{"unreachable"};
+ namespace {
+ template<class Iterator>
+ bool allow_strange(
+ Iterator first_parton, Iterator last_parton,
+ Process const & proc
+ ) {
+ const auto boson = proc.boson;
+ return !(
+ boson
+ && std::abs(*boson)== HEJ::pid::Wp
+ && std::none_of(
+ first_parton, last_parton,
+ [boson](HEJ::Particle const & p){
+ return can_couple_to_W(p, *boson);
+ })
+ );
}
}
- void PhaseSpacePoint::maybe_turn_to_subl(
- double chance, Subleading channels, Process const & proc, HEJ::RNG & ran
- ){
+ void PhaseSpacePoint::turn_to_subl(
+ subleading::Channels const channel,
+ Process const & proc,
+ HEJ::RNG & ran
+ ) {
using namespace HEJ;
- if(proc.njets <= 2) return;
- assert(outgoing_.size() >= 2);
+ assert(proc.njets > 2);
- // decide what kind of subleading process is allowed
- bool const can_be_uno_backward = uno_possible(cbegin_partons(),
+ switch(channel) {
+ case subleading::unordered: {
+ bool const can_be_uno_backward = uno_possible(cbegin_partons(),
outgoing_.cbegin());
- bool const can_be_uno_forward = uno_possible(crbegin_partons(),
+ bool const can_be_uno_forward = uno_possible(crbegin_partons(),
outgoing_.crbegin());
- if(channels[subleading::uno]){
- channels.set(subleading::uno, can_be_uno_backward || can_be_uno_forward);
+ return turn_to_uno(can_be_uno_backward, can_be_uno_forward, ran);
}
- channels &= possible_qqbar(cbegin_partons(), crbegin_partons());
-
- bool allow_strange = true;
- if(is_AWZ_proccess(proc)) {
- channels &= ensure_AWZ(chance, allow_strange, *proc.boson,
- cbegin_partons(), cend_partons());
+ case subleading::central_qqbar: {
+ bool const strange_allowed = allow_strange(
+ begin_partons(),
+ end_partons(),
+ proc
+ );
+ return turn_to_cqqbar(strange_allowed, ran);
}
-
- std::size_t const nchannels = channels.count();
- // no subleading
- if(nchannels==0) return;
- if(ran.flat() >= chance){
- weight_ /= 1 - chance;
- return;
+ case subleading::extremal_qqbar: {
+ bool const strange_allowed = allow_strange(
+ begin_partons(),
+ end_partons(),
+ proc
+ );
+ return turn_to_eqqbar(strange_allowed, ran);
}
- weight_ /= chance;
-
- // select channel
- return turn_to_subl( channels, can_be_uno_backward, can_be_uno_forward,
- allow_strange, ran);
+ default:;
+ }
+ throw std::logic_error{"enum value not covered"};
}
void PhaseSpacePoint::turn_to_uno(
const bool can_be_uno_backward, const bool can_be_uno_forward,
HEJ::RNG & ran
){
- if(!can_be_uno_backward && !can_be_uno_forward) return;
if(can_be_uno_backward && can_be_uno_forward){
weight_ *= 2.;
if(ran.flat() < 0.5){
return std::swap(begin_partons()->type, std::next(begin_partons())->type);
}
return std::swap(rbegin_partons()->type, std::next(rbegin_partons())->type);
}
if(can_be_uno_backward){
return std::swap(begin_partons()->type, std::next(begin_partons())->type);
}
assert(can_be_uno_forward);
std::swap(rbegin_partons()->type, std::next(rbegin_partons())->type);
}
//! select flavour of quark
HEJ::ParticleID PhaseSpacePoint::select_qqbar_flavour(
const bool allow_strange, HEJ::RNG & ran
){
const double r1 = 2.*ran.flat()-1.;
const double max_flavour = allow_strange?HEJ::N_F:HEJ::N_F-1;
weight_ *= max_flavour*2;
double const flavour = HEJ::pid::down + std::floor(std::abs(r1)*max_flavour);
return static_cast<HEJ::ParticleID>(flavour*(r1<0.?-1:1));
}
void PhaseSpacePoint::turn_to_cqqbar(const bool allow_strange, HEJ::RNG & ran){
// we assume all FKL partons to be gluons
auto first = std::next(begin_partons());
auto last = std::next(rbegin_partons());
auto const ng = std::distance(first, last.base());
if(ng < 2)
throw std::logic_error("not enough gluons to create qqbar");
auto flavour = select_qqbar_flavour(allow_strange, ran);
// select gluon for switch
if(ng!=2){
const double steps = 1./(ng-1.);
weight_ /= steps;
for(auto rnd = ran.flat(); rnd>steps; ++first){
rnd-=steps;
}
}
first->type = flavour;
std::next(first)->type = anti(flavour);
}
void PhaseSpacePoint::turn_to_eqqbar(const bool allow_strange, HEJ::RNG & ran){
/// find first and last gluon in FKL chain
auto first = begin_partons();
const bool can_forward = !is_anyquark(*first);
auto last = rbegin_partons();
const bool can_backward = !is_anyquark(*last);
if(std::distance(first, last.base()) < 2)
throw std::logic_error("not enough gluons to create qqbar");
auto flavour = select_qqbar_flavour(allow_strange, ran);
// select gluon for switch
if(can_forward && !can_backward){
first->type = flavour;
std::next(first)->type = anti(flavour);
return;
}
if(!can_forward && can_backward){
last->type = flavour;
std::next(last)->type = anti(flavour);
return;
}
assert(can_forward && can_backward);
weight_*=2.;
if(ran.flat()>0.5){
first->type = flavour;
std::next(first)->type = anti(flavour);
return;
}
last->type = flavour;
std::next(last)->type = anti(flavour);
}
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 = 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};
}
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;
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"};
}
namespace {
//! generate decay products of a boson
std::vector<HEJ::Particle> decay_boson(
HEJ::Particle const & parent,
std::vector<HEJ::ParticleID> const & decays,
HEJ::RNG & ran
){
if(decays.size() != 2){
throw HEJ::not_implemented{
"only decays into two particles are implemented"
};
}
std::vector<HEJ::Particle> 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;
}
} // namespace
std::vector<HEJ::Particle> PhaseSpacePoint::decay_channel(
HEJ::Particle const & parent,
std::vector<Decay> const & decays,
HEJ::RNG & ran
){
const auto channel = select_decay_channel(decays, ran);
return decay_boson(parent, channel.products, ran);
}
namespace {
//! adds a particle to target (in correct rapidity ordering)
//! @returns positon of insertion
auto insert_particle(std::vector<HEJ::Particle> & target,
HEJ::Particle && particle
){
const auto pos = std::upper_bound(
begin(target),end(target),particle,HEJ::rapidity_less{}
);
target.insert(pos, std::move(particle));
return pos;
}
} // namespace
PhaseSpacePoint::PhaseSpacePoint(
Process const & proc,
JetParameters const & jet_param,
HEJ::PDF & pdf, double E_beam,
double const subl_chance,
- Subleading subl_channels,
+ Subleading const subl_channels,
ParticlesDecayMap const & particle_decays,
HEJ::EWConstants const & ew_parameters,
HEJ::RNG & ran
){
assert(proc.njets >= 2);
status_ = Status::good;
weight_ = 1;
// ensure that all setting are consistent
- if(subl_chance == 0.)
- subl_channels.reset();
+ // a more thorough check is in EventGenerator
+ assert(subl_chance > 0. || subl_channels.none());
const std::size_t 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
);
+ if(status_ != Status::good) return;
// pre fill flavour with gluons
- for( auto it = std::make_move_iterator(partons.begin());
- it != std::make_move_iterator(partons.end());
- ++it
- ){
- outgoing_.emplace_back(HEJ::Particle{HEJ::pid::gluon, *it, {}});
+ for(auto && parton: std::move(partons)){
+ outgoing_.emplace_back(HEJ::Particle{HEJ::pid::gluon, std::move(parton), {}});
}
- if(status_ != Status::good) return;
+
+ const std::optional<subleading::Channels> channel = select_channel(
+ subl_channels,
+ subl_chance,
+ ran
+ );
if(proc.boson){ // decay boson
auto const & boson_prop = (*proc.boson == HEJ::pid::Z_photon_mix
? ew_parameters.prop(HEJ::pid::Z)
: ew_parameters.prop(*proc.boson));
- auto boson{ gen_boson(*proc.boson, boson_prop.mass, boson_prop.width, ran) };
+ auto boson = gen_boson(
+ *proc.boson, boson_prop.mass, boson_prop.width,
+ proc,
+ channel,
+ 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_channel(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_chance, subl_channels, pdf, E_beam, jet_param.min_pt, ran);
+ reconstruct_incoming(proc, channel, pdf, E_beam, jet_param.min_pt, ran);
if(status_ != Status::good) return;
// set outgoing states
begin_partons()->type = incoming_[0].type;
rbegin_partons()->type = incoming_[1].type;
- maybe_turn_to_subl(subl_chance, subl_channels, proc, ran);
+ if(channel) turn_to_subl(*channel, 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 */,
HEJ::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, 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_ = 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_ /= std::pow(16.*std::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_ = Status::not_enough_jets;
return {};
}
std::sort(begin(partons), end(partons), HEJ::rapidity_less{});
return partons;
}
HEJ::Particle PhaseSpacePoint::gen_boson(
HEJ::ParticleID bosonid, double mass, double width,
+ Process const & proc,
+ std::optional<subleading::Channels> const channel,
HEJ::RNG & ran
){
// Usual phase space measure for single particle,
// see eq:single_particle_phase_space in developer manual
weight_ /= 16.*std::pow(M_PI, 3);
const double y = (bosonid == HEJ::pid::Higgs)?
- gen_Higgs_rapidity(ran):
+ gen_Higgs_rapidity(proc, channel, ran):
gen_V_boson_rapidity(ran);
// off-shell sampling, see eq:breit-wigner-sampling in developer manual
const double r1 = ran.flat();
const double s_boson = mass*(
mass + width*std::tan(M_PI/2.*r1 + (r1-1.)*std::atan(mass/width))
);
if(bosonid != HEJ::pid::Higgs){
// two-particle phase space,
// compare eq:two_particle_phase_space and eq:one_particle_phase_space
// in developer manual
weight_/=M_PI*M_PI*16.;
// Jacobian, see eq:breit-wigner-sampling_jacobian in developer manual
weight_*= mass*width*( M_PI+2.*std::atan(mass/width) )
/ ( 1. + std::cos( M_PI*r1 + 2.*(r1-1.)*std::atan(mass/width) ) );
}
return {
bosonid,
gen_last_momentum(outgoing_, s_boson, y),
{}
};
}
double PhaseSpacePoint::gen_Higgs_rapidity(
+ Process const & proc,
+ std::optional<subleading::Channels> const channel,
HEJ::RNG & ran
) {
constexpr double STDDEV_Y_H = 1.6;
+ if(channel && *channel == subleading::uno) {
+ assert(outgoing().size() >= 3);
+ if(proc.incoming[0] == HEJ::pid::gluon) {
+ // backward uno not possible
+ // Higgs has to be before the second most forward parton
+ assert(proc.incoming[1] != HEJ::pid::gluon);
+ return random_normal_trunc(
+ STDDEV_Y_H,
+ ran,
+ std::numeric_limits<double>::lowest(),
+ outgoing_[outgoing().size() - 2].rapidity()
+ );
+ }
+ if(proc.incoming[1] == HEJ::pid::gluon) {
+ // forward uno not possible
+ // Higgs has to be after the second most backward parton
+ assert(proc.incoming[0] != HEJ::pid::gluon);
+ return random_normal_trunc(
+ STDDEV_Y_H,
+ ran,
+ outgoing_[1].rapidity(),
+ std::numeric_limits<double>::max()
+ );
+ }
+ }
return random_normal(STDDEV_Y_H, ran);
}
double PhaseSpacePoint::gen_V_boson_rapidity(
HEJ::RNG & ran
){
// two Gaussians around (+/-)2.6 of std width ~1.8
const int backward = int (2*ran.flat()); //0:forward 1:backward
// This is not an integral over two distributions, but a random choice of
// which option to take, each one representing the full integral.
// So the weight of the integral should not be multiplied by 2
constexpr double STDDEV_Y_V = 1.8;
const double y = random_normal(STDDEV_Y_V, ran);
return y + (1.-2.*backward)*2.6; // add or subtract 2.6
}
namespace {
/// partons are ordered: even = anti, 0 = gluon
- HEJ::ParticleID index_to_pid(size_t i){
+ constexpr HEJ::ParticleID index_to_pid(size_t i){
if(!i) return HEJ::pid::gluon;
return static_cast<HEJ::ParticleID>( i%2 ? (i+1)/2 : -i/2 );
}
/// partons are ordered: even = anti, 0 = gluon
- size_t pid_to_index(HEJ::ParticleID id){
+ constexpr size_t pid_to_index(HEJ::ParticleID id){
if(id==HEJ::pid::gluon) return 0;
return id>0 ? id*2-1 : std::abs(id)*2;
}
+ constexpr PhaseSpacePoint::part_mask GLUON = 1u << pid_to_index(HEJ::pid::gluon);
+ constexpr PhaseSpacePoint::part_mask ALL = ~0;
+
PhaseSpacePoint::part_mask init_allowed(HEJ::ParticleID const id){
if(std::abs(id) == HEJ::pid::proton)
- return ~0;
+ return ALL;
PhaseSpacePoint::part_mask out{0};
if(HEJ::is_parton(id))
out[pid_to_index(id)] = true;
return out;
}
/// decides which "index" (see index_to_pid) are allowed for process
PhaseSpacePoint::part_mask allowed_quarks(HEJ::ParticleID const boson){
if(std::abs(boson) != HEJ::pid::Wp){
- return ~1; // not a gluon
+ return ~GLUON;
}
// special case W:
// Wp: anti-down or up-type quark, no b/t
// Wm: down or anti-up-type quark, no b/t
return boson>0?0b00011001100 // NOLINT(readability-magic-numbers)
:0b00100110010; // NOLINT(readability-magic-numbers)
}
} // namespace
std::array<PhaseSpacePoint::part_mask,2> PhaseSpacePoint::incoming_AWZ(
- Process const & proc, Subleading const subl_channels,
+ Process const & proc,
std::array<part_mask,2> allowed_partons,
HEJ::RNG & ran
){
assert(proc.boson);
auto couple_parton = allowed_quarks(*proc.boson);
- // eqqbar possible if one incoming is a gluon
- if(proc.njets >= 3 && subl_channels[subleading::eqqbar]){
- couple_parton.set(pid_to_index(HEJ::ParticleID::gluon));
- }
if( // coupling possible through input
allowed_partons[0] == (couple_parton&allowed_partons[0])
|| allowed_partons[1] == (couple_parton&allowed_partons[1])
- // cqqbar possible
- || (proc.njets >= 4 && subl_channels[subleading::cqqbar])
){
return allowed_partons;
}
// only first can couple
if( (allowed_partons[0]&couple_parton).any()
&&(allowed_partons[1]&couple_parton).none()
){
return {allowed_partons[0]&couple_parton, allowed_partons[1]};
}
// only second can couple
if( (allowed_partons[0]&couple_parton).none()
&& (allowed_partons[1]&couple_parton).any()
){
return {allowed_partons[0], allowed_partons[1]&couple_parton};
}
// both can couple
if( (allowed_partons[0]&couple_parton).any()
&& (allowed_partons[1]&couple_parton).any()
){
double rnd = ran.flat();
weight_*=3.;
if(rnd<1./3.){
return {
allowed_partons[0] & couple_parton,
allowed_partons[1] & ~couple_parton
};
}
if(rnd<2./3.){
return {
allowed_partons[0] & ~couple_parton,
allowed_partons[1] & couple_parton
};
}
return {
allowed_partons[0] & couple_parton,
allowed_partons[1] & couple_parton
};
}
throw std::invalid_argument{"Incoming state not allowed."};
}
std::array<PhaseSpacePoint::part_mask,2> PhaseSpacePoint::incoming_eqqbar(
std::array<part_mask,2> allowed_partons, HEJ::RNG & ran
){
auto const gluon_idx = pid_to_index(HEJ::pid::gluon);
auto & first_beam = allowed_partons[0];
auto & second_beam = allowed_partons[1];
if(first_beam[gluon_idx] && !second_beam[gluon_idx]){
first_beam.reset();
first_beam.set(gluon_idx);
return allowed_partons;
}
if(!first_beam[gluon_idx] && second_beam[gluon_idx]) {
second_beam.reset();
second_beam.set(gluon_idx);
return allowed_partons;
}
if(first_beam[gluon_idx] && second_beam[gluon_idx]) {
// both beams can be gluons
// if one can't be a quark everything is good
auto first_quarks = first_beam;
first_quarks.reset(gluon_idx);
auto second_quarks = second_beam;
second_quarks.reset(gluon_idx);
if(first_quarks.none() || second_quarks.none()){
return allowed_partons;
}
// else choose one to be a gluon
double rnd = ran.flat();
weight_*=3.;
if(rnd<1./3.){
allowed_partons[0].reset();
allowed_partons[0].set(gluon_idx);
allowed_partons[1].reset(gluon_idx);
} else if(rnd<2./3.){
allowed_partons[1].reset();
allowed_partons[1].set(gluon_idx);
allowed_partons[0].reset(gluon_idx);
} else {
allowed_partons[0].reset();
allowed_partons[0].set(gluon_idx);
allowed_partons[1].reset();
allowed_partons[1].set(gluon_idx);
}
return allowed_partons;
}
throw std::invalid_argument{
"Incoming state not allowed for pure extremal qqbar."};
}
std::array<PhaseSpacePoint::part_mask,2> PhaseSpacePoint::incoming_uno(
std::array<part_mask,2> allowed_partons, HEJ::RNG & ran
){
auto const gluon_idx = pid_to_index(HEJ::pid::gluon);
auto & first_beam = allowed_partons[0];
+ auto & second_beam = allowed_partons[1];
+ // cannot have Higgs on same side as uno
+ if(outgoing_[0].type == HEJ::pid::Higgs || outgoing_[1].type == HEJ::pid::Higgs) {
+ assert(outgoing().size() >= 4);
+ second_beam.reset(gluon_idx);
+ assert(second_beam.any());
+ } else if(
+ outgoing_.back().type == HEJ::pid::Higgs
+ || outgoing_[outgoing_.size() - 2].type == HEJ::pid::Higgs
+ ) {
+ assert(outgoing().size() >= 4);
+ first_beam.reset(gluon_idx);
+ assert(first_beam.any());
+ }
auto first_quarks = first_beam;
first_quarks.reset(gluon_idx);
- auto & second_beam = allowed_partons[1];
auto second_quarks = second_beam;
second_quarks.reset(gluon_idx);
if(first_quarks.any() && second_quarks.none()){
first_beam.reset(gluon_idx);
return allowed_partons;
}
if(first_quarks.none() && second_quarks.any()) {
second_beam.reset(gluon_idx);
return allowed_partons;
}
if(first_quarks.any() && second_quarks.any()) {
// both beams can be quarks
// if one can't be gluon everything is good
if(!first_beam[gluon_idx] || !second_beam[gluon_idx]){
return allowed_partons;
}
// else choose one to be a quark
double rnd = ran.flat();
weight_*=3.;
if(rnd<1./3.){
allowed_partons[0].reset(gluon_idx);
allowed_partons[1].reset();
allowed_partons[1].set(gluon_idx);
} else if(rnd<2./3.){
allowed_partons[1].reset(gluon_idx);
allowed_partons[0].reset();
allowed_partons[0].set(gluon_idx);
} else {
allowed_partons[0].reset(gluon_idx);
allowed_partons[1].reset(gluon_idx);
}
return allowed_partons;
}
throw std::invalid_argument{
"Incoming state not allowed for pure unordered."};
}
/**
* @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 qqbar => no incoming gluon
* b) 2j => no incoming gluon
* c) >3j => can couple OR is gluon => 2 gluons become qqbar later
* 3. pure eqqbar requires at least one gluon
* 4. pure uno requires at least one quark
*/
std::array<PhaseSpacePoint::part_mask,2> PhaseSpacePoint::allowed_incoming(
Process const & proc,
- double const subl_chance, Subleading const subl_channels,
+ std::optional<subleading::Channels> channel,
HEJ::RNG & ran
){
// all possible incoming states
std::array<part_mask,2> allowed_partons{
init_allowed(proc.incoming[0]),
init_allowed(proc.incoming[1])
};
- // special case A/W/Z
- if(proc.boson && is_AWZ_proccess(proc)){
- allowed_partons = incoming_AWZ(proc, subl_channels, allowed_partons, ran);
+ // special case: A/W/Z without qqbar
+ if(
+ is_AWZ_proccess(proc)
+ && (!channel || *channel == subleading::unordered)
+ ) {
+ return incoming_AWZ(proc, allowed_partons, ran);
}
- // special case: pure subleading
- if(subl_chance!=1.){
+ if(!channel) return allowed_partons;
+ switch(*channel) {
+ case subleading::central_qqbar:
return allowed_partons;
- }
- auto other_channels = subl_channels;
- // pure eqqbar
- other_channels.reset(subleading::eqqbar);
- if(other_channels.none()){
+ case subleading::extremal_qqbar:
return incoming_eqqbar(allowed_partons, ran);
- }
- other_channels = subl_channels;
- // pure uno
- other_channels.reset(subleading::uno);
- if(other_channels.none()){
+ case subleading::unordered:
return incoming_uno(allowed_partons, ran);
- }
- return allowed_partons;
+ default:;
+ };
+ throw std::logic_error{"enum value not covered"};
}
-
void PhaseSpacePoint::reconstruct_incoming(
Process const & proc,
- double const subl_chance, Subleading const subl_channels,
+ std::optional<subleading::Channels> const channel,
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].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_ = Status::too_much_energy;
return;
}
auto const & ids = proc.incoming;
std::array<part_mask,2> allowed_partons
- = allowed_incoming(proc, subl_chance, subl_channels, ran);
+ = allowed_incoming(proc, channel, ran);
for(size_t i = 0; i < 2; ++i){
if(ids[i] == HEJ::pid::proton || ids[i] == HEJ::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));
}
HEJ::ParticleID PhaseSpacePoint::generate_incoming_id(
size_t const beam_idx, double const x, double const uf,
HEJ::PDF & pdf, part_mask allowed_partons, HEJ::RNG & ran
){
std::array<double,NUM_PARTONS> pdf_wt{};
pdf_wt[0] = allowed_partons[0]?
std::fabs(pdf.pdfpt(beam_idx,x,uf,HEJ::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: "<<x<<" "<<uf<<" "
<<sum<<" "<<pdftot<<" "<<r1<<std::endl;
throw std::logic_error{"Failed to choose parton flavour"};
}
void PhaseSpacePoint::couple_boson(
HEJ::ParticleID const boson, HEJ::RNG & ran
){
if(std::abs(boson) != HEJ::pid::Wp) return; // only matters for W
/// @TODO this could be use to sanity check gamma and Z
// find all possible quarks
std::vector<PartonIterator> 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_it, boson) )
allowed_parts.push_back(part_it);
}
if(allowed_parts.empty()){
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<HEJ::ParticleID>( allowed_parts[idx]->type - W_charge );
}
double PhaseSpacePoint::random_normal( double stddev, HEJ::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;
}
+ double PhaseSpacePoint::random_normal_trunc(
+ const double stddev,
+ HEJ::RNG & ran,
+ const double min,
+ const double max
+ ) {
+ double result;
+ std::uint64_t trials = 0;
+ do {
+ ++trials;
+ const double r1 = ran.flat();
+ const double r2 = ran.flat();
+ const double lninvr1 = -std::log(r1);
+ result = stddev*std::sqrt(2.*lninvr1)*std::cos(2.*M_PI*r2);
+ } while(result < min || result > max);
+ weight_ *= exp(result*result/(2*stddev*stddev))*std::sqrt(2.*M_PI)*stddev / trials;
+ 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 HEJ::nearby_ep(diff, fastjet::PseudoJet{}, ep);
}
+ std::optional<subleading::Channels> PhaseSpacePoint::select_channel(
+ Subleading const subl_channels,
+ double const chance,
+ HEJ::RNG & ran
+ ) {
+ const double r = ran.flat();
+ if(r > chance) {
+ weight_ /= 1 - chance;
+ return {};
+ }
+ // pick a random subleading channel (flat)
+ const std::size_t num_channels = subl_channels.count();
+ weight_ *= num_channels / chance;
+ std::size_t channel_idx = r / chance * num_channels;
+ for(
+ unsigned channel = subleading::first;
+ channel <= subleading::last;
+ ++channel
+ ) {
+ if(subl_channels.test(channel)) {
+ if(channel_idx == 0) {
+ return {static_cast<subleading::Channels>(channel)};
+ } else {
+ --channel_idx;
+ }
+ }
+ }
+ throw std::logic_error{"unreachable"};
+ }
+
} // namespace HEJFOG
diff --git a/FixedOrderGen/t/W_2j_classify.cc b/FixedOrderGen/t/W_2j_classify.cc
index 7b7ee17..2e0c264 100644
--- a/FixedOrderGen/t/W_2j_classify.cc
+++ b/FixedOrderGen/t/W_2j_classify.cc
@@ -1,163 +1,163 @@
/**
* \brief check that the PSP generates only "valid" W + 2 jets events
*
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2019-2020
* \copyright GPLv2 or later
*/
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include "fastjet/JetDefinition.hh"
#include "HEJ/EWConstants.hh"
#include "HEJ/exceptions.hh"
#include "HEJ/Mixmax.hh"
#include "HEJ/Particle.hh"
#include "HEJ/PDF.hh"
#include "HEJ/PDG_codes.hh"
#include "Decay.hh"
#include "JetParameters.hh"
#include "PhaseSpacePoint.hh"
#include "Process.hh"
#include "Status.hh"
#include "Subleading.hh"
namespace {
using namespace HEJFOG;
using namespace HEJ;
void print_psp(PhaseSpacePoint const & psp){
std::cerr << "Process:\n"
<< psp.incoming()[0].type << " + "<< psp.incoming()[1].type << " -> ";
for(auto const & out: psp.outgoing()){
std::cerr << out.type << " ";
}
std::cerr << "\n";
}
void bail_out(PhaseSpacePoint const & psp, std::string msg){
print_psp(psp);
throw std::logic_error{msg};
}
bool is_up_type(Particle const & part){
return HEJ::is_anyquark(part) && !(std::abs(part.type)%2);
}
bool is_down_type(Particle const & part){
return HEJ::is_anyquark(part) && std::abs(part.type)%2;
}
bool check_W2j(PhaseSpacePoint const & psp, ParticleID const W_type){
bool found_quark = false;
bool found_anti = false;
std::vector<Particle> out_partons;
std::vector<Particle> Wp;
for(auto const & p: psp.outgoing()){
if(p.type == W_type) Wp.push_back(p);
else if(is_parton(p)) out_partons.push_back(p);
else bail_out(psp, "Found particle with is not "
+std::to_string(int(W_type))+" or parton");
}
if(Wp.size() != 1 || out_partons.size() != 2){
bail_out(psp, "Found wrong number of outgoing partons");
}
for(std::size_t j=0; j<2; ++j){
auto const & in = psp.incoming()[j];
auto const & out = out_partons[j];
if(is_quark(in) || is_antiquark(in)) {
found_quark = true;
if(in.type != out.type) { // switch in quark type -> Wp couples to it
if(found_anti){ // already found qq for coupling to W
bail_out(psp, "Found second up/down pair");
} else if(std::abs(in.type)>4 || std::abs(out.type)>4){
bail_out(psp, "Found bottom/top pair");
}
found_anti = true;
if( is_up_type(in)) { // "up" in
if(W_type > 0){
// -> only allowed u -> Wp + d
if(in.type < 0 || is_up_type(out) || out.type < 0)
bail_out(psp, "u -/> Wp + d");
} else {
// -> only allowed ux -> Wm + dx
if(in.type > 0 || is_up_type(out) || out.type > 0)
bail_out(psp, "ux -/> Wm + dx");
}
} else { // "down" in
if(W_type > 0){
// -> only allowed dx -> Wp + ux
if(in.type > 0 || is_down_type(out) || out.type > 0)
bail_out(psp, "dx -/> Wp + ux");
} else {
// -> only allowed d -> Wm + u
if(in.type < 0 || is_down_type(out) || out.type < 0)
bail_out(psp, "d -/> Wm + u");
}
}
}
}
}
if(!found_quark) {
bail_out(psp, "Found no initial quarks");
} else if(!found_anti){
bail_out(psp, "Found no up/down pair");
}
return true;
}
}
int main(){
constexpr std::size_t n_psp_base = 1337;
const JetParameters jet_para{
fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30, 5, 30};
PDF pdf(11000, pid::proton, pid::proton);
constexpr double E_cms = 13000.;
- constexpr double subl_chance = 0.5;
- const Subleading subl_channels = subleading::ALL;
+ constexpr double subl_chance = 0.0;
+ const Subleading subl_channels = subleading::NONE;
const ParticlesDecayMap boson_decays{
{pid::Wp, {Decay{ {pid::e_bar, pid::nu_e}, 1.} }},
{pid::Wm, {Decay{ {pid::e, pid::nu_e_bar}, 1.} }}
};
constexpr EWConstants ew_constants{246.2196508, {80.385, 2.085},
{91.187, 2.495},
{125, 0.004165} };
HEJ::Mixmax ran{};
// Wp2j
Process proc {{pid::proton,pid::proton}, 2, pid::Wp, {}};
std::size_t n_psp = n_psp_base;
for( std::size_t i = 0; i<n_psp; ++i){
const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_chance,subl_channels,
boson_decays, ew_constants, ran};
if(psp.status()==Status::good){
check_W2j(psp, *proc.boson);
} else { // bad process -> try again
++n_psp;
}
}
std::cout << "Wp+2j: Took " << n_psp << " to generate "
<< n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl;
// Wm2j
proc = Process{{pid::proton,pid::proton}, 2, pid::Wm, {}};
n_psp = n_psp_base;
for( std::size_t i = 0; i<n_psp; ++i){
const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_chance,subl_channels,
boson_decays, ew_constants, ran};
if(psp.status()==Status::good){
check_W2j(psp, *proc.boson);
} else { // bad process -> try again
++n_psp;
}
}
std::cout << "Wm+2j: Took " << n_psp << " to generate "
<< n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl;
std::cout << "All processes passed." << std::endl;
return EXIT_SUCCESS;
}
diff --git a/FixedOrderGen/t/W_nj_classify.cc b/FixedOrderGen/t/W_nj_classify.cc
index 44437d8..c6576db 100644
--- a/FixedOrderGen/t/W_nj_classify.cc
+++ b/FixedOrderGen/t/W_nj_classify.cc
@@ -1,226 +1,227 @@
/**
* \brief check that the PSP generates the all W+jet subleading processes
*
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2019-2020
* \copyright GPLv2 or later
*/
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include "HEJ/Event.hh"
#include "HEJ/event_types.hh"
#include "HEJ/EWConstants.hh"
#include "HEJ/exceptions.hh"
#include "HEJ/Mixmax.hh"
#include "HEJ/PDF.hh"
#include "HEJ/PDG_codes.hh"
#include "fastjet/JetDefinition.hh"
#include "Decay.hh"
#include "JetParameters.hh"
#include "PhaseSpacePoint.hh"
#include "Process.hh"
#include "Status.hh"
#include "Subleading.hh"
namespace {
using namespace HEJFOG;
using namespace HEJ;
void print_psp(PhaseSpacePoint const & psp){
std::cerr << "Process:\n"
<< psp.incoming()[0].type << " + "<< psp.incoming()[1].type << " -> ";
for(auto const & out: psp.outgoing()){
std::cerr << out.type << " ";
}
std::cerr << "\n";
}
void bail_out(PhaseSpacePoint const & psp, std::string msg){
print_psp(psp);
throw std::logic_error{msg};
}
#if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ < 6)
// gcc version < 6 explicitly needs hash function for enum
// see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970
using type_map = std::unordered_map<event_type::EventType, std::size_t, std::hash<std::size_t>>;
#else
using type_map = std::unordered_map<event_type::EventType, std::size_t>;
#endif
}
int main(){
constexpr std::size_t n_psp_base = 10375;
const JetParameters jet_para{
fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30, 5, 30};
PDF pdf(11000, pid::proton, pid::proton);
constexpr double E_cms = 13000.;
constexpr double subl_chance = 0.8;
const ParticlesDecayMap boson_decays{
{pid::Wp, {Decay{ {pid::e_bar, pid::nu_e}, 1.} }},
{pid::Wm, {Decay{ {pid::e, pid::nu_e_bar}, 1.} }}
};
constexpr EWConstants ew_constants{246.2196508, {80.385, 2.085},
{91.187, 2.495},
{125, 0.004165}
};
HEJ::Mixmax ran{};
Subleading subl_channels = subleading::ALL;
+ subl_channels.reset(subleading::cqqbar);
std::vector<event_type::EventType> allowed_types{event_type::FKL,
event_type::unob, event_type::unof, event_type::qqbar_exb, event_type::qqbar_exf};
std::cout << "Wp3j" << std::endl;
// Wp3j
Process proc {{pid::proton,pid::proton}, 3, pid::Wp, {}};
std::size_t n_psp = n_psp_base;
type_map type_counter;
for( std::size_t i = 0; i<n_psp; ++i){
const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_chance,subl_channels,
boson_decays, ew_constants, ran};
if(psp.status()==Status::good){
const Event ev{ to_EventData(psp).cluster(jet_para.def, jet_para.min_pt) };
++type_counter[ev.type()];
if( std::find(allowed_types.cbegin(), allowed_types.cend(), ev.type())
== allowed_types.cend()) {
bail_out(psp, "Found not allowed event of type " + name(ev.type()) );
}
} else { // bad process -> try again
++n_psp;
}
}
std::cout << "Wp+3j: Took " << n_psp << " to generate "
<< n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl;
std::cout << "States by classification:\n";
for(auto const & entry: type_counter){
const double fraction = static_cast<double>(entry.second)/n_psp_base;
const int percent = std::round(100*fraction);
std::cout << std::left << std::setw(25)
<< (name(entry.first) + std::string(":"))
<< entry.second << " (" << percent << "%)\n";
}
for(auto const & t: allowed_types){
if(type_counter[t] < 0.05 * n_psp_base){
std::cerr << "Less than 5% of the events are of type " << name(t) << std::endl;
return EXIT_FAILURE;
}
}
// Wm3j - only uno
proc = Process{{pid::proton,pid::proton}, 3, pid::Wm, {}};
n_psp = n_psp_base;
subl_channels.reset();
subl_channels.set(subleading::uno);
allowed_types = {event_type::FKL, event_type::unob, event_type::unof};
type_counter.clear();
for( std::size_t i = 0; i<n_psp; ++i){
const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_chance,subl_channels,
boson_decays, ew_constants, ran};
if(psp.status()==Status::good){
const Event ev{ to_EventData(psp).cluster(jet_para.def, jet_para.min_pt) };
++type_counter[ev.type()];
if( std::find(allowed_types.cbegin(), allowed_types.cend(), ev.type())
== allowed_types.cend()) {
bail_out(psp, "Found not allowed event of type " + name(ev.type()) );
}
} else { // bad process -> try again
++n_psp;
}
}
std::cout << "Wm+3j (only uno): Took " << n_psp << " to generate "
<< n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl;
std::cout << "States by classification:\n";
for(auto const & entry: type_counter){
const double fraction = static_cast<double>(entry.second)/n_psp_base;
const int percent = std::round(100*fraction);
std::cout << std::left << std::setw(25)
<< (name(entry.first) + std::string(":"))
<< entry.second << " (" << percent << "%)\n";
}
for(auto const & t: allowed_types){
if(type_counter[t] < 0.05 * n_psp_base){
std::cerr << "Less than 5% of the events are of type " << name(t) << std::endl;
return EXIT_FAILURE;
}
}
// Wm4j
proc = Process{{pid::proton,pid::proton}, 4, pid::Wm, {}};
n_psp = n_psp_base;
subl_channels.set();
allowed_types = {event_type::FKL,
event_type::unob, event_type::unof, event_type::qqbar_exb, event_type::qqbar_exf,
event_type::qqbar_mid};
type_counter.clear();
std::array<type_map,3> wpos_counter; // position of the W boson (back, central, forward)
for( std::size_t i = 0; i<n_psp; ++i){
const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_chance,subl_channels,
boson_decays, ew_constants, ran};
if(psp.status()==Status::good){
const Event ev{ to_EventData(psp).cluster(jet_para.def, jet_para.min_pt)};
++type_counter[ev.type()];
if( std::find(allowed_types.cbegin(), allowed_types.cend(), ev.type())
== allowed_types.cend()) {
bail_out(psp, "Found not allowed event of type " + name(ev.type()) );
}
if(ev.outgoing().begin()->type==pid::Wm){
++wpos_counter[0][ev.type()];
} else if(ev.outgoing().rbegin()->type==pid::Wm){
++wpos_counter[2][ev.type()];
} else {
++wpos_counter[1][ev.type()];
}
} else { // bad process -> try again
++n_psp;
}
}
std::cout << "Wm+4j: Took " << n_psp << " to generate "
<< n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl;
std::cout << "States by classification:\n";
for(auto const & entry: type_counter){
const double fraction = static_cast<double>(entry.second)/n_psp_base;
const int percent = std::round(100*fraction);
std::cout << std::left << std::setw(25)
<< (name(entry.first) + std::string(":"))
<< entry.second << " (" << percent << "%)\n";
}
for(auto const & t: allowed_types){
if(type_counter[t] < 0.03 * n_psp_base){
std::cerr << "Less than 3% of the events are of type " << name(t) << std::endl;
return EXIT_FAILURE;
}
}
std::cout << "Stats by Wpos:\n";
for(std::size_t i=0; i<wpos_counter.size(); ++i){
std::cout << "position " << i << std::endl;;
for(auto const & t: allowed_types){
std::cout << name(t) << " " << wpos_counter[i][t] << std::endl;
if(wpos_counter[i][t] < 0.05 * type_counter[t]){
std::cerr << "Less than 5% of the events are of type " << name(t)
<< " with W at position " << i << std::endl;
return EXIT_FAILURE;
}
}
}
std::cout << "All processes passed." << std::endl;
return EXIT_SUCCESS;
}
diff --git a/FixedOrderGen/t/Z_2j_classify.cc b/FixedOrderGen/t/Z_2j_classify.cc
index b428a0e..9600e53 100644
--- a/FixedOrderGen/t/Z_2j_classify.cc
+++ b/FixedOrderGen/t/Z_2j_classify.cc
@@ -1,111 +1,111 @@
/**
* \brief check that the PSP generates only "valid" Z + 2 jets events
*
* \authors The HEJ collaboration (see AUTHORS for details)
* \date 2019-2020
* \copyright GPLv2 or later
*/
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include "fastjet/JetDefinition.hh"
#include "HEJ/EWConstants.hh"
#include "HEJ/exceptions.hh"
#include "HEJ/Mixmax.hh"
#include "HEJ/Particle.hh"
#include "HEJ/PDF.hh"
#include "HEJ/PDG_codes.hh"
#include "Decay.hh"
#include "JetParameters.hh"
#include "PhaseSpacePoint.hh"
#include "Process.hh"
#include "Status.hh"
#include "Subleading.hh"
namespace {
using namespace HEJFOG;
using namespace HEJ;
void print_psp(PhaseSpacePoint const & psp){
std::cerr << "Process:\n"
<< psp.incoming()[0].type << " + "<< psp.incoming()[1].type << " -> ";
for(auto const & out: psp.outgoing()){
std::cerr << out.type << " ";
}
std::cerr << "\n";
}
void bail_out(PhaseSpacePoint const & psp, std::string msg){
print_psp(psp);
throw std::logic_error{msg};
}
bool check_Z2j(PhaseSpacePoint const & psp){
bool found_quark = false;
std::vector<Particle> out_partons;
std::vector<Particle> Z;
for(auto const & p: psp.outgoing()){
if(p.type == pid::Z_photon_mix) Z.push_back(p);
else if(is_parton(p)) out_partons.push_back(p);
else bail_out(psp, "Found particle which is not Z or parton");
}
if(Z.size() != 1 || out_partons.size() != 2){
bail_out(psp, "Found wrong number of outgoing partons");
}
for(std::size_t j=0; j<2; ++j){
auto const & in = psp.incoming()[j];
auto const & out = out_partons[j];
if(is_quark(in) || is_antiquark(in)) {
found_quark = true;
if(in.type != out.type) {
bail_out(psp, "Switch in quark type");
}
}
}
if(!found_quark) {
bail_out(psp, "Found no initial quark");
}
return true;
}
}
int main(){
constexpr std::size_t n_psp_base = 1337;
const JetParameters jet_para{
fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30, 5, 30};
PDF pdf(11000, pid::proton, pid::proton);
constexpr double E_cms = 13000.;
- constexpr double subl_chance = 0.5;
- const Subleading subl_channels = subleading::ALL;
+ constexpr double subl_chance = 0.0;
+ const Subleading subl_channels = subleading::NONE;
const ParticlesDecayMap boson_decays{
{pid::Wp, {Decay{ {pid::e_bar, pid::nu_e}, 1.} }},
{pid::Wm, {Decay{ {pid::e, pid::nu_e_bar}, 1.} }}
};
constexpr EWConstants ew_constants{246.2196508, {80.385, 2.085},
{91.187, 2.495},
{125, 0.004165} };
HEJ::Mixmax ran{};
// Z2j
Process proc {{pid::proton,pid::proton}, 2, pid::Z_photon_mix, {pid::e_bar,pid::e}};
std::size_t n_psp = n_psp_base;
for( std::size_t i = 0; i<n_psp; ++i){
const PhaseSpacePoint psp{proc,jet_para,pdf,E_cms, subl_chance,subl_channels,
boson_decays, ew_constants, ran};
if(psp.status()==Status::good){
check_Z2j(psp);
} else { // bad process -> try again
++n_psp;
}
}
std::cout << "Z+2j: Took " << n_psp << " to generate "
<< n_psp_base << " successfully PSP (" << 1.*n_psp/n_psp_base << " trials/PSP)" << std::endl;
std::cout << "All processes passed." << std::endl;
return EXIT_SUCCESS;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Nov 19, 2:48 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3804813
Default Alt Text
(69 KB)
Attached To
rHEJ HEJ
Event Timeline
Log In to Comment