Back to home page

EIC code displayed by LXR

 
 

    


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