Rivet analyses referenceATLAS_2021_I1941095energy asymmetry in ttj @ 13 TeVExperiment: ATLAS (LHC) Inspire ID: 1941095 Status: VALIDATED Authors:
Beam energies: (6500.0, 6500.0) GeV Run details:
A measurement of the energy asymmetry in jet-associated top-quark pair production is presented using 139 fb$^{-1}$ of data collected by the ATLAS detector at the Large Hadron Collider during $pp$ collisions at $\sqrt{s}=13$ TeV. The observable measures the different probability of top and antitop quarks to have the higher energy as a function of the jet scattering angle with respect to the beam axis. The energy asymmetry is measured in the semileptonic $t\bar{t}$ decay channel, and the hadronically decaying top quark must have transverse momentum above 350 GeV. The results are corrected for detector effects to particle level in three bins of the scattering angle of the associated jet. The measurement agrees with the SM prediction at next-to-leading-order accuracy in quantum chromodynamics in all three bins. In the bin with the largest expected asymmetry, where the jet is emitted perpendicular to the beam, the energy asymmetry is measured to be $-0.043\pm0.020$, in agreement with the SM prediction of $-0.037\pm0.003$. Interpreting this result in the framework of the Standard Model effective field theory (SMEFT), it is shown that the energy asymmetry is sensitive to the top-quark chirality in four-quark operators and is therefore a valuable new observable in global SMEFT fits. Source code: ATLAS_2021_I1941095.cc 1// -*- C++ -*-
2#include "Rivet/Analysis.hh"
3#include "Rivet/Projections/FinalState.hh"
4#include "Rivet/Projections/InvisibleFinalState.hh"
5#include "Rivet/Projections/FastJets.hh"
6#include "Rivet/Projections/DressedLeptons.hh"
7#include "Rivet/Projections/MissingMomentum.hh"
8#include "Rivet/Projections/PromptFinalState.hh"
9#include "Rivet/Projections/PartonicTops.hh"
10#include "Rivet/Projections/VetoedFinalState.hh"
11#include "Rivet/Tools/MendelMin.hh"
12
13#include "fastjet/tools/Filter.hh"
14
15namespace Rivet {
16
17
18 /// @brief Energy asymmetry in ttj at 13 TeV
19 class ATLAS_2021_I1941095 : public Analysis {
20 public:
21
22 /// Constructor
23 RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2021_I1941095);
24
25
26 /// @name Analysis methods
27 /// @{
28
29 /// Book histograms and initialise projections before the run
30 void init() {
31
32 // Declare projections
33
34 // Photons
35 PromptFinalState promptphotons(Cuts::abspid == PID::PHOTON, false);
36
37 // Electrons
38 PromptFinalState bare_el(Cuts::abspid == PID::ELECTRON, true); // true = use electrons from prompt tau decays
39 DressedLeptons all_dressed_el(promptphotons, bare_el, 0.1, Cuts::abseta < 2.5, true);
40 DressedLeptons electrons(promptphotons, bare_el, 0.1, Cuts::abseta < 2.5 && Cuts::pT > 25*GeV, true);
41 declare(electrons,"electrons");
42
43 // Muons
44 PromptFinalState bare_mu(Cuts::abspid == PID::MUON, true); // true = use muons from prompt tau decays
45 DressedLeptons all_dressed_mu(promptphotons, bare_mu, 0.1, Cuts::abseta < 2.5, true);
46 DressedLeptons muons(promptphotons,bare_mu, 0.1, Cuts::abseta <2.5 && Cuts::pT > 25*GeV, true);
47 declare(muons,"muons");
48
49 // AntiKt4TruthWZJets as AntiKt4TruthWZJets, but w/o photons from hadrons in dressing
50 const InvisibleFinalState invisibles(true, true);
51 VetoedFinalState vfs(FinalState(Cuts::abseta < 5.0)); // changed from 4.5 to 5.0
52 vfs.addVetoOnThisFinalState(all_dressed_el);
53 vfs.addVetoOnThisFinalState(all_dressed_mu);
54 vfs.addVetoOnThisFinalState(invisibles); // new
55 FastJets jets(vfs, FastJets::ANTIKT, 0.4, JetAlg::Muons::ALL, JetAlg::Invisibles::ALL); // changed invisible from DECAY to ALL
56 declare(jets,"jets");
57
58 // AntiKt10TruthTrimmedPtFrac5SmallR20Jets
59 FinalState fs(Cuts::abseta < 5.0);
60 FastJets fjets(fs, FastJets::ANTIKT, 1.0, JetAlg::Muons::NONE, JetAlg::Invisibles::NONE);
61 _trimmer = fastjet::Filter(fastjet::JetDefinition(fastjet::kt_algorithm, 0.2), fastjet::SelectorPtFractionMin(0.05));
62 declare(fjets,"fjets");
63
64 // Missing momentum
65 declare(MissingMomentum(), "MissingMomentum");
66
67 // Parton level top quarks after FSR
68 // options are: decaymode, emu_from_prompt_tau, include_hadronic_taus
69 declare(PartonicTops(PartonicTops::DecayMode::E_MU, true, false), "PartonicTops_EMU");
70 declare(PartonicTops(PartonicTops::DecayMode::E_MU, false, false), "PartonicTops_EMU_notau");
71 declare(PartonicTops(PartonicTops::DecayMode::HADRONIC, false, true), "PartonicTops_HADRONIC");
72 declare(PartonicTops(PartonicTops::DecayMode::HADRONIC, false, false), "PartonicTops_HADRONIC_notau");
73
74 // Book histograms
75 const Scatter2D& ref_asymm = refData(1, 1, 1);
76 book(_h["pos"], "_thetaj_opt_depos", ref_asymm);
77 book(_h["neg"], "_thetaj_opt_deneg", ref_asymm);
78 book(_asymm, 1, 1, 1, true);
79
80 }
81
82
83 /// Perform the per-event analysis
84 void analyze(const Event& event) {
85
86 // Parton-level top quarks // after FSR
87 const Particles partonicTops_EMU = apply<ParticleFinder>(event, "PartonicTops_EMU").particlesByPt();
88 const Particles partonicTops_EMU_notau = apply<ParticleFinder>(event, "PartonicTops_EMU_notau").particlesByPt();
89 const Particles partonicTops_HADRONIC = apply<ParticleFinder>(event, "PartonicTops_HADRONIC").particlesByPt();
90 const Particles partonicTops_HADRONIC_notau = apply<ParticleFinder>(event, "PartonicTops_HADRONIC_notau").particlesByPt();
91
92 // Filter semi-leptonic (e,mu,tau) events: Veto dileptonic/nonleptonic events and events with 2 taus
93 int nLeptons = partonicTops_EMU.size() + partonicTops_HADRONIC.size() - partonicTops_HADRONIC_notau.size();
94 if (nLeptons != 1) vetoEvent;
95
96 // Get the selected objects, using the projections.
97 vector<DressedLepton> electrons = apply<DressedLeptons>(event, "electrons").dressedLeptons();
98 vector<DressedLepton> muons = apply<DressedLeptons>(event, "muons").dressedLeptons();
99 const Jets& jets = apply<FastJets>(event, "jets").jetsByPt(Cuts::pT > 25*GeV && Cuts::abseta < 2.5);
100 const Jets& fjets = apply<FastJets>(event, "fjets").jetsByPt(Cuts::pT > 200*GeV && Cuts::abseta < 2.0);
101 PseudoJets ljets;
102 for (const Jet& fjet : fjets) { ljets += _trimmer(fjet); }
103 sort(ljets.begin(), ljets.end(), [](PseudoJet const &l, PseudoJet const &r) { return l.pt() > r.pt(); });
104 const FourMomentum& met = apply<MissingMomentum>(event, "MissingMomentum").missingMomentum();
105
106 // Overlap removal
107 for (const Jet& jet : jets) {
108 ifilter_discard(electrons, deltaRLess(jet, 0.4, RAPIDITY));
109 ifilter_discard(muons, deltaRLess(jet, 0.4, RAPIDITY));
110 }
111
112 // Reconstruct event
113
114 // Lepton l
115 size_t n_el_25 = 0, n_el_27 = 0;
116 for (const DressedLepton& electron : electrons ) {
117 if (electron.pT() >= 25*GeV) ++n_el_25;
118 if (electron.pT() >= 27*GeV) ++n_el_27;
119 }
120 size_t n_mu_25 = 0, n_mu_27 = 0;
121 for (const DressedLepton& muon : muons ) {
122 if (muon.pT() >= 25*GeV) ++n_mu_25;
123 if (muon.pT() >= 27*GeV) ++n_mu_27;
124 }
125 if ((n_el_25 + n_mu_25 != 1) || (n_el_27 + n_mu_27 != 1)) vetoEvent;
126 DressedLepton lepton = (n_el_27 == 1)? electrons[0] : muons[0];
127 FourMomentum l = lepton.mom();
128 int lep_charge = lepton.charge();
129
130 // Neutrino nu
131 FourMomentum nu = getNeutrino(l, met);
132
133 // Hadronic top candidate jh
134 int jh_idx = -1;
135 for (size_t ijet = 0; ijet < ljets.size(); ++ijet) {
136 Jet ljet = Jet(ljets[ijet]);
137 if (ljet.pT() < 350*GeV) continue;
138 if (ljet.abseta() > 2.0) continue;
139 if (ljet.mass() < 140*GeV) continue;
140 if (deltaPhi(ljet,l) < 1.0) continue;
141 bool btagged = false;
142 for (const Jet& jet : jets) {
143 if ( jet.bTagged(Cuts::pT > 5*GeV) ) {
144 if ( deltaR(ljet, jet) < 1.0 ) {
145 btagged = true;
146 break;
147 }
148 }
149 }
150 if (btagged) {
151 jh_idx = ijet;
152 break;
153 }
154 }
155 if ( jh_idx == -1 ) vetoEvent;
156 FourMomentum jh = Jet(ljets[jh_idx]).mom();
157
158 // Leptonic top b-jet candidate jl
159 int jl_idx = -1;
160 for (size_t ijet = 0; ijet < jets.size(); ++ijet) {
161 if ( !jets[ijet].bTagged(Cuts::pT > 5*GeV) ) continue;
162 if ( deltaR(jets[ijet], l) > 2.0 ) continue;
163 if ( deltaR(jets[ijet], jh) < 1.5 ) continue;
164 jl_idx = ijet;
165 break;
166 }
167 if ( jl_idx == -1) {
168 for (size_t ijet = 0; ijet < jets.size(); ++ijet) {
169 if ( deltaR(jets[ijet], l) > 2.0 ) continue;
170 if ( deltaR(jets[ijet], jh) < 1.5 ) continue;
171 jl_idx = ijet;
172 break;
173 }
174 }
175 if ( jl_idx == -1 ) vetoEvent;
176 FourMomentum jl = Jet(jets[jl_idx]).mom();
177
178 // b-tagging
179 size_t n_btagged = 0;
180 size_t n_btagged_matched = 1; // Large-jet jh is b-tagged
181 for (const Jet& jet : jets ) {
182 if (jet.bTagged(Cuts::pT > 5*GeV)) ++n_btagged;
183 }
184 if ( jets[jl_idx].bTagged(Cuts::pT > 5*GeV) ) ++n_btagged_matched;
185 if ( n_btagged >= 2 && n_btagged_matched < 2 ) vetoEvent;
186
187 // Associated jet candidate ja
188 int ja_idx = -1;
189 for (int ijet = 0; ijet < int(jets.size()); ++ijet) {
190 if ( ijet == jl_idx ) continue;
191 if ( jets[ijet].pT() < 100*GeV ) continue;
192 if ( deltaR(jets[ijet], jh) < 1.5 ) continue;
193 if ( deltaR(jets[ijet], l) < 0.4 ) continue;
194 ja_idx = ijet;
195 break;
196 }
197 if ( ja_idx == -1 ) vetoEvent;
198 FourMomentum ja = jets[ja_idx].mom();
199
200 FourMomentum thad = jh;
201 FourMomentum tlep = l+nu+jl;
202 FourMomentum top = lep_charge > 0 ? tlep : thad;
203 FourMomentum tbar = lep_charge > 0 ? thad : tlep;
204 FourMomentum ttbar = top + tbar;
205 FourMomentum ttj = top + tbar + ja;
206
207 // Boost into ttj reference frame
208 FourMomentum ttj_inv( ttj.E(), -ttj.px(), -ttj.py(), -ttj.pz() );
209 Vector3 boostVector = ttj_inv.betaVec();
210 LorentzTransform lt_boost;
211 lt_boost.setBetaVec( boostVector );
212 FourMomentum top_boosted = lt_boost.transform( top );
213 FourMomentum tbar_boosted = lt_boost.transform( tbar );
214 FourMomentum ja_boosted = lt_boost.transform( ja );
215
216 // Get observables
217 const double deltaE = top_boosted.E() - tbar_boosted.E();
218 const double thetaj_opt = ttj.rapidity() > 0 ? ja_boosted.theta() : pi - ja_boosted.theta();
219
220 // Fill auxiliary histograms
221 _h[deltaE > 0 ? "pos" : "neg"]->fill(thetaj_opt);
222
223 }
224
225
226 /// Normalise histograms etc., after the run
227 void finalize() {
228
229 scale(_h, crossSection()/picobarn/sumW());
230
231 // Calculate differential energy asymmetry
232 asymm(_h["pos"], _h["neg"], _asymm);
233
234 }
235
236 /// @}
237
238
239 private:
240
241 fastjet::Filter _trimmer;
242
243 // Histograms
244 map<string, Histo1DPtr> _h;
245 Scatter2DPtr _asymm;
246
247
248 static double delta2_fcn(const MendelMin::Params& p, const MendelMin::Params& pfix) {
249 double delta2 = 0;
250 double alpha = p[0]*6.30-3.15; // Map p[0] in [0,1] to alpha in [-3.15,3.15]
251 double r = pfix[0];
252 double dphi = pfix[1];
253 double l_pt = pfix[2];
254 double l_m = pfix[3];
255 double n_px = pfix[4];
256 double n_py = pfix[5];
257 r /= sqrt(l_pt * l_pt + l_m * l_m) - l_pt * cos(dphi + alpha);
258 FourMomentum neut(0.0, n_px, n_py, 0.0); // E, px, py, pz
259 neut.setE(neut.p());
260 FourMomentum neut_new( 0.0, r * neut.p() * cos(neut.phi() + alpha), r * neut.p() * sin(neut.phi() + alpha), 0.0 );
261 neut_new.setE(neut_new.p());
262 delta2 = pow((neut_new.px() - neut.px()), 2) + pow((neut_new.py() - neut.py()), 2);
263 return delta2;
264 }
265
266
267 FourMomentum getNeutrino(const FourMomentum& lepton, const FourMomentum& met) {
268 const double m_mWpdg = 80.4*GeV;
269 double pxNu = met.px();
270 double pyNu = met.py();
271 double ptNu = met.pt();
272 double pzNu;
273
274 double c1 = pow(m_mWpdg,2) - pow(lepton.mass(),2) + 2 * (lepton.px() * pxNu + lepton.py() * pyNu);
275 double b1 = 2 * lepton.pz();
276 double A = 4*pow(lepton.E(),2) - b1*b1;
277 double B = -2 * c1 * b1;
278 double C = 4 * pow(lepton.E(), 2) * ptNu * ptNu - c1 * c1;
279 double discr = B*B - 4*A*C;
280 double r = 1;
281 double sol1, sol2;
282 if (discr > 0){
283 sol1 = (-B + sqrt(discr)) / (2*A);
284 sol2 = (-B - sqrt(discr)) / (2*A);
285 }
286 else {
287 // fitAlpha
288 std::valarray<double> pfix = { (m_mWpdg * m_mWpdg - lepton.mass() * lepton.mass()) / (2 * ptNu), met.phi() - lepton.phi(),
289 lepton.pt(), lepton.mass(), pxNu, pyNu };
290 MendelMin mm(delta2_fcn, 1, pfix);
291 mm.evolve(100);
292 valarray<double> fittest = mm.fittest();
293
294 const double alpha = fittest[0]*6.30-3.15; // map p[0] in [0,1] to alpha in [-3.15,3.15]
295 const double dphi = met.phi() - lepton.phi();
296 r = ( pow(m_mWpdg,2) - pow(lepton.mass(),2) );
297 r /= (2 * ptNu * (sqrt(pow(lepton.pt(),2) + pow(lepton.mass(),2)) - lepton.pt() * cos(dphi + alpha)));
298
299 const double old_p = ptNu;
300 const double old_phi = met.phi();
301 pxNu = r * old_p * cos(old_phi + alpha);
302 pyNu = r * old_p * sin(old_phi + alpha);
303 ptNu = sqrt (pxNu*pxNu + pyNu*pyNu);
304
305 c1 = pow(m_mWpdg,2) - pow(lepton.mass(),2) + 2 * (lepton.px() * pxNu + lepton.py() * pyNu);
306 B = -2 * c1 * b1;
307 C = 4 * pow(lepton.E(),2) * ptNu * ptNu - c1 * c1;
308 discr = B*B - 4*A*C;
309
310 sol1 = -B / (2*A);
311 sol2 = -B / (2*A);
312 }
313 // useSmallestPz
314 pzNu = (fabs(sol1) > fabs(sol2)) ? sol2 : sol1;
315
316 FourMomentum nu( sqrt(sqr(pxNu) + sqr(pyNu) + sqr(pzNu)), pxNu, pyNu, pzNu);
317
318 return nu;
319 }
320
321 };
322
323
324 RIVET_DECLARE_PLUGIN(ATLAS_2021_I1941095);
325
326}
|