Page MenuHomeHEPForge

No OneTemporary

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

Mime Type
text/x-diff
Expires
Tue, Nov 19, 2:48 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3804813
Default Alt Text
(69 KB)

Event Timeline