Repository: latinos/mkShapesRDF
Pull request #59: Added electron scale and smearing corrections
Author: login: squinto5
Created: 2025-04-03T10:14:08Z | Merged: 2025-04-03T15:43:24Z

This PR updates the `LeptonScaleSmearing.py` module by incorporating electron scale and smearing corrections. These corrections are applied using functions defined in the `ElecScaRe.cc` macro, which follow the [EGM POG recommendations](https://twiki.cern.ch/twiki/bin/viewauth/CMS/EgammSFandSSRun3).  

Additionally, the `scEta.cc` macro has been introduced to compute the supercluster eta and can be used for both electrons and photons.  




Diff:
diff --git a/mkShapesRDF/processor/data/electron_scale/EleScaRe.cc b/mkShapesRDF/processor/data/electron_scale/EleScaRe.cc
new file mode 100644
index 00000000..8ddf4fcd
--- /dev/null
+++ b/mkShapesRDF/processor/data/electron_scale/EleScaRe.cc
@@ -0,0 +1,67 @@
+// This macro contains the function needed to apply scale and smearing correction to electrons and photons
+// as described here https://twiki.cern.ch/twiki/bin/viewauth/CMS/EgammSFandSSRun3
+
+#include <iostream>
+#include "TString.h"
+#include "TFile.h"
+#include "TRandom3.h"   
+#include <string>
+#include <vector>
+#include "correction.h"
+
+// Obtain the uncertainty on the smearing width using MC original variables
+double ele_unc_smear(double Lepton_pt, double Electron_r9, double sc_eta, double random_number, string updn)
+{
+    double smear = elecset_smear->evaluate({"smear", Lepton_pt, Electron_r9, abs(sc_eta)});
+    double unc_smear = elecset_smear->evaluate({"esmear", Lepton_pt, Electron_r9, abs(sc_eta)});
+    double mc_unc_smear = Lepton_pt;
+    if (updn=="up"){
+        mc_unc_smear = Lepton_pt * (1 + (smear + unc_smear) * random_number);
+    }
+    else if (updn=="dn"){
+        mc_unc_smear = Lepton_pt * (1 + (smear - unc_smear) * random_number);
+    }
+    else {
+        cout << "ERROR: updn must be 'up' or 'dn'" << endl;
+    }
+    return mc_unc_smear;
+}
+
+// Scale correction applied to data
+double ele_scale(double run, double sc_eta, double Electron_r9, double Lepton_pt, double Electron_seedGain)
+{
+    double scale = elecset_scale->evaluate({"scale", run, sc_eta, Electron_r9, abs(sc_eta), Lepton_pt, Electron_seedGain});
+    double data_scale = scale * Lepton_pt;
+    return data_scale;
+}
+
+// Calculate the nominal smearing factor for MC
+double ele_smear(double Lepton_pt, double Electron_r9, double sc_eta, double random_number)
+{
+    double smear = elecset_smear->evaluate({"smear", Lepton_pt, Electron_r9, abs(sc_eta)});
+    double mc_smear = Lepton_pt * (1 + smear * random_number);
+    return mc_smear;
+}
+
+// Function that compute the scale uncertainties
+// It is evaluated on the original MC variables BUT applied on the smeared pT
+// Beware that unlike the documentation (/https://gitlab.cern.ch/cms-nanoAOD/jsonpog-integration/-/blob/master/examples/egmScaleAndSmearingExample.py)
+// all the inputs are needed for the evaluation
+double ele_unc_scale(double run, double sc_eta, double Electron_r9, double Lepton_pt, double Electron_seedGain, double Lepton_newPt, string updn)
+{
+    double unc_scale = elecset_scale->evaluate({"escale", run, sc_eta, Electron_r9, abs(sc_eta), Lepton_pt, Electron_seedGain});
+    double mc_unc_scale = Lepton_newPt;
+    if (updn=="up")
+    {
+        mc_unc_scale = (1 + unc_scale) * Lepton_newPt;
+    }
+    else if (updn=="dn")
+    {
+        mc_unc_scale = (1 - unc_scale) * Lepton_newPt;
+    }
+    else 
+    {
+        cout << "ERROR: updn must be 'up' or 'dn'" << endl;
+    }
+    return mc_unc_scale;
+}
\ No newline at end of file
diff --git a/mkShapesRDF/processor/data/electron_scale/scEta.cc b/mkShapesRDF/processor/data/electron_scale/scEta.cc
new file mode 100644
index 00000000..3dbbc972
--- /dev/null
+++ b/mkShapesRDF/processor/data/electron_scale/scEta.cc
@@ -0,0 +1,50 @@
+#include <iostream>
+#include <cmath>
+
+using namespace std;
+
+// This macro is used to compute the supercluster eta for electrons and photons as described here: 
+// https://twiki.cern.ch/twiki/bin/view/CMS/EgammaNanoAOD#How_to_get_photon_supercluster_e
+
+float scEta(float eta, float phi, float PV_x, float PV_y, float PV_z) 
+{
+
+    float tg_theta_over_2 = exp(-eta);
+    float tg_theta = 2 * tg_theta_over_2 / (1 - tg_theta_over_2 * tg_theta_over_2);
+    float tg_sctheta;
+    if (abs(eta) <= 1.479) 
+    {
+        float R = 130;
+        float angle_x0_y0 = 0;
+        if (PV_x > 0) angle_x0_y0 = atan(PV_y / PV_x);
+        else if (PV_x < 0) angle_x0_y0 = M_PI + atan(PV_y / PV_x);
+        else if (PV_y > 0) angle_x0_y0 = M_PI / 2;
+        else angle_x0_y0 = -M_PI / 2;
+        float alpha = angle_x0_y0 + (M_PI - phi);
+        float sin_beta = sqrt(PV_x * PV_x + PV_y * PV_y) / R * sin(alpha);
+        float beta = abs(asin(sin_beta));
+        float gamma = M_PI / 2 - alpha - beta;
+        float l = sqrt(R * R + (PV_x * PV_x + PV_y * PV_y) - 2 * R * sqrt(PV_x * PV_x + PV_y * PV_y) * cos(gamma));
+        float z0_zSC = l / tg_theta;
+        tg_sctheta = R / (PV_z + z0_zSC);
+    } 
+    else if (abs(eta) > 1.479) 
+    {
+        float intersection_z = (eta > 0) ? 310 : -310;
+        float base = intersection_z - PV_z;
+        float r = base * tg_theta;
+        float crystalX = PV_x + r * cos(phi);
+        float crystalY = PV_y + r * sin(phi);
+        tg_sctheta = sqrt(crystalX * crystalX + crystalY * crystalY) / intersection_z;
+    } 
+    else 
+    {
+        return eta;
+    }
+    float sctheta = atan(tg_sctheta);
+    if (sctheta < 0) sctheta += M_PI;
+    float tg_sctheta_over_2 = tan(sctheta / 2);
+    float SCEta = -log(tg_sctheta_over_2);
+    //cout << "Computed SCEta: " << SCEta << endl;
+    return SCEta;
+}
diff --git a/mkShapesRDF/processor/modules/LeptonScaleSmearing.py b/mkShapesRDF/processor/modules/LeptonScaleSmearing.py
index f78a2cb3..2197f1a2 100644
--- a/mkShapesRDF/processor/modules/LeptonScaleSmearing.py
+++ b/mkShapesRDF/processor/modules/LeptonScaleSmearing.py
@@ -10,16 +10,29 @@ def __init__(self, era, isData, framework_path=None):
         super().__init__("LeptonScaleSmearing")
 
         self.isData = isData
-        self.scale_path = ""
+        self.muonscale_path = ""
+        self.elescale_path = ""
+        self.macroele_path = ""
         self.columnsToDrop = []
-        self.correction_file = ""
+        self.muoncorrection_file = ""
+        self.elecorrection_file = ""
         
+        # The JSON files were recently added to cvmfs. If you can't find them in /processor/data/jsonpog-integration/POG/EGM, reinstall mkShapesRDF.
+
+        # The muon_scale folder contains the MuonScaRe.cc macro, which defines the functions for computing corrections. This folder also includes the necessary JSON files.
+
+        # The electron_scale folder contains the EleScaRe.cc and scEta.cc macros. EleScaRe.cc handles correction calculations, while scEta.cc computes the supercluster eta.
+
         if framework_path:
-            self.scale_path = framework_path.split("framework")[0] + "/processor/data/muon_scale"
+            self.muonscale_path = framework_path.split("framework")[0] + "/processor/data/muon_scale"
+            self.elescale_path = framework_path.split("framework")[0] + "/processor/data/jsonpog-integration/POG/EGM"
+            self.macroele_path = framework_path.split("framework")[0] + "/processor/data/electron_scale"
         else:
-            self.scale_path = os.path.dirname(os.path.dirname(__file__)).split("processor")[0] + "/processor/data/muon_scale"        
+            self.muonscale_path = os.path.dirname(os.path.dirname(__file__)).split("processor")[0] + "/processor/data/muon_scale"
+            self.elescale_path = os.path.dirname(os.path.dirname(__file__)).split("processor")[0] + "/processor/data/jsonpog-integration/POG/EGM"
+            self.macroele_path = os.path.dirname(os.path.dirname(__file__)).split("processor")[0] + "/processor/data/electron_scale"      
             
-        if "2022" or "2023" in era:
+        if "2022" in era or "2023" in era:
             self.prodTime = "Summer"
         else:
             print("LeptonScaleSmearing")
@@ -29,28 +42,55 @@ def __init__(self, era, isData, framework_path=None):
             
         year = re.findall(r'\d+', era)[0]
         key = era.split("Full20")[1].split("v")[0]
-        self.correction_file = self.scale_path + "/" + year + "_" + self.prodTime + key + ".json"
+        self.muoncorrection_file = self.muonscale_path + "/" + year + "_" + self.prodTime + key + ".json"
+        # We use the EtDependent corrections, as recommended here: https://twiki.cern.ch/twiki/bin/viewauth/CMS/EgammSFandSSRun3
+        self.elecorrection_file = self.elescale_path + "/" + year + "_" + self.prodTime + key + "/electronSS_EtDependent.json.gz"
+
+        # This section computes the `year_key` needed to access the correct part of the correction files.  
+        # Since valid year_keys are ['2022preEE', '2022postEE', '2023preBPIX', '2023postBPIX'],  
+        # the `year_key` must include at least one letter.  
 
-        print(f"LeptonScaleSmearing: running scale and smearing corrections for muons from {era}")
+        evaluator = correctionlib.CorrectionSet.from_file(self.elecorrection_file)
+        keys = list(evaluator.keys())
+        for key in keys:
+            year_key = key.split('_')[-1]            
+            if any(c.isalpha() for c in year_key):  
+                self.year_key = year_key  
+                break 
+        print(self.year_key) 
+
+        print(f"LeptonScaleSmearing: running scale and smearing corrections for leptons from {era}")
         
     def runModule(self, df, values):
-
+        # Note that the elecset_scale is a CompoundCorrection object
         ROOT.gROOT.ProcessLine(
-            f'auto cset = correction::CorrectionSet::from_file("{self.correction_file}");'
+            f'auto cset = correction::CorrectionSet::from_file("{self.muoncorrection_file}");'
+            f'auto elecset = correction::CorrectionSet::from_file("{self.elecorrection_file}");'
+            f'correction::CompoundCorrection::Ref elecset_scale = elecset->compound().at("EGMScale_Compound_Ele_{self.year_key}");'
+            f'auto elecset_smear = elecset->at("EGMSmearAndSyst_ElePTsplit_{self.year_key}");'
         )
-        ROOT.gROOT.ProcessLine(f'#include "{self.scale_path}/MuonScaRe.cc"')
 
-        ## Function to loop over muons and run the scale corrections
-        if not hasattr(ROOT, "doMuonScale"):
+        ROOT.gROOT.ProcessLine(f'#include "{self.muonscale_path}/MuonScaRe.cc"')
+        ROOT.gROOT.ProcessLine(f'#include "{self.macroele_path}/scEta.cc"')
+        ROOT.gROOT.ProcessLine(f'#include "{self.macroele_path}/EleScaRe.cc"')
+        ROOT.gROOT.ProcessLine('#include <TRandom3.h>')
+
+        # Function to loop over leptons and run the scale corrections. For more details, refer to the macros.
+        if not hasattr(ROOT, "doLeptonScale"):
             ROOT.gInterpreter.Declare(
                 """
-                std::vector<RVecF> doMuonScale(RVecF Lepton_pt,
+                std::vector<RVecF> doLeptonScale(RVecF Lepton_pt,
                                                RVecF Lepton_phi,
                                                RVecF Lepton_eta,
                                                RVecI Muon_charge,
                                                RVecI Lepton_pdgId,
                                                RVecI Lepton_muonIdx,
                                                RVecI Muon_nTrackerLayers,
+                                               RVecF Electron_seedGain,
+                                               RVecF Electron_r9,
+                                               RVecI Lepton_electronIdx,
+                                               float run,
+                                               float PV_x, float PV_y, float PV_z,
                                                bool is_data)
                 {
                     
@@ -60,19 +100,32 @@ def runModule(self, df, values):
                     RVecF Lepton_newPt_ScaleDn = Lepton_pt;
                     RVecF Lepton_newPt_ResUp = Lepton_pt;
                     RVecF Lepton_newPt_ResDn = Lepton_pt;
+
+                    static TRandom3 rng(125);
                     
                     std::vector<RVecF> results = {Lepton_newPt, Lepton_newPt_ScaleUp, Lepton_newPt_ScaleDn, Lepton_newPt_ResUp, Lepton_newPt_ResDn};
                     
                     if (is_data){
-                        for (unsigned int i=0; i<Lepton_pt.size(); i++){
+                        for (unsigned int i=0; i<Lepton_pt.size(); i++)
+                        {
                             if (abs(Lepton_pdgId[i])==13)
-                            Lepton_newPt[i] = pt_scale(is_data, Lepton_pt[i], Lepton_eta[i], Lepton_phi[i], Muon_charge[Lepton_muonIdx[i]]);                            
+                            {
+                                Lepton_newPt[i] = pt_scale(is_data, Lepton_pt[i], Lepton_eta[i], Lepton_phi[i], Muon_charge[Lepton_muonIdx[i]]);
+                            }
+                            else if (abs(Lepton_pdgId[i])==11)
+                            {
+                                float sc_eta = scEta(Lepton_eta[i], Lepton_phi[i], PV_x, PV_y, PV_z);
+                                Lepton_newPt[i] = ele_scale(run, sc_eta, Electron_r9[Lepton_electronIdx[i]], Lepton_pt[i], (float)Electron_seedGain[Lepton_electronIdx[i]]);
+
+                            }
                         }
                         results[0] = Lepton_newPt;
                     }else{
-                        for (unsigned int i=0; i<Lepton_pt.size(); i++){
+                        for (unsigned int i=0; i<Lepton_pt.size(); i++)
+                        {
                             tmpScale_mc = -99.9;
-                            if (abs(Lepton_pdgId[i])==13){
+                            if (abs(Lepton_pdgId[i])==13)
+                            {
                                 tmpScale_mc = pt_scale(is_data, Lepton_pt[i], Lepton_eta[i], Lepton_phi[i], Muon_charge[Lepton_muonIdx[i]]);
                                 Lepton_newPt[i] = pt_resol(tmpScale_mc, Lepton_eta[i], (float)Muon_nTrackerLayers[Lepton_muonIdx[i]]);
                                 Lepton_newPt_ScaleUp[i] = pt_scale_var(Lepton_newPt[i], Lepton_eta[i], Lepton_phi[i], Muon_charge[Lepton_muonIdx[i]], "up");
@@ -80,6 +133,16 @@ def runModule(self, df, values):
                                 Lepton_newPt_ResUp[i] = pt_resol_var(tmpScale_mc, Lepton_newPt[i], Lepton_eta[i], "up");
                                 Lepton_newPt_ResDn[i] = pt_resol_var(tmpScale_mc, Lepton_newPt[i], Lepton_eta[i], "dn");
                             }
+                            else if (abs(Lepton_pdgId[i])==11)
+                            {
+                                double sc_eta = scEta(Lepton_eta[i], Lepton_phi[i], PV_x, PV_y, PV_z);
+                                double random_number = rng.Gaus(0.0, 1.0);
+                                Lepton_newPt[i] = ele_smear(Lepton_pt[i], Electron_r9[Lepton_electronIdx[i]], sc_eta, random_number);
+                                Lepton_newPt_ScaleUp[i] = ele_unc_scale(run, sc_eta, Electron_r9[Lepton_electronIdx[i]], Lepton_pt[i], (float)Electron_seedGain[Lepton_electronIdx[i]], Lepton_newPt[i], "up");
+                                Lepton_newPt_ScaleDn[i] = ele_unc_scale(run, sc_eta, Electron_r9[Lepton_electronIdx[i]], Lepton_pt[i], (float)Electron_seedGain[Lepton_electronIdx[i]], Lepton_newPt[i], "dn");
+                                Lepton_newPt_ResUp[i] = ele_unc_smear(Lepton_pt[i], Electron_r9[Lepton_electronIdx[i]], sc_eta, random_number, "up");
+                                Lepton_newPt_ResDn[i] = ele_unc_smear(Lepton_pt[i], Electron_r9[Lepton_electronIdx[i]], sc_eta, random_number, "dn");
+                            }
                         }
                         results[0] = Lepton_newPt;
                         results[1] = Lepton_newPt_ScaleUp;
@@ -92,6 +155,7 @@ def runModule(self, df, values):
                 """
             )
 
+
         # Order pT again
         if not hasattr(ROOT, "sortedIndices"):
             ROOT.gInterpreter.Declare(
@@ -119,10 +183,10 @@ def runModule(self, df, values):
                     TLorentzVector Lepton_new;
 
                     MET.SetPtEtaPhiM(MET_pt, 0.0, MET_phi, 0.0);
-                    for (unsigned int i=0; i<Lepton_pt.size(); i++){
-                        if (abs(Lepton_pdgId[i])!=13)
+                    for (unsigned int i=0; i<Lepton_pt.size(); i++)
+                    {
+                        if ((abs(Lepton_pdgId[i]) != 13) && (abs(Lepton_pdgId[i]) != 11))
                             continue;
-
                         Lepton.SetPtEtaPhiM(Lepton_pt[i], Lepton_eta[i], Lepton_phi[i], 0.0);
                         Lepton_new.SetPtEtaPhiM(Lepton_newPt[i], Lepton_eta[i], Lepton_phi[i], 0.0);
                         MET = MET + Lepton_new - Lepton;
@@ -132,12 +196,13 @@ def runModule(self, df, values):
                 """
             )
 
-        #### Compute the muon scale and smearing corrections
+        #### Compute the lepton scale and smearing corrections
 
         isData = str(self.isData).lower()
+        print(isData)
         df = df.Define(
             "Lepton_ScaleSmearing",
-            f"doMuonScale(Lepton_pt, Lepton_phi, Lepton_eta, Muon_charge, Lepton_pdgId, Lepton_muonIdx, Muon_nTrackerLayers, {isData})"
+            f"doLeptonScale(Lepton_pt, Lepton_phi, Lepton_eta, Muon_charge, Lepton_pdgId, Lepton_muonIdx, Muon_nTrackerLayers,  Electron_seedGain, Electron_r9, Lepton_electronIdx, run, PV_x, PV_y, PV_z, {isData})"
         )
 
         df = df.Define(
@@ -153,15 +218,15 @@ def runModule(self, df, values):
             df = df.Define(
                 "Lepton_pt_ScaleDo",
                 "Lepton_ScaleSmearing[2]"
-	    )
+	        )
             df = df.Define(
                 "Lepton_pt_ResUp",
                 "Lepton_ScaleSmearing[3]"
-	    )
+	        )
             df = df.Define(
                 "Lepton_pt_ResDo",
                 "Lepton_ScaleSmearing[4]"
-	    )
+	        )
 
             #### Vary lepton pT for the following l2kin, l3kin, ....
 
@@ -170,34 +235,34 @@ def runModule(self, df, values):
                 "Lepton_pt",
                 "ROOT::RVec<ROOT::RVecF>{Lepton_pt_ScaleUp, Lepton_pt_ScaleDo}",
                 ["up", "do"],
-                "muonScale"
+                "leptonScale"
             )
             df = df.Vary(
                 "Lepton_pt",
                 "ROOT::RVec<ROOT::RVecF>{Lepton_pt_ResUp, Lepton_pt_ResDo}",
                 ["up", "do"],
-                "muonResolution"
+                "leptonResolution"
             )
 
         df = df.DropColumns("Lepton_ScaleSmearing")
 
-        ### Recompute MET with the corrected muon-pT
+        ### Recompute MET with the corrected lepton-pT
         df = df.Define(
-            "PuppiMET_MuonScale",
+            "PuppiMET_LeptonScale",
             "CorrectMET(Lepton_pt, Lepton_newPt, Lepton_phi, Lepton_eta, Lepton_pdgId, PuppiMET_pt, PuppiMET_phi)"
         )
         df = df.Define(
-            "MET_MuonScale",
+            "MET_LeptonScale",
             "CorrectMET(Lepton_pt, Lepton_newPt, Lepton_phi, Lepton_eta, Lepton_pdgId, PuppiMET_pt, PuppiMET_phi)"
         )
-        self.columnsToDrop.append("PuppiMET_MuonScale")
-        self.columnsToDrop.append("MET_MuonScale")
+        self.columnsToDrop.append("PuppiMET_LeptonScale")
+        self.columnsToDrop.append("MET_LeptonScale")
         
-        df = df.Redefine("PuppiMET_pt", "PuppiMET_MuonScale[0]")
-        df = df.Redefine("PuppiMET_phi", "PuppiMET_MuonScale[1]")
+        df = df.Redefine("PuppiMET_pt", "PuppiMET_LeptonScale[0]")
+        df = df.Redefine("PuppiMET_phi", "PuppiMET_LeptonScale[1]")
 
-        df = df.Redefine("MET_pt", "MET_MuonScale[0]")
-        df = df.Redefine("MET_phi", "MET_MuonScale[1]")
+        df = df.Redefine("MET_pt", "MET_LeptonScale[0]")
+        df = df.Redefine("MET_phi", "MET_LeptonScale[1]")
 
         #### Re-order lepton-pT again
             


Reviews:
user: login: mlizzo
body: Thank for the PR, looks very polished already! I've just added a comment related to the derivation of the electron eta supercluster, we can merge the code once you fix that. @NTrevisani please let us know if you have further comments
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-04-03T13:57:30Z
user: login: squinto5
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T14:02:21Z

Review comments:
diff_hunk: @@ -0,0 +1,50 @@
+#include <iostream>
+#include <cmath>
+
+using namespace std;
+
+// This macro is used to compute the supercluster eta for electrons and photons as described here: 
+// https://twiki.cern.ch/twiki/bin/view/CMS/EgammaNanoAOD#How_to_get_photon_supercluster_e
+
+float scEta(float eta, float phi, float PV_x, float PV_y, float PV_z) 
+{
+
+    float tg_theta_over_2 = exp(-eta);
+    float tg_theta = 2 * tg_theta_over_2 / (1 - tg_theta_over_2 * tg_theta_over_2);
+    float tg_sctheta;
+    if (abs(eta) <= 2.5) 
path: mkShapesRDF/processor/data/electron_scale/scEta.cc
user: login: mlizzo
body: Hi @squinto5 , if I look at the [EGM code](https://swmukher.web.cern.ch/swmukher/scEtaPhoNanoAOD.C) - which is for photons though, but it should be equivalent for electrons iiuc - they do a distinction based on the photon being reconstructed in either the barrel or the endcap. The abs(eta) limit for the ECAL barrel is 1.479, please update it - assuming we need to treat electrons and photons in the same way
created_at: 2025-04-03T13:29:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+#include <iostream>
+#include <cmath>
+
+using namespace std;
+
+// This macro is used to compute the supercluster eta for electrons and photons as described here: 
+// https://twiki.cern.ch/twiki/bin/view/CMS/EgammaNanoAOD#How_to_get_photon_supercluster_e
+
+float scEta(float eta, float phi, float PV_x, float PV_y, float PV_z) 
+{
+
+    float tg_theta_over_2 = exp(-eta);
+    float tg_theta = 2 * tg_theta_over_2 / (1 - tg_theta_over_2 * tg_theta_over_2);
+    float tg_sctheta;
+    if (abs(eta) <= 2.5) 
+    {
+        float R = 130;
+        float angle_x0_y0 = 0;
+        if (PV_x > 0) angle_x0_y0 = atan(PV_y / PV_x);
+        else if (PV_x < 0) angle_x0_y0 = M_PI + atan(PV_y / PV_x);
+        else if (PV_y > 0) angle_x0_y0 = M_PI / 2;
+        else angle_x0_y0 = -M_PI / 2;
+        float alpha = angle_x0_y0 + (M_PI - phi);
+        float sin_beta = sqrt(PV_x * PV_x + PV_y * PV_y) / R * sin(alpha);
+        float beta = abs(asin(sin_beta));
+        float gamma = M_PI / 2 - alpha - beta;
+        float l = sqrt(R * R + (PV_x * PV_x + PV_y * PV_y) - 2 * R * sqrt(PV_x * PV_x + PV_y * PV_y) * cos(gamma));
+        float z0_zSC = l / tg_theta;
+        tg_sctheta = R / (PV_z + z0_zSC);
+    } 
+    else if (abs(eta) > 2.5) 
path: mkShapesRDF/processor/data/electron_scale/scEta.cc
user: login: mlizzo
body: Same as above
created_at: 2025-04-03T13:29:37Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+#include <iostream>
+#include <cmath>
+
+using namespace std;
+
+// This macro is used to compute the supercluster eta for electrons and photons as described here: 
+// https://twiki.cern.ch/twiki/bin/view/CMS/EgammaNanoAOD#How_to_get_photon_supercluster_e
+
+float scEta(float eta, float phi, float PV_x, float PV_y, float PV_z) 
+{
+
+    float tg_theta_over_2 = exp(-eta);
+    float tg_theta = 2 * tg_theta_over_2 / (1 - tg_theta_over_2 * tg_theta_over_2);
+    float tg_sctheta;
+    if (abs(eta) <= 2.5) 
+    {
+        float R = 130;
+        float angle_x0_y0 = 0;
+        if (PV_x > 0) angle_x0_y0 = atan(PV_y / PV_x);
+        else if (PV_x < 0) angle_x0_y0 = M_PI + atan(PV_y / PV_x);
+        else if (PV_y > 0) angle_x0_y0 = M_PI / 2;
+        else angle_x0_y0 = -M_PI / 2;
+        float alpha = angle_x0_y0 + (M_PI - phi);
+        float sin_beta = sqrt(PV_x * PV_x + PV_y * PV_y) / R * sin(alpha);
+        float beta = abs(asin(sin_beta));
+        float gamma = M_PI / 2 - alpha - beta;
+        float l = sqrt(R * R + (PV_x * PV_x + PV_y * PV_y) - 2 * R * sqrt(PV_x * PV_x + PV_y * PV_y) * cos(gamma));
+        float z0_zSC = l / tg_theta;
+        tg_sctheta = R / (PV_z + z0_zSC);
+    } 
+    else if (abs(eta) > 2.5) 
path: mkShapesRDF/processor/data/electron_scale/scEta.cc
user: login: squinto5
body: Thanks, it should be fixed now.
created_at: 2025-04-03T14:02:21Z
author_association: NONE

PR comments:
user: login: BlancoFS
created_at: 2025-04-03T13:29:46Z
body: Hi @squinto5, thank you very much for the PR! I only have one comment. 

It's seems like at the end the electron json files are taking from the `jsonpog-integration` folder. Then, is it really needed to have a copy within `mkShapesRDF/processor/data/electron_scale`?

Thanks!
Sergio
author_association: NONE
user: login: squinto5
created_at: 2025-04-03T13:35:15Z
body: > Hi @squinto5, thank you very much for the PR! I only have one comment.
> 
> It's seems like at the end the electron json files are taking from the `jsonpog-integration` folder. Then, is it really needed to have a copy within `mkShapesRDF/processor/data/electron_scale`?
> 
> Thanks! Sergio

Hi @BlancoFS, thank you for the quick review. Yes, you are right, I didn't realize I included those in the PR. I am deleting the additional files.

Thanks!
author_association: NONE
user: login: NTrevisani
created_at: 2025-04-03T14:08:14Z
body: Thanks! The PR looks good to me after a quick look.
author_association: NONE