Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-03-28 08:36:10

0001 # =============================================================================
0002 ## @file   CalculateJESRWithPODIO.py
0003 #  @author Derek Anderson, building on work by Brian Page
0004 #  @date   07.28.2025
0005 # -----------------------------------------------------------------------------
0006 ## @brief Example python script that calculates the Jet Energy Scale
0007 #    (JES) and Resolution (JER) from EICrecon output using PODIO
0008 #    interface. This builds on the calculation originally implemented
0009 #    by Brian Page at
0010 #
0011 #      https://github.com/eic/physics_benchmarks/blob/master/benchmarks/Jets-HF/jets/analysis/jets.cxx
0012 #
0013 #  @usage Inside the eic-shell:
0014 #
0015 #      python CalculateJESRWithPODIO.py
0016 #
0017 #    Or, if you want to specify input/output files:
0018 #
0019 #      python CalculateJESRWithPODIO.py -i <my input file> -o <my output file>
0020 #
0021 # =============================================================================
0022 
0023 import argparse as ap
0024 import ROOT
0025 import numpy as np
0026 
0027 from podio.reading import get_reader
0028 
0029 # default input/output files
0030 IFileDefault = "root://dtn-eic.jlab.org//volatile/eic/EPIC/RECO/25.10.4/epic_craterlake/DIS/pythia6.428-1.0/NC/noRad/ep/10x130/q2_10to100/pythia6.428-1.0_NC_noRad_ep_10x130_q2_10to100_ab.0625.eicrecon.edm4eic.root"
0031 OFileDefault = "test_podio.root"
0032 
0033 
0034 def Calculate(
0035     ifile = IFileDefault,
0036     ofile = OFileDefault,
0037 ):
0038     """Calculate JES/R
0039 
0040     Calculate JES/R for a collection of jets. Excludes
0041     any generator-level jets with electrons to avoid
0042     DIS electron.
0043 
0044     Args:
0045       ifile: input file produced by EICrecon
0046       ofile: output file
0047     Returns:
0048       tuple of the JES and JER
0049     """
0050 
0051     # use podio reader to open example EICrecon output
0052     reader = get_reader(ifile)
0053     print("\n  Starting calculation:")
0054     print(f"      input  = {ifile}")
0055     print(f"      output = {ofile}")
0056 
0057     # create histogram for extracting JES/R
0058     hres = ROOT.TH1D("hEneResPODIO", ";(E_{jet}^{rec} - E_{jet}^{gen}) / E_{jet}^{gen}", 50, -2., 3.)
0059     hres.Sumw2()
0060 
0061     nframes = len(reader.get("events"))
0062     print(f"    Starting event loop, {nframes} events to process.")
0063 
0064     # now loop through all events in output
0065     for iframe, frame in enumerate(reader.get("events")):
0066 
0067         # print progress
0068         if (iframe % 100 == 0):
0069             print(f"      Processing event {iframe + 1}/{nframes}...")
0070 
0071         # grab collection of jets
0072         # available options:
0073         #   - ReconstructedChargedJets
0074         #   - GeneratedChargedJets
0075         #   - ReconstructedJets
0076         #   - GeneratedJets
0077         #   - ReconstructedCentauroJets
0078         #   - GeneratedCentauroJets
0079         gen_jets = frame.get("GeneratedChargedJets")
0080         rec_jets = frame.get("ReconstructedChargedJets")
0081 
0082         # loop through generated jets
0083         for gjet in gen_jets:
0084 
0085             # calculate pseudorapidity, azimuth
0086             gpt = np.sqrt((gjet.getMomentum().x**2) + (gjet.getMomentum().y**2))
0087             gphi = np.arctan2(gjet.getMomentum().y, gjet.getMomentum().x)
0088             gtheta = np.arctan2(gpt, gjet.getMomentum().z)
0089             geta = -np.log(np.tan(gtheta/2))
0090 
0091             # select only jets w/ pt > 5 (jet
0092             # reco below this gets tricky)
0093             if gjet.getEnergy() < 5:
0094                 continue
0095 
0096             # select only jets in |eta_max| - R =
0097             # 3.5 - 1 = 2.5 (to avoid jets cut off
0098             # by acceptance)
0099             if np.abs(geta) > 2.5:
0100                 continue 
0101 
0102             # filter out generated jets with electrons
0103             # in them (to avoid DIS electron)
0104             has_electron = False
0105             for gcst in gjet.getParticles():
0106                 if gcst.getPDG() == 11:
0107                     has_electron = True
0108                     break
0109 
0110             if has_electron:
0111                 continue
0112 
0113             # loop through reconstructed jets to find closest
0114             # one in eta-phi space
0115             match = None
0116             min_dist = 9999.
0117             for rjet in rec_jets:
0118 
0119                 # calculate pseudorapidity, azimuth
0120                 rpt = np.sqrt((rjet.getMomentum().x**2) + (rjet.getMomentum().y**2))
0121                 rphi = np.arctan2(rjet.getMomentum().y, rjet.getMomentum().x)
0122                 rtheta = np.arctan2(rpt, rjet.getMomentum().z)
0123                 reta = -np.log(np.tan(rtheta/2))
0124 
0125                 # calculate distance in eta-phi space between
0126                 # gen & reco jets
0127                 deta = reta - geta
0128                 dphi = rphi - gphi
0129                 dist = np.sqrt((deta**2) + (dphi**2))
0130 
0131                 # if distance is outside tolerance, continue
0132                 if dist > 0.05:
0133                     continue
0134 
0135                 # if reco jet is closest so far, set as match
0136                 if dist < min_dist:
0137                     match = rjet
0138                     min_dist = dist
0139 
0140             # if no match found, continue 
0141             if match is None:
0142                 continue
0143 
0144             # otherwise calculate %-difference 
0145             eres = (match.getEnergy() - gjet.getEnergy())/gjet.getEnergy()
0146             hres.Fill(eres)
0147 
0148     # extract JES/R
0149     fres = ROOT.TF1("fEneRes", "gaus(0)", -0.5, 0.5)
0150     fres.SetParameter(0, hres.Integral())
0151     fres.SetParameter(1, hres.GetMean())
0152     fres.SetParameter(2, hres.GetRMS())
0153     hres.Fit(fres, "r")
0154     JES = fres.GetParameter(1) + 1
0155     JER = fres.GetParameter(2)
0156     print(f"    Finished event loop:")
0157     print(f"      JES = {JES}, JER = {JER}")
0158 
0159     # save histogram + fit for reference
0160     with ROOT.TFile(ofile, "recreate") as ofile:
0161         ofile.WriteObject(hres, "hEneResPODIO")
0162         ofile.Close()
0163 
0164     print("  Finished calculation!\n")
0165     return (JES, JER)
0166 
0167 
0168 # main ========================================================================
0169 
0170 if __name__ == "__main__":
0171 
0172     # set up argments
0173     parser = ap.ArgumentParser()
0174     parser.add_argument(
0175         "-i",
0176         "--input",
0177         help = "Input file",
0178         nargs = '?',
0179         const = IFileDefault,
0180         default = IFileDefault,
0181         type = str
0182     )
0183     parser.add_argument(
0184         "-o",
0185         "--output",
0186         help = "Output file",
0187         nargs = '?',
0188         const = OFileDefault,
0189         default = OFileDefault,
0190         type = str
0191     )
0192     args = parser.parse_args()
0193 
0194     # run calculation
0195     Calculate(args.input, args.output)
0196 
0197 # end =========================================================================