Rivet analyses referenceMC_HFDECAYSMonte Carlo validation observables for heavy-flavour decaysExperiment: () Status: VALIDATED Authors:
Beams: * * Beam energies: ANY Run details:
Jets with $p_\perp>25$ GeV are constructed with an anti-$k_\perp$ jet finder with $R=0.4$ and projected onto many different observables sensitive to the decays of $b$- and $c$-hadrons in the heavy-flavour-tagged jet. Source code: MC_HFDECAYS.cc 1// -*- C++ -*-
2#include "Rivet/Analysis.hh"
3#include "Rivet/Projections/FinalState.hh"
4#include "Rivet/Projections/FastJets.hh"
5#include "Rivet/Projections/HeavyHadrons.hh"
6
7namespace Rivet {
8
9
10 class MC_HFDECAYS : public Analysis {
11 public:
12
13 /// Constructor
14 RIVET_DEFAULT_ANALYSIS_CTOR(MC_HFDECAYS);
15
16 const string whoDis(const int pid) const {
17 switch (pid) {
18 case PID::B0: return "B0";
19 case PID::BPLUS: return "BPLUS";
20 case PID::B0S: return "B0S";
21 case PID::LAMBDAB: return "LAMBDAB";
22 case PID::D0: return "D0";
23 case PID::DPLUS: return "DPLUS";
24 case PID::DSPLUS: return "DSPLUS";
25 case PID::LAMBDACPLUS: return "LAMBDACPLUS";
26 default: return "";
27 }
28 }
29
30 /// Book histograms and initialise projections before the run
31 void init() {
32
33 declare(HeavyHadrons(),"HA");
34
35 FastJets jetpro(FinalState(), JetAlg::ANTIKT, 0.4, JetMuons::DECAY, JetInvisibles::DECAY);
36 declare(jetpro, "Jets");
37
38 // bar charts
39 book(_h["bar_b_jet_width"], "width_B_jet", 7, 0., 0.3);
40 book(_h["bar_c_jet_width"], "width_C_jet", 7, 0., 0.3);
41 // profiles
42 book(_p["b_jet_rho"], "avg_rho_B_jet", 10, 0., 0.4);
43 book(_p["c_jet_rho"], "avg_rho_C_jet", 10, 0., 0.4);
44 book(_p["b_jet_psi"], "avg_psi_B_jet", 10, 0., 0.4);
45 book(_p["c_jet_psi"], "avg_psi_C_jet", 10, 0., 0.4);
46 // histograms
47 book(_h["b_frac"], "prod_frac_B", 11, 0.5,11.5);
48 book(_h["c_frac"], "prod_frac_C", 7, 0.5, 7.5);
49 book(_h["b_jet_frag"], "frag_B_jet", 50, 0., 1.1);
50 book(_h["c_jet_frag"], "frag_C_jet", 50, 0., 1.1);
51 book(_h["b_jet_pT"], "pT_B_jet", 25, 25., 1025.);
52 book(_h["c_jet_pT"], "pT_C_jet", 25, 25., 1025.);
53 book(_h["b_jet_pThad"], "pThad_B_jet", 10, 0., 200.);
54 book(_h["c_jet_pThad"], "pThad_C_jet", 10, 0., 200.);
55 book(_h["b_jet_high_pT"], "high_pT_frag_B_jet", 46, 0., 1.);
56 book(_h["c_jet_high_pT"], "high_pT_frag_C_jet", 46, 0., 1.);
57 book(_h["b_jet_high_pT_1H"], "high_pT_frag_B_jet_1H", 46, 0., 1.);
58 book(_h["c_jet_high_pT_1H"], "high_pT_frag_C_jet_1H", 46, 0., 1.);
59
60
61 book(_h["ch_B0" ], "B0_charged_mult", 30, 0.5, 30.5);
62 book(_h["ch_BPLUS" ], "BPLUS_charged_mult", 30, 0.5, 30.5);
63 book(_h["ch_B0S" ], "B0S_charged_mult", 30, 0.5, 30.5);
64 book(_h["ch_LAMBDAB" ], "LAMBDAB_charged_mult", 30, 0.5, 30.5);
65 book(_h["ch_D0" ], "D0_charged_mult", 16, 0.5, 16.5);
66 book(_h["ch_DPLUS" ], "DPLUS_charged_multh", 16, 0.5, 16.5);
67 book(_h["ch_DSPLUS" ], "DSPLUS_charged_mult", 16, 0.5, 16.5);
68 book(_h["ch_LAMBDACPLUS"], "LAMBDACPLUS_charged_mult", 16, 0.5, 16.5);
69
70 book(_h["st_B0" ], "B0_stable_mult", 40, 0.5, 40.5);
71 book(_h["st_BPLUS" ], "BPLUS_stable_mult", 40, 0.5, 40.5);
72 book(_h["st_B0S" ], "B0S_stable_mult", 40, 0.5, 40.5);
73 book(_h["st_LAMBDAB" ], "LAMBDAB_stable_mult", 40, 0.5, 40.5);
74 book(_h["st_D0" ], "D0_stable_mult", 20, 0.5, 20.5);
75 book(_h["st_DPLUS" ], "DPLUS_stable_mult", 20, 0.5, 20.5);
76 book(_h["st_DSPLUS" ], "DSPLUS_stable_mult", 20, 0.5, 20.5);
77 book(_h["st_LAMBDACPLUS"], "LAMBDACPLUS_stable_mult", 20, 0.5, 20.5);
78
79 book(_h["pt_B0" ], "B0_pT", 10, 25., 425.);
80 book(_h["pt_BPLUS" ], "BPLUS_pT", 10, 25., 425.);
81 book(_h["pt_B0S" ], "B0S_pT", 10, 25., 425.);
82 book(_h["pt_LAMBDAB" ], "LAMBDAB_pT", 10, 25., 425.);
83 book(_h["pt_D0" ], "D0_pT", 10, 25., 425.);
84 book(_h["pt_DPLUS" ], "DPLUS_pT", 10, 25., 425.);
85 book(_h["pt_DSPLUS" ], "DSPLUS_pT", 10, 25., 425.);
86 book(_h["pt_LAMBDACPLUS"], "LAMBDACPLUS_pT", 10, 25., 425.);
87
88 book(_h["b_jet_ch_mult"], "charged_mult_B_jets", 40, 0.5, 40.5);
89 book(_h["c_jet_ch_mult"], "charged_mult_C_jets", 40, 0.5, 40.5);
90
91 book(_h["b_jet_l_pTrel"], "lepton_pTrel_B_jets", 8, 0., 15.);
92 book(_h["c_jet_l_pTrel"], "lepton_pTrel_C_jets", 8, 0., 10.);
93
94 book(_h["b_jet_l_pT"], "lepton_pT_B_jets", 10, 0., 100.);
95 book(_h["c_jet_l_pT"], "lepton_pT_C_jets", 10, 0., 100.);
96
97 // double-differentials
98 vector<double> groupEdges = ptaxis.edges(); // includes +/- inf
99 groupEdges.erase(groupEdges.begin()); groupEdges.pop_back(); // removes +/- inf
100 book(_g["avg_B_jet_ch_mult"], groupEdges);
101 book(_g["avg_C_jet_ch_mult"], groupEdges);
102 book(_g["avg_B_jet_l_pTrel"], groupEdges);
103 book(_g["avg_C_jet_l_pTrel"], groupEdges);
104 for (size_t i = 0; i < ptaxis.numBins(); ++i) {
105 const std::pair<double,double> ptbin = std::make_pair(ptaxis.min(i+1),ptaxis.max(i+1));
106 const string suff = to_string(int(ptbin.first)) + "_" + to_string(int(ptbin.second));
107 book(_p["avg_B_jet_rho_"+to_string(i)], "avg_B_jet_rho_"+suff, 10, 0., 0.4);
108 book(_p["avg_C_jet_rho_"+to_string(i)], "avg_C_jet_rho_"+suff, 10, 0., 0.4);
109
110 book(_g["avg_B_jet_ch_mult"]->bin(i+1), "avg_B_jet_ch_mult_"+suff, 40, 0.5, 40.5);
111 book(_g["avg_C_jet_ch_mult"]->bin(i+1), "avg_C_jet_ch_mult_"+suff, 40, 0.5, 40.5);
112 if (i == 0) {
113 book(_g["avg_B_jet_l_pTrel"]->bin(i+1), "avg_B_jet_l_pTrel_"+suff, 2, 0., 4.);
114 book(_g["avg_C_jet_l_pTrel"]->bin(i+1), "avg_C_jet_l_pTrel_"+suff, 2, 0., 3.);
115 }
116 else if (i <= 2) {
117 book(_g["avg_B_jet_l_pTrel"]->bin(i+1), "avg_B_jet_l_pTrel_"+suff, 4, 0., 8.);
118 book(_g["avg_C_jet_l_pTrel"]->bin(i+1), "avg_C_jet_l_pTrel_"+suff, 5, 0., 6.);
119 }
120 else {
121 book(_g["avg_B_jet_l_pTrel"]->bin(i+1), "avg_B_jet_l_pTrel_"+suff, 8, 0., 15.);
122 book(_g["avg_C_jet_l_pTrel"]->bin(i+1), "avg_C_jet_l_pTrel_"+suff, 8, 0., 10.);
123 }
124 }
125 }
126
127 double p_annulus(const Jet &jet, const double a, const double b) const {
128 // calculate the total momentum inside an annulus with a <= R < b
129 return sum(select(jet.particles(), [&](const Particle &p) {
130 const double dr = deltaR(p, jet);
131 return (dr < b && dr >= a);
132 }), Kin::pT, 0.)/GeV;
133 }
134
135 void count_mult(const Particle& p) {
136 unsigned int nst = p.stableDescendants().size() + 0.5;
137 unsigned int nch = p.stableDescendants(Cuts::charge != 0).size() + 0.5;
138 _h["st_"+whoDis(p.abspid())]->fill(nst);
139 _h["ch_"+whoDis(p.abspid())]->fill(nch);
140 }
141
142 double pTrel (const Jet& jet, const Particle& p) const {
143 return (p.p3().cross(jet.p3())).mod()/(jet.p3().mod());
144 }
145
146
147 /// Perform the per-event analysis
148 void analyze(const Event& event) {
149
150 const HeavyHadrons &ha = apply<HeavyHadrons>(event,"HA");
151
152 if (ha.bHadrons().empty() && ha.cHadrons().empty()) vetoEvent;
153
154 for (const Particle &p : ha.bHadrons()) {
155 const string name = "pt_" + whoDis(p.abspid());
156 if (p.abspid() == PID::B0) { //Take into consideration both particles and anti-particles with abs()
157 count_mult(p);
158 _h["b_frac"]->fill(1);
159 _h[name]->fill(p.pT()/GeV);
160 }
161 else if (p.abspid() == PID::BPLUS) {
162 count_mult(p);
163 _h["b_frac"]->fill(2);
164 _h[name]->fill(p.pT()/GeV);
165 }
166 else if (p.abspid() == PID::B0S) {
167 count_mult(p);
168 _h["b_frac"]->fill(3);
169 _h[name]->fill(p.pT()/GeV);
170 }
171 else if (p.abspid() == PID::BCPLUS) _h["b_frac"]->fill(4);
172 else if (p.abspid() == PID::LAMBDAB) {
173 count_mult(p);
174 _h["b_frac"]->fill(5);
175 _h[name]->fill(p.pT()/GeV);
176 }
177 else if (p.abspid() == PID::XIBMINUS) _h["b_frac"]->fill(6);
178 else if (p.abspid() == PID::XI0B) _h["b_frac"]->fill(7);
179 else if (p.abspid() == PID::OMEGABMINUS) _h["b_frac"]->fill(8);
180 else if (p.abspid() == PID::SIGMABMINUS) _h["b_frac"]->fill(9);
181 else if (p.abspid() == PID::SIGMAB) _h["b_frac"]->fill(10);
182 else if (p.abspid() == PID::SIGMABPLUS) _h["b_frac"]->fill(11);
183 }
184
185 for (const Particle &p : ha.cHadrons()) {
186 if( !p.fromBottom()){ //take into account only c-hadrons that don't come from a b-hadron decay
187 //Avoid double-counting b-hadron fractions
188 const string name = "pt_" + whoDis(p.abspid());
189 if (p.abspid() == PID::DPLUS) {
190 count_mult(p);
191 _h["c_frac"]->fill(1);
192 _h[name]->fill(p.pT()/GeV);
193 }
194 else if (p.abspid() == PID::D0) {
195 count_mult(p);
196 _h["c_frac"]->fill(2);
197 _h[name]->fill(p.pT()/GeV);
198 }
199 else if (p.abspid() == PID::DSPLUS) {
200 count_mult(p);
201 _h["c_frac"]->fill(3);
202 _h[name]->fill(p.pT()/GeV);
203 }
204 else if (p.abspid() == PID::LAMBDACPLUS) {
205 count_mult(p);
206 _h["c_frac"]->fill(4);
207 _h[name]->fill(p.pT()/GeV);
208 }
209 else if (p.abspid() == PID::XICPLUS) _h["c_frac"]->fill(5);
210 else if (p.abspid() == PID::XI0C) _h["c_frac"]->fill(6);
211 else if (p.abspid() == PID::OMEGA0C) _h["c_frac"]->fill(7);
212 }
213 }
214 Jets jets = apply<FastJets>(event, "Jets").jetsByPt(Cuts::pT > 25.*GeV && Cuts::absrap < 2.5);
215 if (jets.empty()) vetoEvent;
216
217 auto r_bins = _p["b_jet_rho"]->xEdges();
218 const double dr = r_bins[1]-r_bins[0]; //dr is equal for all bins
219 for (const Jet& thisJet : jets) {
220 double p_0_R = p_annulus(thisJet, 0., 0.4);
221 if (fuzzyEquals(p_0_R, 0., 1e-5)) continue;
222 Particles bjets = thisJet.bTags(Cuts::pT > 5*GeV);
223 iselect(bjets, deltaRLess(thisJet, 0.3));
224 for (const Particle &thisB : bjets) {
225 _h["b_jet_pThad"]->fill(thisB.pT()/GeV);
226 double z = thisJet.p3().dot(thisB.p3())/thisJet.p2();
227 _h["b_jet_frag"]->fill(z);
228 if (inRange(thisJet.pT(), 500*GeV, 1000*GeV)) {
229 _h["b_jet_high_pT"]->fill(z);
230 if (bjets.size() == 1) _h["b_jet_high_pT_1H"]->fill(z);
231 }
232 for (size_t i = 0; i < r_bins.size()-1; ++i) {
233 double r = 0.5*(r_bins[i]+r_bins[i+1]);
234 double rho = p_annulus(thisJet, r_bins[i], r_bins[i+1])/p_0_R/dr;
235 double psi = p_annulus(thisJet, 0, r_bins[i+1])/p_0_R;
236 _p["b_jet_rho"]->fill(r, rho);
237 _p["b_jet_psi"]->fill(r, psi);
238 size_t ptb = ptaxis.index(thisJet.pT()/GeV) - 1; // index in {1, nBins}
239 if (ptb < ptaxis.numBins()) _p["avg_B_jet_rho_"+to_string(ptb)]->fill(r, rho);
240 }
241 }
242
243 Particles cjets = thisJet.cTags(Cuts::pT > 5*GeV);
244 iselect(cjets, deltaRLess(thisJet, 0.3));
245 if (bjets.empty()) {
246 for (const Particle& thisC : cjets) {
247 _h["c_jet_pThad"]->fill(thisC.pT()/GeV);
248 double z = thisJet.p3().dot(thisC.p3())/thisJet.p2();
249 _h["c_jet_frag"]->fill(z);
250 if (inRange(thisJet.pT(), 500*GeV, 1000*GeV)) {
251 _h["c_jet_high_pT"]->fill(z);
252 if (cjets.size() == 1) {
253 _h["c_jet_high_pT_1H"]->fill(z);
254 }
255 }
256 for (size_t i = 0; i < r_bins.size()-1; ++i) {
257 double r = 0.5*(r_bins[i]+r_bins[i+1]);
258 double rho = p_annulus(thisJet, r_bins[i], r_bins[i+1])/p_0_R/dr;
259 double psi = p_annulus(thisJet, 0, r_bins[i+1])/p_0_R;
260 _p["c_jet_rho"]->fill(r, rho);
261 _p["c_jet_psi"]->fill(r, psi);
262 size_t ptb = ptaxis.index(thisJet.pT()/GeV) - 1; // index in {0, nBins - 1}
263 if (ptb < ptaxis.numBins()) _p["avg_C_jet_rho_"+to_string(ptb)]->fill(r, rho);
264 }
265 }
266 }
267
268 if (bjets.size()) {
269 double W_num = 0., W_den = 0.;
270 long N_charged = 0;
271 for (const Particle& pp : thisJet.particles()) {
272 if(pp.isStable()) {
273 W_num += pp.pT()*deltaR(thisJet,pp);
274 W_den += pp.pT();
275 if (pp.isCharged()) ++N_charged;
276 }
277 if(pp.isLepton()) {
278 _h["b_jet_l_pT"]->fill(pp.pT()/GeV);
279 _h["b_jet_l_pTrel"]->fill(pTrel(thisJet,pp)/GeV);
280 _g["avg_B_jet_l_pTrel"]->fill(thisJet.pT()/GeV, pTrel(thisJet,pp)/GeV);
281 }
282 }
283 if (W_den) _h["bar_b_jet_width"]->fill(W_num/W_den);
284 _h["b_jet_pT"]->fill(thisJet.pT()/GeV);
285 if (N_charged) {
286 _h["b_jet_ch_mult"]->fill(N_charged);
287 _g["avg_B_jet_ch_mult"]->fill(thisJet.pT()/GeV, (double)N_charged);
288 }
289 }
290 else if(cjets.size()) {
291 double W_num = 0., W_den = 0.;
292 long N_charged = 0;
293 for (const Particle& pp : thisJet.particles()) {
294 if(pp.isStable()) {
295 W_num += pp.pT()*deltaR(thisJet,pp);
296 W_den += pp.pT();
297 if(pp.isCharged()) ++N_charged;
298 }
299 if(pp.isLepton()) {
300 _h["c_jet_l_pT"]->fill(pp.pT()/GeV);
301 _h["c_jet_l_pTrel"]->fill(pTrel(thisJet,pp)/GeV);
302 _g["avg_C_jet_l_pTrel"]->fill(thisJet.pT()/GeV, pTrel(thisJet,pp)/GeV);
303 }
304 }
305 if (W_den) _h["bar_c_jet_width"]->fill(W_num/W_den);
306 _h["c_jet_pT"]->fill(thisJet.pT()/GeV);
307 if (N_charged) {
308 _h["c_jet_ch_mult"]->fill(N_charged);
309 _g["avg_C_jet_ch_mult"]->fill(thisJet.pT()/GeV, (double)N_charged);
310 }
311 }
312 }
313 }
314
315 /// Normalise histograms etc., after the run
316 void finalize() {
317 for (const auto &hit : _h) {
318 double sf = 1.0;
319 if (hit.first.find("bar_") != string::npos) {
320 sf = (hit.second->xMax()-hit.second->xMin())/hit.second->numBins();
321 }
322 normalize(hit.second, sf);
323 }
324 normalize(_g);
325 }
326
327 private:
328 map<string, Histo1DPtr> _h;
329 map<string, Profile1DPtr> _p;
330 map<string, Histo1DGroupPtr> _g;
331 YODA::Axis<double> ptaxis{25, 30, 50, 70, 100, 150, 300, 500, 1000};
332
333 };
334
335 RIVET_DECLARE_PLUGIN(MC_HFDECAYS);
336}
|