rivet is hosted by Hepforge, IPPP Durham

Rivet analyses reference

ATLAS_2022_I2077575

All-hadronic boosted ttbar at 13 TeV
Experiment: ATLAS (LHC)
Inspire ID: 2077575
Status: VALIDATED
Authors:
  • Ovidiu Miu
References: Beams: p+ p+
Beam energies: (6500.0, 6500.0) GeV
Run details:
  • p + p -> ttbar (all-hadronic, boosted)

Measurements of single-, double-, and triple-differential cross-sections are presented for boosted top-quark pair-production in 13 TeV proton--proton collisions recorded by the ATLAS detector at the LHC. The top quarks are observed through their hadronic decay and reconstructed as large-radius jets with the leading jet having transverse momentum ($p_\mathrm{T}$) greater than 500 GeV. The observed data are unfolded to remove detector effects. The particle-level cross-section, multiplied by the $t\bar{t}\rightarrow WWb\bar{b}$ branching fraction and measured in a fiducial phase space defined by requiring the leading and second-leading jets to have $p_\mathrm{T}$ > 500 GeV and $p_\mathrm{T}$ > 350 GeV, respectively, is $331 \pm 3 \mathrm{(stat.)} \pm 39 \mathrm{(syst.)}$ fb. This is approximately 20% lower than the prediction of $398^{+48}_{-49}$ fb by POWHEG+PYTHIA8 with next-to-leading-order (NLO) accuracy but consistent within the theoretical uncertainties. Results are also presented at the parton level, where the effects of top-quark decay, parton showering, and hadronization are removed such that they can be compared with fixed-order next-to-next-to-leading-order (NNLO) calculations. The parton-level cross-section, measured in a fiducial phase space similar to that at particle level, is $1.94 \pm 0.02 \mathrm{(stat.)} \pm 0.25 \mathrm{(syst.)}$ pb. This agrees with the NNLO prediction of $1.96^{+0.02}_{-0.17}$ pb. Reasonable agreement with the differential cross-sections is found for most NLO models, while the NNLO calculations are generally in better agreement with the data. The differential cross-sections are interpreted using a Standard Model effective field-theory formalism and limits are set on Wilson coefficients of several four-fermion operators.

Source code: ATLAS_2022_I2077575.cc
  1// -*- C++ -*-
  2#include "Rivet/Analysis.hh"
  3#include "Rivet/Projections/FinalState.hh"
  4#include "Rivet/Projections/VetoedFinalState.hh"
  5#include "Rivet/Projections/IdentifiedFinalState.hh"
  6#include "Rivet/Projections/PromptFinalState.hh"
  7#include "Rivet/Projections/LeptonFinder.hh"
  8#include "Rivet/Projections/FastJets.hh"
  9#include "Rivet/Projections/PartonicTops.hh"
 10#include "Rivet/Math/LorentzTrans.hh"
 11
 12namespace Rivet {
 13
 14
 15  /// @brief All-hadronic ttbar at 13 TeV
 16  class ATLAS_2022_I2077575 : public Analysis {
 17  public:
 18
 19      /// Constructor
 20      RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2022_I2077575);
 21
 22      /// Book histograms and initialise projections before the run
 23      void init() {
 24
 25        // Get options particle-level only.
 26        _mode = 0;
 27        if ( getOption("TMODE") == "PARTICLE" ) _mode = 0;
 28        if ( getOption("TMODE") == "BOTH" ) _mode = 1;
 29
 30        // External bins for 2D and 3D cross-sections
 31        std::vector<double> t1_pt_2D_bins_1 = {0.5, 0.55, 0.6, 0.75, 2.0};
 32        std::vector<double> t1_pt_2D_bins_2 = {0.5, 0.55, 0.625, 0.75, 2.0};
 33        std::vector<double> t_and_tt_y_2D_bins = {0.0, 0.2, 0.5, 1.0, 2.0};
 34        std::vector<double> tt_pt_2D_bins = {0.0, 0.1, 0.2, 0.35, 1.0};
 35        std::vector<double> tt_m_3D_bins = {0.9, 1.2, 1.5, 4.0};
 36
 37        //histogram booking
 38        book(_h["inclusive_particle"], 2, 1, 1);
 39        if (_mode)  book(_h["inclusive_parton"], 147, 1, 1);
 40        book_hist("t_pt", 	       3);
 41        book_hist("t_y",  	       4);
 42        book_hist("t1_pt",         5);
 43        book_hist("t1_y",          6);
 44        book_hist("t2_pt",         7);
 45        book_hist("t2_y",          8);
 46        book_hist("tt_m",          9);
 47        book_hist("tt_pt",         10);
 48        book_hist("tt_y",          11);
 49        book_hist("tt_chi",        12);
 50        book_hist("tt_yboost",     13);
 51        book_hist("tt_pout",       14);
 52        book_hist("tt_dPhi",       15);
 53        book_hist("tt_Ht",         16);
 54        book_hist("tt_cosThStar",  17);
 55        book_hist_2D("t1_pt_t2_pt_2D", 		    t1_pt_2D_bins_1, 	    18);
 56        book_hist_2D("t1_y_t2_y_2D", 		      t_and_tt_y_2D_bins, 	22);
 57        book_hist_2D("t1_y_t1_pt_2D", 		    t_and_tt_y_2D_bins, 	26);
 58        book_hist_2D("t2_y_t2_pt_2D", 		    t_and_tt_y_2D_bins, 	30);
 59        book_hist_2D("t1_pt_tt_pt_2D", 		    t1_pt_2D_bins_2, 	    34);
 60        book_hist_2D("t1_pt_tt_m_2D", 		    t1_pt_2D_bins_2, 	    38);
 61        book_hist_2D("tt_y_t1_pt_2D", 		    t_and_tt_y_2D_bins, 	42);
 62        book_hist_2D("tt_y_t1_y_2D", 		      t_and_tt_y_2D_bins, 	46);
 63        book_hist_2D("t1_y_tt_m_2D", 		      t_and_tt_y_2D_bins, 	50);
 64        book_hist_2D("tt_y_tt_m_2D", 		      t_and_tt_y_2D_bins, 	54);
 65        book_hist_2D("tt_pt_tt_m_2D", 		    tt_pt_2D_bins, 		    58);
 66        book_hist_2D("tt_y_tt_pt_2D",         t_and_tt_y_2D_bins, 	62);
 67        book_hist_2D("tt_y_1_tt_m_t1_pt_3D", 	tt_m_3D_bins, 		    66);
 68        book_hist_2D("tt_y_2_tt_m_t1_pt_3D", 	tt_m_3D_bins, 		    69);
 69        book_hist_2D("tt_y_3_tt_m_t1_pt_3D", 	tt_m_3D_bins, 		    72);
 70
 71        // Projections
 72        const Cut dressed_lep = Cuts::abseta < 2.5 && Cuts::pT >= 25*GeV;
 73        const Cut all_dressed_lep = Cuts::abseta < 2.5;
 74        const Cut eta_full = Cuts::abseta < 4.5;
 75
 76        // All final state particles
 77        const FinalState fs(eta_full);
 78
 79        // Get photons to dress leptons
 80        const FinalState photons(Cuts::abspid == PID::PHOTON);
 81
 82        // Projection to find the electrons
 83        PromptFinalState electrons(Cuts::abspid == PID::ELECTRON, TauDecaysAs::PROMPT);
 84        LeptonFinder dressedelectrons(electrons, photons, 0.1, dressed_lep);
 85        declare(dressedelectrons, "elecs");
 86        LeptonFinder alldressedelectrons(electrons, photons, 0.1, all_dressed_lep);
 87
 88        // Projection to find the muons
 89        PromptFinalState muons(Cuts::abspid == PID::MUON, TauDecaysAs::PROMPT);
 90        LeptonFinder dressedmuons(muons, photons, 0.1, dressed_lep);
 91        declare(dressedmuons, "muons");
 92        LeptonFinder alldressedmuons(muons, photons, 0.1, all_dressed_lep);
 93
 94        // Small-R jet clustering
 95        VetoedFinalState vfs(fs);
 96        vfs.addVetoOnThisFinalState(alldressedelectrons);
 97        vfs.addVetoOnThisFinalState(alldressedmuons);
 98        FastJets sjets(vfs, JetAlg::ANTIKT, 0.4, JetMuons::ALL, JetInvisibles::DECAY);
 99        declare(sjets, "sjets");
100
101        // Large-R jet clustering.
102        FastJets ljets(fs, JetAlg::ANTIKT, 1.0, JetMuons::NONE, JetInvisibles::NONE);
103        ljets.addTrf(new fastjet::Filter(fastjet::JetDefinition(fastjet::kt_algorithm, 0.2),
104                                         fastjet::SelectorPtFractionMin(0.05)));
105        declare(ljets, "ljets");
106
107        if (_mode) {
108          PartonicTops partonTops;
109          declare(partonTops, "partonicTops");
110        }
111      }
112
113
114      void analyze(const Event& event) {
115
116        if (_mode) {
117
118          // Parton-level top quarks
119          const Particles partonicTops = apply<PartonicTops>( event, "partonicTops").particlesByPt();
120          FourMomentum top, tbar;
121          bool foundT = false, foundTBar = false;
122          for (const Particle& ptop : partonicTops) {
123            const int pid = ptop.pid();
124            if (pid == PID::TQUARK) {
125              top = ptop.momentum();
126              foundT = true;
127            }
128            else if (pid == -PID::TQUARK) {
129              tbar = ptop.momentum();
130              foundTBar = true;
131            }
132          }
133
134          FourMomentum t1_parton, t2_parton, ttbar_parton;
135          if ( foundT && foundTBar ) {
136            t1_parton = top.pT2() > tbar.pT2() ? top : tbar;
137            t2_parton = top.pT2() > tbar.pT2() ? tbar : top;
138            ttbar_parton = t1_parton + t2_parton;
139
140            if ( t1_parton.pT() > 500*GeV && t2_parton.pT() > 350*GeV) {
141
142              const double chi_parton = calcChi(t1_parton, t2_parton);
143              const double cosThetaStar_parton = abs(calcCosThetaStar(t1_parton, t2_parton));
144              if (cosThetaStar_parton == -99) {
145                MSG_DEBUG("ttbar going faster than light! Vetoing event. Try turning of partonic tops?");
146                vetoEvent;
147              }
148              const double pout_parton = abs(calcPout(t1_parton, t2_parton));
149              const double dPhi_parton = deltaPhi(t1_parton, t2_parton);
150
151              const FourMomentum& randomTopParton = t1_parton; // : t2_parton;
152
153              if (_mode) _h["inclusive_parton"]->fill(0);
154
155              fill_hist_parton("t_pt", randomTopParton.pT()/TeV);
156              fill_hist_parton("t_y",  randomTopParton.absrap());
157
158              fill_hist_parton("t1_pt", t1_parton.pT()/TeV);
159              fill_hist_parton("t1_y",  t1_parton.absrap());
160              fill_hist_parton("t2_pt", t2_parton.pT()/TeV);
161              fill_hist_parton("t2_y",  t2_parton.absrap());
162
163              fill_hist_parton("tt_m",  ttbar_parton.mass()/TeV);
164              fill_hist_parton("tt_pt", ttbar_parton.pT()/TeV);
165              fill_hist_parton("tt_Ht", (t1_parton.pT() + t2_parton.pT())/TeV);
166              fill_hist_parton("tt_y",  ttbar_parton.absrap());
167
168              fill_hist_parton("tt_yboost", 0.5 * abs(t1_parton.rapidity() + t2_parton.rapidity()));
169              fill_hist_parton("tt_chi", chi_parton);
170              fill_hist_parton("tt_cosThStar", cosThetaStar_parton);
171              fill_hist_parton("tt_pout", pout_parton/TeV);
172              fill_hist_parton("tt_dPhi", dPhi_parton);
173
174              fill_hist_2D_parton("t1_pt_t2_pt_2D", t1_parton.pT()/TeV, t2_parton.pT()/TeV);
175              fill_hist_2D_parton("t1_y_t2_y_2D", t1_parton.absrap(), t2_parton.absrap());
176              fill_hist_2D_parton("t1_y_t1_pt_2D", t1_parton.absrap(), t1_parton.pT()/TeV);
177              fill_hist_2D_parton("t2_y_t2_pt_2D", t2_parton.absrap(), t2_parton.pT()/TeV);
178              fill_hist_2D_parton("t1_pt_tt_pt_2D", t1_parton.pT()/TeV, ttbar_parton.pT()/TeV);
179              fill_hist_2D_parton("t1_pt_tt_m_2D", t1_parton.pT()/TeV, ttbar_parton.mass()/TeV);
180              fill_hist_2D_parton("tt_y_t1_pt_2D", ttbar_parton.absrap(), t1_parton.pT()/TeV);
181              fill_hist_2D_parton("tt_y_t1_y_2D", ttbar_parton.absrap(), t1_parton.absrap());
182              fill_hist_2D_parton("t1_y_tt_m_2D", t1_parton.absrap(), ttbar_parton.mass()/TeV);
183              fill_hist_2D_parton("tt_y_tt_m_2D", ttbar_parton.absrap(), ttbar_parton.mass()/TeV);
184              fill_hist_2D_parton("tt_pt_tt_m_2D", ttbar_parton.pT()/TeV, ttbar_parton.mass()/TeV);
185              fill_hist_2D_parton("tt_y_tt_pt_2D", ttbar_parton.absrap(), ttbar_parton.pT()/TeV);
186              if (ttbar_parton.absrap() < 0.3) fill_hist_2D_parton("tt_y_1_tt_m_t1_pt_3D", ttbar_parton.mass()/TeV, t1_parton.pT()/TeV);
187              else if (ttbar_parton.absrap() < 0.9) fill_hist_2D_parton("tt_y_2_tt_m_t1_pt_3D", ttbar_parton.mass()/TeV, t1_parton.pT()/TeV);
188              else if (ttbar_parton.absrap() < 2.0) fill_hist_2D_parton("tt_y_3_tt_m_t1_pt_3D", ttbar_parton.mass()/TeV, t1_parton.pT()/TeV);
189            }
190          }
191        }
192
193        // Get small-R jets
194        const FastJets& sjets_fj = apply<FastJets>(event, "sjets");
195        const Jets all_sjets = sjets_fj.jetsByPt(Cuts::pT > 25*GeV && Cuts::abseta < 2.5);
196
197        // Get dressed leptons
198        DressedLeptons dressedElectrons = apply<LeptonFinder>(event, "elecs").dressedLeptons();
199        DressedLeptons dressedMuons     = apply<LeptonFinder>(event, "muons").dressedLeptons();
200
201        // Perform lepton isolation
202        idiscardIfAnyDeltaRLess(dressedElectrons, all_sjets, 0.4);
203        idiscardIfAnyDeltaRLess(dressedMuons, all_sjets, 0.4);
204
205        // Veto on leptons
206        if (!dressedElectrons.empty()) vetoEvent;
207        if (!dressedMuons.empty()) vetoEvent;
208
209        // Get large-R jets
210        const FastJets& ljets_fj = apply<FastJets>(event, "ljets");
211        const Jets& trimmedJets = ljets_fj.jetsByPt();
212
213        // Check large-R jets
214        Jets ljets;
215        vector<bool> b_tagged;
216        for (const Jet& jet : trimmedJets) {
217
218          if (jet.pT() < 200 * GeV)  continue;
219          if (jet.pT() > 3000 * GeV) continue;
220          if (jet.mass() > jet.pT()) continue;
221          if (jet.mass() < 50 * GeV) continue;
222          if (jet.abseta() > 2.0 )   continue;
223
224          ljets += jet;
225          b_tagged += jet.bTagged(Cuts::pT > 5 * GeV);
226        }
227
228        if (ljets.size() < 2)  vetoEvent;
229
230        // Identify top and anti top, compute some event variables
231        int top1Index(-1);
232        int top2Index(-1);
233
234        double deltaMass(FLT_MAX);
235        for(int i = 0; i < (int)ljets.size(); i++) {
236          if (ljets[i].pT() < 500 * GeV)  continue;
237          const double diff = std::abs(ljets[i].mass() - 172.5 * GeV);
238          if (diff < deltaMass) {
239            deltaMass = diff;
240            top1Index = i;
241          }
242        }
243
244        if (top1Index == -1)  vetoEvent;
245
246        deltaMass = FLT_MAX;
247        for (int i = 0; i < (int)ljets.size(); ++i) {
248          if (i == top1Index || ljets[i].pT() < 350 * GeV)  continue;
249          const double diff = std::abs(ljets[i].mass() - 172.5 * GeV);
250          if (diff < deltaMass) {
251            deltaMass = diff;
252            top2Index = i;
253          }
254        }
255
256        if (top2Index == -1) vetoEvent;
257
258        if (ljets[top1Index].pT() < ljets[top2Index].pT()) std::swap(top1Index,top2Index);
259
260        const FourMomentum ttbar = ljets[top1Index].momentum() + ljets[top2Index].momentum();
261        const FourMomentum t1 = ljets[top1Index].momentum();
262        const FourMomentum t2 = ljets[top2Index].momentum();
263
264        const double chi = calcChi(t1, t2);
265        const double cosThetaStar = abs(calcCosThetaStar(t1, t2));
266        if (cosThetaStar == -99) {
267          MSG_DEBUG("real ttbar going faster than light! This should not happen. Vetoing event.");
268          vetoEvent;
269        }
270        const double pout = abs(calcPout(t1, t2));
271        const double dPhi = deltaPhi(t1, t2);
272
273        // b-tagging for particle done on large-R jets
274        if (!(b_tagged[top1Index] && b_tagged[top2Index]))  vetoEvent;
275
276        // Continues with signal region cuts
277        if ( abs(t1.mass() - 172.5 * GeV) > 50*GeV )  vetoEvent;
278        if ( abs(t2.mass() - 172.5 * GeV) > 50*GeV )  vetoEvent;
279
280        const FourMomentum& randomTopJet = t1; // : t2;
281
282        _h["inclusive_particle"]->fill(0);
283
284        fill_hist("t_pt", randomTopJet.pT()/TeV);
285        fill_hist("t_y",  randomTopJet.absrap());
286
287        fill_hist("t1_pt", t1.pT()/TeV);
288        fill_hist("t1_y",  t1.absrap());
289        fill_hist("t2_pt", t2.pT()/TeV);
290        fill_hist("t2_y",  t2.absrap());
291
292        fill_hist("tt_m",  ttbar.mass()/TeV);
293        fill_hist("tt_pt", ttbar.pT()/TeV);
294        fill_hist("tt_Ht", (t1.pT() + t2.pT())/TeV);
295        fill_hist("tt_y",  ttbar.absrap());
296
297        fill_hist("tt_yboost", 0.5 * abs(t1.rapidity() + t2.rapidity()));
298        fill_hist("tt_chi", chi);
299        fill_hist("tt_cosThStar", cosThetaStar);
300        fill_hist("tt_pout", pout/TeV);
301        fill_hist("tt_dPhi", dPhi);
302
303        fill_hist_2D("t1_pt_t2_pt_2D", t1.pT()/TeV, t2.pT()/TeV);
304        fill_hist_2D("t1_y_t2_y_2D", t1.absrap(), t2.absrap());
305        fill_hist_2D("t1_y_t1_pt_2D", t1.absrap(), t1.pT()/TeV);
306        fill_hist_2D("t2_y_t2_pt_2D", t2.absrap(), t2.pT()/TeV);
307        fill_hist_2D("t1_pt_tt_pt_2D", t1.pT()/TeV, ttbar.pT()/TeV);
308        fill_hist_2D("t1_pt_tt_m_2D", t1.pT()/TeV, ttbar.mass()/TeV);
309        fill_hist_2D("tt_y_t1_pt_2D", ttbar.absrap(), t1.pT()/TeV);
310        fill_hist_2D("tt_y_t1_y_2D", ttbar.absrap(), t1.absrap());
311        fill_hist_2D("t1_y_tt_m_2D", t1.absrap(), ttbar.mass()/TeV);
312        fill_hist_2D("tt_y_tt_m_2D", ttbar.absrap(), ttbar.mass()/TeV);
313        fill_hist_2D("tt_pt_tt_m_2D", ttbar.pT()/TeV, ttbar.mass()/TeV);
314        fill_hist_2D("tt_y_tt_pt_2D", ttbar.absrap(), ttbar.pT()/TeV);
315        if (ttbar.absrap() < 0.3) fill_hist_2D("tt_y_1_tt_m_t1_pt_3D", ttbar.mass()/TeV, t1.pT()/TeV);
316        else if (ttbar.absrap() < 0.9) fill_hist_2D("tt_y_2_tt_m_t1_pt_3D", ttbar.mass()/TeV, t1.pT()/TeV);
317        else if (ttbar.absrap() < 2.0) fill_hist_2D("tt_y_3_tt_m_t1_pt_3D", ttbar.mass()/TeV, t1.pT()/TeV);
318
319      }
320
321
322
323      void finalize() {
324        // Normalize histograms to cross-section in femtobarns (for consistency with HEPData)
325        const double sf = crossSection()/picobarn * 1000 / sumOfWeights();
326        for (auto& h_it : _h) {
327          scale(h_it.second, sf);
328          // Parton-level distributions corrected for all-hadronic branching fraction
329          if (h_it.first.find("_parton") != string::npos) scale(h_it.second, 2.1882987);
330          // Normalized distributions
331          if (h_it.first.find("_norm") != string::npos)  normalize(h_it.second, 1.0, false);
332        }
333        // Multi-dimensional cross-sections
334        double norm_3D = 0, norm_3D_parton = 0;
335        for (auto& h_it : _h_multi) {
336          if (h_it.first.find("_parton") != string::npos)  scale(h_it.second, 2.1882987);
337          if (h_it.first.find("_norm") != string::npos) {
338            scale(h_it.second, sf);
339            if (h_it.first.find("_3D") != string::npos) {
340              if (h_it.first.find("_parton") != string::npos) norm_3D_parton += h_it.second->integral(false);
341              else norm_3D += h_it.second->integral(false);
342              if (h_it.first.find("tt_y_1") != string::npos) { scale(h_it.second, 1. / 0.3); }
343              if (h_it.first.find("tt_y_2") != string::npos) { scale(h_it.second, 1. / 0.6); }
344              if (h_it.first.find("tt_y_3") != string::npos) { scale(h_it.second, 1. / 1.1); }
345            }
346            else {
347             const double norm_2D = h_it.second->integral(false);
348             scale(h_it.second, safediv(1.0, norm_2D));
349            }
350          }
351          else {
352            if (h_it.first.find("_3D") != string::npos) {
353              if (h_it.first.find("tt_y_1") != string::npos) { scale(h_it.second, 1. / 0.3); }
354              if (h_it.first.find("tt_y_2") != string::npos) { scale(h_it.second, 1. / 0.6); }
355              if (h_it.first.find("tt_y_3") != string::npos) { scale(h_it.second, 1. / 1.1); }
356              scale(h_it.second, sf);
357            }
358            else scale(h_it.second, sf);
359          }
360        }
361        scale(_h_multi["tt_y_1_tt_m_t1_pt_3D_norm"], safediv(1, norm_3D));
362        scale(_h_multi["tt_y_2_tt_m_t1_pt_3D_norm"], safediv(1, norm_3D));
363        scale(_h_multi["tt_y_3_tt_m_t1_pt_3D_norm"], safediv(1, norm_3D));
364        if (_mode) {
365          scale(_h_multi["tt_y_1_tt_m_t1_pt_3D_parton_norm"], safediv(1, norm_3D_parton));
366          scale(_h_multi["tt_y_2_tt_m_t1_pt_3D_parton_norm"], safediv(1, norm_3D_parton));
367          scale(_h_multi["tt_y_3_tt_m_t1_pt_3D_parton_norm"], safediv(1, norm_3D_parton));
368        }
369      }
370
371
372      double calcChi(const FourMomentum& t1, const FourMomentum& t2) {
373        double ystar = 0.5 * (t1.rapidity()-t2.rapidity());
374        double chi = exp( 2 * abs(ystar));
375        return chi;
376      }
377
378      double calcCosThetaStar(const FourMomentum& t1, const FourMomentum& t2) {
379        FourMomentum ttbar = t1 + t2;
380        LorentzTransform centreOfMassTrans;
381        ttbar.setX(0);
382        ttbar.setY(0);
383        if (ttbar.betaVec().mod2() > 1) return -99;
384        centreOfMassTrans.setBetaVec( -ttbar.betaVec() );
385        FourMomentum t1_star = centreOfMassTrans.transform(t1);
386        double cosThetaStar;
387        if (t1_star.p3().mod2() >= 0){
388          cosThetaStar = t1_star.pz()/t1_star.p3().mod();
389        }
390        else {
391          return -99;
392        }
393        return cosThetaStar;
394      }
395
396      double calcPout(const FourMomentum& t1, const FourMomentum& t2) {
397        Vector3 t1V = t1.p3();
398        Vector3 t2V = t2.p3();
399        Vector3 zUnit = Vector3(0., 0., 1.);
400        Vector3 vPerp = zUnit.cross(t1V);
401
402        double pout = vPerp.dot(t2V)/vPerp.mod();
403        return pout;
404      }
405
406
407    private:
408
409      size_t _mode;
410      map<string, Histo1DPtr> _h;
411      map<string, Histo1DGroupPtr> _h_multi;
412
413      //some functions for booking, filling and scaling the histograms
414      void fill_hist(const std::string name, double value) {
415        _h[name]->fill(value);
416        _h[name + "_norm"]->fill(value);
417      }
418
419      void fill_hist_parton(const std::string name, double value) {
420        _h[name + "_parton"]->fill(value);
421        _h[name + "_parton_norm"]->fill(value);
422      }
423
424      void fill_hist_2D(const std::string name, double value_external, double value_internal) {
425       _h_multi[name]->fill(value_external, value_internal);
426       _h_multi[name + "_norm"]->fill(value_external, value_internal);
427      }
428
429      void fill_hist_2D_parton(const std::string name, double value_external, double value_internal) {
430       _h_multi[name + "_parton"]->fill(value_external, value_internal);
431       _h_multi[name + "_parton_norm"]->fill(value_external, value_internal);
432      }
433
434      void book_hist(const std::string name, unsigned int index) {
435        book(_h[name], index, 1, 1);
436        book(_h[name + "_norm"], index + 72, 1, 1);
437        if (_mode) {
438          book(_h[name + "_parton"], index + 145, 1, 1);
439          book(_h[name + "_parton_norm"], index + 217, 1, 1);
440        }
441      }
442
443      void book_hist_2D(const std::string name, const vector<double>& external_bins, unsigned int index) {
444        book(_h_multi[name], external_bins);
445        book(_h_multi[name+"_norm"], external_bins);
446        for (size_t i=0; i < _h_multi[name]->numBins(); ++i) {
447          book(_h_multi[name]->bin(i+1), index+i, 1, 1);
448          book(_h_multi[name+"_norm"]->bin(i+1), index+72+i, 1, 1);
449          if (_mode != 0) {
450            book(_h_multi[name+"_parton"]->bin(i+1), index+145+i, 1, 1);
451            book(_h_multi[name+"_parton_norm"]->bin(i+1), index+217+i, 1, 1);
452          }
453        }
454      }
455
456  };
457
458
459  RIVET_DECLARE_PLUGIN(ATLAS_2022_I2077575);
460}