EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
extendedjetexample.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file extendedjetexample.cxx
1 
2 
3 // eicsmear
9 #include "eicsmear/smear/Smear.h"
11 
12 // ROOT
13 #include <TDatabasePDG.h>
14 #include <TFile.h>
15 #include <TChain.h>
16 #include <TH1.h>
17 #include <TH2.h>
18 #include <TCanvas.h>
19 #include <TLegend.h>
20 #include <TText.h>
21 #include <TStyle.h>
22 
23 // C++
24 #include <vector>
25 #include <map>
26 
27 // fastjet
28 #include "fastjet/ClusterSequence.hh"
29 #include "fastjet/PseudoJet.hh"
30 
31 #ifdef GROOMING
32 // fjcontrib
33 #include "fastjet/contrib/Recluster.hh"
34 #include "fastjet/contrib/SoftDrop.hh"
35 #endif
36 
37 
38 // Convenience
39 using namespace fastjet;
40 using namespace std;
41 
42 int main(int argc, char* argv[]){
43 
44  // Parse arguments
45  // ---------------
46  TString truthname="truth.root";
47  TString smearedname = "";
48  TString outfilebase = "jets";
49  int nevents = -1;
50 
51  vector<string> arguments(argv + 1, argv + argc);
52  bool argsokay=true;
53  for ( auto parg = arguments.begin() ; parg!=arguments.end() ; ++parg){
54  string arg=*parg;
55  if ( arg == "-o" ){
56  if (++parg == arguments.end() ){ argsokay=false; break; }
57  outfilebase=*parg;
58  } else if ( arg == "-t" ){
59  if (++parg ==arguments.end() ){ argsokay=false; break; }
60  truthname=*parg;
61  } else if ( arg == "-s" ){
62  if (++parg ==arguments.end() ){ argsokay=false; break; }
63  smearedname=*parg;
64  } else if ( arg == "-N" ){
65  if (++parg==arguments.end() ){ argsokay=false; break; }
66  nevents=std::stoi(parg->data());
67  } else {
68  argsokay=false;
69  break;
70  }
71  }
72 
73  if ( !argsokay ) {
74  cerr << "usage: " << argv[0] << endl
75  << " [-t truthname] (root file)" << endl
76  << " [-s smearedname] (root file)" << endl
77  << " [-o OutFileBase] (extension will be added)" << endl
78  << " [-N Nevents] (<0 for all)" << endl
79  << endl;
80  return -1;
81  }
82 
83  if (smearedname == "" ){
84  smearedname = truthname;
85  smearedname.ReplaceAll (".root",".smeared.root" );
86  }
87 
88  // jet definition - could also be made parameters
89  // ----------------------------------------------
90  // Note: We're doing everything in the lab frame
91  // In a real analysis, you may want to boost to for example
92  // the Breit frame before and/or after jetfinding
93 
94  double R=0.5;
95  JetDefinition jet_def = JetDefinition(kt_algorithm, R);
96 
97  // Only do jetfinding in the region covered by tracking AND calorimetry
98  double ConstEtaMax = 3.5;
99  double ConstEtaMin = -ConstEtaMax;
100 
101  double JetEtaMax = ConstEtaMax - R;
102  double JetEtaMin = -JetEtaMax;
103  double jetPtCut=4.0;
104  auto select_jet_eta = fastjet::SelectorEtaRange ( JetEtaMin + R, JetEtaMax -R );
105  auto select_jet_pt = SelectorPtRange( jetPtCut, 1000 );
106  auto select_jet = select_jet_eta * select_jet_pt;
107 
108 
109  // some cuts
110  // ---------
111  double xmin = 1e-5;
112  double xmax = 0.99;
113  double ymin = 0.01;
114  double ymax = 0.95;
115  double Q2min = 1;
116  double Q2max = 1000000;
117 
118  // Afterburners etc.
119  // -----------------
120  // Here, we demonstrate a toy pT acceptance / efficiency combination
121  // PURELY a toy, NO connection to reality
122  TF1* eff = new TF1("eff","(x>[2]) * [0]*TMath::Erf(x-[1])",0, 100);
123 
124  // mostly 99%, dropping toward small pT, sharp cutoff at 0.2
125  eff->SetParameters (0.99,-0.8, 0.2);
126 
127  // --------------
128  // Load the trees
129  // --------------
130  TChain* inTree = new TChain("EICTree");
131  inTree->Add(truthname);
132  inTree->AddFriend("Smeared",smearedname);
133 
134  // Setup Input Event Buffer
135  erhic::EventMC* inEvent(NULL);
136  Smear::Event* inEventS(NULL);
137  inTree->SetBranchAddress("event",&inEvent);
138  inTree->SetBranchAddress("eventS",&inEventS);
139 
140  // Open histo root file and book histograms
141  // ----------------------------------------
142  TFile * outfile = new TFile ( outfilebase + ".root", "RECREATE");
143 
144  TH1::SetDefaultSumw2(true);
145  float ptmin = 0;
146  float ptmax = 20;
147  int ptbins = 40;
148  TH1D* truthpt=new TH1D("truthpt",";p_{T}^{truth} [GeV/c];counts", ptbins, ptmin, ptmax );
149  TH1D* smearpt=new TH1D("smearpt",";p_{T}^{smear} [GeV/c];counts", ptbins, ptmin, ptmax );
150  TH2D* smearvtruept=new TH2D("smearvtruept",";p_{T}^{truth} [GeV/c];p_{T}^{smear} [GeV/c];counts", ptbins, ptmin, ptmax, ptbins, ptmin, ptmax );
151 
152  // brief example for substructure and other fjcontrib tools
153 #ifdef GROOMING
154  int zgbins = 30;
155  double zgmin = 0;
156  double zgmax = 0.6;
157  TH2D* smearvtruezg=new TH2D("smearvtruezg",";z_{g}^{truth};z_{g}^{smear};counts", zgbins, zgmin, zgmax, zgbins, zgmin, zgmax );
158  double beta = 0;
159  double z_cut = 0.1;
160  contrib::SoftDrop sd( beta, z_cut);
161 #endif
162 
163 
164  // -------------
165  // Analysis loop
166  // -------------
167  // Important: We're running over the truth tree and getting smeared information
168  // via the friend mechanism. This is only necessary for truth-smeared comparisons.
169  // One can run purely on the smeared tree as well without access to the truth.
170 
171  // Book-keping
172  long rejectevent=0;
173  long keptevent=0;
174  long lostevent=0;
175  long fakeevent=0;
176 
177 
178  // Containers for jet constituents
179  // -------------------------------
180  vector<PseudoJet> TruthConstituents;
181  vector<PseudoJet> SmearedConstituents;
182  if ( nevents < 0 ) nevents = inTree->GetEntries();
183 
184  for(long iEvent=0; iEvent<nevents; iEvent++){
185 
186  //Read next Event
187  if(inTree->GetEntry(iEvent) <=0) break;
188  if(iEvent%10000 == 0) cout << "Event " << iEvent << endl;
189 
190  // ---------------
191  // event-wise cuts
192  // ---------------
193  bool truthacceptev = true;
194  bool smearacceptev = true;
195 
196  // truth level
197  // -----------
198  if ( inEvent->GetX() < xmin || inEvent->GetX() > xmax ) truthacceptev = false;
199  if ( inEvent->GetY() < ymin || inEvent->GetY() > ymax ) truthacceptev = false;
200  if ( inEvent->GetQ2() < Q2min || inEvent->GetQ2() > Q2max ) truthacceptev = false;
201 
202  // Detector level
203  // --------------
204  // For the high-Q^2 example, the double angle method does pretty well
205  auto xS = inEventS->GetXDoubleAngle();
206  auto yS = inEventS->GetYDoubleAngle();
207  auto Q2S = inEventS->GetQ2DoubleAngle();
208  if ( xS < xmin || xS > xmax ) smearacceptev = false;
209  if ( yS < ymin || yS > ymax ) smearacceptev = false;
210  if ( Q2S < Q2min || Q2S > Q2max ) smearacceptev = false;
211 
212  if ( !truthacceptev ) rejectevent++;
213 
214  if ( !truthacceptev && !smearacceptev ) continue;
215 
216  // ----------------
217  // Categorize event
218  // ----------------
219  // We have three classes
220  // - Accepted at truth and detector level
221  // - Accepted at truth level, LOST via smearing
222  // - Rejected at truth level, GAINED (FAKE) via smearing
223  // How to handle each depends on details of the analysis,
224  // and proper interpretation also requires understanding of the input.
225  // Here, all we do is book-keeping
226  if ( truthacceptev && smearacceptev ) keptevent++;
227  if ( truthacceptev && !smearacceptev ) lostevent++;
228  if ( !truthacceptev && smearacceptev ) fakeevent++;
229 
230  // -------------------
231  // Loop over Particles
232  // -------------------
233  TruthConstituents.clear();
234  SmearedConstituents.clear();
235 
236  for(unsigned int j=0; j<inEventS->GetNTracks(); j++){
237  // first three particles are always beam and the virtual photon. skip.
238  if ( j<3) continue;
239 
240  const Smear::ParticleMCS* inParticleS = inEventS->GetTrack(j); // Smeared Particle
241  const Particle* inParticle = inEvent->GetTrack(j); // Unsmeared Particle
242 
243  // Skip non-final particles.
244  if ( inParticle->GetStatus() != 1 ) continue;
245 
246  // Particle cuts for truth level
247  // -----------------------------
248  bool truthacceptp = truthacceptev;
249  // only imposing constituent eta cut here
250  if ( inParticle->GetEta() < ConstEtaMin || inParticle->GetEta() > ConstEtaMax) truthacceptp=false;
251 
252  // Particle cuts for smeared (detector) level
253  // ------------------------------------------
254  bool smearacceptp = smearacceptev;
255  if ( !inParticleS ) smearacceptp=false; // lost this particle
256 
257  // Now we need to work like an experimentalist.
258  // For most particles, only partial information is available,
259  // and we need to use something "reasonable" to supply the rest
260  // This is complicated by the fact that in the default handbook, we don't have
261  // PID information. We can pull some from the truth tree,
262  // but make sure to not use information we "shouldn't" have
263 
264  // Non-measured is indicated by a 0 in the field
265  // (this is unfortunate, will be fixed in the future)
266 
267  PseudoJet pj;
268  if ( inParticleS ) {
269  double epsilon = 1e-7;
270  double E = inParticleS->GetE();
271  double px = inParticleS->GetPx();
272  double py = inParticleS->GetPy();
273  double pz = inParticleS->GetPz();
274  double P = inParticleS->GetP(); // not used, but will help identification
275 
276  bool charged=( fabs(P)>epsilon );
277  // Here is the right place for the pT efficiency afterburner.
278  // If the particle is lost, we set P=0 and the logic below will treat it right
279  if ( charged ){
280  auto ran = gRandom->Uniform (0,1);
281  auto pt = std::sqrt ( px*px + py*py ); // GetPt() may be better or worse - for now the same
282  if ( ran > eff->Eval ( pt ) ) {
283  P=px=py=pz=0;
284  }
285  }
286 
287  // the following logic should be safe. a particle should be treated
288  // by exactly one of the of cases.
289 
290  // Nothing measured:
291  if ( fabs(P) <= epsilon && fabs(E) <= epsilon ){
292  // This can happen when e.g. a low-p particle gets smeared below 0
293  // Should be rare but should count as if inParticleS==0 in the first place
294  smearacceptp=false;
295  }
296 
297  // Both measured
298  if ( fabs(P) > epsilon && fabs(E) > epsilon ){
299  // seen by tracker and a calorimeter
300  // either charged hadron or electron
301 
302  // Here an analyzer would have to use their judgement and decide
303  // whether for some phase space discarding one measurement may make sense
304  // because the other one has a better resolution.
305  // In this example, we use both.
306 
307  // Note: In this case, P and E are smeared independently, so in general
308  // the mass-energy relation is not conserved. FastJet does not mind at all.
309 
310  /* nothing to do */
311  }
312 
313  // Tracker only
314  if ( fabs(P) > epsilon && fabs(E) <= epsilon ){
315  // particle seems to be seen only by a tracker (or the calorimeter smeared E to <=0)
316  // For the handbook, this can only happen for the second case, but in principle
317  // it indicates a region not covered by calorimetry.
318 
319  // We tap into the truth level information to make up for a framework short-coming:
320  // A real analyser knows whether the (missing!) calo information should be EM or hadronic, but the
321  // ParticleMCS class has lost that information
322  auto abspid=std::abs(inParticle->GetPdgCode());
323  bool IsElectroMagnetic = ( abspid==22 || abspid == 11);
324 
325  if (IsElectroMagnetic){
326  // This must be an electron (or in theory a mismatch)
327  double m = 0.000511; // GeV
328  E = std::sqrt(P*P + m*m);
329  } else {
330  // A charged hadron. A typical assumption is a pion
331  double m = 0.13957; // GeV
332  E = std::sqrt(P*P + m*m);
333  }
334  }
335 
336  // Calo only
337  if ( fabs(P) <= epsilon && fabs(E) > epsilon ){
338  // - neutral species (neutron, photon) that's covered only by calorimetry
339  // - region that's only covered by calorimetry (_should_ imply EMCAL in a sane detector)
340  // - missed track (or failed match)
341 
342  // IMPORTANT: In the following, we assume that we have phi, eta information
343  // (i.e. from the struck detector region).
344  // HOWEVER, this information is unnaturally correct for the case of a charged particle.
345  // The conservative thing to do is to reject all of these particles.
346  smearacceptp = false;
347 
348  // // If you assume you can somehow ascertain whether this particle is charged,
349  // // you could instead proceed along the following lines
350  // // bool charged= OutsideKnowledgeFunction();
351 
352  // // Again tap into the truth level information to make up for a framework short-coming:
353  // // A real analyser knows whether the calo information is EM or hadronic, but the
354  // // ParticleMCS class has lost that information
355  // auto abspid=std::abs(inParticle->GetPdgCode());
356  // bool IsElectroMagnetic = ( abspid==22 || abspid == 11);
357  // double m=0;
358 
359  // if (IsElectroMagnetic){
360  // // Either a photon or an electron with a lost pointing track
361  // // Either way, assume P=E and we're not far off
362  // m=0;
363  // } else {
364  // // A neutral hadron or a charged one that lost its track.
365  // // P^2 = E^2 - m^2
366  // if ( charged ){
367  // // charged: most likely a pion
368  // m = 0.13957; // GeV
369  // } else {
370  // // neutral: neutron or K0L;
371  // // In the absence of further knowledge, either reject this case or assume
372  // // as an example neutron mass;
373  // m = 0.93957; // GeV
374  // }
375 
376  // }
377 
378  // auto P2 = E*E- m*m;
379  // if ( P2 < 0 ) {
380  // // Something's not right. reject.
381  // smearacceptp = false;
382  // } else {
383  // P = sqrt ( P2);
384  // auto phi = inParticleS->GetPhi();
385  // auto theta = inParticleS->GetTheta();
386  // px = P * sin(theta) * cos(phi);
387  // py = P * sin(theta) * sin(phi);
388  // pz = P * cos(theta);
389  // }
390 
391  }
392 
393  // Combine into a Pseudojet now. In general, a more featureful class may
394  // be preferable (such as TParticle or better) to track pid etc.
395  pj = PseudoJet (px, py, pz, E);
396  }
397 
398  // Particle cuts
399  // -------------
400  // The previous section made up for missing information.
401  // Here, we can add cuts that define or refine our analysis
402 
403  // constituent eta cut
404  if ( pj.eta() < ConstEtaMin || pj.eta() > ConstEtaMax) smearacceptp=false;
405 
406  // --------------------
407  // Collect constituents
408  // --------------------
409  if ( truthacceptp ){
410  TruthConstituents.push_back ( PseudoJet (inParticle->GetPx(),
411  inParticle->GetPy(),
412  inParticle->GetPz(),
413  inParticle->GetE()) );
414  }
415  if ( smearacceptp ){
416  SmearedConstituents.push_back ( pj );
417  }
418 
419  }
420  // --------------------
421  // Perform jet analysis
422  // --------------------
423  ClusterSequence truthcs(TruthConstituents, jet_def);
424  vector<PseudoJet> truthjets = sorted_by_pt( select_jet(truthcs.inclusive_jets()) );
425 
426  ClusterSequence smearcs(SmearedConstituents, jet_def);
427  vector<PseudoJet> smearjets = sorted_by_pt( select_jet(smearcs.inclusive_jets()) );
428 
429  // -------------------
430  // Extract observables
431  // -------------------
432  for ( auto tj : truthjets ){
433  truthpt->Fill ( tj.pt() );
434  }
435 
436  for ( auto sj : smearjets ){
437  smearpt->Fill ( sj.pt() );
438  }
439 
440  // -------------------------
441  // Simple geometric matching
442  // -------------------------
443  fastjet::Selector SelectClose = fastjet::SelectorCircle( R );
444  for ( auto tj : truthjets ){
445  SelectClose.set_reference (tj);
446  vector<PseudoJet> matchedjets = sorted_by_pt( SelectClose (smearjets) );
447  // Shouldn't have more than 1 match
448  if ( matchedjets.size() > 1 ) {
449  cout << "Warning: multiple matches. Skipping." << endl;
450  continue;
451  }
452  if ( matchedjets.size() > 0 ){
453  auto sj = matchedjets.at(0);
454  smearvtruept->Fill ( tj.pt(), sj.pt() );
455 
456 #ifdef GROOMING
457  PseudoJet sd_truth = sd( tj );
458  PseudoJet sd_smear = sd( sj );
459  double tzg = sd_truth.structure_of<contrib::SoftDrop>().symmetry();
460  double szg = sd_smear.structure_of<contrib::SoftDrop>().symmetry();
461  smearvtruezg->Fill ( tzg, szg );
462 #endif // GROOMING
463  }
464  }
465 
466  }
467 
468  cout << " Done." << endl;
469  cout << "Processed " << nevents << " events. Of these, " << endl;
470  cout << " --- " << keptevent << " were ACCEPTED in both truth and smeared tree," << endl;
471  cout << " --- " << lostevent << " were LOST due to cuts," << endl;
472  cout << " --- " << rejectevent << " were REJECTED at truth level, and " << endl;
473  cout << " --- " << fakeevent << " were recovered as FAKEs, i.e. they passed the cuts only after smearing." << endl;
474 
475  new TCanvas;
476  gPad->SetLogy();
477  truthpt->SetLineColor( kBlack );
478  truthpt->Draw();
479  smearpt->SetLineColor( kRed );
480  smearpt->Draw("same");
481  gPad->SaveAs( outfilebase + "_pt.png");
482 
483  new TCanvas;
484  gPad->SetLogz();
485  smearvtruept->Draw("colz");
486  gPad->SaveAs( outfilebase + "_responsept.png");
487 
488 #ifdef GROOMING
489  new TCanvas;
490  smearvtruezg->Draw("colz");
491  gPad->SaveAs( outfilebase + "_responsezg.png");
492 #endif
493 
494  outfile->Write();
495 
496  return 0;
497 }