diff --git a/include/HEJ/Event.hh b/include/HEJ/Event.hh index 7d828d8..ebafcad 100644 --- a/include/HEJ/Event.hh +++ b/include/HEJ/Event.hh @@ -1,351 +1,369 @@ /** \file * \brief Declares the Event class and helpers * * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019 * \copyright GPLv2 or later */ #pragma once #include <array> #include <memory> #include <string> #include <unordered_map> #include <vector> #include "boost/iterator/filter_iterator.hpp" #include "fastjet/ClusterSequence.hh" #include "HEJ/event_types.hh" #include "HEJ/Parameters.hh" #include "HEJ/Particle.hh" #include "HEJ/RNG.hh" namespace LHEF { class HEPEUP; class HEPRUP; } namespace fastjet { class JetDefinition; } namespace HEJ { struct UnclusteredEvent; /** @brief An event with clustered jets * * This is the main HEJ 2 event class. * It contains kinematic information including jet clustering, * parameter (e.g. scale) settings and the event weight. */ class Event { public: class EventData; //! Iterator over partons using ConstPartonIterator = boost::filter_iterator< bool (*)(Particle const &), std::vector<Particle>::const_iterator >; //! Reverse Iterator over partons using ConstReversePartonIterator = std::reverse_iterator< ConstPartonIterator>; //! No default Constructor Event() = delete; //! Event Constructor adding jet clustering to an unclustered event //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.2.0 [[deprecated("UnclusteredEvent will be replaced by EventData")]] Event( UnclusteredEvent const & ev, fastjet::JetDefinition const & jet_def, double min_jet_pt ); //! @name Particle Access //! @{ //! Incoming particles std::array<Particle, 2> const & incoming() const{ return incoming_; } //! Outgoing particles std::vector<Particle> const & outgoing() const{ return outgoing_; } //! 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; //! Particle decays /** * The key in the returned map corresponds to the index in the * vector returned by outgoing() */ std::unordered_map<size_t, std::vector<Particle>> const & decays() const{ return decays_; } //! The jets formed by the outgoing partons, sorted in rapidity std::vector<fastjet::PseudoJet> const & jets() const{ return jets_; } //! @} //! @name Weight variations //! @{ //! All chosen parameter, i.e. scale choices (const version) Parameters<EventParameters> const & parameters() const{ return parameters_; } //! All chosen parameter, i.e. scale choices Parameters<EventParameters> & parameters(){ return parameters_; } //! Central parameter choice (const version) EventParameters const & central() const{ return parameters_.central; } //! Central parameter choice EventParameters & central(){ return parameters_.central; } //! Parameter (scale) variations (const version) std::vector<EventParameters> const & variations() const{ return parameters_.variations; } //! Parameter (scale) variations std::vector<EventParameters> & variations(){ return parameters_.variations; } //! Parameter (scale) variation (const version) /** * @param i Index of the requested variation */ EventParameters const & variations(size_t i) const{ return parameters_.variations.at(i); } //! Parameter (scale) variation /** * @param i Index of the requested variation */ EventParameters & variations(size_t i){ return parameters_.variations.at(i); } //! @} //! Indices of the jets the outgoing partons belong to /** * @param jets Jets to be tested * @returns A vector containing, for each outgoing parton, * the index in the vector of jets the considered parton * belongs to. If the parton is not inside any of the * passed jets, the corresponding index is set to -1. */ std::vector<int> particle_jet_indices( std::vector<fastjet::PseudoJet> const & jets ) const { return cs_.particle_jet_indices(jets); } //! particle_jet_indices() of the Event jets() std::vector<int> particle_jet_indices() const { return particle_jet_indices(jets()); } //! Jet definition used for clustering fastjet::JetDefinition const & jet_def() const{ return cs_.jet_def(); } //! Minimum jet transverse momentum double min_jet_pt() const{ return min_jet_pt_; } //! Event type event_type::EventType type() const{ return type_; } //! Give colours to each particle /** * @returns true if new colours are generated, i.e. same as is_resummable() * @details Colour ordering is done according to leading colour in the MRK * limit, see \cite Andersen:2011zd. This only affects \ref * is_resummable() "HEJ" configurations, all other \ref event_type * "EventTypes" will be ignored. * @note This overwrites all previously set colours. */ bool generate_colours(HEJ::RNG &); //! Check that current colours are leading in the high energy limit /** * @details Checks that the colour configuration can be split up in * multiple, rapidity ordered, non-overlapping ladders. Such * configurations are leading in the MRK limit, see * \cite Andersen:2011zd * * @note This is _not_ to be confused with \ref is_resummable(), however * for all resummable states it is possible to create a leading colour * configuration, see generate_colours() */ bool is_leading_colour() const; /** * @brief Check if given event could have been produced by HEJ * @details A HEJ state has to fulfil: * 1. type() has to be \ref is_resummable() "resummable" * 2. Soft radiation in the tagging jets contributes at most to * `max_ext_soft_pt_fraction` of the total jet \f$ p_\perp \f$ * * @note This is true for any resummed stated produced by the * EventReweighter or any \ref is_resummable() "resummable" Leading * Order state. * * @param max_ext_soft_pt_fraction Maximum transverse momentum fraction from * soft radiation in extremal jets * @param min_extparton_pt Absolute minimal \f$ p_\perp \f$, * \b deprecated use max_ext_soft_pt_fraction * instead * @return True if this state could have been produced by HEJ */ bool valid_hej_state( double max_ext_soft_pt_fraction, double min_extparton_pt = 0.) const; private: //! \internal //! @brief Construct Event explicitly from input. /** This is only intended to be called from EventData. * * \warning The input is taken _as is_, sorting and classification has to be * done externally, i.e. by EventData */ Event( std::array<Particle, 2> && incoming, std::vector<Particle> && outgoing, std::unordered_map<size_t, std::vector<Particle>> && decays, Parameters<EventParameters> && parameters, fastjet::JetDefinition const & jet_def, double const min_jet_pt ); + //! Iterator over partons (non-const) + using PartonIterator = boost::filter_iterator< + bool (*)(Particle const &), + std::vector<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::array<Particle, 2> incoming_; std::vector<Particle> outgoing_; std::unordered_map<size_t, std::vector<Particle>> decays_; std::vector<fastjet::PseudoJet> jets_; Parameters<EventParameters> parameters_; fastjet::ClusterSequence cs_; double min_jet_pt_; event_type::EventType type_; }; // end class Event //! Class to store general Event setup, i.e. Phase space and weights class Event::EventData { public: //! Default Constructor EventData() = default; //! Constructor from LesHouches event information EventData(LHEF::HEPEUP const & hepeup); //! Constructor with all values given EventData( std::array<Particle, 2> incoming, std::vector<Particle> outgoing, std::unordered_map<size_t, std::vector<Particle>> decays, Parameters<EventParameters> parameters ): incoming(std::move(incoming)), outgoing(std::move(outgoing)), decays(std::move(decays)), parameters(std::move(parameters)) {} //! Generate an Event from the stored EventData. /** * @details Do jet clustering and classification. * Use this to generate an Event. * * @note Calling this function destroys EventData * * @param jet_def Jet definition * @param min_jet_pt minimal \f$p_T\f$ for each jet * * @returns Full clustered and classified event. */ Event cluster( fastjet::JetDefinition const & jet_def, double const min_jet_pt); //! Alias for cluster() Event operator()( fastjet::JetDefinition const & jet_def, double const min_jet_pt){ return cluster(jet_def, min_jet_pt); } //! Sort particles in rapidity void sort(); //! Reconstruct intermediate particles from final-state leptons /** * Final-state leptons are created from virtual photons, W, or Z bosons. * This function tries to reconstruct such intermediate bosons if they * are not part of the event record. */ void reconstruct_intermediate(); //! Incoming particles std::array<Particle, 2> incoming; //! Outcoing particles std::vector<Particle> outgoing; //! Particle decays in the format {outgoing index, decay products} std::unordered_map<size_t, std::vector<Particle>> decays; //! Parameters, e.g. scale or inital weight Parameters<EventParameters> parameters; }; // end class EventData //! Print Event std::ostream& operator<<(std::ostream & os, Event const & ev); //! Square of the partonic centre-of-mass energy \f$\hat{s}\f$ double shat(Event const & ev); //! Convert an event to a LHEF::HEPEUP LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP *); // put deprecated warning at the end, so don't get the warning inside Event.hh, // additionally doxygen can not identify [[deprecated]] correctly struct [[deprecated("UnclusteredEvent will be replaced by EventData")]] UnclusteredEvent; //! An event before jet clustering //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.2.0 struct UnclusteredEvent{ //! Default Constructor UnclusteredEvent() = default; //! Constructor from LesHouches event information UnclusteredEvent(LHEF::HEPEUP const & hepeup); std::array<Particle, 2> incoming; /**< Incoming Particles */ std::vector<Particle> outgoing; /**< Outgoing Particles */ //! Particle decays in the format {outgoing index, decay products} std::unordered_map<size_t, std::vector<Particle>> decays; //! Central parameter (e.g. scale) choice EventParameters central; std::vector<EventParameters> variations; /**< For parameter variation */ }; } // namespace HEJ diff --git a/src/Event.cc b/src/Event.cc index 400b2e7..9bed9d5 100644 --- a/src/Event.cc +++ b/src/Event.cc @@ -1,1099 +1,1137 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2019 * \copyright GPLv2 or later */ #include "HEJ/Event.hh" #include <algorithm> #include <assert.h> #include <iterator> #include <numeric> #include <unordered_set> #include <utility> #include "LHEF/LHEF.h" #include "fastjet/JetDefinition.hh" #include "HEJ/Constants.hh" #include "HEJ/exceptions.hh" #include "HEJ/PDG_codes.hh" namespace HEJ{ namespace { constexpr int status_in = -1; constexpr int status_decayed = 2; constexpr int status_out = 1; //! true if leptonic W decay bool valid_W_decay( int const w_type, // sign of W std::vector<Particle> const & decays ){ if(decays.size() != 2) // no 1->2 decay return false; const int pidsum = decays[0].type + decays[1].type; if( std::abs(pidsum) != 1 || pidsum != w_type ) // correct charge return false; // leptonic decay (only check first, second follows from pidsum) if( w_type == 1 ) // W+ return is_antilepton(decays[0]) || is_neutrino(decays[0]); // W- return is_lepton(decays[0]) || is_antineutrino(decays[0]); } /// @name helper functions to determine event type //@{ /** * \brief check if final state valid for HEJ * * check if there is at most one photon, W, H, Z in the final state * and all the rest are quarks or gluons */ bool final_state_ok(Event const & ev){ std::vector<Particle> const & outgoing = ev.outgoing(); if(ev.decays().size() > 1) // at most one decay return false; bool has_AWZH_boson = false; for( size_t i=0; i<outgoing.size(); ++i ){ auto const & out{ outgoing[i] }; if(is_AWZH_boson(out.type)){ // at most one boson if(has_AWZH_boson) return false; has_AWZH_boson = true; // valid decay for W if(std::abs(out.type) == ParticleID::Wp){ // exactly 1 decay of W if( ev.decays().size() != 1 || ev.decays().cbegin()->first != i ) return false; if( !valid_W_decay(out.type>0?+1:-1, ev.decays().cbegin()->second) ) return false; } } else if(! is_parton(out.type)) return false; } return true; } /** * returns all EventTypes implemented in HEJ */ size_t implemented_types(std::vector<Particle> const & bosons){ using namespace event_type; if(bosons.empty()) return FKL | unob | unof | qqxexb | qqxexf | qqxmid; if(bosons.size()>1) return non_resummable; // multi boson switch (bosons[0].type) { case ParticleID::Wp: case ParticleID::Wm: return FKL | unob | unof | qqxexb | qqxexf | qqxmid; case ParticleID::h: return FKL | unob | unof; default: return non_resummable; } } /** * \brief function which determines if type change is consistent with Wp emission. * @param in incoming Particle id * @param out outgoing Particle id * @param qqx Current both incoming/both outgoing? * * \see is_Wm_Change */ bool is_Wp_Change(ParticleID in, ParticleID out, bool qqx){ if(!qqx && (in==-1 || in== 2 || in==-3 || in== 4)) return out== (in-1); if( qqx && (in== 1 || in==-2 || in== 3 || in==-4)) return out==-(in+1); return false; } /** * \brief function which determines if type change is consistent with Wm emission. * @param in incoming Particle id * @param out outgoing Particle id * @param qqx Current both incoming/both outgoing? * * Ensures that change type of quark line is possible by a flavour changing * Wm emission. Allows checking of qqx currents also. */ bool is_Wm_Change(ParticleID in, ParticleID out, bool qqx){ if(!qqx && (in== 1 || in==-2 || in== 3 || in==-4)) return out== (in+1); if( qqx && (in==-1 || in== 2 || in==-3 || in== 4)) return out==-(in-1); return false; } /** * \brief checks if particle type remains same from incoming to outgoing * @param in incoming Particle * @param out outgoing Particle * @param qqx Current both incoming/outgoing? */ bool no_flavour_change(ParticleID in, ParticleID out, bool qqx){ const int qqxCurrent = qqx?-1:1; if(abs(in)<=6 || in==pid::gluon) return (in==out*qqxCurrent); else return false; } bool has_2_jets(Event const & event){ return event.jets().size() >= 2; } /** * \brief check if we have a valid Impact factor * @param in incoming Particle * @param out outgoing Particle * @param qqx Current both incoming/outgoing? * @param W_change returns +1 if Wp, -1 if Wm, else 0 */ bool is_valid_impact_factor( ParticleID in, ParticleID out, bool qqx, int & W_change ){ if( no_flavour_change(in, out, qqx) ){ return true; } if( is_Wp_Change(in, out, qqx) ) { W_change+=1; return true; } if( is_Wm_Change(in, out, qqx) ) { W_change-=1; return true; } return false; } //! Returns all possible classifications from the impact factors // the beginning points are changed s.t. after the the classification they // point to the beginning of the (potential) FKL chain // sets W_change: + if Wp change // 0 if no change // - if Wm change // This function can be used with forward & backwards iterators template<class OutIterator> size_t possible_impact_factors( ParticleID incoming_id, // incoming OutIterator & begin_out, OutIterator const & end_out, // outgoing int & W_change, std::vector<Particle> const & boson, bool const backward // backward? ){ using namespace event_type; assert(boson.size() < 2); // keep track of all states that we don't test size_t not_tested = qqxmid; if(backward) not_tested |= unof | qqxexf; else not_tested |= unob | qqxexb; // Is this LL current? if( is_valid_impact_factor(incoming_id, begin_out->type, false, W_change) ){ ++begin_out; return not_tested | FKL; } // or NLL current? // -> needs two partons in two different jets if( std::distance(begin_out, end_out)>=2 ){ // Is this unordered emisson? if( incoming_id!=pid::gluon && begin_out->type==pid::gluon ){ if( is_valid_impact_factor( incoming_id, (begin_out+1)->type, false, W_change ) ){ // veto Higgs inside uno assert((begin_out+1)<end_out); if( !boson.empty() && boson.front().type == ParticleID::h ){ if( (backward && boson.front().rapidity() < (begin_out+1)->rapidity()) ||(!backward && boson.front().rapidity() > (begin_out+1)->rapidity())) return non_resummable; } begin_out+=2; return not_tested | (backward?unob:unof); } } // Is this QQbar? else if( incoming_id==pid::gluon ){ if( is_valid_impact_factor( begin_out->type, (begin_out+1)->type, true, W_change ) ){ // veto Higgs inside qqx assert((begin_out+1)<end_out); if( !boson.empty() && boson.front().type == ParticleID::h ){ if( (backward && boson.front().rapidity() < (begin_out+1)->rapidity()) ||(!backward && boson.front().rapidity() > (begin_out+1)->rapidity())) return non_resummable; } begin_out+=2; return not_tested | (backward?qqxexb:qqxexf); } } } return non_resummable; } //! Returns all possible classifications from central emissions // the beginning points are changed s.t. after the the classification they // point to the end of the emission chain // sets W_change: + if Wp change // 0 if no change // - if Wm change template<class OutIterator> size_t possible_central( OutIterator & begin_out, OutIterator const & end_out, int & W_change, std::vector<Particle> const & boson ){ using namespace event_type; assert(boson.size() < 2); // if we already passed the central chain, // then it is not a valid all-order state if(std::distance(begin_out, end_out) < 0) return non_resummable; // keep track of all states that we don't test size_t possible = unob | unof | qqxexb | qqxexf; // Find the first non-gluon/non-FKL while( (begin_out->type==pid::gluon) && (begin_out<end_out) ){ ++begin_out; } // end of chain -> FKL if( begin_out==end_out ){ return possible | FKL; } // is this a qqbar-pair? // needs two partons in two separate jets if( is_valid_impact_factor( begin_out->type, (begin_out+1)->type, true, W_change ) ){ // veto Higgs inside qqx if( !boson.empty() && boson.front().type == ParticleID::h && boson.front().rapidity() > begin_out->rapidity() && boson.front().rapidity() < (begin_out+1)->rapidity() ){ return non_resummable; } begin_out+=2; // remaining chain should be pure gluon/FKL for(; begin_out<end_out; ++begin_out){ if(begin_out->type != pid::gluon) return non_resummable; } return possible | qqxmid; } return non_resummable; } /** * \brief Checks for all event types * @param ev Event * @returns Event Type * */ event_type::EventType classify(Event const & ev){ using namespace event_type; if(! has_2_jets(ev)) return no_2_jets; // currently we can't handle multiple boson states in the ME. So they are // considered "bad_final_state" even though the "classify" could work with // them. if(! final_state_ok(ev)) return bad_final_state; // initialise variables auto const & in = ev.incoming(); auto const & out = filter_partons(ev.outgoing()); assert(std::distance(begin(in), end(in)) == 2); assert(out.size() >= 2); assert(std::distance(begin(out), end(out)) >= 2); assert(std::is_sorted(begin(out), end(out), rapidity_less{})); auto const boson{ filter_AWZH_bosons(ev.outgoing()) }; // we only allow one boson through final_state_ok assert(boson.size()<=1); // keep track of potential W couplings, at the end the sum should be 0 int remaining_Wp = 0; int remaining_Wm = 0; if(!boson.empty() && abs(boson.front().type) == ParticleID::Wp ){ if(boson.front().type>0) ++remaining_Wp; else ++remaining_Wm; } int W_change = 0; // range for current checks auto begin_out{out.cbegin()}; auto end_out{out.crbegin()}; size_t final_type = ~(no_2_jets | bad_final_state); // check forward impact factor final_type &= possible_impact_factors( in.front().type, begin_out, end_out.base(), W_change, boson, true ); if( final_type == non_resummable ) return non_resummable; if(W_change>0) remaining_Wp-=W_change; else if(W_change<0) remaining_Wm+=W_change; W_change = 0; // check backward impact factor final_type &= possible_impact_factors( in.back().type, end_out, std::make_reverse_iterator(begin_out), W_change, boson, false ); if( final_type == non_resummable ) return non_resummable; if(W_change>0) remaining_Wp-=W_change; else if(W_change<0) remaining_Wm+=W_change; W_change = 0; // check central emissions final_type &= possible_central( begin_out, end_out.base(), W_change, boson ); if( final_type == non_resummable ) return non_resummable; if(W_change>0) remaining_Wp-=W_change; else if(W_change<0) remaining_Wm+=W_change; // Check whether the right number of Ws are present if( remaining_Wp != 0 || remaining_Wm != 0 ) return non_resummable; // result has to be unique if( (final_type & (final_type-1)) != 0) return non_resummable; // check that each sub processes is implemented // (has to be done at the end) if( (final_type & ~implemented_types(boson)) != 0 ) return non_resummable; return static_cast<EventType>(final_type); } //@} Particle extract_particle(LHEF::HEPEUP const & hepeup, size_t i){ const ParticleID id = static_cast<ParticleID>(hepeup.IDUP[i]); const fastjet::PseudoJet momentum{ hepeup.PUP[i][0], hepeup.PUP[i][1], hepeup.PUP[i][2], hepeup.PUP[i][3] }; if(is_parton(id)) return Particle{ id, std::move(momentum), hepeup.ICOLUP[i] }; return Particle{ id, std::move(momentum), {} }; } bool is_decay_product(std::pair<int, int> const & mothers){ if(mothers.first == 0) return false; return mothers.second == 0 || mothers.first == mothers.second; } } // namespace anonymous Event::EventData::EventData(LHEF::HEPEUP const & hepeup){ parameters.central = EventParameters{ hepeup.scales.mur, hepeup.scales.muf, hepeup.XWGTUP }; size_t in_idx = 0; for (int i = 0; i < hepeup.NUP; ++i) { // skip decay products // we will add them later on, but we have to ensure that // the decayed particle is added before if(is_decay_product(hepeup.MOTHUP[i])) continue; auto particle = extract_particle(hepeup, i); // needed to identify mother particles for decay products particle.p.set_user_index(i+1); if(hepeup.ISTUP[i] == status_in){ if(in_idx > incoming.size()) { throw std::invalid_argument{ "Event has too many incoming particles" }; } incoming[in_idx++] = std::move(particle); } else outgoing.emplace_back(std::move(particle)); } // add decay products for (int i = 0; i < hepeup.NUP; ++i) { if(!is_decay_product(hepeup.MOTHUP[i])) continue; const int mother_id = hepeup.MOTHUP[i].first; const auto mother = std::find_if( begin(outgoing), end(outgoing), [mother_id](Particle const & particle){ return particle.p.user_index() == mother_id; } ); if(mother == end(outgoing)){ throw std::invalid_argument{"invalid decay product parent"}; } const int mother_idx = std::distance(begin(outgoing), mother); assert(mother_idx >= 0); decays[mother_idx].emplace_back(extract_particle(hepeup, i)); } } Event::Event( UnclusteredEvent const & ev, fastjet::JetDefinition const & jet_def, double const min_jet_pt ): Event( Event::EventData{ ev.incoming, ev.outgoing, ev.decays, Parameters<EventParameters>{ev.central, ev.variations} }.cluster(jet_def, min_jet_pt) ) {} //! @TODO remove in HEJ 2.2.0 UnclusteredEvent::UnclusteredEvent(LHEF::HEPEUP const & hepeup){ Event::EventData const evData{hepeup}; incoming = evData.incoming; outgoing = evData.outgoing; decays = evData.decays; central = evData.parameters.central; variations = evData.parameters.variations; } void Event::EventData::sort(){ // sort particles std::sort( begin(incoming), end(incoming), [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();} ); auto old_outgoing = std::move(outgoing); std::vector<size_t> idx(old_outgoing.size()); std::iota(idx.begin(), idx.end(), 0); std::sort(idx.begin(), idx.end(), [&old_outgoing](size_t i, size_t j){ return old_outgoing[i].rapidity() < old_outgoing[j].rapidity(); }); outgoing.clear(); outgoing.reserve(old_outgoing.size()); for(size_t i: idx) { outgoing.emplace_back(std::move(old_outgoing[i])); } // find decays again if(!decays.empty()){ auto old_decays = std::move(decays); decays.clear(); for(size_t i=0; i<idx.size(); ++i) { auto decay = old_decays.find(idx[i]); if(decay != old_decays.end()) decays.emplace(i, std::move(decay->second)); } assert(old_decays.size() == decays.size()); } } namespace { Particle reconstruct_boson(std::vector<Particle> const & leptons) { Particle decayed_boson; decayed_boson.p = leptons[0].p + leptons[1].p; const int pidsum = leptons[0].type + leptons[1].type; if(pidsum == +1) { assert(is_antilepton(leptons[0])); if(is_antineutrino(leptons[0])) { throw not_implemented{"lepton-flavour violating final state"}; } assert(is_neutrino(leptons[1])); // charged antilepton + neutrino means we had a W+ decayed_boson.type = pid::Wp; } else if(pidsum == -1) { assert(is_antilepton(leptons[0])); if(is_neutrino(leptons[1])) { throw not_implemented{"lepton-flavour violating final state"}; } assert(is_antineutrino(leptons[0])); // charged lepton + antineutrino means we had a W- decayed_boson.type = pid::Wm; } else { throw not_implemented{ "final state with leptons " + name(leptons[0].type) + " and " + name(leptons[1].type) }; } return decayed_boson; } } void Event::EventData::reconstruct_intermediate() { const auto begin_leptons = std::partition( begin(outgoing), end(outgoing), [](Particle const & p) {return !is_anylepton(p);} ); if(begin_leptons == end(outgoing)) return; assert(is_anylepton(*begin_leptons)); std::vector<Particle> leptons(begin_leptons, end(outgoing)); outgoing.erase(begin_leptons, end(outgoing)); if(leptons.size() != 2) { throw not_implemented{"Final states with one or more than two leptons"}; } std::sort( begin(leptons), end(leptons), [](Particle const & p0, Particle const & p1) { return p0.type < p1.type; } ); outgoing.emplace_back(reconstruct_boson(leptons)); decays.emplace(outgoing.size()-1, std::move(leptons)); } Event Event::EventData::cluster( fastjet::JetDefinition const & jet_def, double const min_jet_pt ){ sort(); Event ev{ std::move(incoming), std::move(outgoing), std::move(decays), std::move(parameters), jet_def, min_jet_pt }; assert(std::is_sorted(begin(ev.outgoing_), end(ev.outgoing_), rapidity_less{})); ev.type_ = classify(ev); return ev; } Event::Event( std::array<Particle, 2> && incoming, std::vector<Particle> && outgoing, std::unordered_map<size_t, std::vector<Particle>> && decays, Parameters<EventParameters> && parameters, fastjet::JetDefinition const & jet_def, double const min_jet_pt ): incoming_{std::move(incoming)}, outgoing_{std::move(outgoing)}, decays_{std::move(decays)}, parameters_{std::move(parameters)}, cs_{ to_PseudoJet( filter_partons(outgoing_) ), jet_def }, min_jet_pt_{min_jet_pt} { jets_ = sorted_by_rapidity(cs_.inclusive_jets(min_jet_pt_)); } namespace { //! check that Particles have a reasonable colour bool correct_colour(Particle const & part){ ParticleID id{ part.type }; if(!is_parton(id)) return !part.colour; if(!part.colour) return false; Colour const & col{ *part.colour }; if(is_quark(id)) return col.first != 0 && col.second == 0; if(is_antiquark(id)) return col.first == 0 && col.second != 0; assert(id==ParticleID::gluon); return col.first != 0 && col.second != 0 && col.first != col.second; } //! Connect parton to a colour line & update the line //! returns false if connection not possible bool can_connect(Particle const & part, Colour & line_colour){ if( line_colour.first == part.colour->second ){ line_colour.first = part.colour->first; return true; } if( line_colour.second == part.colour->first ){ line_colour.second = part.colour->second; return true; } return false; } } bool Event::is_leading_colour() const { if( !correct_colour(incoming()[0]) || !correct_colour(incoming()[1]) ) return false; Colour line_colour = *incoming()[0].colour; std::swap(line_colour.first, line_colour.second); - for(auto it_part = outgoing().cbegin(); it_part<outgoing().cend(); ++it_part){ - // reasonable colour - if(!correct_colour(*it_part)) - return false; - if(!is_parton(*it_part)) // skip colour neutral particles - continue; + // reasonable colour + if(!std::all_of(outgoing().cbegin(), outgoing().cend(), correct_colour)) + return false; + + for(auto it_part = cbegin_partons(); it_part!=cend_partons(); ++it_part){ // if possible connect to line (t-channel) if( !can_connect(*it_part, line_colour) ){ // else try u-channel switch (type()) { case event_type::FKL: return false; case event_type::unob: case event_type::qqxexb: { // u-channel only allowed at impact factor - if(std::distance(outgoing().cbegin(), it_part)==0 - && can_connect(*(it_part+1), line_colour) - && can_connect(*it_part, line_colour) - ){ - ++it_part; - break; + if(std::distance(cbegin_partons(), it_part)==0){ + auto it_next = it_part; + ++it_next; + if( can_connect(*it_next, line_colour) + && can_connect(*it_part, line_colour) + ){ + it_part=it_next; + break; + } } return false; } case event_type::unof: case event_type::qqxexf: { // u-channel only allowed at impact factor - if(std::distance(it_part, outgoing().cend())==2 - && can_connect(*(it_part+1), line_colour) - && can_connect(*it_part, line_colour) - ){ - ++it_part; - break; + if(std::distance(it_part, cend_partons())==2){ + auto it_next = it_part; + ++it_next; + if( can_connect(*it_next, line_colour) + && can_connect(*it_part, line_colour) + ){ + it_part=it_next; + break; + } } return false; } case event_type::qqxmid:{ // u-channel only allowed at qqx/qxq pair - if( std::distance(outgoing_.begin(), it_part)>0 - && std::distance(it_part, outgoing_.end())>2 - && ( ( (is_quark(*it_part) && is_antiquark(*(it_part+1)) ) - || (is_antiquark(*it_part) && is_quark(*(it_part+1))) ) - ) - && can_connect(*(it_part+1), line_colour) - && can_connect(*it_part, line_colour) + if( std::distance(cbegin_partons(), it_part)>0 + && std::distance(it_part, cend_partons())>2 ){ - ++it_part; - break; + auto it_next = it_part; + ++it_next; + if( ( (is_quark(*it_part) && is_antiquark(*it_next)) + || (is_antiquark(*it_part) && is_quark(*it_next)) ) + && can_connect(*it_next, line_colour) + && can_connect(*it_part, line_colour) + ){ + it_part=it_next; + break; + } } return false; } default: throw std::logic_error{"unreachable"}; } } // no colour singlet exchange/disconnected diagram if(line_colour.first == line_colour.second) return false; } return (incoming()[1].colour->first == line_colour.first) && (incoming()[1].colour->second == line_colour.second); } namespace { //! connect incoming Particle to colour flow void connect_incoming(Particle & in, int & colour, int & anti_colour){ in.colour = std::make_pair(anti_colour, colour); // gluon if(in.type == pid::gluon) return; if(in.type > 0){ // quark assert(is_quark(in)); in.colour->second = 0; colour*=-1; return; } // anti-quark assert(is_antiquark(in)); in.colour->first = 0; anti_colour*=-1; return; } //! connect outgoing Particle to colour flow void connect_outgoing( Particle & part, int & colour, int & anti_colour, RNG & ran ){ assert(colour>0 || anti_colour>0); if(part.type == ParticleID::gluon){ // gluon if(colour>0 && anti_colour>0){ // on g line => connect to colour OR anti-colour (random) if(ran.flat() < 0.5){ part.colour = std::make_pair(colour+2,colour); colour+=2; } else { part.colour = std::make_pair(anti_colour, anti_colour+2); anti_colour+=2; } } else if(colour > 0){ // on q line => connect to available colour part.colour = std::make_pair(colour+2, colour); colour+=2; } else { assert(colour<0 && anti_colour>0); // on qx line => connect to available anti-colour part.colour = std::make_pair(anti_colour, anti_colour+2); anti_colour+=2; } } else if(is_quark(part)) { // quark assert(anti_colour>0); if(colour>0){ // on g line => connect and remove anti-colour part.colour = std::make_pair(anti_colour, 0); anti_colour+=2; anti_colour*=-1; } else { // on qx line => new colour colour*=-1; part.colour = std::make_pair(colour, 0); } } else if(is_antiquark(part)) { // anti-quark assert(colour>0); if(anti_colour>0){ // on g line => connect and remove colour part.colour = std::make_pair(0, colour); colour+=2; colour*=-1; } else { // on q line => new anti-colour anti_colour*=-1; part.colour = std::make_pair(0, anti_colour); } } else { // not a parton assert(!is_parton(part)); part.colour = {}; } } //! connect to t- or u-channel colour flow template<class OutIterator> void connect_utchannel( OutIterator & it_part, int & colour, int & anti_colour, RNG & ran ){ + OutIterator it_first = it_part++; if(ran.flat()<.5) {// t-channel + connect_outgoing(*it_first, colour, anti_colour, ran); connect_outgoing(*it_part, colour, anti_colour, ran); - connect_outgoing(*(it_part+1), colour, anti_colour, ran); } else { // u-channel - connect_outgoing(*(it_part+1), colour, anti_colour, ran); connect_outgoing(*it_part, colour, anti_colour, ran); + connect_outgoing(*it_first, colour, anti_colour, ran); } - ++it_part; } } bool Event::generate_colours(RNG & ran){ // generate only for HEJ events if(!event_type::is_resummable(type())) return false; assert(std::is_sorted( begin(outgoing()), end(outgoing()), rapidity_less{})); assert(incoming()[0].pz() < incoming()[1].pz()); // positive (anti-)colour -> can connect // negative (anti-)colour -> not available/used up by (anti-)quark int colour = COLOUR_OFFSET; int anti_colour = colour+1; // initialise first connect_incoming(incoming_[0], colour, anti_colour); - for(auto it_part = outgoing_.begin(); it_part<outgoing_.end(); ++it_part){ + // reset outgoing colours + std::for_each(outgoing_.begin(), outgoing_.end(), + [](Particle & part){ part.colour = {};}); + + for(auto it_part = begin_partons(); it_part!=end_partons(); ++it_part){ switch (type()) { // subleading can connect to t- or u-channel case event_type::unob: case event_type::qqxexb: { - if( std::distance(outgoing_.begin(), it_part)==0) + if( std::distance(begin_partons(), it_part)==0) connect_utchannel(it_part, colour, anti_colour, ran); else connect_outgoing(*it_part, colour, anti_colour, ran); break; } case event_type::unof: case event_type::qqxexf: { - if( std::distance(it_part, outgoing_.end())==2) + if( std::distance(it_part, end_partons())==2) connect_utchannel(it_part, colour, anti_colour, ran); else connect_outgoing(*it_part, colour, anti_colour, ran); break; } case event_type::qqxmid:{ - if( std::distance(outgoing_.begin(), it_part)>0 - && std::distance(it_part, outgoing_.end())>2 - && ( (is_quark(*it_part) && is_antiquark(*(it_part+1))) - || (is_antiquark(*it_part) && is_quark(*(it_part+1))) ) + auto it_next = it_part; + ++it_next; + if( std::distance(begin_partons(), it_part)>0 + && std::distance(it_part, end_partons())>2 + && ( (is_quark(*it_part) && is_antiquark(*it_next)) + || (is_antiquark(*it_part) && is_quark(*it_next)) ) ) connect_utchannel(it_part, colour, anti_colour, ran); else connect_outgoing(*it_part, colour, anti_colour, ran); break; } default: // rest has to be t-channel connect_outgoing(*it_part, colour, anti_colour, ran); } } // Connect last connect_incoming(incoming_[1], anti_colour, colour); assert(is_leading_colour()); return true; } // generate_colours namespace { bool valid_parton( std::vector<fastjet::PseudoJet> const & jets, Particle const & parton, int const idx, double const max_ext_soft_pt_fraction, double const min_extparton_pt ){ // TODO code overlap with PhaseSpacePoint::pass_extremal_cuts if(min_extparton_pt > parton.pt()) return false; if(idx<0) return false; assert(static_cast<int>(jets.size())>=idx); auto const & jet{ jets[idx] }; if( (parton.p - jet).pt()/jet.pt() > max_ext_soft_pt_fraction) return false; return true; } } // this should work with multiple types bool Event::valid_hej_state(double const max_frac, double const min_pt ) const { using namespace event_type; if(!is_resummable(type())) return false; auto const & jet_idx{ particle_jet_indices() }; auto idx_begin{ jet_idx.cbegin() }; auto idx_end{ jet_idx.crbegin() }; auto part_begin{ cbegin_partons() }; auto part_end{ crbegin_partons() }; // always seperate extremal jets if( !valid_parton(jets(), *part_begin, *idx_begin, max_frac, min_pt) ) return false; ++part_begin; ++idx_begin; if( !valid_parton(jets(), *part_end, *idx_end, max_frac, min_pt) ) return false; ++part_end; ++idx_end; // unob -> second parton in own jet if( type() & (unob | qqxexb) ){ if( !valid_parton(jets(), *part_begin, *idx_begin, max_frac, min_pt) ) return false; ++part_begin; ++idx_begin; } if( type() & (unof | qqxexf) ){ if( !valid_parton(jets(), *part_end, *idx_end, max_frac, min_pt) ) return false; ++part_end; ++idx_end; } if( type() & qqxmid ){ // find qqx pair auto begin_qqx{ std::find_if( part_begin, part_end.base(), [](Particle const & part) -> bool { return part.type != ParticleID::gluon; } )}; assert(begin_qqx != part_end.base()); long int qqx_pos{ std::distance(part_begin, begin_qqx) }; assert(qqx_pos >= 0); idx_begin+=qqx_pos; if( !( valid_parton(jets(),*begin_qqx, *idx_begin, max_frac,min_pt) && valid_parton(jets(),*(++begin_qqx),*(++idx_begin),max_frac,min_pt) )) return false; } return true; } Event::ConstPartonIterator Event::begin_partons() const { return cbegin_partons(); } Event::ConstPartonIterator Event::cbegin_partons() const { return boost::make_filter_iterator( static_cast<bool (*)(Particle const &)>(is_parton), cbegin(outgoing()), cend(outgoing()) ); } Event::ConstPartonIterator Event::end_partons() const { return cend_partons(); } Event::ConstPartonIterator Event::cend_partons() const { return boost::make_filter_iterator( static_cast<bool (*)(Particle const &)>(is_parton), cend(outgoing()), cend(outgoing()) ); } Event::ConstReversePartonIterator Event::rbegin_partons() const { return crbegin_partons(); } Event::ConstReversePartonIterator Event::crbegin_partons() const { return std::reverse_iterator<ConstPartonIterator>( cend_partons() ); } Event::ConstReversePartonIterator Event::rend_partons() const { return crend_partons(); } Event::ConstReversePartonIterator Event::crend_partons() const { return std::reverse_iterator<ConstPartonIterator>( cbegin_partons() ); } + Event::PartonIterator Event::begin_partons() { + return boost::make_filter_iterator( + static_cast<bool (*)(Particle const &)>(is_parton), + begin(outgoing_), + end(outgoing_) + ); + } + + Event::PartonIterator Event::end_partons() { + return boost::make_filter_iterator( + static_cast<bool (*)(Particle const &)>(is_parton), + end(outgoing_), + end(outgoing_) + ); + } + + Event::ReversePartonIterator Event::rbegin_partons() { + return std::reverse_iterator<PartonIterator>( end_partons() ); + } + + Event::ReversePartonIterator Event::rend_partons() { + return std::reverse_iterator<PartonIterator>( begin_partons() ); + } + namespace { void print_momentum(std::ostream & os, fastjet::PseudoJet const & part){ const std::streamsize orig_prec = os.precision(); os <<std::scientific<<std::setprecision(6) << "[" <<std::setw(13)<<std::right<< part.px() << ", " <<std::setw(13)<<std::right<< part.py() << ", " <<std::setw(13)<<std::right<< part.pz() << ", " <<std::setw(13)<<std::right<< part.E() << "]"<< std::fixed; os.precision(orig_prec); } void print_colour(std::ostream & os, optional<Colour> const & col){ if(!col) os << "(no color)"; // American spelling for better alignment else os << "(" <<std::setw(3)<<std::right<< col->first << ", " <<std::setw(3)<<std::right<< col->second << ")"; } } std::ostream& operator<<(std::ostream & os, Event const & ev){ const std::streamsize orig_prec = os.precision(); os <<std::setprecision(4)<<std::fixed; os << "########## " << event_type::name(ev.type()) << " ##########" << std::endl; os << "Incoming particles:\n"; for(auto const & in: ev.incoming()){ os <<std::setw(3)<< in.type << ": "; print_colour(os, in.colour); os << " "; print_momentum(os, in.p); os << std::endl; } os << "\nOutgoing particles: " << ev.outgoing().size() << "\n"; for(auto const & out: ev.outgoing()){ os <<std::setw(3)<< out.type << ": "; print_colour(os, out.colour); os << " "; print_momentum(os, out.p); os << " => rapidity=" <<std::setw(7)<<std::right<< out.rapidity() << std::endl; } os << "\nForming Jets: " << ev.jets().size() << "\n"; for(auto const & jet: ev.jets()){ print_momentum(os, jet); os << " => rapidity=" <<std::setw(7)<<std::right<< jet.rapidity() << std::endl; } if(ev.decays().size() > 0 ){ os << "\nDecays: " << ev.decays().size() << "\n"; for(auto const & decay: ev.decays()){ os <<std::setw(3)<< ev.outgoing()[decay.first].type << " (" << decay.first << ") to:\n"; for(auto const & out: decay.second){ os <<" "<<std::setw(3)<< out.type << ": "; print_momentum(os, out.p); os << " => rapidity=" <<std::setw(7)<<std::right<< out.rapidity() << std::endl; } } } os << std::defaultfloat; os.precision(orig_prec); return os; } double shat(Event const & ev){ return (ev.incoming()[0].p + ev.incoming()[1].p).m2(); } LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP * heprup){ LHEF::HEPEUP result; result.heprup = heprup; result.weights = {{event.central().weight, nullptr}}; for(auto const & var: event.variations()){ result.weights.emplace_back(var.weight, nullptr); } size_t num_particles = event.incoming().size() + event.outgoing().size(); for(auto const & decay: event.decays()) num_particles += decay.second.size(); result.NUP = num_particles; // the following entries are pretty much meaningless result.IDPRUP = event.type(); // event type result.AQEDUP = 1./128.; // alpha_EW //result.AQCDUP = 0.118 // alpha_QCD // end meaningless part result.XWGTUP = event.central().weight; result.SCALUP = event.central().muf; result.scales.muf = event.central().muf; result.scales.mur = event.central().mur; result.scales.SCALUP = event.central().muf; result.pdfinfo.p1 = event.incoming().front().type; result.pdfinfo.p2 = event.incoming().back().type; result.pdfinfo.scale = event.central().muf; result.IDUP.reserve(num_particles); // PID result.ISTUP.reserve(num_particles); // status (in, out, decay) result.PUP.reserve(num_particles); // momentum result.MOTHUP.reserve(num_particles); // index mother particle result.ICOLUP.reserve(num_particles); // colour // incoming std::array<Particle, 2> incoming{ event.incoming() }; // First incoming should be positive pz according to LHE standard // (or at least most (everyone?) do it this way, and Pythia assumes it) if(incoming[0].pz() < incoming[1].pz()) std::swap(incoming[0], incoming[1]); for(Particle const & in: incoming){ result.IDUP.emplace_back(in.type); result.ISTUP.emplace_back(status_in); result.PUP.push_back({in.p[0], in.p[1], in.p[2], in.p[3], in.p.m()}); result.MOTHUP.emplace_back(0, 0); assert(in.colour); result.ICOLUP.emplace_back(*in.colour); } // outgoing for(size_t i = 0; i < event.outgoing().size(); ++i){ Particle const & out = event.outgoing()[i]; result.IDUP.emplace_back(out.type); const int status = event.decays().count(i)?status_decayed:status_out; result.ISTUP.emplace_back(status); result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()}); result.MOTHUP.emplace_back(1, 2); if(out.colour) result.ICOLUP.emplace_back(*out.colour); else{ result.ICOLUP.emplace_back(std::make_pair(0,0)); } } // decays for(auto const & decay: event.decays()){ for(auto const & out: decay.second){ result.IDUP.emplace_back(out.type); result.ISTUP.emplace_back(status_out); result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()}); const size_t mother_idx = 1 + event.incoming().size() + decay.first; result.MOTHUP.emplace_back(mother_idx, mother_idx); result.ICOLUP.emplace_back(0,0); } } assert(result.ICOLUP.size() == num_particles); static constexpr double unknown_spin = 9.; //per Les Houches accord result.VTIMUP = std::vector<double>(num_particles, unknown_spin); result.SPINUP = result.VTIMUP; return result; } } diff --git a/t/test_colours2.cc b/t/test_colours2.cc index a06301e..e590a4f 100644 --- a/t/test_colours2.cc +++ b/t/test_colours2.cc @@ -1,200 +1,236 @@ /** * \authors The HEJ collaboration (see AUTHORS for details) * \date 2020 * \copyright GPLv2 or later */ #include <array> #include <stdlib.h> #include <string> #include <vector> #include "HEJ/Event.hh" #include "HEJ/Constants.hh" #include "hej_test.hh" namespace { const fastjet::JetDefinition jet_def{fastjet::JetAlgorithm::antikt_algorithm, 0.4}; const double min_jet_pt{30.}; struct colour_flow { std::array<std::string,2> incoming; std::vector<std::string> outgoing; std::string colour; HEJ::Event to_event(){ using namespace HEJ; - ASSERT(colour.front()=='a' && colour.back()=='a'); // only closed loops - auto event = parse_configuration(incoming, outgoing); + if(colour.size()==0) + return event.cluster(jet_def,min_jet_pt); + + ASSERT(colour.front()=='a' && colour.back()=='a'); // only closed loops event.sort(); // fill colours with dummy value for(auto & p: event.incoming) p.colour = Colour{-1, -1}; for(auto & p: event.outgoing) p.colour = Colour{-1, -1}; int current_colour = COLOUR_OFFSET; int backup_colour = current_colour; Particle* last_part = &event.incoming.front(); // loop connections for(auto const & entry: colour){ if(entry == '_'){ // '_' -> skip connection backup_colour = current_colour; current_colour = 0; continue; } auto & part = get_particle(event, entry); part.colour->first = last_part->colour->second = current_colour; current_colour = ++backup_colour; last_part = ∂ } for(auto & in: event.incoming) std::swap(in.colour->first, in.colour->second); // reset untouched colours for(auto & out: event.outgoing) if(out.colour->first == -1 && out.colour->second ==-1) out.colour = {}; shuffle_particles(event); return event.cluster(jet_def,min_jet_pt); } private: HEJ::Particle & get_particle( HEJ::Event::EventData & event, char const name ){ if(name == 'a') return event.incoming[0]; if(name == 'b') return event.incoming[1]; size_t idx = name-'0'; ASSERT(idx<event.outgoing.size()); return event.outgoing[idx]; } }; const colour_flow FKL_ggg{{"g","g"},{"g","g","g"},{}}; const colour_flow FKL_qgq{{"1","2"},{"1","g","2"},{}}; const colour_flow FKL_qxgqx{{"-1","-2"},{"-1","g","-2"},{}}; const colour_flow FKL_qhgq{{"1","2"},{"1","h","g","2"},{}}; + const colour_flow uno_gqgq{{"1","2"},{"g","1","g","2"},{}}; const colour_flow uno_qgqg{{"1","2"},{"1","g","2","g"},{}}; + const colour_flow uno_Wgqq{{"2","2"},{"W+","g","1","2"},{}}; + const colour_flow uno_gWqq{{"2","2"},{"g","W+","1","2"},{}}; + const colour_flow uno_gqWq{{"2","2"},{"g","1","W+","2"},{}}; + const colour_flow uno_qWqg{{"2","2"},{"1","W+","2","g"},{}}; + const colour_flow uno_qqWg{{"2","2"},{"1","2","W+","g"},{}}; + const colour_flow uno_qqgW{{"2","2"},{"1","2","g","W+"},{}}; + const colour_flow qqx_qxqgq{{"g","2"},{"-1","1","g","2"},{}}; const colour_flow qqx_qgqqx{{"1","g"},{"1","g","2","-2"},{}}; const colour_flow qqx_qgqxq{{"1","g"},{"1","g","-2","2"},{}}; + const colour_flow qqx_Wqxqq{{"g","2"},{"W+","-2","1","2"},{}}; + const colour_flow qqx_qxWqq{{"g","2"},{"-2","W+","1","2"},{}}; + const colour_flow qqx_qxqWq{{"g","2"},{"-2","1","W+","2"},{}}; + const colour_flow qqx_qWqqx{{"2","g"},{"1","W+","2","-2"},{}}; + const colour_flow qqx_qqWqx{{"2","g"},{"1","2","W+","-2"},{}}; + const colour_flow qqx_qqqxW{{"2","g"},{"1","2","-2","W+"},{}}; + const colour_flow qqx_gqqxq{{"g","2"},{"g","3","-3","2"},{}}; const colour_flow qqx_qqxqg{{"1","g"},{"1","-3","3","g"},{}}; const colour_flow qqx_qqxqqx{{"1","-1"},{"1","-1","1","-1"},{}}; + const colour_flow qqx_qWqxqqx{{"2","-1"},{"1","W+","-1","1","-1"},{}}; + const colour_flow qqx_qqxWqqx{{"2","-1"},{"1","-1","W+","1","-1"},{}}; + const colour_flow qqx_qqxqWqx{{"2","-1"},{"1","-1","1","W+","-1"},{}}; void verify_colour(colour_flow configuration, std::string line, bool const expectation = true ){ configuration.colour = std::move(line); auto const event = configuration.to_event(); if(event.is_leading_colour() != expectation){ std::cerr << "Expected "<< (expectation?"":"non-") <<"leading colour\n" << event << "\nwith connection: " << configuration.colour << "\n"; throw std::logic_error("Colour verification failed"); } } void all_colours_possible( colour_flow momenta, std::vector<std::string> allowed ){ - std::vector<HEJ::Event> possible_connections; + std::vector<HEJ::Event> possible; for(auto & line: allowed){ momenta.colour = std::move(line); - possible_connections.push_back(momenta.to_event()); - if(!possible_connections.back().is_leading_colour()){ + possible.push_back(momenta.to_event()); + if(!possible.back().is_leading_colour()){ std::cerr << "Expected leading colour\n" - << possible_connections.back() + << possible.back() << "\nwith connection: " << momenta.colour << "\n"; throw std::logic_error("Colour verification failed"); } } } } int main() { // FKL all_colours_possible(FKL_ggg, {"a012ba","a01b2a","a02b1a","a0b21a", "a12b0a","a1b20a","a2b10a","ab210a"}); all_colours_possible(FKL_qgq, {"a12_b0_a", "a2_b10_a"}); all_colours_possible(FKL_qxgqx, {"a_01b_2a", "a_0b_21a"}); all_colours_possible(FKL_qhgq, {"a23_b0_a", "a3_b20_a"}); // uno all_colours_possible(uno_gqgq, {"a023_b1_a","a03_b21_a", "a23_b01_a","a3_b201_a"}); // u-channel all_colours_possible(uno_qgqg, {"a12_b30_a","a2_b310_a", "a132_b0_a","a32_b10_a"}); // u-channel + all_colours_possible(uno_Wgqq, {"a13_b2_a","a3_b12_a"}); + all_colours_possible(uno_gWqq, {"a03_b2_a","a3_b02_a"}); + all_colours_possible(uno_gqWq, {"a03_b1_a","a3_b01_a"}); + all_colours_possible(uno_qWqg, {"a2_b30_a","a32_b0_a"}); + all_colours_possible(uno_qqWg, {"a1_b30_a","a31_b0_a"}); + all_colours_possible(uno_qqgW, {"a1_b20_a","a21_b0_a"}); + // extremal qqx all_colours_possible(qqx_qgqqx, {"a12_3b0_a","a2_3b10_a", "a1b2_30_a","ab2_310_a"}); // u-channel all_colours_possible(qqx_qgqxq, {"a1b3_20_a", "ab3_210_a", "a13_2b0_a","a3_2b10_a"}); // u-channel all_colours_possible(qqx_qxqgq, {"a23_b1_0a","a3_b21_0a", "a1_023_ba","a1_03_b2a"}); // u-channel + all_colours_possible(qqx_Wqxqq, {"a3_b2_1a","a2_13_ba"}); + all_colours_possible(qqx_qxWqq, {"a3_b2_0a","a2_03_ba"}); + all_colours_possible(qqx_qxqWq, {"a3_b1_0a","a1_03_ba"}); + all_colours_possible(qqx_qWqqx, {"a2_3b0_a","ab2_30_a"}); + all_colours_possible(qqx_qqWqx, {"a1_3b0_a","ab1_30_a"}); + all_colours_possible(qqx_qqqxW, {"a1_2b0_a","ab1_20_a"}); + // central qqx all_colours_possible(qqx_gqqxq, {"a01_23_ba","a1_23_b0a", "a03_b1_2a","a3_b1_20a"}); // u-channel all_colours_possible(qqx_qqxqg, {"a3b2_10_a","ab32_10_a", "a2_13b0_a","a2_1b30_a"}); // u-channel - all_colours_possible(qqx_qqxqqx, {"ab_32_10_a", - "a2_1b_30_a"}); // u-channel + all_colours_possible(qqx_qqxqqx, {"ab_32_10_a","a2_1b_30_a"}); + all_colours_possible(qqx_qWqxqqx, {"ab_43_20_a","a3_2b_40_a"}); + all_colours_possible(qqx_qqxWqqx, {"ab_43_10_a","a3_1b_40_a"}); + all_colours_possible(qqx_qqxqWqx, {"ab_42_10_a","a2_1b_40_a"}); // forbidden // crossed FKL verify_colour(FKL_ggg, "a021ba",false); verify_colour(FKL_ggg, "a0b12a",false); verify_colour(FKL_ggg, "a10b2a",false); verify_colour(FKL_ggg, "a1b02a",false); verify_colour(FKL_ggg, "a20b1a",false); verify_colour(FKL_ggg, "a21b0a",false); verify_colour(FKL_ggg, "a2b01a",false); verify_colour(FKL_ggg, "ab120a",false); // quark with anti-colour verify_colour(FKL_qgq, "a_01b_2a",false); verify_colour(FKL_qgq, "a_0b_21a",false); verify_colour(FKL_qxgqx, "a12_b0_a",false); verify_colour(FKL_qxgqx, "a2_b10_a",false); // higgs with colour verify_colour(FKL_qhgq, "a123_b0_a",false); verify_colour(FKL_qhgq, "a3_1_b20_a",false); verify_colour(FKL_qhgq, "a3_11_b20_a",false); // not-connected verify_colour(FKL_ggg, "a012a",false); verify_colour(FKL_ggg, "a012aa",false); verify_colour(FKL_ggg, "a01ba",false); verify_colour(FKL_ggg, "a0b2a",false); verify_colour(FKL_ggg, "a_01b2a",false); verify_colour(FKL_ggg, "a01_b2a",false); verify_colour(FKL_ggg, "a0b_12a",false); verify_colour(FKL_ggg, "a012b_a",false); verify_colour(uno_gqgq, "a_1023_ba",false); // uno verify_colour(uno_gqgq, "a203_b1_a",false); verify_colour(uno_qgqg, "a312_b0_a",false); // extremal qqx verify_colour(qqx_qgqqx, "a10_3b2_a",false); verify_colour(qqx_qgqqx, "a2_31b0_a",false); verify_colour(qqx_qgqqx, "a2_31b0_a",false); verify_colour(qqx_qgqxq, "ab13_20_a",false); verify_colour(qqx_qxqgq, "a3_b1_02a",false); verify_colour(qqx_qxqgq, "a21_b3_0a",false); // central qqx verify_colour(qqx_gqqxq, "a1_203_ba",false); verify_colour(qqx_gqqxq, "a3_21_b0a",false); verify_colour(qqx_qqxqg, "ab2_130_a",false); verify_colour(qqx_qqxqg, "a3b0_12_a",false); verify_colour(qqx_qqxqqx, "a0_1b_32_a",false); verify_colour(qqx_qqxqqx, "a0_3b_12_a",false); verify_colour(qqx_qqxqqx, "a2_3b_10_a",false); verify_colour(qqx_qqxqqx, "ab_12_30_a",false); return EXIT_SUCCESS; }