ATLAS Offline Software
Loading...
Searching...
No Matches
PowhegHooksST.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5// PowhegHooksST.h
6// Copyright (C) 2025 Silvia Ferrario Ravasio and Tomas Jezo
7// Authors: Silvia Ferrario Ravasio and Tomas Jezo based on
8// PowhegHooks.h by Richard Corke
9// PowhegHooksBB4l.h by Tomas Jezo, Markus Seidel, Ben Nachman
10
11// Adapted to be Athena and Pythia8_i compliant by Lukas Kretschmann (lukas.kretschmann@cern.ch)
12
13#include "Pythia8/Pythia.h"
14#include "UserHooksUtils.h"
16
17#if PYTHIA_VERSION_INTEGER >= 8310
18// Disable the pythia 8.310 plugin mechanism.
19// We use our own mechanism to create these classes, and we get link errors
20// if these are used in more than one compilation unit.
21# include "Pythia8/Plugins.h"
22# undef PYTHIA8_PLUGIN_CLASS
23# undef PYTHIA8_PLUGIN_VERSIONS
24# define PYTHIA8_PLUGIN_CLASS(BASE, CLASS, PYTHIA, SETTINGS, LOGGER)
25# define PYTHIA8_PLUGIN_VERSIONS(...)
26#endif
27
28#include "Pythia8Plugins/PowhegHooks.h"
29#include "UserSetting.h"
30
31#include <vector>
32#include <string>
33#include <iostream>
34#include <cmath>
35#include <algorithm>
36#include <map>
37#include <sstream>
38#include <cstdio>
39
40namespace Pythia8 { class PowhegHooksST; }
43
44namespace Pythia8 {
45
70class PowhegHooksST : public PowhegHooks {
71
72public:
73
81 : m_pTcutoffSetting("POWHEG:pTcutoff", 0.8),
82 m_emscalesSetting("POWHEG:single-top:emscales", 0),
83 m_emscalesAltSetting("POWHEG:emscales", 0),
84 m_debugSetting("POWHEG:single-top:DEBUG", false),
85 m_eventtype(1),
86 m_pTcutoff(0.8),
88 m_vetoMode(0),
91 {
92 std::cout << "**********************************************************" << std::endl;
93 std::cout << "* *" << std::endl;
94 std::cout << "* Applying PowhegHooksST UserHook (Single-Top)! *" << std::endl;
95 std::cout << "* Resonance-aware matching for single-top-dec-pwhg *" << std::endl;
96 std::cout << "* *" << std::endl;
97 std::cout << "**********************************************************" << std::endl;
98 }
99
100 ~PowhegHooksST() override {}
101
110 bool initAfterBeams() override {
111 // Initialize base PowhegHooks settings
112 PowhegHooks::initAfterBeams();
113
114 if (settingsPtr->isParm("POWHEG:pTcutoff")) {
115 m_pTcutoff = settingsPtr->parm("POWHEG:pTcutoff");
116 } else {
117 m_pTcutoff = m_pTcutoffSetting(settingsPtr);
118 }
119
120 m_vetoMode = settingsPtr->mode("POWHEG:veto");
121 m_vetoCount = settingsPtr->mode("POWHEG:vetoCount");
122 m_QEDvetoMode = settingsPtr->mode("POWHEG:QEDveto");
123
124 if (settingsPtr->isMode("POWHEG:single-top:emscales")) {
125 m_emscalesMode = settingsPtr->mode("POWHEG:single-top:emscales");
126 } else if (settingsPtr->isMode("POWHEG:emscales")) {
127 m_emscalesMode = settingsPtr->mode("POWHEG:emscales");
128 } else {
129 m_emscalesMode = m_emscalesSetting(settingsPtr);
130 }
131
132 if (m_emscalesMode == 1) {
133 std::cout << "PowhegHooksST: resonance veto scales taken from the POWHEG emission scales [emscales 1]\n";
134 m_readScales.clear();
135 } else {
136 std::cout << "PowhegHooksST: resonance veto scales reconstructed from the event record [emscales 0]\n";
137 m_vetoResScale.clear();
138 }
139 return true;
140 }
141
151 double pTpowhegFromRes(const Event &event, int iEmt, int iRad, int iRes) {
152 // Rad should be the radiated gluon for q->qg.
153 // For g->gg or g->qq it does not matter.
154 if (event[iEmt].id() == 21 || event[iRad].id() != 21)
155 std::swap(iEmt, iRad);
156
157 // Resonance, post branching emitter, and radiated parton
158 Vec4 pres(event[iRes].p());
159 Vec4 pemt(event[iEmt].p());
160 Vec4 prad(event[iRad].p());
161
162 // Energies in the resonance rest frame
163 double mRes = std::sqrt(pres * pres);
164 double Erad = prad * pres / mRes;
165 double Eemt = pemt * pres / mRes;
166 double scale;
167 if ((event[iEmt].id() != 21 && std::abs(event[iEmt].id()) > 6)
168 || (event[iRad].id() != 21 && std::abs(event[iRad].id()) > 6)) {
169 if (loggerPtr) loggerPtr->ABORT_MSG("pTpowheg should be called only over QCD splittings");
170 exit(1);
171 }
172
173 // Gluon (rad) emission from quark (emt) line: 2*(1 - cos theta)*E_g^2
174 if (std::abs(event[iEmt].id()) != std::abs(event[iRad].id())) {
175 scale = 2.0 * pemt * prad * Erad / Eemt;
176 } else {
177 // Gluon splitting: use a symmetrised scale
178 scale = 2.0 * pemt * prad * Eemt * Erad / (std::pow(Eemt, 2) + std::pow(Erad, 2));
179 }
180 return std::sqrt(scale);
181 }
182
191 bool canSetResonanceScale() override { return false; }
192
204 double scaleResonance(int iRes, const Event &event) override {
205 double veto_res_scale;
206 if (event[iRes].daughterList().size() > 3) {
207 if (loggerPtr) loggerPtr->ABORT_MSG("A resonance can have at most 3 children");
208 exit(1);
209 }
210
211 // For the time being, only from btilde events with tops
212 if (m_eventtype != 1) {
213 veto_res_scale = 1e30;
214 } else {
215 // Determine if W or Z boson is decaying leptonically or hadronically
216 if (std::abs(event[iRes].id()) == 23 || std::abs(event[iRes].id()) == 24) {
217 int iq = -1, iqb = -1, ig = -1;
218 for (int i = 0; i < int(event[iRes].daughterList().size()); i++) {
219 int iDau = event[iRes].daughterList()[i];
220 if (event[iDau].id() == 21) ig = iDau;
221 if (event[iDau].id() > 0 && event[iDau].id() < 6) iq = iDau;
222 if (event[iDau].id() > -6 && event[iDau].id() < 0) iqb = iDau;
223 }
224
225 // Leptonic decay
226 if (iq == -1 && iqb == -1) {
227 veto_res_scale = 1e30;
228 } else {
229 // Hadronic decay without gluon emission
230 if (ig == -1) {
231 veto_res_scale = m_pTcutoff;
232 } else {
233 double pTqg = (iq > 0) ? pTpowhegFromRes(event, iq, ig, iRes) : 1e30;
234 double pTqbg = (iqb > 0) ? pTpowhegFromRes(event, iqb, ig, iRes) : 1e30;
235 veto_res_scale = std::min(pTqg, pTqbg);
236 }
237 }
238 }
239 // Top decay
240 else if (std::abs(event[iRes].id()) == 6) {
241 // Now handle tops
242 if (event[iRes].daughterList().size() < 3) {
243 veto_res_scale = m_pTcutoff;
244 } else {
245 // Find top daughters
246 int iw = -1, ib = -1, ig = -1;
247 for (int i = 0; i < int(event[iRes].daughterList().size()); i++) {
248 int iDau = event[iRes].daughterList()[i];
249 if (std::abs(event[iDau].id()) == 24) iw = iDau;
250 if (std::abs(event[iDau].id()) == 5) ib = iDau;
251 if (std::abs(event[iDau].id()) == 21) ig = iDau;
252 }
253 if (iw < 0 || ib < 0 || ig < 0) {
254 if (loggerPtr) loggerPtr->ABORT_MSG("Top decay products not found");
255 exit(1);
256 }
257 veto_res_scale = pTpowhegFromRes(event, ib, ig, iRes);
258 }
259 } else {
260 // Other resonance: no veto
261 veto_res_scale = 1e30;
262 }
263 }
264 return veto_res_scale;
265 }
266
272 bool canVetoProcessLevel() override { return true; }
273
283 bool doVetoProcessLevel(Event &event) override {
284 m_vetoResScale.clear();
286
287 // Regular or remnant events have no POWHEG radiation in their decays. In particular,
288 // an empty emission-scale list must not turn into a cutoff veto for them.
289 if (m_eventtype != 1) return false;
290
291 if (m_emscalesMode == 1) {
293 // ires refers to the ORIGINAL LHA record, before Pythia reorders it.
294 // In Pythia's event record, LHE particles are offset by 2 beam entries (indices 1 and 2),
295 // so the original LHE index is i - 2.
296 // Identify a resonance by its signed IDs from the resonance to the root.
297 // This distinguishes e.g. W* -> t bbar -> W b bbar and W* -> W Z.
298 for (int i = 0; i < event.size(); ++i) {
299 if (event[i].statusAbs() == 22 && !event[i].daughterList().empty()) {
300 bool hasQCDDecay = false;
301 for (int d : event[i].daughterList()) {
302 if (particleDataPtr->colType(event[d].id()) != 0) hasQCDDecay = true;
303 }
304 double scale = readScale(i - 2, hasQCDDecay ? m_pTcutoff : 1e30);
305 storeResonanceScale(resonanceKey(i, event), scale);
306 }
307 }
308 } else {
309 for (int i = 0; i < event.size(); ++i) {
310 if (event[i].status() == -22 && !event[i].daughterList().empty()) {
311 storeResonanceScale(resonanceKey(i, event), scaleResonance(i, event));
312 }
313 }
314 }
315 return false;
316 }
317
332 bool doVetoFSREmission(int sizeOld, const Event &e, int iSys, bool inResonance) override {
333 // Radiation from the hard system: delegate to PowhegHooks
334 if (iSys == 0 || !inResonance) return PowhegHooks::doVetoFSREmission(sizeOld, e, iSys, inResonance);
335
336 if (m_eventtype != 1) return false;
337
338 // Get the participants of the splitting: the radiation and the emitter
339 const int iRad = e.size() - 2;
340 const int iEmtAft = e.size() - 3;
341 const int iEmtBef = e[iRad].mother1();
342
343 // Test colour rather than isParton(), which excludes the top in t -> t g
344 // radiation inside the s-channel W* decay.
345 // Do not veto QED radiation (i.e. if (pre or post) emitter or emission are not coloured;
346 // can be f -> f gamma, gamma -> f fb, with f quark or lepton)
347 if (m_QEDvetoMode == 0 && !(
348 e[iEmtAft].colType() != 0 && e[iEmtBef].colType() != 0
349 && e[iRad].colType() != 0)) {
350 return false;
351 }
352
353 // Get the resonance ID from the partonSystem pointer
354 const int iRes = partonSystemsPtr->getInRes(iSys);
355 // Systems are created as resonances decay, possibly interleaved with the
356 // shower. Resolve the actual incoming resonance instead of guessing iSys.
357 auto scale = m_vetoResScale.find(resonanceKey(iRes, e));
358 if (scale == m_vetoResScale.end()) {
359 if (loggerPtr) loggerPtr->ABORT_MSG("Cannot find the resonance the showering is coming from");
360 exit(1);
361 }
362 const double pTmax = scale->second;
363 if (pTmax == 1e30) return false;
364
365 const double pTcurr = pTpowhegFromRes(e, iEmtAft, iRad, iRes);
366 // Veto emissions harder than the powheg one
367 return (pTcurr > pTmax);
368 }
369
370private:
371
376
383
384 // Scales keyed by the resonance ancestry, independent of event/system ordering.
385 std::map<std::vector<int>, double> m_vetoResScale;
386
387 //--------------------------------------------------------------------------
388 // Everything below is only used when emscalesMode == 1
389 //--------------------------------------------------------------------------
390
392 int idx;
393 double scalup;
396 int ires;
397 };
398 std::vector<EmissionScale> m_readScales;
399
410 std::vector<int> resonanceKey(int iRes, const Event &event) const {
411 std::vector<int> key;
412 // Original resonance entries retain |status| = 22. Shower/recoil copies
413 // have other statuses; follow their mothers without counting them again.
414 for (int i = iRes; i > 0; i = event[i].mother1()) {
415 if (event[i].statusAbs() == 22) key.push_back(event[i].id());
416 }
417 return key;
418 }
419
426 void storeResonanceScale(const std::vector<int> &key, double scale) {
427 // The s- and t-channel histories have unique resonance paths. Do not
428 // silently assign one scale to two indistinguishable sibling resonances.
429 if (!m_vetoResScale.emplace(key, scale).second) {
430 if (loggerPtr) loggerPtr->ABORT_MSG("Ambiguous resonance ancestry in POWHEG event");
431 exit(1);
432 }
433 }
434
444 int getEventType() const {
445 int eventtype = 1;
446 if (!infoPtr) return eventtype;
447 std::string comments = infoPtr->getEventComments();
448 if (comments.empty()) return eventtype;
449
450 std::istringstream iss(comments);
451 std::string line;
452 while (std::getline(iss, line)) {
453 if (line.compare(0, 5, "#rwgt") == 0) {
454 std::istringstream lss(line.substr(5));
455 int type = 0;
456 if (lss >> type) return type;
457 }
458 if (line.find("rad_type") != std::string::npos) {
459 int type = 0;
460 if (std::sscanf(line.c_str(), "# %d rad_type", &type) == 1) return type;
461 }
462 }
463 return eventtype;
464 }
465
473 m_readScales.clear();
474 if (!infoPtr) return;
475 std::string comments = infoPtr->getEventComments();
476 if (comments.empty()) return;
477
478 std::istringstream iss(comments);
479 std::string line;
480 while (std::getline(iss, line)) {
481 if (line.compare(0, 9, "#emscales") != 0) continue;
482 if (line.find("nemissions=") != std::string::npos) continue;
483
484 EmissionScale es{};
485 int idx = 0;
486 if (std::sscanf(line.c_str(),
487 "#emscales i=%d scalup=%lf id_emitter=%d id_radiated=%d ires=%d",
488 &idx, &es.scalup, &es.id_emitter, &es.id_radiated, &es.ires) == 5) {
489 es.idx = idx;
490 m_readScales.push_back(es);
491 }
492 }
493 }
494
502 double readScale(int ires, double fallback) const {
503 for (const auto &es : m_readScales) {
504 if (es.ires == ires) return es.scalup;
505 }
506 return fallback;
507 }
508
509};
510
511} // namespace Pythia8
Pythia8_UserHooks::UserHooksFactory::Creator< Pythia8::PowhegHooksST > powhegSTCreator("PowhegST")
Pythia8_UserHooks::UserHooksFactory::Creator< Pythia8::PowhegHooksST > powhegHooksSTCreator("PowhegHooksST")
size_t size() const
Number of registered mappings.
static const Attributes_t empty
POWHEG resonance-veto hooks for single-top t- and s-channel events.
Pythia8_UserHooks::UserSetting< bool > m_debugSetting
std::vector< EmissionScale > m_readScales
PowhegHooksST()
Constructor and destructor for Athena UserHooksFactory.
bool initAfterBeams() override
Initialize settings for the POWHEG resonance-veto strategy.
bool doVetoProcessLevel(Event &event) override
Build resonance veto scales before showering starts.
double readScale(int ires, double fallback) const
Look up a POWHEG scale by original LHA resonance index.
int getEventType() const
Extract rad_type / eventtype from LHE event comments.
Pythia8_UserHooks::UserSetting< int > m_emscalesSetting
void storeResonanceScale(const std::vector< int > &key, double scale)
Store a scale for a resonance ancestry and reject ambiguity.
std::vector< int > resonanceKey(int iRes, const Event &event) const
Construct the stable signed-ID ancestry key for a resonance.
bool doVetoFSREmission(int sizeOld, const Event &e, int iSys, bool inResonance) override
Veto too-hard QCD radiation from a decayed resonance.
std::map< std::vector< int >, double > m_vetoResScale
double scaleResonance(int iRes, const Event &event) override
Reconstruct a resonance scale from the Pythia event record.
double pTpowhegFromRes(const Event &event, int iEmt, int iRad, int iRes)
Compute the POWHEG transverse scale of a resonance splitting.
bool canVetoProcessLevel() override
Advertise support for process-level processing.
Pythia8_UserHooks::UserSetting< int > m_emscalesAltSetting
void fillReadScales()
Populate POWHEG emission scales for the current event.
Pythia8_UserHooks::UserSetting< double > m_pTcutoffSetting
bool canSetResonanceScale() override
Disable Pythia's ordinary resonance-scale assignment.
Author: James Monk (jmonk@cern.ch).
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)