Warning, /tutorial-analysis/learners/exercise-scripts.md is written in an unsupported language. File is not indexed.
0001 ---
0002 title: "Exercise Scripts"
0003 ---
0004
0005 Included below is a selection of scripts for the exercises in part 3 of this tutorial.
0006
0007 You should be able to copy the code text directly into a new file. The name of the file is included as the title of each script section and in the accompanying descriptive text.
0008
0009 ## ROOT TTreeReader Scripts
0010
0011 ### EfficiencyAnalysis.C
0012
0013 Create a file called `EfficiencyAnalysis.C` and copy in the code below to get started on the efficiency analysis exercise. Note that you will need to correctly specify your input file path in the first line.
0014
0015 ```c++
0016 void EfficiencyAnalysis(TString infile="PATH_TO_INPUT_FILE"){
0017 // Set output file for the histograms
0018 TFile *ofile = TFile::Open("EfficiencyAnalysis_Out.root","RECREATE");
0019
0020 // Set up input file chain
0021 TChain *mychain = new TChain("events");
0022 mychain->Add(infile);
0023
0024 // Initialize reader
0025 TTreeReader tree_reader(mychain);
0026
0027 // Get Particle Information
0028 TTreeReaderArray<int> partGenStat(tree_reader, "MCParticles.generatorStatus");
0029 TTreeReaderArray<double> partMomX(tree_reader, "MCParticles.momentum.x");
0030 TTreeReaderArray<double> partMomY(tree_reader, "MCParticles.momentum.y");
0031 TTreeReaderArray<double> partMomZ(tree_reader, "MCParticles.momentum.z");
0032 TTreeReaderArray<int> partPdg(tree_reader, "MCParticles.PDG");
0033
0034 // Get Reconstructed Track Information
0035 TTreeReaderArray<float> trackMomX(tree_reader, "ReconstructedChargedParticles.momentum.x");
0036 TTreeReaderArray<float> trackMomY(tree_reader, "ReconstructedChargedParticles.momentum.y");
0037 TTreeReaderArray<float> trackMomZ(tree_reader, "ReconstructedChargedParticles.momentum.z");
0038
0039 // Get Associations Between MCParticles and ReconstructedChargedParticles
0040 TTreeReaderArray<int> recoAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_rec.index");
0041 TTreeReaderArray<int> simuAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_sim.index");
0042
0043 // Define Histograms
0044 TH1D *partEta = new TH1D("partEta","Eta of Thrown Charged Particles;Eta",100,-5.,5.);
0045 TH1D *matchedPartEta = new TH1D("matchedPartEta","Eta of Thrown Charged Particles That Have Matching Track",100,-5.,5.);
0046 TH1D *matchedPartTrackDeltaR = new TH1D("matchedPartTrackDeltaR","Delta R Between Matching Thrown and Reconstructed Charged Particle",5000,0.,5.);
0047
0048 while(tree_reader.Next()) { // Loop over events
0049 for(unsigned int i=0; i<partGenStat.GetSize(); i++){ // Loop over thrown particles
0050 if(partGenStat[i] == 1){ // Select stable thrown particles
0051 int pdg = TMath::Abs(partPdg[i]);
0052 if(pdg == 11 || pdg == 13 || pdg == 211 || pdg == 321 || pdg == 2212){ // Look at charged particles (electrons, muons, pions, kaons, protons)
0053 TVector3 trueMom(partMomX[i],partMomY[i],partMomZ[i]);
0054
0055 float trueEta = trueMom.PseudoRapidity();
0056 float truePhi = trueMom.Phi();
0057
0058 partEta->Fill(trueEta);
0059
0060 for(unsigned int j=0; j<simuAssoc.GetSize(); j++){ // Loop over associations to find matching ReconstructedChargedParticle
0061 if(simuAssoc[j] == i){ // Find association index matching the index of the thrown particle we are looking at
0062 TVector3 recMom(trackMomX[recoAssoc[j]],trackMomY[recoAssoc[j]],trackMomZ[recoAssoc[j]]); // recoAssoc[j] is the index of the matched ReconstructedChargedParticle
0063
0064 // Check the distance between the thrown and reconstructed particle
0065 float deltaEta = trueEta - recMom.PseudoRapidity();
0066 float deltaPhi = TVector2::Phi_mpi_pi(truePhi - recMom.Phi());
0067 float deltaR = TMath::Sqrt(deltaEta*deltaEta + deltaPhi*deltaPhi);
0068
0069 matchedPartTrackDeltaR->Fill(deltaR);
0070
0071 matchedPartEta->Fill(trueEta); // Plot the thrown eta if a matched ReconstructedChargedParticle was found
0072 }
0073 } // End loop over associations
0074 } // End PDG check
0075 } // End stable particles condition
0076 } // End loop over thrown particles
0077 } // End loop over events
0078 ofile->Write(); // Write histograms to file
0079 ofile->Close(); // Close output file
0080 }
0081 ```
0082 A "solution" version of the script for the exercise is included below -
0083
0084 ```c++
0085 void EfficiencyAnalysis_Exercise(TString infile="PATH_TO_FILE"){
0086
0087 // Set output file for the histograms
0088 TFile *ofile = TFile::Open("EfficiencyAnalysis_Exercise_Out.root","RECREATE");
0089
0090 // Analysis code will go here
0091 // Set up input file chain
0092 TChain *mychain = new TChain("events");
0093 mychain->Add(infile);
0094
0095 // Initialize reader
0096 TTreeReader tree_reader(mychain);
0097
0098 // Get Particle Information
0099 TTreeReaderArray<int> partGenStat(tree_reader, "MCParticles.generatorStatus");
0100 TTreeReaderArray<double> partMomX(tree_reader, "MCParticles.momentum.x");
0101 TTreeReaderArray<double> partMomY(tree_reader, "MCParticles.momentum.y");
0102 TTreeReaderArray<double> partMomZ(tree_reader, "MCParticles.momentum.z");
0103 TTreeReaderArray<int> partPdg(tree_reader, "MCParticles.PDG");
0104
0105 // Get Reconstructed Track Information
0106 TTreeReaderArray<float> trackMomX(tree_reader, "ReconstructedChargedParticles.momentum.x");
0107 TTreeReaderArray<float> trackMomY(tree_reader, "ReconstructedChargedParticles.momentum.y");
0108 TTreeReaderArray<float> trackMomZ(tree_reader, "ReconstructedChargedParticles.momentum.z");
0109
0110 // Get Associations Between MCParticles and ReconstructedChargedParticles
0111 TTreeReaderArray<int> recoAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_rec.index");
0112 TTreeReaderArray<int> simuAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_sim.index");
0113
0114 // Define Histograms
0115 TH1D *partEta = new TH1D("partEta","#eta of Thrown Charged Particles; #eta", 120, -6, 6);
0116 TH1D *matchedPartEta = new TH1D("matchedPartEta","#eta of Thrown Charged Particles That Have Matching Track; #eta", 120, -6, 6);
0117 TH1D* partMom = new TH1D("partMom", "Momentum of Thrown Charged Particles (truth); P(GeV/c)", 150, 0, 150);
0118 TH1D* matchedPartMom = new TH1D("matchedPartMom", "Momentum of Thrown Charged Particles (truth), with matching track; P(GeV/c)", 150, 0, 150);
0119 TH1D* partPhi = new TH1D("partPhi", "#phi of Thrown Charged Particles (truth); #phi(rad)", 320, -3.2, 3.2);
0120 TH1D* matchedPartPhi = new TH1D("matchedPartPhi", "#phi of Thrown Charged Particles (truth), with matching track; #phi(rad)", 320, -3.2, 3.2);
0121
0122 TH2D* partPEta = new TH2D("partPEta", "P vs #eta of Thrown Charged Particles; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6);
0123 TH2D* matchedPartPEta = new TH2D("matchedPartPEta", "P vs #eta of Thrown Charged Particles, with matching track; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6);
0124 TH2D* partPhiEta = new TH2D("partPhiEta", "#phi vs #eta of Thrown Charged Particles; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6);
0125 TH2D* matchedPartPhiEta = new TH2D("matchedPartPhiEta", "#phi vs #eta of Thrown Charged Particles; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6);
0126
0127 TH1D *matchedPartTrackDeltaEta = new TH1D("matchedPartTrackDeltaEta","#Delta#eta Between Matching Thrown and Reconstructed Charged Particle; #Delta#eta", 100, -0.25, 0.25);
0128 TH1D *matchedPartTrackDeltaPhi = new TH1D("matchedPartTrackDeltaPhi","#Detla #phi Between Matching Thrown and Reconstructed Charged Particle; #Delta#phi", 200, -0.2, 0.2);
0129 TH1D *matchedPartTrackDeltaR = new TH1D("matchedPartTrackDeltaR","#Delta R Between Matching Thrown and Reconstructed Charged Particle; #Delta R", 300, 0, 0.3);
0130 TH1D *matchedPartTrackDeltaMom = new TH1D("matchedPartTrackDeltaMom","#Delta P Between Matching Thrown and Reconstructed Charged Particle; #Delta P", 200, -10, 10);
0131
0132 // Define some histograms for our efficiencies
0133 TH1D *TrackEff_Eta = new TH1D("TrackEff_Eta", "Tracking efficiency as fn of #eta; #eta; Eff(%)", 120, -6, 6);
0134 TH1D *TrackEff_Mom = new TH1D("TrackEff_Mom", "Tracking efficiency as fn of P; P(GeV/c); Eff(%)", 150, 0, 150);
0135 TH1D *TrackEff_Phi = new TH1D("TrackEff_Phi", "Tracking efficiency as fn of #phi; #phi(rad); Eff(%)", 320, -3.2, 3.2);
0136
0137 // 2D Efficiencies
0138 TH2D* TrackEff_PEta = new TH2D("TrackEff_PEta", "Tracking efficiency as fn of P and #eta; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6);
0139 TH2D* TrackEff_PhiEta = new TH2D("TrackEff_PhiEta", "Tracking efficiency as fn of #phi and #eta; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6);
0140
0141 // All charged particle histos
0142 TH1D *ChargedEta = new TH1D("ChargedEta", "#eta of all charged particles; #eta", 120, -6, 6);
0143 TH1D *ChargedPhi = new TH1D("ChargedPhi", "#phi of all charged particles; #phi (rad)", 120, -3.2, 3.2);
0144 TH1D *ChargedP = new TH1D("ChargedP", "P of all charged particles; P(GeV/c)", 150, 0, 150);
0145
0146 while(tree_reader.Next()) { // Loop over events
0147
0148 for(unsigned int i=0; i<partGenStat.GetSize(); i++) // Loop over thrown particles
0149 {
0150 if(partGenStat[i] == 1) // Select stable thrown particles
0151 {
0152 int pdg = TMath::Abs(partPdg[i]);
0153
0154 if(pdg == 11 || pdg == 13 || pdg == 211 || pdg == 321 || pdg == 2212) // Look at charged particles (electrons, muons, pions, kaons, protons)
0155 {
0156 TVector3 trueMom(partMomX[i],partMomY[i],partMomZ[i]);
0157
0158 float trueEta = trueMom.PseudoRapidity();
0159 float truePhi = trueMom.Phi();
0160
0161 partEta->Fill(trueEta);
0162 partPhi->Fill(truePhi);
0163 partMom->Fill(trueMom.Mag());
0164 partPEta->Fill(trueMom.Mag(), trueEta);
0165 partPhiEta->Fill(truePhi, trueEta);
0166
0167 // Loop over associations to find matching ReconstructedChargedParticle
0168 for(unsigned int j=0; j<simuAssoc.GetSize(); j++)
0169 {
0170 if(simuAssoc[j] == i) // Find association index matching the index of the thrown particle we are looking at
0171 {
0172 TVector3 recMom(trackMomX[recoAssoc[j]],trackMomY[recoAssoc[j]],trackMomZ[recoAssoc[j]]); // recoAssoc[j] is the index of the matched ReconstructedChargedParticle
0173
0174 // Check the distance between the thrown and reconstructed particle
0175 float deltaEta = trueEta - recMom.PseudoRapidity();
0176 float deltaPhi = TVector2::Phi_mpi_pi(truePhi - recMom.Phi());
0177 float deltaR = TMath::Sqrt(deltaEta*deltaEta + deltaPhi*deltaPhi);
0178 float deltaMom = ((trueMom.Mag()) - (recMom.Mag()));
0179
0180 matchedPartTrackDeltaEta->Fill(deltaEta);
0181 matchedPartTrackDeltaPhi->Fill(deltaPhi);
0182 matchedPartTrackDeltaR->Fill(deltaR);
0183 matchedPartTrackDeltaMom->Fill(deltaMom);
0184
0185 matchedPartEta->Fill(trueEta); // Plot the thrown eta if a matched ReconstructedChargedParticle was found
0186 matchedPartPhi->Fill(truePhi);
0187 matchedPartMom->Fill(trueMom.Mag());
0188
0189 matchedPartPEta->Fill(trueMom.Mag(), trueEta);
0190 matchedPartPhiEta->Fill(truePhi, trueEta);
0191
0192 }
0193 }// End loop over associations
0194 } // End PDG check
0195 } // End stable particles condition
0196 } // End loop over thrown particles
0197 // Loop over all charged particles and fill some histograms of kinematics quantities
0198 for(unsigned int k=0; k<trackMomX.GetSize(); k++){ // Loop over all charged particles, thrown or not
0199
0200 TVector3 CPartMom(trackMomX[k], trackMomY[k], trackMomZ[k]);
0201
0202 float CPartEta = CPartMom.PseudoRapidity();
0203 float CPartPhi = CPartMom.Phi();
0204
0205 ChargedEta->Fill(CPartEta);
0206 ChargedPhi->Fill(CPartPhi);
0207 ChargedP->Fill(CPartMom.Mag());
0208
0209 } // End loop over all charged particles
0210 } // End loop over events
0211
0212 // Take the ratio of the histograms above to get our efficiency plots
0213 TrackEff_Eta->Divide(matchedPartEta, partEta, 1, 1, "b");
0214 TrackEff_Mom->Divide(matchedPartMom, partMom, 1, 1, "b");
0215 TrackEff_Phi->Divide(matchedPartPhi, partPhi, 1, 1, "b");
0216 TrackEff_PEta->Divide(matchedPartPEta, partPEta, 1, 1, "b");
0217 TrackEff_PhiEta->Divide(matchedPartPhiEta, partPhiEta, 1, 1, "b");
0218
0219 ofile->Write(); // Write histograms to file
0220 ofile->Close(); // Close output file
0221 }
0222 ```
0223 Insert your input file path and execute as the example code above.
0224
0225 ### ResolutionAnalysis.C
0226
0227 Create a file called `ResolutionAnalysis.C` and copy in the code below to get started on the resolution analysis exercise. Note that you will need to correctly specify your input file path in the first line.
0228
0229 ```c++
0230 void ResolutionAnalysis(TString infile="PATH_TO_INPUT_FILE"){
0231 // Set output file for the histograms
0232 TFile *ofile = TFile::Open("ResolutionAnalysis_Out.root","RECREATE");
0233
0234 // Analysis code will go here
0235 // Set up input file chain
0236 TChain *mychain = new TChain("events");
0237 mychain->Add(infile);
0238
0239 // Initialize reader
0240 TTreeReader tree_reader(mychain);
0241
0242 // Get Particle Information
0243 TTreeReaderArray<int> partGenStat(tree_reader, "MCParticles.generatorStatus");
0244 TTreeReaderArray<double> partMomX(tree_reader, "MCParticles.momentum.x");
0245 TTreeReaderArray<double> partMomY(tree_reader, "MCParticles.momentum.y");
0246 TTreeReaderArray<double> partMomZ(tree_reader, "MCParticles.momentum.z");
0247 TTreeReaderArray<int> partPdg(tree_reader, "MCParticles.PDG");
0248
0249 // Get Reconstructed Track Information
0250 TTreeReaderArray<float> trackMomX(tree_reader, "ReconstructedChargedParticles.momentum.x");
0251 TTreeReaderArray<float> trackMomY(tree_reader, "ReconstructedChargedParticles.momentum.y");
0252 TTreeReaderArray<float> trackMomZ(tree_reader, "ReconstructedChargedParticles.momentum.z");
0253
0254 // Get Associations Between MCParticles and ReconstructedChargedParticles
0255 TTreeReaderArray<int> recoAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_rec.index");
0256 TTreeReaderArray<int> simuAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_sim.index");
0257
0258 // Define Histograms
0259 TH1D *trackMomentumRes = new TH1D("trackMomentumRes","Track Momentum Resolution", 400, -2, 2);
0260
0261 TH1D *matchedPartTrackDeltaEta = new TH1D("matchedPartTrackDeltaEta","#Delta#eta Between Matching Thrown and Reconstructed Charged Particle; #Delta#eta", 100, -0.25, 0.25);
0262 TH1D *matchedPartTrackDeltaPhi = new TH1D("matchedPartTrackDeltaPhi","#Detla #phi Between Matching Thrown and Reconstructed Charged Particle; #Delta#phi", 200, -0.2, 0.2);
0263 TH1D *matchedPartTrackDeltaR = new TH1D("matchedPartTrackDeltaR","#Delta R Between Matching Thrown and Reconstructed Charged Particle; #Delta R", 300, 0, 0.3);
0264 TH1D *matchedPartTrackDeltaMom = new TH1D("matchedPartTrackDeltaMom","#Delta P Between Matching Thrown and Reconstructed Charged Particle; #Delta P", 200, -10, 10);
0265 while(tree_reader.Next()) { // Loop over events
0266 for(unsigned int i=0; i<partGenStat.GetSize(); i++){ // Loop over thrown particles
0267 if(partGenStat[i] == 1){ // Select stable thrown particles
0268 int pdg = TMath::Abs(partPdg[i]);
0269 if(pdg == 11 || pdg == 13 || pdg == 211 || pdg == 321 || pdg == 2212){ // Look at charged particles (electrons, muons, pions, kaons, protons)
0270 TVector3 trueMom(partMomX[i],partMomY[i],partMomZ[i]);
0271
0272 float trueEta = trueMom.PseudoRapidity();
0273 float truePhi = trueMom.Phi();
0274
0275 for(unsigned int j=0; j<simuAssoc.GetSize(); j++){ // Loop over associations to find matching ReconstructedChargedParticle
0276 if(simuAssoc[j] == i){ // Find association index matching the index of the thrown particle we are looking at
0277 TVector3 recMom(trackMomX[recoAssoc[j]],trackMomY[recoAssoc[j]],trackMomZ[recoAssoc[j]]); // recoAssoc[j] is the index of the matched ReconstructedChargedParticle
0278
0279 // Check the distance between the thrown and reconstructed particle
0280 float deltaEta = trueEta - recMom.PseudoRapidity();
0281 float deltaPhi = TVector2::Phi_mpi_pi(truePhi - recMom.Phi());
0282 float deltaR = TMath::Sqrt(deltaEta*deltaEta + deltaPhi*deltaPhi);
0283 float deltaMom = ((trueMom.Mag()) - (recMom.Mag()));
0284 double momRes = (recMom.Mag()- trueMom.Mag())/trueMom.Mag();
0285
0286 trackMomentumRes->Fill(momRes);
0287
0288 matchedPartTrackDeltaEta->Fill(deltaEta);
0289 matchedPartTrackDeltaPhi->Fill(deltaPhi);
0290 matchedPartTrackDeltaR->Fill(deltaR);
0291 matchedPartTrackDeltaMom->Fill(deltaMom);
0292 }
0293 } // End loop over associations
0294 } // End PDG check
0295 } // End stable particles condition
0296 } // End loop over thrown particles
0297 } // End loop over events
0298 ofile->Write(); // Write histograms to file
0299 ofile->Close(); // Close output file
0300 }
0301 ```
0302 A "solution" version of the script for the exercise is included below -
0303
0304 ```c++
0305 void ResolutionAnalysis_Exercise(TString infile="PATH_TO_FILE"){
0306 // Set output file for the histograms
0307 TFile *ofile = TFile::Open("ResolutionAnalysis_Exercise_Out.root","RECREATE");
0308
0309 // Analysis code will go here
0310 // Set up input file chain
0311 TChain *mychain = new TChain("events");
0312 mychain->Add(infile);
0313
0314 // Initialize reader
0315 TTreeReader tree_reader(mychain);
0316
0317 // Get Particle Information
0318 TTreeReaderArray<int> partGenStat(tree_reader, "MCParticles.generatorStatus");
0319 TTreeReaderArray<double> partMomX(tree_reader, "MCParticles.momentum.x");
0320 TTreeReaderArray<double> partMomY(tree_reader, "MCParticles.momentum.y");
0321 TTreeReaderArray<double> partMomZ(tree_reader, "MCParticles.momentum.z");
0322 TTreeReaderArray<int> partPdg(tree_reader, "MCParticles.PDG");
0323
0324 // Get Reconstructed Track Information
0325 TTreeReaderArray<float> trackMomX(tree_reader, "ReconstructedChargedParticles.momentum.x");
0326 TTreeReaderArray<float> trackMomY(tree_reader, "ReconstructedChargedParticles.momentum.y");
0327 TTreeReaderArray<float> trackMomZ(tree_reader, "ReconstructedChargedParticles.momentum.z");
0328
0329 // Get Associations Between MCParticles and ReconstructedChargedParticles
0330 TTreeReaderArray<int> recoAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_rec.index");
0331 TTreeReaderArray<int> simuAssoc(tree_reader, "_ReconstructedChargedParticleAssociations_sim.index");
0332
0333 // Define Histograms
0334 TH1D *trackMomentumRes = new TH1D("trackMomentumRes","Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
0335 TH2D* trackMomResP = new TH2D("trackMomResP", "Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
0336 TH2D* trackMomResEta = new TH2D("trackMomResEta", "Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
0337
0338 TH1D *trackMomentumRes_e = new TH1D("trackMomentumRes_e","e^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
0339 TH2D* trackMomResP_e = new TH2D("trackMomResP_e", "e^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 25);
0340 TH2D* trackMomResEta_e = new TH2D("trackMomResEta_e", "e^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
0341
0342 TH1D *trackMomentumRes_mu = new TH1D("trackMomentumRes_mu","#mu^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
0343 TH2D* trackMomResP_mu = new TH2D("trackMomResP_mu", "#mu^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 25);
0344 TH2D* trackMomResEta_mu = new TH2D("trackMomResEta_mu", "#mu^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
0345
0346 TH1D *trackMomentumRes_pi = new TH1D("trackMomentumRes_pi","#pi^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
0347 TH2D* trackMomResP_pi = new TH2D("trackMomResP_pi", "#pi^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
0348 TH2D* trackMomResEta_pi = new TH2D("trackMomResEta_pi", "#pi^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
0349
0350 TH1D *trackMomentumRes_K = new TH1D("trackMomentumRes_K","K^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
0351 TH2D* trackMomResP_K = new TH2D("trackMomResP_K", "K^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
0352 TH2D* trackMomResEta_K = new TH2D("trackMomResEta_K", "K^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
0353
0354 TH1D *trackMomentumRes_p = new TH1D("trackMomentumRes_p","p Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
0355 TH2D* trackMomResP_p = new TH2D("trackMomResP_p", "p Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
0356 TH2D* trackMomResEta_p = new TH2D("trackMomResEta_p", "p Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
0357
0358 TH1D *matchedPartTrackDeltaEta = new TH1D("matchedPartTrackDeltaEta","#Delta#eta Between Matching Thrown and Reconstructed Charged Particle; #Delta#eta", 100, -0.25, 0.25);
0359 TH1D *matchedPartTrackDeltaPhi = new TH1D("matchedPartTrackDeltaPhi","#Detla #phi Between Matching Thrown and Reconstructed Charged Particle; #Delta#phi", 200, -0.2, 0.2);
0360 TH1D *matchedPartTrackDeltaR = new TH1D("matchedPartTrackDeltaR","#Delta R Between Matching Thrown and Reconstructed Charged Particle; #Delta R", 300, 0, 0.3);
0361 TH1D *matchedPartTrackDeltaMom = new TH1D("matchedPartTrackDeltaMom","#Delta P Between Matching Thrown and Reconstructed Charged Particle; #Delta P", 200, -10, 10);
0362
0363 while(tree_reader.Next()) { // Loop over events
0364
0365 for(unsigned int i=0; i<partGenStat.GetSize(); i++) // Loop over thrown particles
0366 {
0367 if(partGenStat[i] == 1) // Select stable thrown particles
0368 {
0369 int pdg = TMath::Abs(partPdg[i]);
0370
0371 if(pdg == 11 || pdg == 13 || pdg == 211 || pdg == 321 || pdg == 2212) // Look at charged particles (electrons, muons, pions, kaons, protons)
0372 {
0373 TVector3 trueMom(partMomX[i],partMomY[i],partMomZ[i]);
0374
0375 float trueEta = trueMom.PseudoRapidity();
0376 float truePhi = trueMom.Phi();
0377
0378 // Loop over associations to find matching ReconstructedChargedParticle
0379 for(unsigned int j=0; j<simuAssoc.GetSize(); j++)
0380 {
0381 if(simuAssoc[j] == i) // Find association index matching the index of the thrown particle we are looking at
0382 {
0383 TVector3 recMom(trackMomX[recoAssoc[j]],trackMomY[recoAssoc[j]],trackMomZ[recoAssoc[j]]); // recoAssoc[j] is the index of the matched ReconstructedChargedParticle
0384
0385 // Check the distance between the thrown and reconstructed particle
0386 float deltaEta = trueEta - recMom.PseudoRapidity();
0387 float deltaPhi = TVector2::Phi_mpi_pi(truePhi - recMom.Phi());
0388 float deltaR = TMath::Sqrt(deltaEta*deltaEta + deltaPhi*deltaPhi);
0389 float deltaMom = ((trueMom.Mag()) - (recMom.Mag()));
0390
0391 double momRes = (recMom.Mag() - trueMom.Mag())/trueMom.Mag();
0392
0393 trackMomentumRes->Fill(momRes); // Could also multiply by 100 and express as a percentage instead
0394 trackMomResP->Fill(momRes, trueMom.Mag());
0395 trackMomResEta->Fill(momRes, trueEta);
0396
0397 if( pdg == 11){
0398 trackMomentumRes_e->Fill(momRes);
0399 trackMomResP_e->Fill(momRes, trueMom.Mag());
0400 trackMomResEta_e->Fill(momRes, trueEta);
0401 }
0402 else if( pdg == 13){
0403 trackMomentumRes_mu->Fill(momRes);
0404 trackMomResP_mu->Fill(momRes, trueMom.Mag());
0405 trackMomResEta_mu->Fill(momRes, trueEta);
0406 }
0407 else if( pdg == 211){
0408 trackMomentumRes_pi->Fill(momRes);
0409 trackMomResP_pi->Fill(momRes, trueMom.Mag());
0410 trackMomResEta_pi->Fill(momRes, trueEta);
0411 }
0412 else if( pdg == 321){
0413 trackMomentumRes_K->Fill(momRes);
0414 trackMomResP_K->Fill(momRes, trueMom.Mag());
0415 trackMomResEta_K->Fill(momRes, trueEta);
0416 }
0417 else if( pdg == 2212){
0418 trackMomentumRes_p->Fill(momRes);
0419 trackMomResP_p->Fill(momRes, trueMom.Mag());
0420 trackMomResEta_p->Fill(momRes, trueEta);
0421 }
0422
0423 matchedPartTrackDeltaEta->Fill(deltaEta);
0424 matchedPartTrackDeltaPhi->Fill(deltaPhi);
0425 matchedPartTrackDeltaR->Fill(deltaR);
0426 matchedPartTrackDeltaMom->Fill(deltaMom);
0427
0428 }
0429 }// End loop over associations
0430 } // End PDG check
0431 } // End stable particles condition
0432 } // End loop over thrown particles
0433 } // End loop over events
0434
0435 ofile->Write(); // Write histograms to file
0436 ofile->Close(); // Close output file
0437 }
0438 ```
0439
0440 Insert your input file path and execute as the example code above.
0441
0442 ### Compiled ROOT Scripts
0443
0444 As brought up in the tutorial, you may wish to compile your ROOT based scripts for faster processing. Included below are some scripts and a short example of a compiled ROOT macro provided by Kolja Kauder.
0445
0446 Each file is uploaded individually, but your directory should be structured as follows -
0447
0448 - helloroot
0449 - README.md
0450 - CMakeLists.txt
0451 - include
0452 - helloroot
0453 - helloroot.hh
0454 - src
0455 - helloexec.cxx
0456 - helloroot.cxx
0457
0458 Note that any entry in the above without a file extension is a directory.
0459
0460 The contents of README.md are -
0461
0462 ```
0463 To build using cmake, create a build directory, navigate to it and run cmake. e.g.:
0464
0465 \```
0466 mkdir build
0467 cd build
0468 cmake ..
0469 make
0470 \```
0471 You can specify a number of parallel build threads with the -j flag, e.g.
0472 \```
0473 make -j4
0474 \```
0475
0476 You can specify an install directory to cmake with
0477 -DCMAKE_INSTALL_PREFIX=<path>
0478 then, after building,
0479 \```
0480 make install
0481 \```
0482 to install the headers and libraries under that location.
0483 There is no "make uninstall" but (on Unix-like systems)
0484 you can do
0485 xargs rm < install_manifest.txt
0486 from the cmake build directory.
0487 ```
0488
0489 Note that you should delete the \ characters in this block.
0490
0491 The contents of CMakeLists.txt are -
0492
0493 ```cmake
0494 # CMakeLists.txt for helloroot.
0495 # More complicated than needed but demonstrates making and linking your own libraries
0496 # cf. https://cliutils.gitlab.io/modern-cmake/
0497 # https://root.cern/manual/integrate_root_into_my_cmake_project/
0498
0499 cmake_minimum_required(VERSION 3.10)
0500 project(helloroot VERSION 1.0 LANGUAGES CXX ) # not needed
0501
0502 # Find ROOT. Use at least 6.20 for smoother cmake support
0503 find_package(ROOT 6.20 REQUIRED )
0504
0505 message ( " ROOT Libraries = " ${ROOT_LIBRARIES} )
0506
0507 ##############################################################################################################
0508
0509 # Main target is the libhelloroot library
0510 add_library(
0511 # You can use wildcards but it's cleaner to list the files explicitly
0512 helloroot
0513 SHARED
0514 src/helloroot.cxx
0515 )
0516 ## The particular syntax here is a bit annoying because you have to list all the sub-modules you need
0517 ## but it picks up automatically all the compile options needed for root, e.g. the c++ std version
0518 ## Find all available ROOT modules with `root-config --libs`
0519 target_link_libraries(helloroot PUBLIC ROOT::Core ROOT::RIO ROOT::Rint ROOT::Tree ROOT::EG ROOT::Physics )
0520
0521 ## The above _should_ be true, and it is on most systems. If it's not, uncoment one of the following lines
0522 # target_compile_features(helloroot PUBLIC cxx_std_17)
0523 # target_compile_features(helloroot PUBLIC cxx_std_20)
0524
0525 # include directories - this is also overkill but useful if you want to create dictionaries
0526 # Contact kkauder@gmail.com for that - it's too much for this example
0527 target_include_directories(helloroot
0528 PUBLIC
0529 $<INSTALL_INTERFACE:include>
0530 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
0531 )
0532
0533 # Can add addtional options here
0534 target_compile_options(helloroot PRIVATE -Wall -Wextra -pedantic -g)
0535
0536 ##############################################################################################################
0537
0538 ## Build executables
0539 add_executable(helloexec src/helloexec.cxx)
0540 # target_compile_options(helloexec PRIVATE -Wall -Wextra -pedantic -g)
0541 target_link_libraries(helloexec helloroot )
0542 target_include_directories(helloexec
0543 PRIVATE
0544 ${ROOT_INCLUDE_DIRS}
0545 )
0546
0547 install(TARGETS helloexec DESTINATION bin)
0548
0549 ##############################################################################################################
0550
0551 ## Install library
0552 # Could also use include(GNUInstallDirs)
0553 # and then destinations of the form ${CMAKE_INSTALL_INCLUDEDIR}
0554 install(TARGETS helloroot
0555 EXPORT helloroot-export
0556 LIBRARY DESTINATION lib
0557 ARCHIVE DESTINATION lib
0558 )
0559
0560 ## Install headers
0561 install (DIRECTORY ${CMAKE_SOURCE_DIR}/include/helloroot
0562 DESTINATION include/helloroot
0563 )
0564
0565 ## Generate configuration file - this allows you to use cmake in another project
0566 ## to find and link the installed helloroot library
0567 install(EXPORT helloroot-export
0568 FILE
0569 hellorootConfig.cmake
0570 NAMESPACE
0571 helloroot::
0572 DESTINATION
0573 cmake
0574 )
0575
0576 ## Final message
0577 message( " Done!")
0578 ```
0579
0580 The contents of helloroot.hh are -
0581
0582 ```c++
0583 #ifndef HELLO_ROOT_H
0584 #define HELLO_ROOT_H
0585
0586 void HelloRoot();
0587
0588 #endif // HELLO_ROOT_H
0589 ```
0590
0591 The contents of helloexec.cxx are -
0592
0593 ```c++
0594 #include<helloroot/helloroot.hh>
0595
0596 #include<iostream>
0597 #include<string>
0598
0599 int main()
0600 {
0601 std::cout << "Hello from main " << std::endl;
0602 HelloRoot();
0603
0604 return 0;
0605 }
0606 ```
0607
0608 And finally, the contents of helloroot.cxx are -
0609
0610 ```c++
0611 #include<helloroot/helloroot.hh>
0612
0613 #include<iostream>
0614 #include<string>
0615
0616 #include<TH1D.h>
0617 #include<TPad.h>
0618
0619 void HelloRoot()
0620 {
0621 std::cout << "Hello from HelloRoot" << std::endl;
0622
0623 // do something with root
0624 TH1D h("h", "h", 100, -5, 5);
0625 h.FillRandom("gaus", 1000);
0626 h.Draw();
0627 gPad->SaveAs("hello.png");
0628
0629 return;
0630 }
0631 ```
0632 Please consult the README and script comments for further instructions.
0633
0634 ## Python Uproot Scripts - Pythonic Versions
0635
0636 Some template scripts that utilise an python array based approach are included below. For some examples of using uproot to access information in .root files, please consult [this notebook](https://github.com/eic/HSF-India/blob/main/Working_With_Uproot/Working_With_Uproot_Standalone.ipynb) which can be run in Google Colab.
0637
0638 ### Pythonic_EfficiencyAnalysis.py
0639
0640 Create a file called `EfficiencyAnalysis.py` and copy in the code below to get started on the efficiency analysis exercise. Note that you will need to correctly specify your input file path in the variable `fname`. Note that some example code to process the division of two histograms is included as a commented section at the end of this example.
0641
0642 ```python
0643 #! /usr/bin/python
0644 # Import some relevant packages
0645 import uproot as up
0646 import awkward as ak
0647 import numpy as np
0648 import pandas as pd
0649 import matplotlib as mpl
0650 import matplotlib.ticker as ticker
0651 import matplotlib.cm as cm
0652 import matplotlib.pylab as plt
0653 import scipy, vector, os
0654 from XRootD import client
0655 from scipy import stats
0656 from matplotlib import pyplot as plt
0657 from matplotlib.gridspec import GridSpec
0658 from matplotlib import colors as colours
0659
0660 # Set some matplot lib features
0661 plt.rcParams['ytick.direction'] = 'in'
0662 plt.rcParams['xtick.direction'] = 'in'
0663 plt.rcParams['xaxis.labellocation'] = 'right'
0664 plt.rcParams['yaxis.labellocation'] = 'top'
0665 plt.rcParams["figure.figsize"] = (16,9)
0666 kP6 = ['#5790fc','#f89c20','#e42536','#964a8b','#9c9ca1','#7a21dd'] # Set ROOT kP6 colours - see https://root.cern.ch/doc/v636/classTColor.html
0667
0668 # Open our file
0669 fname = "INPUT_FILE.root"
0670 if os.path.isfile(fname):
0671 file=up.open(fname)
0672 else:
0673 print("Error opening file - ", fname, " check your fname variable!")
0674
0675 # Open the tree
0676 tree = file['events']
0677
0678 # Convert relevant branches to arrays
0679 MCPartBr = tree["MCParticles"].arrays()
0680 RecoAssocRec = tree['_ReconstructedChargedParticleAssociations_rec'].arrays()
0681 RecoAssocSim = tree['_ReconstructedChargedParticleAssociations_sim'].arrays()
0682 ReconChPartBr = tree["ReconstructedChargedParticles"].arrays()
0683
0684 RecID=RecoAssocRec['_ReconstructedChargedParticleAssociations_rec.index'] # Array of reconstructed IDs
0685 SimID=RecoAssocSim['_ReconstructedChargedParticleAssociations_sim.index'] # Array of simulated IDs
0686
0687 # Create some filters, anything with [SimID] or [RecID] will index the event by the associations. This means we will only retain events with a matching truth particle/matching reconstructed particle
0688 BoolMatch=(MCPartBr["MCParticles.PDG"][SimID])==(ReconChPartBr["ReconstructedChargedParticles.PDG"][RecID]) # Use simulated or reconstructed IDs as indices, this checks if the pdg between each array matches
0689 BoolChargeTrack = ((abs(MCPartBr["MCParticles.charge"])!=0) & (MCPartBr["MCParticles.generatorStatus"]==1))
0690 BoolChargeTrackMatch = ((abs(MCPartBr["MCParticles.charge"][SimID])!=0) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1))
0691 BoolElec=((abs(MCPartBr["MCParticles.PDG"])==11) & (MCPartBr["MCParticles.generatorStatus"]==1))
0692 BoolMuon=((abs(MCPartBr["MCParticles.PDG"])==13) & (MCPartBr["MCParticles.generatorStatus"]==1))
0693 BoolPion=((abs(MCPartBr["MCParticles.PDG"])==211) & (MCPartBr["MCParticles.generatorStatus"]==1)) # Use abs to include both positive and negative pions
0694 BoolKaon=((abs(MCPartBr["MCParticles.PDG"])==321) & (MCPartBr["MCParticles.generatorStatus"]==1)) # Use abs to include both positive and negative kaons
0695 BoolProton=((abs(MCPartBr["MCParticles.PDG"])==2212) & (MCPartBr["MCParticles.generatorStatus"]==1)) # Use abs to include both positive and negative protons
0696
0697 # Define some doubles
0698 ElecMass = 511*(10**-6) # Electron mass in GeV
0699
0700 # Convert some branches into arrays of vectors
0701 MC_Parts = vector.zip({'px': MCPartBr["MCParticles.momentum.x"], 'py': MCPartBr["MCParticles.momentum.y"], 'pz': MCPartBr["MCParticles.momentum.z"]})
0702 Rec_Parts = vector.zip({'px': ReconChPartBr["ReconstructedChargedParticles.momentum.x"], 'py': ReconChPartBr["ReconstructedChargedParticles.momentum.y"], 'pz': ReconChPartBr["ReconstructedChargedParticles.momentum.z"], 'E':ReconChPartBr["ReconstructedChargedParticles.energy"]})
0703
0704 # Determine the energy for a few MC particles of specific types
0705 MCEnerElec = np.sqrt(MC_Parts[BoolElec].p**2 + ElecMass**2)
0706
0707 # Calculate some additional quantities which are differences between true and reconstructed values for MC particles with a matched reconstructed track
0708 DeltaEta = MC_Parts[SimID][BoolChargeTrackMatch].eta - Rec_Parts[RecID][BoolChargeTrackMatch].eta
0709 DeltaPhi = MC_Parts[SimID][BoolChargeTrackMatch].phi - Rec_Parts[RecID][BoolChargeTrackMatch].phi
0710 DeltaR = np.sqrt(DeltaEta**2 + DeltaPhi**2)
0711
0712 # Plot some quantities as one image
0713 fig, axs = plt.subplots(2,2, tight_layout=True) # Ironically, this makes things *less* tight
0714 axs[-1, -1].axis('off') # Don't draw any blank subfigs
0715 axs[0,0].hist(ak.flatten(MC_Parts[BoolChargeTrack].eta), bins=100, range=(-5,5),alpha=0.5, color=kP6[1]) # Plot the MC eta values for all charged particles at an MC level
0716 axs[0,0].set_title(r"$\eta_{MC}$ of Charged Particles")
0717 axs[0,0].set(xlabel=r'$\eta_{MC}$', ylabel=r'# Entries / 0.1')
0718 axs[0,1].hist(ak.flatten(MC_Parts[SimID][BoolChargeTrackMatch].eta), bins=100, range=(-5,5),alpha=0.5, color=kP6[1]) # Plot the MC eta values for all charged particles at an MC level that have a matching reconstructed track
0719 axs[0,1].set_title(r"$\eta_{MC}$ of matched Charged Particles")
0720 axs[0,1].set(xlabel=r'$\eta_{MC}$', ylabel=r'# Entries / 0.1')
0721 axs[1,0].hist(ak.flatten(DeltaR), bins=5000, range=(0,5),alpha=0.5, color=kP6[1]) # Plot one of our calculated quantities
0722 axs[1,0].set_title(r"$\Delta R$ of Matched Charged Particles")
0723 axs[1,0].set(xlabel=r'$\Delta R$', ylabel=r'# Entries / 0.001')
0724 plt.savefig("EfficiencyAnalysis_Out.png", dpi = (160))
0725
0726 # Commented out, but to divide histograms we can do the following, just put the array we want to plot as the histo in place of Quantity
0727 #MCHist = np.histogram(ak.flatten(Quantity), bins=100, range=(0,25))
0728 #RecHist = np.histogram(ak.flatten(Quantity), bins=100, range=(0,25))
0729 #with np.errstate(divide='ignore'):
0730 # Division = RecHist[0] / MCHist[0]
0731 #Division = np.nan_to_num(Division,nan=0, posinf = 0)
0732 #Bin_Edges=MCHist[1]
0733 #Bars = 0.5 * (Bin_Edges[1:] + Bin_Edges[:-1])
0734 #BarWidth=Bars[1]-Bars[0]
0735 #plt.bar(Bars, Division, width=BarWidth, alpha=0.5, color='kP6[0]')
0736 ```
0737
0738
0739 "Complete" exercise example included below
0740
0741 ```python
0742 #! /usr/bin/python
0743 # Import some relevant packages
0744 import uproot as up
0745 import awkward as ak
0746 import numpy as np
0747 import pandas as pd
0748 import matplotlib as mpl
0749 import matplotlib.ticker as ticker
0750 import matplotlib.cm as cm
0751 import matplotlib.pylab as plt
0752 import scipy, vector, os
0753 from XRootD import client
0754 from scipy import stats
0755 from matplotlib import pyplot as plt
0756 from matplotlib.gridspec import GridSpec
0757 from matplotlib import colors as colours
0758
0759 # Set some matplot lib features
0760 plt.rcParams['ytick.direction'] = 'in'
0761 plt.rcParams['xtick.direction'] = 'in'
0762 plt.rcParams['xaxis.labellocation'] = 'right'
0763 plt.rcParams['yaxis.labellocation'] = 'top'
0764 plt.rcParams["figure.figsize"] = (16,9)
0765 kP6 = ['#5790fc','#f89c20','#e42536','#964a8b','#9c9ca1','#7a21dd'] # Set ROOT kP6 colours - see https://root.cern.ch/doc/v636/classTColor.html
0766
0767 # Open our file
0768 fname = "YOUR_INPUT_FILE"
0769 if os.path.isfile(fname):
0770 file=up.open(fname)
0771 else:
0772 print("Error opening file - ", fname, " check your fname variable!")
0773
0774 # Open the tree
0775 tree = file['events']
0776
0777 # Convert relevant branches to arrays
0778 MCPartBr = tree["MCParticles"].arrays()
0779 RecoAssocRec = tree['_ReconstructedChargedParticleAssociations_rec'].arrays()
0780 RecoAssocSim = tree['_ReconstructedChargedParticleAssociations_sim'].arrays()
0781 ReconChPartBr = tree["ReconstructedChargedParticles"].arrays()
0782
0783 RecID=RecoAssocRec['_ReconstructedChargedParticleAssociations_rec.index'] # Array of reconstructed IDs
0784 SimID=RecoAssocSim['_ReconstructedChargedParticleAssociations_sim.index'] # Array of simulated IDs
0785
0786 # Create some filters, anything with [SimID] or [RecID] will index the event by the associations. This means we will only retain events with a matching truth particle/matching reconstructed particle
0787 BoolMatch=(MCPartBr["MCParticles.PDG"][SimID])==(ReconChPartBr["ReconstructedChargedParticles.PDG"][RecID]) # Use simulated or reconstructed IDs as indices, this checks if the pdg between each array matches
0788 BoolChargeTrack = ((abs(MCPartBr["MCParticles.charge"])!=0) & (MCPartBr["MCParticles.generatorStatus"]==1))
0789 BoolChargeTrackMatch = ((abs(MCPartBr["MCParticles.charge"][SimID])!=0) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1))
0790 BoolElec=((abs(MCPartBr["MCParticles.PDG"])==11) & (MCPartBr["MCParticles.generatorStatus"]==1))
0791 BoolMuon=((abs(MCPartBr["MCParticles.PDG"])==13) & (MCPartBr["MCParticles.generatorStatus"]==1))
0792 BoolPion=((abs(MCPartBr["MCParticles.PDG"])==211) & (MCPartBr["MCParticles.generatorStatus"]==1)) # Use abs to include both positive and negative pions
0793 BoolKaon=((abs(MCPartBr["MCParticles.PDG"])==321) & (MCPartBr["MCParticles.generatorStatus"]==1)) # Use abs to include both positive and negative kaons
0794 BoolProton=((abs(MCPartBr["MCParticles.PDG"])==2212) & (MCPartBr["MCParticles.generatorStatus"]==1)) # Use abs to include both positive and negative protons
0795 BoolElecMatch=((MCPartBr["MCParticles.PDG"][SimID]==11) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1))
0796 BoolPionMatch=((abs(MCPartBr["MCParticles.PDG"][SimID])==211) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1)) # Use abs to include both positive and negative pions
0797
0798 MCStatus = MCPartBr['MCParticles.generatorStatus'] == 1
0799 MCNegCharge = MCPartBr['MCParticles.charge'] == -1
0800 MCScElecPDG = MCPartBr['MCParticles.PDG'] == 11
0801 MCPionPDG = abs(MCPartBr['MCParticles.PDG']) == 211
0802
0803 # We index by the simulation ID to ONLY select events with a matching track
0804
0805 # Define some doubles
0806 ElecMass = 511*(10**-6) # Electron mass in GeV
0807
0808 # Convert some branches into arrays of vectors
0809 MC_Parts = vector.zip({'px': MCPartBr["MCParticles.momentum.x"], 'py': MCPartBr["MCParticles.momentum.y"], 'pz': MCPartBr["MCParticles.momentum.z"]})
0810 Rec_Parts = vector.zip({'px': ReconChPartBr["ReconstructedChargedParticles.momentum.x"], 'py': ReconChPartBr["ReconstructedChargedParticles.momentum.y"], 'pz': ReconChPartBr["ReconstructedChargedParticles.momentum.z"], 'E':ReconChPartBr["ReconstructedChargedParticles.energy"]})
0811 Rec_Vects = vector.zip({'px': MCPartBr["MCParticles.momentum.x"][SimID], 'py': MCPartBr["MCParticles.momentum.y"][SimID], 'pz': MCPartBr["MCParticles.momentum.z"][SimID]})
0812 MC_ScElec = MC_Parts[MCStatus & MCNegCharge & MCScElecPDG]
0813 MC_Pions = MC_Parts[MCStatus & MCPionPDG]
0814 Rec_ScElec = Rec_Vects[BoolElecMatch]
0815 Rec_Pions = Rec_Vects[BoolPionMatch]
0816
0817 # Determine the energy for a few MC particles of specific types
0818 MCEnerElec = np.sqrt(MC_Parts[BoolElec].p**2 + ElecMass**2)
0819
0820 # Calculate some additional quantities which are differences between true and reconstructed values for MC particles with a matched reconstructed track
0821 DeltaEta = MC_Parts[SimID][BoolChargeTrackMatch].eta - Rec_Parts[RecID][BoolChargeTrackMatch].eta
0822 DeltaPhi = MC_Parts[SimID][BoolChargeTrackMatch].phi - Rec_Parts[RecID][BoolChargeTrackMatch].phi
0823 DeltaR = np.sqrt(DeltaEta**2 + DeltaPhi**2)
0824
0825 # Plot some quantities as one image
0826 fig, axs = plt.subplots(2,2, tight_layout=True) # Ironically, this makes things *less* tight
0827 axs[-1, -1].axis('off') # Don't draw any blank subfigs
0828 axs[0,0].hist(ak.flatten(MC_Parts[BoolChargeTrack].eta), bins=100, range=(-5,5),alpha=0.5, color=kP6[1]) # Plot the MC eta values for all charged particles at an MC level
0829 axs[0,0].set_title(r"$\eta_{MC}$ of Charged Particles")
0830 axs[0,0].set(xlabel=r'$\eta_{MC}$', ylabel=r'# Entries / 0.1')
0831 axs[0,1].hist(ak.flatten(MC_Parts[SimID][BoolChargeTrackMatch].eta), bins=100, range=(-5,5),alpha=0.5, color=kP6[1]) # Plot the MC eta values for all charged particles at an MC level that have a matching reconstructed track
0832 axs[0,1].set_title(r"$\eta_{MC}$ of matched Charged Particles")
0833 axs[0,1].set(xlabel=r'$\eta_{MC}$', ylabel=r'# Entries / 0.1')
0834 axs[1,0].hist(ak.flatten(DeltaR), bins=5000, range=(0,5),alpha=0.5, color=kP6[1]) # Plot one of our calculated quantities
0835 axs[1,0].set_title(r"$\Delta R$ of Matched Charged Particles")
0836 axs[1,0].set(xlabel=r'$\Delta R$', ylabel=r'# Entries / 0.001')
0837 plt.savefig("EfficiencyAnalysis_Out.png", dpi = (160))
0838
0839 bins_eta=100
0840 range_eta=(-5,5)
0841 bins_p=100
0842 range_p_elec=(0,25)
0843 range_p_pi=(0,50)
0844
0845 # Make histograms of our scattered electrons and pions - Full truth distributions in P and eta
0846 MC_ScElec_eta = np.histogram(ak.flatten(MC_ScElec.eta), bins = bins_eta, range= range_eta)
0847 MC_Pions_eta = np.histogram(ak.flatten(MC_Pions.eta), bins = bins_eta, range= range_eta)
0848 MC_ScElec_P = np.histogram(ak.flatten(MC_ScElec.p), bins = bins_p, range= range_p_elec)
0849 MC_Pions_P = np.histogram(ak.flatten(MC_Pions.p), bins = bins_p, range= range_p_pi)
0850 # Make histograms of our scattered electrons and pions - Truth distributions for particles that reconstructed in P and eta
0851 Rec_ScElec_eta = np.histogram(ak.flatten(Rec_ScElec.eta), bins = bins_eta, range= range_eta)
0852 Rec_Pions_eta = np.histogram(ak.flatten(Rec_Pions.eta), bins = bins_eta, range= range_eta)
0853 Rec_ScElec_P = np.histogram(ak.flatten(Rec_ScElec.p), bins = bins_p, range= range_p_elec)
0854 Rec_Pions_P = np.histogram(ak.flatten(Rec_Pions.p), bins = bins_p, range= range_p_pi)
0855 # Divide to get efficiency
0856 with np.errstate(divide='ignore'):
0857 Eff_ScElec_eta = Rec_ScElec_eta[0]/MC_ScElec_eta[0]
0858 Eff_Pion_eta = Rec_Pions_eta[0]/MC_Pions_eta[0]
0859 Eff_ScElec_P = Rec_ScElec_P[0]/MC_ScElec_P[0]
0860 Eff_Pion_P = Rec_Pions_P[0]/MC_Pions_P[0]
0861
0862 Eff_ScElec_eta=np.nan_to_num(Eff_ScElec_eta,nan=0,posinf=0)
0863 Eff_Pion_eta=np.nan_to_num(Eff_Pion_eta,nan=0,posinf=0)
0864 Eff_ScElec_P=np.nan_to_num(Eff_ScElec_P,nan=0,posinf=0)
0865 Eff_Pion_P=np.nan_to_num(Eff_Pion_P,nan=0,posinf=0)
0866
0867 fig, axs = plt.subplots(2,2, tight_layout=True) # Ironically, this makes things *less* tight
0868 #axs[-1, -1].axis('off') # Don't draw any blank subfigs
0869 Bin_Edges=Rec_ScElec_eta[1]
0870 Bars=0.5 * (Bin_Edges[1:] + Bin_Edges[:-1])
0871 BarWidth=Bars[1]-Bars[0]
0872 axs[0,0].bar(Bars, Eff_ScElec_eta, width=BarWidth, alpha=0.75, color=kP6[1]) # Plot the MC eta values for all charged particles at an MC level
0873 axs[0,0].set_title(r"Reconstructed $e'$ Efficiency as fn of $\eta$")
0874 axs[0,0].set(xlabel=r'"$\eta$', ylabel=r"$e'$ Effiency")
0875 Bin_Edges=Rec_ScElec_P[1]
0876 Bars=0.5 * (Bin_Edges[1:] + Bin_Edges[:-1])
0877 BarWidth=Bars[1]-Bars[0]
0878 axs[0,1].bar(Bars, Eff_ScElec_P, width=BarWidth, alpha=0.75, color=kP6[1]) # Plot the MC eta values for all charged particles at an MC level that have a matching reconstructed track
0879 axs[0,1].set_title(r"Reconstructed $e'$ Efficiency as fn of $P$")
0880 axs[0,1].set(xlabel=r"$P_{e'}$", ylabel=r"$P_{e'}$")
0881 Bin_Edges=Rec_Pions_eta[1]
0882 Bars=0.5 * (Bin_Edges[1:] + Bin_Edges[:-1])
0883 BarWidth=Bars[1]-Bars[0]
0884 axs[1,0].bar(Bars, Eff_Pion_eta, width=BarWidth, alpha=0.75, color=kP6[1]) # Plot one of our calculated quantities
0885 axs[1,0].set_title(r"Reconstructed $\pi$ Efficiency as fn of $\eta$")
0886 axs[1,0].set(xlabel=r"$\eta$", ylabel=r"$\pi$ Effiency")
0887 Bin_Edges=Rec_Pions_P[1]
0888 Bars=0.5 * (Bin_Edges[1:] + Bin_Edges[:-1])
0889 BarWidth=Bars[1]-Bars[0]
0890 axs[1,1].bar(Bars, Eff_Pion_P, width=BarWidth, alpha=0.75, color=kP6[1]) # Plot one of our calculated quantities
0891 axs[1,1].set_title(r"Reconstructed $\pi$ Efficiency as fn of $P$")
0892 axs[1,1].set(xlabel=r"$P_{\pi}$", ylabel=r"$\pi$ Effiency")
0893 plt.savefig("EfficiencyAnalysis_Exercise_Out.png", dpi = (160))
0894 ```
0895
0896 ### Pythonic_ResolutionAnalysis.py
0897
0898 Create a file called `ResolutionAnalysis.py` and copy in the code below to get started on the efficiency analysis exercise. Note that you will need to correctly specify your input file path in the variable `fname`.
0899
0900 ```python
0901 #! /usr/bin/python
0902 # Import some relevant packages
0903 import uproot as up
0904 import awkward as ak
0905 import numpy as np
0906 import pandas as pd
0907 import matplotlib as mpl
0908 import matplotlib.ticker as ticker
0909 import matplotlib.cm as cm
0910 import matplotlib.pylab as plt
0911 import scipy, vector, os
0912 from XRootD import client
0913 from scipy import stats
0914 from matplotlib import pyplot as plt
0915 from matplotlib.gridspec import GridSpec
0916 from matplotlib import colors as colours
0917
0918 # Set some matplot lib features
0919 plt.rcParams['ytick.direction'] = 'in'
0920 plt.rcParams['xtick.direction'] = 'in'
0921 plt.rcParams['xaxis.labellocation'] = 'right'
0922 plt.rcParams['yaxis.labellocation'] = 'top'
0923 plt.rcParams["figure.figsize"] = (16,9)
0924 kP6 = ['#5790fc','#f89c20','#e42536','#964a8b','#9c9ca1','#7a21dd'] # Set ROOT kP6 colours - see https://root.cern.ch/doc/v636/classTColor.html
0925
0926 # Open our file
0927 fname = "INPUT_FILE.root"
0928 if os.path.isfile(fname):
0929 file=up.open(fname)
0930 else:
0931 print("Error opening file - ", fname, " check your fname variable!")
0932
0933 # Open the tree
0934 tree = file['events']
0935
0936 # Convert relevant branches to arrays
0937 MCPartBr = tree["MCParticles"].arrays()
0938 RecoAssocRec = tree['_ReconstructedChargedParticleAssociations_rec'].arrays()
0939 RecoAssocSim = tree['_ReconstructedChargedParticleAssociations_sim'].arrays()
0940 ReconChPartBr = tree["ReconstructedChargedParticles"].arrays()
0941
0942 RecID=RecoAssocRec['_ReconstructedChargedParticleAssociations_rec.index'] # Array of reconstructed IDs
0943 SimID=RecoAssocSim['_ReconstructedChargedParticleAssociations_sim.index'] # Array of simulated IDs
0944
0945 # Create some filters, anything with [SimID] or [RecID] will index the event by the associations. This means we will only retain events with a matching truth particle/matching reconstructed particle
0946 BoolChargeTrack = ((abs(MCPartBr["MCParticles.charge"])!=0) & (MCPartBr["MCParticles.generatorStatus"]==1))
0947 BoolChargeTrackMatch = ((abs(MCPartBr["MCParticles.charge"][SimID])!=0) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1))
0948 BoolElec=((abs(MCPartBr["MCParticles.PDG"])==11) & (MCPartBr["MCParticles.generatorStatus"]==1))
0949
0950 # Define some doubles
0951 ElecMass = 511*(10**-6) # Electron mass in GeV
0952
0953 # Convert some branches into arrays of vectors
0954 MC_Parts = vector.zip({'px': MCPartBr["MCParticles.momentum.x"], 'py': MCPartBr["MCParticles.momentum.y"], 'pz': MCPartBr["MCParticles.momentum.z"]})
0955 Rec_Parts = vector.zip({'px': ReconChPartBr["ReconstructedChargedParticles.momentum.x"], 'py': ReconChPartBr["ReconstructedChargedParticles.momentum.y"], 'pz': ReconChPartBr["ReconstructedChargedParticles.momentum.z"], 'E':ReconChPartBr["ReconstructedChargedParticles.energy"]})
0956
0957 # Determine the energy for a few MC particles of specific types
0958 MCEnerElec = np.sqrt(MC_Parts[BoolElec].p**2 + ElecMass**2)
0959
0960 # Calculate some additional quantities which are differences between true and reconstructed values for MC particles with a matched reconstructed track
0961 DeltaEta = MC_Parts[SimID][BoolChargeTrackMatch].eta - Rec_Parts[RecID][BoolChargeTrackMatch].eta
0962 DeltaPhi = MC_Parts[SimID][BoolChargeTrackMatch].phi - Rec_Parts[RecID][BoolChargeTrackMatch].phi
0963 DeltaP = MC_Parts[SimID][BoolChargeTrackMatch].p - Rec_Parts[RecID][BoolChargeTrackMatch].p
0964 DeltaR = np.sqrt(DeltaEta**2 + DeltaPhi**2)
0965 ResP = ((Rec_Parts[RecID][BoolChargeTrackMatch].p - MC_Parts[SimID][BoolChargeTrackMatch].p)/MC_Parts[SimID][BoolChargeTrackMatch].p ) # Momentum resolution as a percentage
0966 # Plot some quantities as one image
0967 fig, axs = plt.subplots(2,3, tight_layout=True) # Ironically, this makes things *less* tight
0968 axs[-1, -1].axis('off') # Don't draw any blank subfigs
0969 axs[0,0].hist(ak.flatten(DeltaEta), bins=100, range=(-0.25,0.25),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed eta
0970 axs[0,0].set_title(r"$\Delta \eta$ of Matched Charged Particles")
0971 axs[0,0].set(xlabel=r'$\Delta \eta$', ylabel=r'# Entries / 0.005')
0972 axs[0,1].hist(ak.flatten(DeltaPhi), bins=200, range=(-0.2,0.2),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed phi
0973 axs[0,1].set_title(r"$\Delta \phi$ of Matched Charged Particles")
0974 axs[0,1].set(xlabel=r'$\Delta \phi$', ylabel='# Entries / 0.002')
0975 axs[0,2].hist(ak.flatten(DeltaR), bins=300, range=(0,0.3),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed R
0976 axs[0,2].set_title(r"$\Delta R$ of Matched Charged Particles")
0977 axs[0,2].set(xlabel=r'$\Delta R$', ylabel=r'# Entries / 0.003')
0978 axs[1,0].hist(ak.flatten(DeltaP), bins=200, range=(-10,10),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed momentum
0979 axs[1,0].set_title(r"$\Delta P$ of Matched Charged Particles")
0980 axs[1,0].set(xlabel=r'$\Delta \eta$', ylabel=r'# Entries / 0.1 GeV/c')
0981 axs[1,1].hist(ak.flatten(ResP), bins=400, range=(-2,2),alpha=0.5, color=kP6[1]) # Plot the momentum resolution
0982 axs[1,1].set_title(r"Momentum Resolution of Matched Charged Particles")
0983 axs[1,1].set(xlabel=r'$(P_{Rec} - P_{MC})/P_{MC}$', ylabel=r'# Entries / 0.01')
0984 plt.savefig("ResolutionAnalysis_Out.png", dpi = (160))
0985 ```
0986
0987
0988 "Complete" exercise example included below
0989
0990 ```python
0991
0992 #! /usr/bin/python
0993 # Import some relevant packages
0994 import uproot as up
0995 import awkward as ak
0996 import numpy as np
0997 import pandas as pd
0998 import matplotlib as mpl
0999 import matplotlib.ticker as ticker
1000 import matplotlib.cm as cm
1001 import matplotlib.pylab as plt
1002 import scipy, vector, os
1003 from XRootD import client
1004 from scipy import stats
1005 from matplotlib import pyplot as plt
1006 from matplotlib.gridspec import GridSpec
1007 from matplotlib import colors as colours
1008
1009 # Set some matplot lib features
1010 plt.rcParams['ytick.direction'] = 'in'
1011 plt.rcParams['xtick.direction'] = 'in'
1012 plt.rcParams['xaxis.labellocation'] = 'right'
1013 plt.rcParams['yaxis.labellocation'] = 'top'
1014 plt.rcParams["figure.figsize"] = (16,9)
1015 kP6 = ['#5790fc','#f89c20','#e42536','#964a8b','#9c9ca1','#7a21dd'] # Set ROOT kP6 colours - see https://root.cern.ch/doc/v636/classTColor.html
1016
1017 # Open our file
1018 fname = "YOUR_INPUT_FILE"
1019 if os.path.isfile(fname):
1020 file=up.open(fname)
1021 else:
1022 print("Error opening file - ", fname, " check your fname variable!")
1023
1024 # Open the tree
1025 tree = file['events']
1026
1027 # Convert relevant branches to arrays
1028 MCPartBr = tree["MCParticles"].arrays()
1029 RecoAssocRec = tree['_ReconstructedChargedParticleAssociations_rec'].arrays()
1030 RecoAssocSim = tree['_ReconstructedChargedParticleAssociations_sim'].arrays()
1031 ReconChPartBr = tree["ReconstructedChargedParticles"].arrays()
1032
1033 RecID=RecoAssocRec['_ReconstructedChargedParticleAssociations_rec.index'] # Array of reconstructed IDs
1034 SimID=RecoAssocSim['_ReconstructedChargedParticleAssociations_sim.index'] # Array of simulated IDs
1035
1036 # Create some filters, anything with [SimID] or [RecID] will index the event by the associations. This means we will only retain events with a matching truth particle/matching reconstructed particle
1037 MCStatus = MCPartBr['MCParticles.generatorStatus'] == 1
1038 MCNegCharge = MCPartBr['MCParticles.charge'] == -1
1039 MCScElecPDG = MCPartBr['MCParticles.PDG'] == 11
1040 MCPionPDG = abs(MCPartBr['MCParticles.PDG']) == 211
1041 BoolChargeTrack = ((abs(MCPartBr["MCParticles.charge"])!=0) & (MCPartBr["MCParticles.generatorStatus"]==1))
1042 BoolChargeTrackMatch = ((abs(MCPartBr["MCParticles.charge"][SimID])!=0) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1))
1043 BoolElec=((abs(MCPartBr["MCParticles.PDG"])==11) & (MCPartBr["MCParticles.generatorStatus"]==1))
1044 BoolElecMatch=((MCPartBr["MCParticles.PDG"][SimID]==11) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1))
1045 BoolPionMatch=((abs(MCPartBr["MCParticles.PDG"][SimID])==211) & (MCPartBr["MCParticles.generatorStatus"][SimID]==1)) # Use abs to include both positive and negative pions
1046
1047 # Define some doubles
1048 ElecMass = 511*(10**-6) # Electron mass in GeV
1049
1050 # Convert some branches into arrays of vectors
1051 MC_Parts = vector.zip({'px': MCPartBr["MCParticles.momentum.x"], 'py': MCPartBr["MCParticles.momentum.y"], 'pz': MCPartBr["MCParticles.momentum.z"]})
1052 Rec_Parts = vector.zip({'px': ReconChPartBr["ReconstructedChargedParticles.momentum.x"], 'py': ReconChPartBr["ReconstructedChargedParticles.momentum.y"], 'pz': ReconChPartBr["ReconstructedChargedParticles.momentum.z"], 'E':ReconChPartBr["ReconstructedChargedParticles.energy"]})
1053
1054 # We redfine MC vects here to ONLY be the MC particles that have a reconstructed track
1055 MC_Vects = vector.zip({'px': MCPartBr["MCParticles.momentum.x"][SimID], 'py': MCPartBr["MCParticles.momentum.y"][SimID], 'pz': MCPartBr["MCParticles.momentum.z"][SimID]})
1056 MC_ScElec = MC_Vects[BoolElecMatch]
1057 MC_Pions = MC_Vects[BoolPionMatch]
1058 # In this case, we need to access our reconstructed charged particles branch and index it by the reconstructed ID.
1059 RecVects = vector.zip({'px': ReconChPartBr["ReconstructedChargedParticles.momentum.x"][RecID], 'py': ReconChPartBr["ReconstructedChargedParticles.momentum.y"][RecID], 'pz': ReconChPartBr["ReconstructedChargedParticles.momentum.z"][RecID]})
1060
1061 Rec_ScElec = RecVects[BoolElecMatch]
1062 Rec_Pions = RecVects[BoolPionMatch]
1063 ElecMomRes = ((Rec_ScElec.p - MC_ScElec.p)/MC_ScElec.p)*100
1064 ElecEtaRes = ((Rec_ScElec.eta - MC_ScElec.eta)/MC_ScElec.eta)*100
1065 PiMomRes = ((Rec_Pions.p - MC_Pions.p)/MC_Pions.p)*100
1066 PiEtaRes = ((Rec_Pions.eta - MC_Pions.eta)/MC_Pions.eta)*100
1067
1068 # Determine the energy for a few MC particles of specific types
1069 MCEnerElec = np.sqrt(MC_Parts[BoolElec].p**2 + ElecMass**2)
1070
1071 # Calculate some additional quantities which are differences between true and reconstructed values for MC particles with a matched reconstructed track
1072 DeltaEta = MC_Parts[SimID][BoolChargeTrackMatch].eta - Rec_Parts[RecID][BoolChargeTrackMatch].eta
1073 DeltaPhi = MC_Parts[SimID][BoolChargeTrackMatch].phi - Rec_Parts[RecID][BoolChargeTrackMatch].phi
1074 DeltaP = MC_Parts[SimID][BoolChargeTrackMatch].p - Rec_Parts[RecID][BoolChargeTrackMatch].p
1075 DeltaR = np.sqrt(DeltaEta**2 + DeltaPhi**2)
1076 ResP = ((Rec_Parts[RecID][BoolChargeTrackMatch].p - MC_Parts[SimID][BoolChargeTrackMatch].p)/MC_Parts[SimID][BoolChargeTrackMatch].p ) # Momentum resolution as a percentage
1077 # Plot some quantities as one image
1078 fig, axs = plt.subplots(2,3, tight_layout=True) # Ironically, this makes things *less* tight
1079 axs[-1, -1].axis('off') # Don't draw any blank subfigs
1080 axs[0,0].hist(ak.flatten(DeltaEta), bins=100, range=(-0.25,0.25),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed eta
1081 axs[0,0].set_title(r"$\Delta \eta$ of Matched Charged Particles")
1082 axs[0,0].set(xlabel=r'$\Delta \eta$', ylabel=r'# Entries / 0.005')
1083 axs[0,1].hist(ak.flatten(DeltaPhi), bins=200, range=(-0.2,0.2),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed phi
1084 axs[0,1].set_title(r"$\Delta \phi$ of Matched Charged Particles")
1085 axs[0,1].set(xlabel=r'$\Delta \phi$', ylabel='# Entries / 0.002')
1086 axs[0,2].hist(ak.flatten(DeltaR), bins=300, range=(0,0.3),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed R
1087 axs[0,2].set_title(r"$\Delta R$ of Matched Charged Particles")
1088 axs[0,2].set(xlabel=r'$\Delta R$', ylabel=r'# Entries / 0.003')
1089 axs[1,0].hist(ak.flatten(DeltaP), bins=200, range=(-10,10),alpha=0.5, color=kP6[1]) # Plot the difference between true and reconstructed momentum
1090 axs[1,0].set_title(r"$\Delta P$ of Matched Charged Particles")
1091 axs[1,0].set(xlabel=r'$\Delta \eta$', ylabel=r'# Entries / 0.1 GeV/c')
1092 axs[1,1].hist(ak.flatten(ResP), bins=400, range=(-2,2),alpha=0.5, color=kP6[1]) # Plot the momentum resolution
1093 axs[1,1].set_title(r"Momentum Resolution of Matched Charged Particles")
1094 axs[1,1].set(xlabel=r'$(P_{Rec} - P_{MC})/P_{MC}$', ylabel=r'# Entries / 0.01')
1095 plt.savefig("ResolutionAnalysis_Out.png", dpi = (160))
1096
1097 fig, axs = plt.subplots(2,3, tight_layout=True) # Ironically, this makes things *less* tight
1098 axs[0,0].hist(ak.flatten(ElecMomRes), bins=50, range=(-25,25),alpha=0.5, color=kP6[1], label=r"$e'$")
1099 axs[0,0].hist(ak.flatten(PiMomRes), bins=50, range=(-25,25),alpha=0.25, color=kP6[0], label=r"$\pi$")
1100 axs[0,0].set_title(r"Reconstructed $P$ Resolution")
1101 axs[0,0].set(xlabel=r"$P$ Resolution [%]", ylabel=r'# Entries')
1102 axs[0,0].legend(loc='upper right')
1103 axs[0,1].hist(ak.flatten(ElecEtaRes), bins=100, range=(-2,2),alpha=0.5, color=kP6[1], label=r"$e'$")
1104 axs[0,1].hist(ak.flatten(PiEtaRes), bins=100, range=(-2,2),alpha=0.25, color=kP6[0], label=r"$\pi$")
1105 axs[0,1].set_title(r"Reconstructed $\eta$ Resolution")
1106 axs[0,1].set(xlabel=r"$\eta$ Resolution [%]", ylabel=r'# Entries')
1107 axs[0,1].legend(loc='upper right')
1108 # 2D plots
1109 Hist2D1 = axs[0,2].hist2d(np.asarray(ak.flatten(ElecMomRes)), np.asarray(ak.flatten(MC_ScElec.p)), bins=[50,50], range=[[-25,25],[0,20]], cmin=1)
1110 axs[0,2].set_title(r"Reconstructed $P_{e'}$ Resolution as a function of $P_{e'MC}$")
1111 axs[0,2].set(xlabel=r"$P_{e'}$ Resolution [%]", ylabel=r"$P_{e'MC}$")
1112 cb1=plt.colorbar(Hist2D1[3],ax=axs[0,2]) # [3] is the z axis info
1113 cb1.set_label('Counts/bin')
1114 Hist2D2=axs[1,0].hist2d(np.asarray(ak.flatten(PiMomRes)), np.asarray(ak.flatten(MC_Pions.p)), bins=[50,50], range=[[-25,25],[0,20]], cmin=1)
1115 axs[1,0].set_title(r"Reconstructed $P_{\pi}$ Resolution as a function of $P_{\pi MC}$")
1116 axs[1,0].set(xlabel=r"$P_{\pi}$ Resolution [%]", ylabel=r"$P_{\pi'MC}$")
1117 cb2=plt.colorbar(Hist2D2[3],ax=axs[1,0]) # [3] is the z axis info
1118 cb2.set_label('Counts/bin')
1119 Hist2D3=axs[1,1].hist2d(np.asarray(ak.flatten(ElecEtaRes)), np.asarray(ak.flatten(MC_ScElec.eta)), bins=[100,100], range=[[-2,2],[-5,5]], cmin=1)
1120 axs[1,1].set_title(r"Reconstructed $\eta_{e'}$ Resolution as a function of $\eta_{e'MC}$")
1121 axs[1,1].set(xlabel=r"$\eta_{e'}$ Resolution [%]", ylabel=r"$\eta_{e'MC}$")
1122 cb3=plt.colorbar(Hist2D3[3],ax=axs[1,1]) # [3] is the z axis info
1123 cb3.set_label('Counts/bin')
1124 Hist2D4=axs[1,2].hist2d(np.asarray(ak.flatten(PiEtaRes)), np.asarray(ak.flatten(MC_Pions.eta)), bins=[100,100], range=[[-2,2],[-5,5]], cmin=1)
1125 axs[1,2].set_title(r"Reconstructed $\eta_{e'}$ Resolution as a function of $\eta_{e'MC}$")
1126 axs[1,2].set(xlabel=r"$\eta_{\pi}$ Resolution [%]", ylabel=r"$\eta_{\pi MC}$")
1127 cb4=plt.colorbar(Hist2D4[3],ax=axs[1,2]) # [3] is the z axis info
1128 cb4.set_label('Counts/bin')
1129 plt.savefig("ResolutionAnalysis_Exercise_Out.png", dpi = (160))
1130 ```
1131
1132 ## Python Uproot Script - C/ROOT Style (Slow, not recommended!)
1133
1134 ### EfficiencyAnalysis.py
1135
1136 Create a file called `EfficiencyAnalysis.py` and copy in the code below to get started on the efficiency analysis exercise. Note that you will need to correctly specify your input file path in the variable `infile`.
1137
1138 ```python
1139 #! /usr/bin/python
1140
1141 #Import relevant packages
1142 import ROOT, math, array
1143 from ROOT import TH1F, TH2F, TMath, TTree, TVector3, TVector2
1144 import uproot as up
1145
1146 #Define and open files
1147 infile="PATH_TO_INPUT_FILE"
1148 ofile=ROOT.TFile.Open("EfficiencyAnalysis_OutPy.root", "RECREATE")
1149
1150 # Open input file and define branches we want to look at with uproot
1151 events_tree = up.open(infile)["events"]
1152
1153 # Get particle information
1154 partGenStat = events_tree["MCParticles.generatorStatus"].array()
1155 partMomX = events_tree["MCParticles.momentum.x"].array()
1156 partMomY = events_tree["MCParticles.momentum.y"].array()
1157 partMomZ = events_tree["MCParticles.momentum.z"].array()
1158 partPdg = events_tree["MCParticles.PDG"].array()
1159
1160 # Get reconstructed track information
1161 trackMomX = events_tree["ReconstructedChargedParticles.momentum.x"].array()
1162 trackMomY = events_tree["ReconstructedChargedParticles.momentum.y"].array()
1163 trackMomZ = events_tree["ReconstructedChargedParticles.momentum.z"].array()
1164
1165 # Get assocations between MCParticles and ReconstructedChargedParticles
1166 recoAssoc = events_tree["_ReconstructedChargedParticleAssociations_rec.index"].array()
1167 simuAssoc = events_tree["_ReconstructedChargedParticleAssociations_sim.index"].array()
1168
1169 # Define histograms below
1170 partEta = ROOT.TH1D("partEta","Eta of Thrown Charged Particles;Eta",100, -5 ,5 )
1171 matchedPartEta = ROOT.TH1D("matchedPartEta","Eta of Thrown Charged Particles That Have Matching Track", 100, -5 ,5);
1172 matchedPartTrackDeltaR = ROOT.TH1D("matchedPartTrackDeltaR","Delta R Between Matching Thrown and Reconstructed Charge Particle", 5000, 0, 5);
1173
1174 # Add main analysis loop(s) below
1175 for i in range(0, len(partGenStat)): # Loop over all events
1176 for j in range(0, len(partGenStat[i])): # Loop over all thrown particles
1177 if partGenStat[i][j] == 1: # Select stable particles
1178 pdg = abs(partPdg[i][j]) # Get PDG for each stable particle
1179 if(pdg == 11 or pdg == 13 or pdg == 211 or pdg == 321 or pdg == 2212):
1180 trueMom = ROOT.TVector3(partMomX[i][j], partMomY[i][j], partMomZ[i][j])
1181 trueEta = trueMom.PseudoRapidity()
1182 truePhi = trueMom.Phi()
1183 partEta.Fill(trueEta)
1184 for k in range(0,len(simuAssoc[i])): # Loop over associations to find matching ReconstructedChargedParticle
1185 if (simuAssoc[i][k] == j):
1186 recMom = ROOT.TVector3(trackMomX[i][recoAssoc[i][k]], trackMomY[i][recoAssoc[i][k]], trackMomZ[i][recoAssoc[i][k]])
1187 deltaEta = trueEta - recMom.PseudoRapidity()
1188 deltaPhi = TVector2. Phi_mpi_pi(truePhi - recMom.Phi())
1189 deltaR = math.sqrt((deltaEta*deltaEta) + (deltaPhi*deltaPhi))
1190 matchedPartEta.Fill(trueEta)
1191 matchedPartTrackDeltaR.Fill(deltaR)
1192
1193 # Write output histograms to file below
1194 partEta.Write()
1195 matchedPartEta.Write()
1196 matchedPartTrackDeltaR.Write()
1197
1198 # Close files
1199 ofile.Close()
1200 ```
1201
1202 A "solution" version of the script for the exercise is included below -
1203
1204 ```python
1205 #! /usr/bin/python
1206
1207 #Import relevant packages
1208 import ROOT, math, array
1209 from ROOT import TCanvas, TColor, TGaxis, TH1F, TH2F, TPad, TStyle, gStyle, gPad, TGaxis, TLine, TMath, TPaveText, TTree, TVector3, TVector2
1210 import uproot as up
1211
1212 #Define and open files
1213 infile="PATH_TO_FILE"
1214 ofile=ROOT.TFile.Open("EfficiencyAnalysis_Exercise_OutPy.root", "RECREATE")
1215
1216 # Open input file and define branches we want to look at with uproot
1217 events_tree = up.open(infile)["events"]
1218
1219 # Get particle information
1220 partGenStat = events_tree["MCParticles.generatorStatus"].array()
1221 partMomX = events_tree["MCParticles.momentum.x"].array()
1222 partMomY = events_tree["MCParticles.momentum.y"].array()
1223 partMomZ = events_tree["MCParticles.momentum.z"].array()
1224 partPdg = events_tree["MCParticles.PDG"].array()
1225
1226 # Get reconstructed track information
1227 trackMomX = events_tree["ReconstructedChargedParticles.momentum.x"].array()
1228 trackMomY = events_tree["ReconstructedChargedParticles.momentum.y"].array()
1229 trackMomZ = events_tree["ReconstructedChargedParticles.momentum.z"].array()
1230
1231 # Get assocations between MCParticles and ReconstructedChargedParticles
1232 recoAssoc = events_tree["_ReconstructedChargedParticleAssociations_rec.index"].array()
1233 simuAssoc = events_tree["_ReconstructedChargedParticleAssociations_sim.index"].array()
1234
1235 # Define histograms below
1236 partEta = ROOT.TH1D("partEta","#eta of Thrown Charged Particles; #eta", 120, -6, 6)
1237 matchedPartEta = ROOT.TH1D("matchedPartEta","#eta of Thrown Charged Particles That Have Matching Track; #eta", 120, -6, 6)
1238 partMom = ROOT.TH1D("partMom", "Momentum of Thrown Charged Particles (truth); P(GeV/c)", 150, 0, 150)
1239 matchedPartMom = ROOT.TH1D("matchedPartMom", "Momentum of Thrown Charged Particles (truth), with matching track; P(GeV/c)", 150, 0, 150)
1240 partPhi = ROOT.TH1D("partPhi", "#phi of Thrown Charged Particles (truth); #phi(rad)", 320, -3.2, 3.2)
1241 matchedPartPhi = ROOT.TH1D("matchedPartPhi", "#phi of Thrown Charged Particles (truth), with matching track; #phi(rad)", 320, -3.2, 3.2)
1242
1243 partPEta = ROOT.TH2D("partPEta", "P vs #eta of Thrown Charged Particles; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6)
1244 matchedPartPEta = ROOT.TH2D("matchedPartPEta", "P vs #eta of Thrown Charged Particles, with matching track; P(GeV/C); #eta", 150, 0, 150, 120, -6, 6)
1245 partPhiEta = ROOT.TH2D("partPhiEta", "#phi vs #eta of Thrown Charged Particles; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6)
1246 matchedPartPhiEta = ROOT.TH2D("matchedPartPhiEta", "#phi vs #eta of Thrown Charged Particles; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6)
1247
1248 matchedPartTrackDeltaEta = ROOT.TH1D("matchedPartTrackDeltaEta","#Delta#eta Between Matching Thrown and Reconstructe Charged Particle; #Delta#eta", 100, -0.25, 0.25)
1249 matchedPartTrackDeltaPhi = ROOT.TH1D("matchedPartTrackDeltaPhi","#Detla #phi Between Matching Thrown and Reconstructed Charged Particle; #Delta#phi", 200, -0.2, 0.2)
1250 matchedPartTrackDeltaR = ROOT.TH1D("matchedPartTrackDeltaR","#Delta R Between Matching Thrown and Reconstructed Charged Particle; #Delta R", 300, 0, 0.3)
1251 matchedPartTrackDeltaMom = ROOT.TH1D("matchedPartTrackDeltaMom","#Delta P Between Matching Thrown and Reconstructed Charged Particle; #Delta P", 200, -10, 10)
1252
1253 # Define some histograms for our efficiencies
1254 TrackEff_Eta = ROOT.TH1D("TrackEff_Eta", "Tracking efficiency as fn of #eta; #eta; Eff(%)", 120, -6, 6)
1255 TrackEffMom = ROOT.TH1D("TrackEff_Mom", "Tracking efficiency as fn of P; P(GeV/c); Eff(%)", 150, 0, 150)
1256 TrackEffPhi = ROOT.TH1D("TrackEff_Phi", "Tracking efficiency as fn of #phi; #phi(rad); Eff(%)", 320, -3.2, 3.2)
1257
1258 # 2D Efficiencies
1259 TrackEff_PEta = ROOT.TH2D("TrackEff_PEta", "Tracking efficiency as fn of P and #eta; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6)
1260 TrackEff_PhiEta = ROOT.TH2D("TrackEff_PhiEta", "Tracking efficiency as fn of #phi and #eta; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6)
1261
1262 # All charged particle histos
1263 ChargedEta = ROOT.TH1D("ChargedEta", "#eta of all charged particles; #eta", 120, -6, 6)
1264 ChargedPhi = ROOT.TH1D("ChargedPhi", "#phi of all charged particles; #phi (rad)", 120, -3.2, 3.2)
1265 ChargedP = ROOT.TH1D("ChargedP", "P of all charged particles; P(GeV/c)", 150, 0, 150)
1266
1267 # Add main analysis loop(s) below
1268 for i in range(0, len(partGenStat)): # Loop over all events
1269 for j in range(0, len(partGenStat[i])): # Loop over all thrown particles
1270 if partGenStat[i][j] == 1: # Select stable particles
1271 pdg = abs(partPdg[i][j]) # Get PDG for each stable particle
1272 if(pdg == 11 or pdg == 13 or pdg == 211 or pdg == 321 or pdg == 2212):
1273 trueMom = ROOT.TVector3(partMomX[i][j], partMomY[i][j], partMomZ[i][j])
1274 trueEta = trueMom.PseudoRapidity()
1275 truePhi = trueMom.Phi()
1276 partEta.Fill(trueEta)
1277 partPhi.Fill(truePhi)
1278 partMom.Fill(trueMom.Mag())
1279 partPEta.Fill(trueMom.Mag(), trueEta)
1280 partPhiEta.Fill(truePhi, trueEta)
1281 for k in range(0,len(simuAssoc[i])): # Loop over associations to find matching ReconstructedChargedParticle
1282 if (simuAssoc[i][k] == j):
1283 recMom = ROOT.TVector3(trackMomX[i][recoAssoc[i][k]], trackMomY[i][recoAssoc[i][k]], trackMomZ[i][recoAssoc[i][k]])
1284 deltaEta = trueEta - recMom.PseudoRapidity()
1285 deltaPhi = TVector2. Phi_mpi_pi(truePhi - recMom.Phi())
1286 deltaR = math.sqrt((deltaEta*deltaEta) + (deltaPhi*deltaPhi))
1287 deltaMom = ((trueMom.Mag()) - (recMom.Mag()))
1288 matchedPartTrackDeltaEta.Fill(deltaEta)
1289 matchedPartTrackDeltaPhi.Fill(deltaPhi)
1290 matchedPartTrackDeltaR.Fill(deltaR)
1291 matchedPartTrackDeltaMom.Fill(deltaMom)
1292 matchedPartEta.Fill(trueEta)
1293 matchedPartPhi.Fill(truePhi)
1294 matchedPartMom.Fill(trueMom.Mag())
1295 matchedPartPEta.Fill(trueMom.Mag(), trueEta)
1296 matchedPartPhiEta.Fill(truePhi, trueEta)
1297 for x in range (0, len(trackMomX[i])): # Loop over all charged particles, thrown or not
1298 CPartMom = ROOT.TVector3(trackMomX[i][x], trackMomY[i][x], trackMomZ[i][x])
1299 CPartEta = CPartMom.PseudoRapidity()
1300 CPartPhi = CPartMom.Phi()
1301 ChargedEta.Fill(CPartEta)
1302 ChargedPhi.Fill(CPartPhi)
1303 ChargedP.Fill(CPartMom.Mag())
1304
1305 # Write output histograms to file below
1306 partEta.Write()
1307 matchedPartEta.Write()
1308 partMom.Write()
1309 matchedPartMom.Write()
1310 partPhi.Write()
1311 matchedPartPhi.Write()
1312 partPEta.Write()
1313 matchedPartPEta.Write()
1314 partPhiEta.Write()
1315 matchedPartPhiEta.Write()
1316 matchedPartTrackDeltaEta.Write()
1317 matchedPartTrackDeltaPhi.Write()
1318 matchedPartTrackDeltaR.Write()
1319 matchedPartTrackDeltaMom.Write()
1320 ChargedEta.Write()
1321 ChargedPhi.Write()
1322 ChargedP.Write()
1323 TrackEff_Eta.Divide(matchedPartEta, partEta, 1, 1, "b")
1324 TrackEffMom.Divide(matchedPartMom, partMom, 1, 1, "b")
1325 TrackEffPhi.Divide(matchedPartPhi, partPhi, 1, 1, "b")
1326 TrackEff_PEta.Divide(matchedPartPEta, partPEta, 1, 1, "b")
1327 TrackEff_PhiEta.Divide(matchedPartPhiEta, partPhiEta, 1, 1, "b")
1328 TrackEff_Eta.Write()
1329 TrackEffMom.Write()
1330 TrackEffPhi.Write()
1331 TrackEff_PEta.Write()
1332 TrackEff_PhiEta.Write()
1333
1334 # Close files
1335 ofile.Close()
1336 ```
1337 Insert your input file path and execute as the example code above.
1338 ### ResolutionAnalysis.py
1339
1340 Create a file called `ResolutionAnalysis.py` and copy in the code below to get started on the resolution analysis exercise. Note that you will need to correctly specify your input file path in the variable `infile`.
1341
1342 ```python
1343 #! /usr/bin/python
1344
1345 #Import relevant packages
1346 import ROOT, math, array
1347 from ROOT import TH1F, TH2F, TMath, TTree, TVector3, TVector2
1348 import uproot as up
1349
1350 #Define and open files
1351 infile="PATH_TO_INPUT_FILE"
1352 ofile=ROOT.TFile.Open("ResolutionAnalysis_OutPy.root", "RECREATE")
1353
1354 # Open input file and define branches we want to look at with uproot
1355 events_tree = up.open(infile)["events"]
1356
1357 # Get particle information
1358 partGenStat = events_tree["MCParticles.generatorStatus"].array()
1359 partMomX = events_tree["MCParticles.momentum.x"].array()
1360 partMomY = events_tree["MCParticles.momentum.y"].array()
1361 partMomZ = events_tree["MCParticles.momentum.z"].array()
1362 partPdg = events_tree["MCParticles.PDG"].array()
1363
1364 # Get reconstructed track information
1365 trackMomX = events_tree["ReconstructedChargedParticles.momentum.x"].array()
1366 trackMomY = events_tree["ReconstructedChargedParticles.momentum.y"].array()
1367 trackMomZ = events_tree["ReconstructedChargedParticles.momentum.z"].array()
1368
1369 # Get assocations between MCParticles and ReconstructedChargedParticles
1370 recoAssoc = events_tree["_ReconstructedChargedParticleAssociations_rec.index"].array()
1371 simuAssoc = events_tree["_ReconstructedChargedParticleAssociations_sim.index"].array()
1372
1373 # Define histograms below
1374 trackMomentumRes = ROOT.TH1D("trackMomentumRes","Track Momentum Resolution", 400, -2, 2)
1375
1376 matchedPartTrackDeltaEta = ROOT.TH1D("matchedPartTrackDeltaEta","#Delta#eta Between Matching Thrown and Reconstructe Charged Particle; #Delta#eta", 100, -0.25, 0.25)
1377 matchedPartTrackDeltaPhi = ROOT.TH1D("matchedPartTrackDeltaPhi","#Detla #phi Between Matching Thrown and Reconstructed Charged Particle; #Delta#phi", 200, -0.2, 0.2)
1378 matchedPartTrackDeltaR = ROOT.TH1D("matchedPartTrackDeltaR","#Delta R Between Matching Thrown and Reconstructed Charged Particle; #Delta R", 300, 0, 0.3)
1379 matchedPartTrackDeltaMom = ROOT.TH1D("matchedPartTrackDeltaMom","#Delta P Between Matching Thrown and Reconstructed Charged Particle; #Delta P", 200, -10, 10)
1380
1381 # Add main analysis loop(s) below
1382 for i in range(0, len(partGenStat)): # Loop over all events
1383 for j in range(0, len(partGenStat[i])): # Loop over all thrown particles
1384 if partGenStat[i][j] == 1: # Select stable particles
1385 pdg = abs(partPdg[i][j]) # Get PDG for each stable particle
1386 if(pdg == 11 or pdg == 13 or pdg == 211 or pdg == 321 or pdg == 2212):
1387 trueMom = ROOT.TVector3(partMomX[i][j], partMomY[i][j], partMomZ[i][j])
1388 trueEta = trueMom.PseudoRapidity()
1389 truePhi = trueMom.Phi()
1390 for k in range(0,len(simuAssoc[i])): # Loop over associations to find matching ReconstructedChargedParticle
1391 if (simuAssoc[i][k] == j):
1392 recMom = ROOT.TVector3(trackMomX[i][recoAssoc[i][k]], trackMomY[i][recoAssoc[i][k]], trackMomZ[i][recoAssoc[i][k]])
1393 deltaEta = trueEta - recMom.PseudoRapidity()
1394 deltaPhi = TVector2. Phi_mpi_pi(truePhi - recMom.Phi())
1395 deltaR = math.sqrt((deltaEta*deltaEta) + (deltaPhi*deltaPhi))
1396 deltaMom = ((trueMom.Mag()) - (recMom.Mag()))
1397 momRes = (recMom.Mag() - trueMom.Mag())/trueMom.Mag()
1398 matchedPartTrackDeltaEta.Fill(deltaEta)
1399 matchedPartTrackDeltaPhi.Fill(deltaPhi)
1400 matchedPartTrackDeltaR.Fill(deltaR)
1401 matchedPartTrackDeltaMom.Fill(deltaMom)
1402 trackMomentumRes.Fill(momRes)
1403
1404 # Write output histograms to file below
1405 trackMomentumRes.Write()
1406 matchedPartTrackDeltaEta.Write()
1407 matchedPartTrackDeltaPhi.Write()
1408 matchedPartTrackDeltaR.Write()
1409 matchedPartTrackDeltaMom.Write()
1410
1411 # Close files
1412 ofile.Close()
1413 ```
1414
1415 A "solution" version of the script for the exercise is included below -
1416
1417 ```python
1418 #! /usr/bin/python
1419
1420 #Import relevant packages
1421 import ROOT, math, array
1422 from ROOT import TCanvas, TColor, TGaxis, TH1F, TH2F, TPad, TStyle, gStyle, gPad, TGaxis, TLine, TMath, TPaveText, TTree, TVector3, TVector2
1423 import uproot as up
1424
1425 #Define and open files
1426 infile="PATH_TO_FILE"
1427 ofile=ROOT.TFile.Open("ResolutionAnalysis_Exercise_OutPy.root", "RECREATE")
1428
1429 # Open input file and define branches we want to look at with uproot
1430 events_tree = up.open(infile)["events"]
1431
1432 # Get particle information
1433 partGenStat = events_tree["MCParticles.generatorStatus"].array()
1434 partMomX = events_tree["MCParticles.momentum.x"].array()
1435 partMomY = events_tree["MCParticles.momentum.y"].array()
1436 partMomZ = events_tree["MCParticles.momentum.z"].array()
1437 partPdg = events_tree["MCParticles.PDG"].array()
1438
1439 # Get reconstructed track information
1440 trackMomX = events_tree["ReconstructedChargedParticles.momentum.x"].array()
1441 trackMomY = events_tree["ReconstructedChargedParticles.momentum.y"].array()
1442 trackMomZ = events_tree["ReconstructedChargedParticles.momentum.z"].array()
1443
1444 # Get assocations between MCParticles and ReconstructedChargedParticles
1445 recoAssoc = events_tree["_ReconstructedChargedParticleAssociations_rec.index"].array()
1446 simuAssoc = events_tree.["_ReconstructedChargedParticleAssociations_sim.index"].array()
1447
1448 # Define histograms below
1449 trackMomentumRes = ROOT.TH1D("trackMomentumRes","Track Momentum Resolution", 400, -2, 2)
1450 trackMomResP = ROOT.TH2D("trackMomResP", "Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
1451 trackMomResEta = ROOT.TH2D("trackMomResEta", "Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
1452
1453 trackMomentumRes_e = ROOT.TH1D("trackMomentumRes_e","e^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
1454 trackMomResP_e = ROOT.TH2D("trackMomResP_e", "e^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 25);
1455 trackMomResEta_e = ROOT.TH2D("trackMomResEta_e", "e^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
1456
1457 trackMomentumRes_mu = ROOT.TH1D("trackMomentumRes_mu","#mu^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
1458 trackMomResP_mu = ROOT.TH2D("trackMomResP_mu", "#mu^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 25);
1459 trackMomResEta_mu = ROOT.TH2D("trackMomResEta_mu", "#mu^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
1460
1461 trackMomentumRes_pi = ROOT.TH1D("trackMomentumRes_pi","#pi^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
1462 trackMomResP_pi = ROOT.TH2D("trackMomResP_pi", "#pi^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
1463 trackMomResEta_pi = ROOT.TH2D("trackMomResEta_pi", "#pi^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
1464
1465 trackMomentumRes_K = ROOT.TH1D("trackMomentumRes_K","K^{#pm} Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
1466 trackMomResP_K = ROOT.TH2D("trackMomResP_K", "K^{#pm} Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
1467 trackMomResEta_K = ROOT.TH2D("trackMomResEta_K", "K^{#pm} Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
1468
1469 trackMomentumRes_p = ROOT.TH1D("trackMomentumRes_p","p Track Momentum Resolution; (P_{rec} - P_{MC})/P_{MC}", 400, -2, 2);
1470 trackMomResP_p = ROOT.TH2D("trackMomResP_p", "p Track Momentum Resolution vs P; (P_{rec} - P_{MC})/P_{MC}; P_{MC}(GeV/c)", 400, -2, 2, 150, 0, 150);
1471 trackMomResEta_p = ROOT.TH2D("trackMomResEta_p", "p Track Momentum Resolution vs #eta; (P_{rec} - P_{MC})/P_{MC}; #eta_{MC}", 400, -2, 2, 120, -6, 6);
1472
1473 matchedPartTrackDeltaEta = ROOT.TH1D("matchedPartTrackDeltaEta","#Delta#eta Between Matching Thrown and Reconstructe Charged Particle; #Delta#eta", 100, -0.25, 0.25)
1474 matchedPartTrackDeltaPhi = ROOT.TH1D("matchedPartTrackDeltaPhi","#Detla #phi Between Matching Thrown and Reconstructed Charged Particle; #Delta#phi", 200, -0.2, 0.2)
1475 matchedPartTrackDeltaR = ROOT.TH1D("matchedPartTrackDeltaR","#Delta R Between Matching Thrown and Reconstructed Charged Particle; #Delta R", 300, 0, 0.3)
1476 matchedPartTrackDeltaMom = ROOT.TH1D("matchedPartTrackDeltaMom","#Delta P Between Matching Thrown and Reconstructed Charged Particle; #Delta P", 200, -10, 10)
1477
1478 # Add main analysis loop(s) below
1479 for i in range(0, len(partGenStat)): # Loop over all events
1480 for j in range(0, len(partGenStat[i])): # Loop over all thrown particles
1481 if partGenStat[i][j] == 1: # Select stable particles
1482 pdg = abs(partPdg[i][j]) # Get PDG for each stable particle
1483 if(pdg == 11 or pdg == 13 or pdg == 211 or pdg == 321 or pdg == 2212):
1484 trueMom = ROOT.TVector3(partMomX[i][j], partMomY[i][j], partMomZ[i][j])
1485 trueEta = trueMom.PseudoRapidity()
1486 truePhi = trueMom.Phi()
1487 for k in range(0,len(simuAssoc[i])): # Loop over associations to find matching ReconstructedChargedParticle
1488 if (simuAssoc[i][k] == j):
1489 recMom = ROOT.TVector3(trackMomX[i][recoAssoc[i][k]], trackMomY[i][recoAssoc[i][k]], trackMomZ[i][recoAssoc[i][k]])
1490 deltaEta = trueEta - recMom.PseudoRapidity()
1491 deltaPhi = TVector2. Phi_mpi_pi(truePhi - recMom.Phi())
1492 deltaR = math.sqrt((deltaEta*deltaEta) + (deltaPhi*deltaPhi))
1493 deltaMom = ((trueMom.Mag()) - (recMom.Mag()))
1494 momRes = (recMom.Mag() - trueMom.Mag())/trueMom.Mag()
1495 trackMomentumRes.Fill(momRes)
1496 trackMomResP.Fill(momRes, trueMom.Mag())
1497 trackMomResEta.Fill(momRes, trueEta)
1498 if( pdg == 11):
1499 trackMomentumRes_e.Fill(momRes)
1500 trackMomResP_e.Fill(momRes, trueMom.Mag())
1501 trackMomResEta_e.Fill(momRes, trueEta)
1502 elif( pdg == 13):
1503 trackMomentumRes_mu.Fill(momRes)
1504 trackMomResP_mu.Fill(momRes, trueMom.Mag())
1505 trackMomResEta_mu.Fill(momRes, trueEta)
1506 elif( pdg == 211):
1507 trackMomentumRes_pi.Fill(momRes)
1508 trackMomResP_pi.Fill(momRes, trueMom.Mag())
1509 trackMomResEta_pi.Fill(momRes, trueEta)
1510 elif( pdg == 321):
1511 trackMomentumRes_K.Fill(momRes)
1512 trackMomResP_K.Fill(momRes, trueMom.Mag())
1513 trackMomResEta_K.Fill(momRes, trueEta)
1514 elif( pdg == 2212):
1515 trackMomentumRes_p.Fill(momRes)
1516 trackMomResP_p.Fill(momRes, trueMom.Mag())
1517 trackMomResEta_p.Fill(momRes, trueEta)
1518 matchedPartTrackDeltaEta.Fill(deltaEta)
1519 matchedPartTrackDeltaPhi.Fill(deltaPhi)
1520 matchedPartTrackDeltaR.Fill(deltaR)
1521 matchedPartTrackDeltaMom.Fill(deltaMom)
1522
1523 # Write output histograms to file below
1524 trackMomentumRes.Write()
1525 trackMomResP.Write()
1526 trackMomResEta.Write()
1527 trackMomentumRes_e.Write()
1528 trackMomResP_e.Write()
1529 trackMomResEta_e.Write()
1530 trackMomentumRes_mu.Write()
1531 trackMomResP_mu.Write()
1532 trackMomResEta_mu.Write()
1533 trackMomentumRes_pi.Write()
1534 trackMomResP_pi.Write()
1535 trackMomResEta_pi.Write()
1536 trackMomentumRes_K.Write()
1537 trackMomResP_K.Write()
1538 trackMomResEta_K.Write()
1539 trackMomentumRes_p.Write()
1540 trackMomResP_p.Write()
1541 trackMomResEta_p.Write()
1542 matchedPartTrackDeltaEta.Write()
1543 matchedPartTrackDeltaPhi.Write()
1544 matchedPartTrackDeltaR.Write()
1545 matchedPartTrackDeltaMom.Write()
1546
1547 # Close files
1548 ofile.Close()
1549 ```
1550 Insert your input file path and execute as the example code above.
1551 ## RDataFrames Example
1552
1553 Note that only the initial stage of the efficiency example is presented here in RDF format. This example was kindly created by [Simon](https://github.com/simonge/EIC_Analysis/blob/main/Analysis-Tutorial/EfficiencyAnalysisRDF.C).
1554
1555 ### EfficiencyAnalysisRDF.C
1556
1557 Create a file called `EfficiencyAnalysisRDF.C` and paste the code below in. Remember to change the file path.
1558
1559 Execute this script via - `root -l -q EfficiencyAnalysisRDF.C++`. Do this within eic-shell or somewhere else with the correct EDM4hep/EDM4eic libraries installed.
1560
1561 ```c++
1562 #include <edm4hep/utils/vector_utils.h>
1563 #include <edm4hep/MCParticle.h>
1564 #include <edm4eic/ReconstructedParticle.h>
1565 #include <ROOT/RDataFrame.hxx>
1566 #include <ROOT/RVec.hxx>
1567 #include <TFile.h>
1568
1569 // Define aliases for the data types
1570 using MCP = edm4hep::MCParticleData;
1571 using RecoP = edm4eic::ReconstructedParticleData;
1572
1573 // Define function to vectorize the edm4hep::utils methods
1574 template <typename T>
1575 auto getEta = [](ROOT::VecOps::RVec<T> momenta) {
1576 return ROOT::VecOps::Map(momenta, [](const T& p) { return edm4hep::utils::eta(p.momentum); });
1577 };
1578
1579 template <typename T>
1580 auto getPhi = [](ROOT::VecOps::RVec<T> momenta) {
1581 return ROOT::VecOps::Map(momenta, [](const T& p) { return edm4hep::utils::angleAzimuthal(p.momentum); });
1582 };
1583
1584 // Define the function to perform the efficiency analysis
1585 void EfficiencyAnalysisRDF(TString infile="PATH_TO_FILE"){
1586
1587 // Set up input file
1588 ROOT::RDataFrame df("events", infile);
1589
1590 // Define new dataframe node with additional columns
1591 auto df1 = df.Define("statusFilter", "MCParticles.generatorStatus == 1" )
1592 .Define("absPDG", "abs(MCParticles.PDG)" )
1593 .Define("pdgFilter", "absPDG == 11 || absPDG == 13 || absPDG == 211 || absPDG == 321 || absPDG == 2212")
1594 .Define("particleFilter","statusFilter && pdgFilter" )
1595 .Define("filtMCParts", "MCParticles[particleFilter]" )
1596 .Define("assoFilter", "Take(particleFilter,ReconstructedChargedParticleAssociations_sim.index)") // Incase any of the associated particles happen to not be charged
1597 .Define("assoMCParts", "Take(MCParticles,ReconstructedChargedParticleAssociations)sim.index)[assoFilter]")
1598 .Define("assoRecParts", "Take(ReconstructedChargedParticles,ReconstructedChargedParticleAssociations._rec.index)[assoFilter]")
1599 .Define("filtMCEta", getEta<MCP> , {"filtMCParts"} )
1600 .Define("filtMCPhi", getPhi<MCP> , {"filtMCParts"} )
1601 .Define("accoMCEta", getEta<MCP> , {"assoMCParts"} )
1602 .Define("accoMCPhi", getPhi<MCP> , {"assoMCParts"} )
1603 .Define("assoRecEta", getEta<RecoP> , {"assoRecParts"})
1604 .Define("assoRecPhi", getPhi<RecoP> , {"assoRecParts"})
1605 .Define("deltaR", "ROOT::VecOps::DeltaR(assoRecEta, accoMCEta, assoRecPhi, accoMCPhi)");
1606
1607 // Define histograms
1608 auto partEta = df1.Histo1D({"partEta","Eta of Thrown Charged Particles;Eta",100,-5.,5.},"filtMCEta");
1609 auto matchedPartEta = df1.Histo1D({"matchedPartEta","Eta of Thrown Charged Particles That Have Matching Track",100,-5.,5.},"accoMCEta");
1610 auto matchedPartTrackDeltaR = df1.Histo1D({"matchedPartTrackDeltaR","Delta R Between Matching Thrown and Reconstructed Charged Particle",5000,0.,5.},"deltaR");
1611
1612 // Write histograms to file
1613 TFile *ofile = TFile::Open("EfficiencyAnalysis_Out_RDF.root","RECREATE");
1614
1615 // Booked Define and Histo1D lazy actions are only performed here
1616 partEta->Write();
1617 matchedPartEta->Write();
1618 matchedPartTrackDeltaR->Write();
1619
1620 ofile->Close(); // Close output file
1621 }
1622 ```
1623
1624 A "solution" using RDataFrames is included below,
1625
1626 ```c++
1627 #include <edm4hep/utils/vector_utils.h>
1628 #include <edm4hep/MCParticle.h>
1629 #include <edm4eic/ReconstructedParticle.h>
1630 #include <ROOT/RDataFrame.hxx>
1631 #include <ROOT/RVec.hxx>
1632 #include <TFile.h>
1633
1634 // Define aliases for the data types
1635 using MCP = edm4hep::MCParticleData;
1636 using RecoP = edm4eic::ReconstructedParticleData;
1637
1638 // Define function to vectorize the edm4hep::utils methods
1639 template <typename T>
1640 auto getEta = [](ROOT::VecOps::RVec<T> momenta) {
1641 return ROOT::VecOps::Map(momenta, [](const T& p) { return edm4hep::utils::eta(p.momentum); });
1642 };
1643
1644 template <typename T>
1645 auto getPhi = [](ROOT::VecOps::RVec<T> momenta) {
1646 return ROOT::VecOps::Map(momenta, [](const T& p) { return edm4hep::utils::angleAzimuthal(p.momentum); });
1647 };
1648
1649 template <typename T>
1650 auto getP = [](ROOT::VecOps::RVec<T> momenta) {
1651 //return ROOT::VecOps::Map(momenta, [](const T& p) { return (p.momentum); }); // This is a vector3f
1652 return ROOT::VecOps::Map(momenta, [](const T& p) { return edm4hep::utils::magnitude(p.momentum); }); // This is a the magnitude of that vector3f
1653 };
1654
1655 // Define the function to perform the efficiency analysis
1656 void EfficiencyAnalysisRDF_Exercise(TString infile="PATH_TO_INPUT_FILE"){
1657
1658 // Set up input file
1659 ROOT::RDataFrame df("events", infile);
1660
1661 // Define new dataframe node with additional columns
1662 auto df1 = df.Define("statusFilter", "MCParticles.generatorStatus == 1" )
1663 .Define("absPDG", "abs(MCParticles.PDG)" )
1664 .Define("pdgFilter", "absPDG == 11 || absPDG == 13 || absPDG == 211 || absPDG == 321 || absPDG == 2212")
1665 .Define("particleFilter","statusFilter && pdgFilter" )
1666 .Define("filtMCParts", "MCParticles[particleFilter]" )
1667 .Define("assoFilter", "Take(particleFilter,_ReconstructedChargedParticleAssociations_sim.index)") // In case any of the associated particles happen to not be charged
1668 .Define("assoMCParts", "Take(MCParticles,_ReconstructedChargedParticleAssociations_sim.index)[assoFilter]")
1669 .Define("assoRecParts", "Take(ReconstructedChargedParticles,_ReconstructedChargedParticleAssociations_rec.index)[assoFilter]")
1670 .Define("filtMCEta", getEta<MCP> , {"filtMCParts"} )
1671 .Define("filtMCPhi", getPhi<MCP> , {"filtMCParts"} )
1672 .Define("filtMCp", getP<MCP> , {"filtMCParts"} )
1673 .Define("assoMCEta", getEta<MCP> , {"assoMCParts"} )
1674 .Define("assoMCPhi", getPhi<MCP> , {"assoMCParts"} )
1675 .Define("assoMCp", getP<MCP> , {"assoMCParts"} )
1676 .Define("assoRecEta", getEta<RecoP> , {"assoRecParts"})
1677 .Define("assoRecPhi", getPhi<RecoP> , {"assoRecParts"})
1678 .Define("assoRecp", getP<RecoP> , {"assoRecParts"})
1679 .Define("deltaEta", "assoMCEta - assoRecEta" )
1680 .Define("deltaPhi", "ROOT::VecOps::DeltaPhi(assoRecPhi, assoMCPhi)")
1681 .Define("deltaR", "ROOT::VecOps::DeltaR(assoRecEta, assoMCEta, assoRecPhi, assoMCPhi)")
1682 .Define("deltaMom", "assoMCp - assoRecp")
1683 .Define("recoEta", getEta<RecoP>, {"ReconstructedChargedParticles"})
1684 .Define("recoPhi", getPhi<RecoP>, {"ReconstructedChargedParticles"})
1685 .Define("recoP", getP<RecoP>, {"ReconstructedChargedParticles"});
1686
1687 // Define histograms. We create a histogram with the usual naming/titles/bins/range etc, then specify how to fill the histogram based upon things we have defined for our dataframe
1688 auto partEta = df1.Histo1D({"partEta","#eta of Thrown Charged Particles;#eta",120,-6.,6.},"filtMCEta");
1689 auto matchedPartEta = df1.Histo1D({"matchedPartEta","#eta of Thrown Charged Particles That Have Matching Track;#eta",120,-6.,6.},"assoMCEta");
1690 auto partMom = df1.Histo1D({"partMom", "Momentum of Thrown Charged Particles (truth); P(GeV/c)", 150, 0, 150}, "filtMCp");
1691 auto matchedPartMom = df1.Histo1D({"matchedPartMom", "Momentum of Thrown Charged Particles (truth), with matching track; P(GeV/c)", 150, 0, 150}, "assoMCp");
1692 auto partPhi = df1.Histo1D({"partPhi", "#phi of Thrown Charged Particles (truth); #phi(rad)", 320, -3.2, 3.2},"filtMCPhi");
1693 auto matchedPartPhi = df1.Histo1D({"matchedPartPhi", "#phi of Thrown Charged Particles (truth), with matching track; #phi(rad)", 320, -3.2, 3.2}, "assoMCPhi");
1694
1695 auto partPEta = df1.Histo2D({"partPEta", "P vs #eta of Thrown Charged Particles; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6}, "filtMCp", "filtMCEta");
1696 auto matchedPartPEta = df1.Histo2D({"matchedPartPEta", "P vs #eta of Thrown Charged Particles, with matching track; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6}, "assoMCp", "assoMCEta");
1697 auto partPhiEta = df1.Histo2D({"partPhiEta", "#phi vs #eta of Thrown Charged Particles; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6}, "filtMCPhi", "filtMCEta");
1698 auto matchedPartPhiEta = df1.Histo2D({"matchedPartPhiEta", "#phi vs #eta of Thrown Charged Particles; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6}, "assoMCPhi", "assoMCEta");
1699
1700 auto matchedPartTrackDeltaEta = df1.Histo1D({"matchedPartTrackDeltaEta","#Delta#eta Between Matching Thrown and Reconstructed Charged Particle; #Delta#eta", 100, -0.25, 0.25}, "deltaEta");
1701 auto matchedPartTrackDeltaPhi = df1.Histo1D({"matchedPartTrackDeltaPhi","#Detla #phi Between Matching Thrown and Reconstructed Charged Particle; #Delta#phi", 200, -0.2, 0.2}, "deltaPhi");
1702 auto matchedPartTrackDeltaR = df1.Histo1D({"matchedPartTrackDeltaR","#Delta R Between Matching Thrown and Reconstructed Charged Particle;#Delta R",300,0.,0.3}, "deltaR");
1703 auto matchedPartTrackDeltaMom = df1.Histo1D({"matchedPartTrackDeltaMom","#Delta P Between Matching Thrown and Reconstructed Charged Particle; #Delta P", 200, -10, 10}, "deltaMom");
1704
1705 // Define some histograms for our efficiencies - Done "old school" root style - Maybe the division can be done direct from a DF?
1706 TH1D *TrackEff_Eta = new TH1D("TrackEff_Eta", "Tracking efficiency as fn of #eta; #eta; Eff(%)", 120, -6, 6);
1707 TH1D *TrackEff_Mom = new TH1D("TrackEff_Mom", "Tracking efficiency as fn of P; P(GeV/c); Eff(%)", 150, 0, 150);
1708 TH1D *TrackEff_Phi = new TH1D("TrackEff_Phi", "Tracking efficiency as fn of #phi; #phi(rad); Eff(%)", 320, -3.2, 3.2);
1709 // 2D Efficiencies
1710 TH2D* TrackEff_PEta = new TH2D("TrackEff_PEta", "Tracking efficiency as fn of P and #eta; P(GeV/c); #eta", 150, 0, 150, 120, -6, 6);
1711 TH2D* TrackEff_PhiEta = new TH2D("TrackEff_PhiEta", "Tracking efficiency as fn of #phi and #eta; #phi(rad); #eta", 160, -3.2, 3.2, 120, -6, 6);
1712
1713 auto ChargedEta = df1.Histo1D({"ChargedEta", "#eta of all charged particles; #eta", 120, -6, 6}, "recoEta");
1714 auto ChargedPhi = df1.Histo1D({"ChargedPhi", "#phi of all charged particles; #phi (rad)", 120, -3.2, 3.2}, "recoPhi");
1715 auto ChargedP = df1.Histo1D({"ChargedP", "P of all charged particles; P(GeV/c)", 150, 0, 150}, "recoP");
1716
1717 // Write histograms to file
1718 TFile *ofile = TFile::Open("EfficiencyAnalysis_Exercise_Out_RDF.root","RECREATE");
1719
1720 // Booked Define and Histo1D lazy actions are only performed here
1721 partEta->Write();
1722 matchedPartEta->Write();
1723 partPhi->Write();
1724 matchedPartPhi->Write();
1725 partMom->Write();
1726 matchedPartMom->Write();
1727 partPEta->Write();
1728 matchedPartPEta->Write();
1729 partPhiEta->Write();
1730 matchedPartPhiEta->Write();
1731 matchedPartTrackDeltaEta->Write();
1732 matchedPartTrackDeltaPhi->Write();
1733 matchedPartTrackDeltaR->Write();
1734 matchedPartTrackDeltaMom->Write();
1735
1736 // Create efficiency histograms by dividing appropriately. Note we must actually get the pointer explicitly.
1737 TrackEff_Eta->Divide(matchedPartEta.GetPtr(), partEta.GetPtr(), 1, 1, "b");
1738 TrackEff_Mom->Divide(matchedPartMom.GetPtr(), partMom.GetPtr(), 1, 1, "b");
1739 TrackEff_Phi->Divide(matchedPartPhi.GetPtr(), partPhi.GetPtr(), 1, 1, "b");
1740 TrackEff_PEta->Divide(matchedPartPEta.GetPtr(), partPEta.GetPtr(), 1, 1, "b");
1741 TrackEff_PhiEta->Divide(matchedPartPhiEta.GetPtr(), partPhiEta.GetPtr(), 1, 1, "b");
1742
1743 TrackEff_Eta->Write();
1744 TrackEff_Mom->Write();
1745 TrackEff_Phi->Write();
1746 TrackEff_PEta->Write();
1747 TrackEff_PhiEta->Write();
1748
1749 ChargedEta->Write();
1750 ChargedPhi->Write();
1751 ChargedP->Write();
1752
1753 ofile->Close(); // Close output file
1754 }
1755 ```
1756