Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-19 08:22:12

0001 // -*- C++ -*-
0002 #ifndef RIVET_ExptSmearingFunctions_HH
0003 #define RIVET_ExptSmearingFunctions_HH
0004 
0005 #include "Rivet/Tools/MomentumSmearingFunctions.hh"
0006 #include "Rivet/Tools/ParticleSmearingFunctions.hh"
0007 #include "Rivet/Tools/JetSmearingFunctions.hh"
0008 
0009 namespace Rivet {
0010 
0011 
0012   /// @defgroup smearing Detector smearing & efficiency functions
0013   ///
0014   /// A family of smearing functions must include the following names and return types:
0015   ///
0016   ///   ParticleEffFn <PARTICLE>_EFF_<FAMILY>_<IDCLASS>
0017   ///   ParticleSmearFn <PARTICLE>_SMEAR_<FAMILY>
0018   ///   ParticleEffFn TRK_EFF_<FAMILY>
0019   ///   ParticleSmearFn TRK_SMEAR_<FAMILY>
0020   ///   JetEffFn JET_BTAG_<FAMILY>_<TAGGER>
0021   ///   JetSmearFn JET_SMEAR_<FAMILY>
0022   ///   METSmearParamsFn MET_SMEARPARAMS_<FAMILY>
0023   ///   METSmearFn MET_SMEAR_<FAMILY>
0024   ///
0025   /// where @c <PARTICLE> is all of @c ELECTRON, @c MUON, @c PHOTON, and @c TAU,
0026   /// @c <IDCLASS> is all of @c LOOSE, @c MEDIUM, and @c TIGHT. The family name
0027   /// and tagger name are free (within the constraints of function naming. Providing
0028   /// a full set may require use of alias/wrapper functions and/or the predefined
0029   /// eff/smearing @c IDENTITY functions.
0030   ///
0031   /// @{
0032 
0033 
0034   /// @defgroup smearing_elec Experiment-specific electron efficiency and smearing functions
0035   /// @{
0036 
0037   /// ATLAS Run 1 electron reconstruction efficiency
0038   /// @todo Include reco eff (but no e/y discrimination) in forward region
0039   inline double ELECTRON_RECOEFF_ATLAS_RUN1(const Particle& e) {
0040     if (e.abspid() != PID::ELECTRON) return 0;
0041     if (e.abseta() > 2.5) return 0;
0042     if (e.pT() < 10*GeV) return 0;
0043     return (e.abseta() < 1.5) ? 0.95 : 0.85;
0044   }
0045 
0046   /// ATLAS Run 2 electron reco efficiency
0047   ///
0048   /// Based on https://arxiv.org/pdf/1902.04655.pdf Fig 2
0049   inline double ELECTRON_RECOEFF_ATLAS_RUN2(const Particle& e) {
0050     if (e.abspid() != PID::ELECTRON) return 0;
0051     const double et = e.Et();
0052     if (e.abseta() > 2.5 || e.Et() < 2*GeV) return 0;
0053     if (et > 25*GeV) return 0.97;
0054     if (et > 10*GeV) return 0.92 + (et/GeV-10)/15.*0.05;
0055     if (et >  6*GeV) return 0.85 + (et/GeV-6)/4.*0.07;
0056     if (et >  5*GeV) return 0.70 + (et/GeV-5)/1.*0.15;
0057     if (et >  2*GeV) return 0.00 + (et/GeV-2)/3.*0.70;
0058     return 0;
0059   }
0060 
0061 
0062   /// @brief ATLAS Run 2 'loose' electron reco+identification efficiency
0063   ///
0064   /// Values read from Fig 3 of ATL-PHYS-PUB-2015-041
0065   ///
0066   /// @todo What about faking by jets or non-electrons?
0067   inline double ELECTRON_EFF_ATLAS_RUN2_LOOSE(const Particle& e) {
0068     if (e.abspid() != PID::ELECTRON) return 0;
0069 
0070     // Manually symmetrised eta eff histogram
0071     const static vector<double> edges_eta = { 0.0,   0.1,   0.8,   1.37,  1.52,  2.01,  2.37,  2.47 };
0072     const static vector<double> effs_eta  = { 0.950, 0.965, 0.955, 0.885, 0.950, 0.935, 0.90 };
0073     // Et eff histogram (10-20 is a guess)
0074     const static vector<double> edges_et = { 0,   10,   20,   25,   30,   35,   40,    45,    50,   60,  80 };
0075     const static vector<double> effs_et  = { 0.0, 0.90, 0.91, 0.92, 0.94, 0.95, 0.955, 0.965, 0.97, 0.98, 0.98 };//Extra value extrapolated for overflow
0076 
0077     if (e.abseta() > 2.47) return 0.0; // no ID outside the tracker
0078 
0079     const int i_eta = binIndex(e.abseta(), edges_eta);
0080     const int i_et = binIndex(e.Et()/GeV, edges_et, true);
0081     const double eff = effs_et[i_et] * effs_eta[i_eta] / 0.95; //< norm factor as approximate double differential
0082     return min(eff, 1.0) * ELECTRON_RECOEFF_ATLAS_RUN2(e);
0083   }
0084 
0085   /// @brief Pretend that ATLAS Run 1 loose was the same as in Run 2
0086   inline double ELECTRON_EFF_ATLAS_RUN1_LOOSE(const Particle& e) {
0087     return ELECTRON_EFF_ATLAS_RUN2_LOOSE(e);
0088   }
0089 
0090 
0091   /// @brief ATLAS Run 1 'medium' electron reco+identification efficiency
0092   inline double ELECTRON_EFF_ATLAS_RUN1_MEDIUM(const Particle& e) {
0093     if (e.abspid() != PID::ELECTRON) return 0;
0094 
0095     const static vector<double> eta_edges_10 = {0.000, 0.049, 0.454, 1.107, 1.46, 1.790, 2.277, 2.500};
0096     const static vector<double> eta_vals_10  = {0.730, 0.757, 0.780, 0.771, 0.77, 0.777, 0.778};
0097 
0098     const static vector<double> eta_edges_15 = {0.000, 0.053, 0.456, 1.102, 1.463, 1.783, 2.263, 2.500};
0099     const static vector<double> eta_vals_15  = {0.780, 0.800, 0.819, 0.759, 0.749, 0.813, 0.829};
0100 
0101     const static vector<double> eta_edges_20 = {0.000, 0.065, 0.362, 0.719, 0.980, 1.289, 1.455, 1.681, 1.942, 2.239, 2.452, 2.500};
0102     const static vector<double> eta_vals_20  = {0.794, 0.806, 0.816, 0.806, 0.797, 0.774, 0.764, 0.788, 0.793, 0.806, 0.825};
0103 
0104     const static vector<double> eta_edges_25 = {0.000, 0.077, 0.338, 0.742, 1.004, 1.265, 1.467, 1.692, 1.940, 2.227, 2.452, 2.500};
0105     const static vector<double> eta_vals_25  = {0.833, 0.843, 0.853, 0.845, 0.839, 0.804, 0.790, 0.825, 0.830, 0.833, 0.839};
0106 
0107     const static vector<double> eta_edges_30 = {0.000, 0.077, 0.350, 0.707, 0.980, 1.289, 1.479, 1.681, 1.942, 2.239, 2.441, 2.500};
0108     const static vector<double> eta_vals_30  = {0.863, 0.872, 0.881, 0.874, 0.870, 0.824, 0.808, 0.847, 0.845, 0.840, 0.842};
0109 
0110     const static vector<double> eta_edges_35 = {0.000, 0.058, 0.344, 0.700, 1.009, 1.270, 1.458, 1.685, 1.935, 2.231, 2.468, 2.500};
0111     const static vector<double> eta_vals_35  = {0.878, 0.889, 0.901, 0.895, 0.893, 0.849, 0.835, 0.868, 0.863, 0.845, 0.832};
0112 
0113     const static vector<double> eta_edges_40 = {0.000, 0.047, 0.355, 0.699, 0.983, 1.280, 1.446, 1.694, 1.943, 2.227, 2.441, 2.500};
0114     const static vector<double> eta_vals_40  = {0.894, 0.901, 0.909, 0.905, 0.904, 0.875, 0.868, 0.889, 0.876, 0.848, 0.827};
0115 
0116     const static vector<double> eta_edges_45 = {0.000, 0.058, 0.356, 0.712, 0.997, 1.282, 1.459, 1.686, 1.935, 2.220, 2.444, 2.500};
0117     const static vector<double> eta_vals_45  = {0.900, 0.911, 0.923, 0.918, 0.917, 0.897, 0.891, 0.904, 0.894, 0.843, 0.796};
0118 
0119     const static vector<double> eta_edges_50 = {0.000, 0.059, 0.355, 0.711, 0.983, 1.280, 1.469, 1.682, 1.919, 2.227, 2.441, 2.500};
0120     const static vector<double> eta_vals_50  = {0.903, 0.913, 0.923, 0.922, 0.923, 0.903, 0.898, 0.908, 0.895, 0.831, 0.774};
0121 
0122     const static vector<double> eta_edges_60 = {0.000, 0.053, 0.351, 0.720, 1.006, 1.291, 1.469, 1.696, 1.946, 2.243, 2.455, 2.500};
0123     const static vector<double> eta_vals_60  = {0.903, 0.917, 0.928, 0.924, 0.927, 0.915, 0.911, 0.915, 0.899, 0.827, 0.760};
0124 
0125     const static vector<double> eta_edges_80 = {0.000, 0.053, 0.351, 0.720, 0.994, 1.292, 1.482, 1.708, 1.934, 2.220, 2.458, 2.500};
0126     const static vector<double> eta_vals_80  = {0.936, 0.942, 0.952, 0.956, 0.956, 0.934, 0.931, 0.944, 0.933, 0.940, 0.948};
0127 
0128     const static vector<double> et_edges = { 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 80 };
0129     const static vector< vector<double> > et_eta_edges = { eta_edges_10, eta_edges_15, eta_edges_20, eta_edges_25, eta_edges_30, eta_edges_35, eta_edges_40, eta_edges_45, eta_edges_50, eta_edges_60, eta_edges_80 };
0130     const static vector< vector<double> > et_eta_vals  = { eta_vals_10, eta_vals_15, eta_vals_20, eta_vals_25, eta_vals_30, eta_vals_35, eta_vals_40, eta_vals_45, eta_vals_50, eta_vals_60, eta_vals_80 };
0131 
0132     if (e.abseta() > 2.5 || e.Et() < 10*GeV) return 0.0;
0133     const int i_et = binIndex(e.Et()/GeV, et_edges, true);
0134     const int i_eta = binIndex(e.abseta(), et_eta_edges[i_et]);
0135     return et_eta_vals[i_et][i_eta] * ELECTRON_RECOEFF_ATLAS_RUN1(e);
0136   }
0137 
0138   /// @brief ATLAS Run 2 'medium' electron reco+identification efficiency
0139   ///
0140   /// ~1% increase over Run 1 informed by Fig 1 in https://cds.cern.ch/record/2157687/files/ATLAS-CONF-2016-024.pdf
0141   inline double ELECTRON_EFF_ATLAS_RUN2_MEDIUM(const Particle& e) {
0142     if (e.abspid() != PID::ELECTRON) return 0;
0143     return 1.01 * ELECTRON_EFF_ATLAS_RUN1_MEDIUM(e);
0144   }
0145 
0146 
0147   /// @brief ATLAS Run 1 'tight' electron reco+identification efficiency
0148   inline double ELECTRON_EFF_ATLAS_RUN1_TIGHT(const Particle& e) {
0149     if (e.abspid() != PID::ELECTRON) return 0;
0150 
0151     const static vector<double> eta_edges_10 = {0.000, 0.049, 0.459, 1.100, 1.461, 1.789, 2.270, 2.500};
0152     const static vector<double> eta_vals_10  = {0.581, 0.632, 0.668, 0.558, 0.548, 0.662, 0.690};
0153 
0154     const static vector<double> eta_edges_15 = {0.000, 0.053, 0.450, 1.096, 1.463, 1.783, 2.269, 2.500};
0155     const static vector<double> eta_vals_15 =  {0.630, 0.678, 0.714, 0.633, 0.616, 0.700, 0.733};
0156 
0157     const static vector<double> eta_edges_20 = {0.000, 0.065, 0.362, 0.719, 0.992, 1.277, 1.479, 1.692, 1.930, 2.227, 2.464, 2.500};
0158     const static vector<double> eta_vals_20 =  {0.653, 0.695, 0.735, 0.714, 0.688, 0.635, 0.625, 0.655, 0.680, 0.691, 0.674};
0159 
0160     const static vector<double> eta_edges_25 = {0.000, 0.077, 0.362, 0.719, 0.992, 1.300, 1.479, 1.692, 1.942, 2.227, 2.464, 2.500};
0161     const static vector<double> eta_vals_25 =  {0.692, 0.732, 0.768, 0.750, 0.726, 0.677, 0.667, 0.692, 0.710, 0.706, 0.679};
0162 
0163     const static vector<double> eta_edges_30 = {0.000, 0.053, 0.362, 0.719, 1.004, 1.277, 1.467, 1.681, 1.954, 2.239, 2.452, 2.500};
0164     const static vector<double> eta_vals_30 =  {0.724, 0.763, 0.804, 0.789, 0.762, 0.702, 0.690, 0.720, 0.731, 0.714, 0.681};
0165 
0166     const static vector<double> eta_edges_35 = {0.000, 0.044, 0.342, 0.711, 0.971, 1.280, 1.456, 1.683, 1.944, 2.218, 2.442, 2.500};
0167     const static vector<double> eta_vals_35 =  {0.736, 0.778, 0.824, 0.811, 0.784, 0.730, 0.718, 0.739, 0.743, 0.718, 0.678};
0168 
0169     const static vector<double> eta_edges_40 = {0.000, 0.047, 0.355, 0.699, 0.983, 1.268, 1.457, 1.671, 1.931, 2.204, 2.453, 2.500};
0170     const static vector<double> eta_vals_40 =  {0.741, 0.774, 0.823, 0.823, 0.802, 0.764, 0.756, 0.771, 0.771, 0.734, 0.684};
0171 
0172     const static vector<double> eta_edges_45 = {0.000, 0.056, 0.354, 0.711, 0.984, 1.280, 1.458, 1.684, 1.945, 2.207, 2.442, 2.500};
0173     const static vector<double> eta_vals_45 =  {0.758, 0.792, 0.841, 0.841, 0.823, 0.792, 0.786, 0.796, 0.794, 0.734, 0.663};
0174 
0175     const static vector<double> eta_edges_50 = {0.000, 0.059, 0.355, 0.699, 0.983, 1.268, 1.446, 1.682, 1.943, 2.216, 2.453, 2.500};
0176     const static vector<double> eta_vals_50 =  {0.771, 0.806, 0.855, 0.858, 0.843, 0.810, 0.800, 0.808, 0.802, 0.730, 0.653};
0177 
0178     const static vector<double> eta_edges_60 = {0.000, 0.050, 0.350, 0.707, 0.981, 1.278, 1.468, 1.694, 1.944, 2.242, 2.453, 2.500};
0179     const static vector<double> eta_vals_60 =  {0.773, 0.816, 0.866, 0.865, 0.853, 0.820, 0.812, 0.817, 0.804, 0.726, 0.645};
0180 
0181     const static vector<double> eta_edges_80 = {0.000, 0.051, 0.374, 0.720, 0.981, 1.279, 1.468, 1.707, 1.945, 2.207, 2.457, 2.500};
0182     const static vector<double> eta_vals_80 =  {0.819, 0.855, 0.899, 0.906, 0.900, 0.869, 0.865, 0.873, 0.869, 0.868, 0.859};
0183 
0184     const static vector<double> et_edges = { 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 80 };
0185     const static vector< vector<double> > et_eta_edges = { eta_edges_10, eta_edges_15, eta_edges_20, eta_edges_25, eta_edges_30, eta_edges_35, eta_edges_40, eta_edges_45, eta_edges_50, eta_edges_60, eta_edges_80 };
0186     const static vector< vector<double> > et_eta_vals  = { eta_vals_10, eta_vals_15, eta_vals_20, eta_vals_25, eta_vals_30, eta_vals_35, eta_vals_40, eta_vals_45, eta_vals_50, eta_vals_60, eta_vals_80 };
0187 
0188     if (e.abseta() > 2.5 || e.Et() < 10*GeV) return 0.0;
0189     const int i_et = binIndex(e.Et()/GeV, et_edges, true);
0190     const int i_eta = binIndex(e.abseta(), et_eta_edges[i_et]);
0191     return et_eta_vals[i_et][i_eta] * ELECTRON_RECOEFF_ATLAS_RUN1(e);
0192   }
0193 
0194   /// @brief ATLAS Run 2 'tight' electron reco+identification efficiency
0195   ///
0196   /// ~1% increase over Run 1 informed by Fig 1 in https://cds.cern.ch/record/2157687/files/ATLAS-CONF-2016-024.pdf
0197   inline double ELECTRON_EFF_ATLAS_RUN2_TIGHT(const Particle& e) {
0198     if (e.abspid() != PID::ELECTRON) return 0;
0199     const static vector<double> et_edges = { /* 10, 15, */ 20, 25, 30, 35, 40, 45, 50, 60, 80 };
0200     const static vector<double> et_effs = { 0.785, 0.805, 0.820, 0.830, 0.840, 0.850, 0.875, 0.910, 0.910 }; //Extra value extrapolated for overflow
0201     const static vector<double> eta_edges = {0.000, 0.051, 0.374, 0.720, 0.981, 1.279, 1.468, 1.707, 1.945, 2.207, 2.457, 2.500}; // from ET > 80 bin
0202     const static vector<double> eta_refs =    {0.819, 0.855, 0.899, 0.906, 0.900, 0.869, 0.865, 0.873, 0.869, 0.868, 0.859};
0203     if (e.abseta() > 2.5 || e.Et() < 20*GeV) return 0.0;
0204     const int i_et = binIndex(e.Et()/GeV, et_edges, true);
0205     const int i_eta = binIndex(e.abseta(), eta_edges);
0206     const double eff_et = et_effs[i_et]; //< integral eff
0207     // Scale to |eta| shape, following the ~85% efficient high-ET bin from Run 1
0208     const double eff = eff_et * (eta_refs[i_eta]/0.85) * ELECTRON_RECOEFF_ATLAS_RUN2(e);
0209     //return ELECTRON_IDEFF_ATLAS_RUN1_TIGHT(e);
0210     return eff;
0211   }
0212 
0213 
0214 
0215   /// ATLAS Run 1 electron reco smearing
0216   inline Particle ELECTRON_SMEAR_ATLAS_RUN1(const Particle& e) {
0217     static const vector<double> edges_eta = {0., 2.5, 3.};
0218     static const vector<double> edges_pt = {0., 0.1, 25.};
0219     static const vector<double> e2s = {0.000, 0.015, 0.005,
0220                                        0.005, 0.005, 0.005,
0221                                        0.107, 0.107, 0.107};
0222     static const vector<double> es = {0.00, 0.00, 0.05,
0223                                       0.05, 0.05, 0.05,
0224                                       2.08, 2.08, 2.08};
0225     static const vector<double> cs = {0.00, 0.00, 0.25,
0226                                       0.25, 0.25, 0.25,
0227                                       0.00, 0.00, 0.00};
0228 
0229     const int i_eta = binIndex(e.abseta(), edges_eta, true);
0230     const int i_pt = binIndex(e.pT()/GeV, edges_pt, true);
0231     const int i = i_eta*edges_pt.size() + i_pt;
0232 
0233     // Calculate absolute resolution in GeV
0234     const double c1 = sqr(e2s[i]), c2 = sqr(es[i]), c3 = sqr(cs[i]);
0235     const double resolution = sqrt(c1*e.E2() + c2*e.E() + c3) * GeV;
0236 
0237     // normal_distribution<> d(e.E(), resolution);
0238     // const double mass = e.mass2() > 0 ? e.mass() : 0; //< numerical carefulness...
0239     // const double smeared_E = max(d(gen), mass); //< can't let the energy go below the mass!
0240     // return Particle(e.pid(), FourMomentum::mkEtaPhiME(e.eta(), e.phi(), mass, smeared_E));
0241     return Particle(e.pid(), P4_SMEAR_E_GAUSS(e, resolution));
0242   }
0243 
0244 
0245   /// ATLAS Run 2 electron reco smearing
0246   ///
0247   /// @todo Currently just a copy of the Run 1 version: fix!
0248   inline Particle ELECTRON_SMEAR_ATLAS_RUN2(const Particle& e) {
0249     return ELECTRON_SMEAR_ATLAS_RUN1(e);
0250   }
0251 
0252 
0253   /// @todo Add charge flip efficiency?
0254 
0255 
0256 
0257   /// CMS Run 1 electron reconstruction efficiency
0258   inline double ELECTRON_EFF_CMS_RUN1(const Particle& e) {
0259     if (e.abspid() != PID::ELECTRON) return 0;
0260     if (e.abseta() > 2.5) return 0;
0261     if (e.pT() < 10*GeV) return 0;
0262     return (e.abseta() < 1.5) ? 0.95 : 0.85;
0263   }
0264   /// CMS Run 1 loose electron reconstruction efficiency
0265   /// @todo Just an alias to generic: improve!
0266   inline double ELECTRON_EFF_CMS_RUN1_LOOSE(const Particle& e) { return ELECTRON_EFF_CMS_RUN1(e); }
0267   /// CMS Run 1 medium electron reconstruction efficiency
0268   /// @todo Just an alias to generic: improve!
0269   inline double ELECTRON_EFF_CMS_RUN1_MEDIUM(const Particle& e) { return ELECTRON_EFF_CMS_RUN1(e); }
0270   /// CMS Run 1 tight electron reconstruction efficiency
0271   /// @todo Just an alias to generic: improve!
0272   inline double ELECTRON_EFF_CMS_RUN1_TIGHT(const Particle& e) { return ELECTRON_EFF_CMS_RUN1(e); }
0273 
0274 
0275   /// CMS Run 2 electron reco efficiency
0276   ///
0277   /// @todo Currently just a copy of Run 1: fix!
0278   inline double ELECTRON_EFF_CMS_RUN2(const Particle& e) {
0279     if (e.abspid() != PID::ELECTRON) return 0;
0280     return ELECTRON_EFF_CMS_RUN1(e);
0281   }
0282   /// CMS Run 2 loose electron reconstruction efficiency
0283   /// @todo Just an alias to generic: improve!
0284   inline double ELECTRON_EFF_CMS_RUN2_LOOSE(const Particle& e) { return ELECTRON_EFF_CMS_RUN2(e); }
0285   /// CMS Run 2 medium electron reconstruction efficiency
0286   /// @todo Just an alias to generic: improve!
0287   inline double ELECTRON_EFF_CMS_RUN2_MEDIUM(const Particle& e) { return ELECTRON_EFF_CMS_RUN2(e); }
0288   /// CMS Run 2 tight electron reconstruction efficiency
0289   /// @todo Just an alias to generic: improve!
0290   inline double ELECTRON_EFF_CMS_RUN2_TIGHT(const Particle& e) { return ELECTRON_EFF_CMS_RUN2(e); }
0291 
0292 
0293   /// @brief CMS electron energy smearing, preserving direction
0294   ///
0295   /// Calculate resolution
0296   /// for pT > 0.1 GeV, E resolution = |eta| < 0.5 -> sqrt(0.06^2 + pt^2 * 1.3e-3^2)
0297   ///                                  |eta| < 1.5 -> sqrt(0.10^2 + pt^2 * 1.7e-3^2)
0298   ///                                  |eta| < 2.5 -> sqrt(0.25^2 + pt^2 * 3.1e-3^2)
0299   inline Particle ELECTRON_SMEAR_CMS_RUN1(const Particle& e) {
0300     // Calculate absolute resolution in GeV from functional form
0301     double resolution = 0;
0302     const double abseta = e.abseta();
0303     if (e.pT() > 0.1*GeV && abseta < 2.5) { //< should be a given from efficiencies
0304       if (abseta < 0.5) {
0305         resolution = add_quad(0.06, 1.3e-3 * e.pT()/GeV) * GeV;
0306       } else if (abseta < 1.5) {
0307         resolution = add_quad(0.10, 1.7e-3 * e.pT()/GeV) * GeV;
0308       } else { // still |eta| < 2.5
0309         resolution = add_quad(0.25, 3.1e-3 * e.pT()/GeV) * GeV;
0310       }
0311     }
0312 
0313     // normal_distribution<> d(e.E(), resolution);
0314     // const double mass = e.mass2() > 0 ? e.mass() : 0; //< numerical carefulness...
0315     // const double smeared_E = max(d(gen), mass); //< can't let the energy go below the mass!
0316     // return Particle(e.pid(), FourMomentum::mkEtaPhiME(e.eta(), e.phi(), mass, smeared_E));
0317     return Particle(e.pid(), P4_SMEAR_E_GAUSS(e, resolution));
0318   }
0319 
0320   /// CMS Run 2 electron reco smearing
0321   ///
0322   /// @todo Currently just a copy of the Run 1 version: fix!
0323   inline Particle ELECTRON_SMEAR_CMS_RUN2(const Particle& e) {
0324     return ELECTRON_SMEAR_CMS_RUN1(e);
0325   }
0326 
0327   /// @}
0328 
0329 
0330 
0331   /// @defgroup smearing_photon Experiment-specific photon efficiency and smearing functions
0332   /// @{
0333 
0334   /// @brief ATLAS Run 2 photon reco efficiency
0335   ///
0336   /// Taken from converted photons, Fig 8, in arXiv:1606.01813
0337   inline double PHOTON_EFF_ATLAS_RUN1(const Particle& y) {
0338     if (y.abspid() != PID::PHOTON) return 0; ///< @todo Allow electron misID? What about jet misID?
0339 
0340     if (y.pT() < 10*GeV) return 0;
0341     if (inRange(y.abseta(), 1.37, 1.52) || y.abseta() > 2.37) return 0;
0342 
0343     static const vector<double> edges_eta = {0., 0.6, 1.37, 1.52, 1.81, 2.37};
0344     static const vector<double> edges_pt = {10., 15., 20., 25., 30., 35., 40., 45.,
0345                                             50., 60., 80., 100., 125., 150., 175., 250.};
0346     static const vector<double> effs = {0.53, 0.65, 0.73, 0.83, 0.86, 0.93, 0.94, 0.96,
0347                                         0.97, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98,//
0348                                         0.45, 0.57, 0.67, 0.74, 0.84, 0.87, 0.93, 0.94,
0349                                         0.95, 0.96, 0.97, 0.98, 0.98, 0.99, 0.99, 0.99,//
0350                                         0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
0351                                         0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,//
0352                                         0.48, 0.56, 0.68, 0.76, 0.86, 0.90, 0.93, 0.95,
0353                                         0.96, 0.97, 0.98, 0.99, 0.99, 1.00, 1.00, 1.00,//
0354                                         0.50, 0.61, 0.74, 0.82, 0.88, 0.92, 0.94, 0.95,
0355                                         0.96, 0.97, 0.98, 0.98, 0.98, 0.98, 0.99, 0.99};
0356 
0357     const int i_eta = binIndex(y.abseta(), edges_eta);
0358     const int i_pt = binIndex(y.pT()/GeV, edges_pt, true);
0359     const int i = i_eta*edges_pt.size() + i_pt;
0360     const double eff = effs[i];
0361     return eff;
0362   }
0363   /// ATLAS Run 1 loose photon reconstruction efficiency
0364   /// @todo Just an alias to generic: improve!
0365   inline double PHOTON_EFF_ATLAS_RUN1_LOOSE(const Particle& y) { return PHOTON_EFF_ATLAS_RUN1(y); }
0366   /// ATLAS Run 1 medium photon reconstruction efficiency
0367   /// @todo Just an alias to generic: improve!
0368   inline double PHOTON_EFF_ATLAS_RUN1_MEDIUM(const Particle& y) { return PHOTON_EFF_ATLAS_RUN1(y); }
0369   /// ATLAS Run 1 tight photon reconstruction efficiency
0370   /// @todo Just an alias to generic: improve!
0371   inline double PHOTON_EFF_ATLAS_RUN1_TIGHT(const Particle& y) { return PHOTON_EFF_ATLAS_RUN1(y); }
0372 
0373 
0374   /// @brief ATLAS Run 2 photon reco efficiency
0375   ///
0376   /// Taken from converted photons, Fig 6, in ATL-PHYS-PUB-2016-014
0377   inline double PHOTON_EFF_ATLAS_RUN2(const Particle& y) {
0378     if (y.abspid() != PID::PHOTON) return 0; ///< @todo Allow electron misID? What about jet misID?
0379 
0380     if (y.pT() < 10*GeV) return 0;
0381     if (inRange(y.abseta(), 1.37, 1.52) || y.abseta() > 2.37) return 0;
0382 
0383     static const vector<double> edges_eta = {0., 0.6, 1.37, 1.52, 1.81, 2.37};
0384     static const vector<double> edges_pt = {10., 15., 20., 25., 30., 35., 40., 45.,
0385                                             50., 60., 80., 100., 125., 150., 175., 250.};
0386     static const vector<double> effs = {0.55, 0.70, 0.85, 0.89, 0.93, 0.95, 0.96, 0.96,
0387                                         0.97, 0.97, 0.98, 0.97, 0.97, 0.97, 0.97, 0.97,//
0388                                         0.47, 0.66, 0.79, 0.86, 0.89, 0.94, 0.96, 0.97,
0389                                         0.97, 0.98, 0.97, 0.98, 0.98, 0.98, 0.98, 0.98,//
0390                                         0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
0391                                         0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,//
0392                                         0.54, 0.71, 0.84, 0.88, 0.92, 0.93, 0.94, 0.95,
0393                                         0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96,//
0394                                         0.61, 0.74, 0.83, 0.88, 0.91, 0.94, 0.95, 0.96,
0395                                         0.97, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98};
0396 
0397     const int i_eta = binIndex(y.abseta(), edges_eta);
0398     const int i_pt = binIndex(y.pT()/GeV, edges_pt, true);
0399     const int i = i_eta*edges_pt.size() + i_pt;
0400     const double eff = effs[i];
0401     return eff;
0402   }
0403   /// ATLAS Run 2 loose photon reconstruction efficiency
0404   /// @todo Just an alias to generic: improve!
0405   inline double PHOTON_EFF_ATLAS_RUN2_LOOSE(const Particle& y) { return PHOTON_EFF_ATLAS_RUN2(y); }
0406   /// ATLAS Run 2 medium photon reconstruction efficiency
0407   /// @todo Just an alias to generic: improve!
0408   inline double PHOTON_EFF_ATLAS_RUN2_MEDIUM(const Particle& y) { return PHOTON_EFF_ATLAS_RUN2(y); }
0409   /// ATLAS Run 2 tight photon reconstruction efficiency
0410   /// @todo Just an alias to generic: improve!
0411   inline double PHOTON_EFF_ATLAS_RUN2_TIGHT(const Particle& y) { return PHOTON_EFF_ATLAS_RUN2(y); }
0412 
0413 
0414   /// CMS Run 1 photon reco efficiency
0415   ///
0416   /// @todo Currently from Delphes
0417   inline double PHOTON_EFF_CMS_RUN1(const Particle& y) {
0418     if (y.abspid() != PID::PHOTON) return 0; ///< @todo Allow electron misID? What about jet misID?
0419     if (y.pT() < 10*GeV || y.abseta() > 2.5) return 0;
0420     return (y.abseta() < 1.5) ? 0.95 : 0.85;
0421   }
0422   /// CMS Run 1 loose photon reconstruction efficiency
0423   /// @todo Just an alias to generic: improve!
0424   inline double PHOTON_EFF_CMS_RUN1_LOOSE(const Particle& y) { return PHOTON_EFF_CMS_RUN1(y); }
0425   /// CMS Run 1 medium photon reconstruction efficiency
0426   /// @todo Just an alias to generic: improve!
0427   inline double PHOTON_EFF_CMS_RUN1_MEDIUM(const Particle& y) { return PHOTON_EFF_CMS_RUN1(y); }
0428   /// CMS Run 1 tight photon reconstruction efficiency
0429   /// @todo Just an alias to generic: improve!
0430   inline double PHOTON_EFF_CMS_RUN1_TIGHT(const Particle& y) { return PHOTON_EFF_CMS_RUN1(y); }
0431 
0432 
0433   /// CMS Run 2 photon reco efficiency
0434   ///
0435   /// @todo Currently just a copy of Run 1: fix!
0436   inline double PHOTON_EFF_CMS_RUN2(const Particle& y) {
0437     if (y.abspid() != PID::PHOTON) return 0; ///< @todo Allow electron misID? What about jet misID?
0438     return PHOTON_EFF_CMS_RUN1(y);
0439   }
0440   /// CMS Run 2 loose photon reconstruction efficiency
0441   /// @todo Just an alias to generic: improve!
0442   inline double PHOTON_EFF_CMS_RUN2_LOOSE(const Particle& y) { return PHOTON_EFF_CMS_RUN2(y); }
0443   /// CMS Run 2 medium photon reconstruction efficiency
0444   /// @todo Just an alias to generic: improve!
0445   inline double PHOTON_EFF_CMS_RUN2_MEDIUM(const Particle& y) { return PHOTON_EFF_CMS_RUN2(y); }
0446   /// CMS Run 2 tight photon reconstruction efficiency
0447   /// @todo Just an alias to generic: improve!
0448   inline double PHOTON_EFF_CMS_RUN2_TIGHT(const Particle& y) { return PHOTON_EFF_CMS_RUN2(y); }
0449 
0450 
0451   /// @todo Use real photon smearing
0452   inline Particle PHOTON_SMEAR_ATLAS_RUN1(const Particle& y) { return y; }
0453   inline Particle PHOTON_SMEAR_ATLAS_RUN2(const Particle& y) { return y; }
0454   inline Particle PHOTON_SMEAR_CMS_RUN1(const Particle& y) { return y; }
0455   inline Particle PHOTON_SMEAR_CMS_RUN2(const Particle& y) { return y; }
0456 
0457   /// @}
0458 
0459 
0460 
0461   /// @defgroup smearing_muon Experiment-specific muon efficiency and smearing functions
0462   /// @{
0463 
0464   /// ATLAS Run 1 muon reco efficiency
0465   inline double MUON_EFF_ATLAS_RUN1(const Particle& m) {
0466     if (m.abspid() != PID::MUON) return 0;
0467     if (m.abseta() > 2.7) return 0;
0468     if (m.pT() < 10*GeV) return 0;
0469     return (m.abseta() < 1.5) ? 0.95 : 0.85;
0470   }
0471   /// ATLAS Run 1 loose muon reconstruction efficiency
0472   /// @todo Just an alias to generic: improve!
0473   inline double MUON_EFF_ATLAS_RUN1_LOOSE(const Particle& m) { return MUON_EFF_ATLAS_RUN1(m); }
0474   /// ATLAS Run 1 medium muon reconstruction efficiency
0475   /// @todo Just an alias to generic: improve!
0476   inline double MUON_EFF_ATLAS_RUN1_MEDIUM(const Particle& m) { return MUON_EFF_ATLAS_RUN1(m); }
0477   /// ATLAS Run 1 tight muon reconstruction efficiency
0478   /// @todo Just an alias to generic: improve!
0479   inline double MUON_EFF_ATLAS_RUN1_TIGHT(const Particle& m) { return MUON_EFF_ATLAS_RUN1(m); }
0480 
0481 
0482   /// ATLAS Run 2 muon reco efficiency
0483   ///
0484   /// From https://arxiv.org/pdf/1603.05598.pdf , Fig3 top
0485   inline double MUON_RECOEFF_ATLAS_RUN2(const Particle& m) {
0486     if (m.abspid() != PID::MUON) return 0;
0487     if (m.abseta() > 2.5) return 0;
0488     if (m.abseta() < 0.1) return 0.61;
0489     // if (m.pT() < 10*GeV) return 0;
0490     return (m.abseta() < 1) ? 0.98 : 0.99;
0491   }
0492 
0493 
0494   /// @brief ATLAS Run 2 muon isolation efficiency
0495   ///
0496   /// For loose ID, from 21 of https://arxiv.org/pdf/2012.00578
0497   inline double MUON_ISOEFF_ATLAS_RUN2_LOOSE(const Particle& m) {
0498     if (m.abspid() != PID::MUON) return 0;
0499     static const vector<double> edges_pt = {0., 3.,   4.,   5.,   6.,   7.,   8.,   9.,   10.,  15.,  20.,  30.,  40.};
0500     static const vector<double> effs_pt =  {0., 0.58, 0.70, 0.75, 0.80, 0.82, 0.85, 0.87, 0.90, 0.95, 0.98, 1.00, 1.00};
0501     const int i_pt = binIndex(m.pT()/GeV, edges_pt, true);
0502     const double eff_pt = effs_pt[i_pt];
0503     return eff_pt;
0504   }
0505 
0506 
0507   /// @brief ATLAS Run 2 muon reco+ID efficiency
0508   ///
0509   /// For loose ID, from Fig 12 of https://arxiv.org/pdf/2012.00578
0510   inline double MUON_EFF_ATLAS_RUN2_LOOSE(const Particle& m) {
0511     if (m.abspid() != PID::MUON) return 0;
0512     if (m.abseta() > 2.5) return 0;
0513     static const vector<double> edges_pt = {0.,   3.5,  4.,   5.,   6.,   7.,   8.,   10.};
0514     static const vector<double> effs =     {0.00, 0.88, 0.92, 0.97, 0.98, 0.98, 0.98, 0.99};
0515     const int i_pt = binIndex(m.pT()/GeV, edges_pt, true);
0516     const double eff = effs[i_pt];
0517     // no eta dependence
0518     return eff * MUON_ISOEFF_ATLAS_RUN2_LOOSE(m);
0519   }
0520 
0521 
0522   /// @brief ATLAS Run 2 muon reco+ID efficiency
0523   ///
0524   /// For medium ID, from Fig 12+13 of https://arxiv.org/pdf/2012.00578
0525   inline double MUON_EFF_ATLAS_RUN2_MEDIUM(const Particle& m) {
0526     if (m.abspid() != PID::MUON) return 0;
0527     if (m.abseta() > 2.5) return 0;
0528     static const vector<double> edges_pt = {0.,   3.5,  4.,   5.,   6.,   7.,   8.,   9.,   10.,  12.,  14.,  16.,  18.,  20.,  25.,  30.,  35.,  40.,  45.,  50.};
0529     static const vector<double> effs_pt =  {0.00, 0.48, 0.62, 0.82, 0.94, 0.95, 0.96, 0.96, 0.97, 0.97, 0.97, 0.97, 0.97, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98};
0530     const int i_pt = binIndex(m.pT()/GeV, edges_pt, true);
0531     const double eff_pt = effs_pt[i_pt];
0532     static const vector<double> edges_eta = {0.,   0.25, 0.50, 0.75, 1.00, 1.25, 1.50, 1.75, 2.00, 2.25, 2.50};
0533     static const vector<double> effs_eta =  {0.85, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99};
0534     const int i_eta = binIndex(m.abseta(), edges_eta, true);
0535     const double eff_eta = effs_eta[i_eta] / 0.99; //< norm factor as approximate double differential
0536     return eff_pt*eff_eta * MUON_ISOEFF_ATLAS_RUN2_LOOSE(m);
0537   }
0538 
0539 
0540   /// @brief ATLAS Run 2 muon reco+ID efficiency
0541   ///
0542   /// For tight ID, from Fig 12 of https://arxiv.org/pdf/2012.00578
0543   inline double MUON_EFF_ATLAS_RUN2_TIGHT(const Particle& m) {
0544     if (m.abspid() != PID::MUON) return 0;
0545     if (m.abseta() > 2.5) return 0;
0546     static const vector<double> edges_pt = {0.,   3.5,  4.,   5.,   6.,   7.,   8.,   9.,   10.,  12.,  14.,  16.,  18.,  20.};
0547     static const vector<double> effs_pt =  {0.00, 0.00, 0.68, 0.81, 0.85, 0.88, 0.89, 0.90, 0.91, 0.92, 0.92, 0.92, 0.93, 0.93};
0548     const int i_pt = binIndex(m.pT()/GeV, edges_pt, true);
0549     const double eff_pt = effs_pt[i_pt];
0550     static const vector<double> edges_eta = {0.,   0.25, 0.50, 0.75, 1.00, 1.25, 1.50, 1.75, 2.00, 2.25, 2.50};
0551     static const vector<double> effs_eta =  {0.78, 0.94, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96};
0552     const int i_eta = binIndex(m.abseta(), edges_eta, true);
0553     const double eff_eta = effs_eta[i_eta] / 0.96; //< norm factor as approximate double differential
0554     return eff_pt*eff_eta * MUON_ISOEFF_ATLAS_RUN2_LOOSE(m);
0555   }
0556 
0557 
0558   /// ATLAS Run 1 muon reco smearing
0559   ///
0560   /// Based on Delphes R1 resolution, augmented with high-pT scaling
0561   /// based on https://arxiv.org/pdf/2012.00578 Fig 2 .
0562   inline Particle MUON_SMEAR_ATLAS_RUN1(const Particle& m) {
0563     static const vector<double> edges_eta = {0, 1.5, 2.5};
0564     static const vector<double> edges_pt = {0, 0.1, 1.0, 10., 200.};
0565     static const vector<double> res = {0., 0.03, 0.02, 0.03, 0.05,
0566                                        0., 0.04, 0.03, 0.04, 0.05};
0567 
0568     const int i_eta = binIndex(m.abseta(), edges_eta);
0569     const int i_pt = binIndex(m.pT()/GeV, edges_pt, true);
0570     const int i = i_eta*edges_pt.size() + i_pt;
0571     const double resolution_fixed = res[i];
0572 
0573     // For pT > 200 GeV, add an ad-hoc linear scaling to follow the trend of
0574     // https://arxiv.org/pdf/2012.00578 Fig 2 but without the high-pT working point,
0575     // slightly generously hitting 45% rather than 66% at 2500 GeV.
0576     const double resolution = resolution_fixed *
0577       (1.0 + max(0.0, (((0.45-0.05)/0.05 - 1.0)/2300) * (m.pT() - 200*GeV)/GeV));
0578 
0579     // Smear by a Gaussian centered on the current pT, with width given by the resolution
0580     // normal_distribution<> d(m.pT(), resolution*m.pT());
0581     // const double smeared_pt = max(d(gen), 0.);
0582     // const double mass = m.mass2() > 0 ? m.mass() : 0; //< numerical carefulness...
0583     // return Particle(m.pid(), FourMomentum::mkEtaPhiMPt(m.eta(), m.phi(), mass, smeared_pt));
0584     return Particle(m.pid(), P4_SMEAR_PT_GAUSS(m, resolution*m.pT()));
0585   }
0586 
0587   /// ATLAS Run 2 muon reco smearing
0588   ///
0589   /// From https://arxiv.org/abs/1603.05598 , eq (10) and Fig 12 and
0590   /// the later https://arxiv.org/pdf/2012.00578 Fig 2 -- we assume
0591   /// that the high-pT working point is being used, and match the 300
0592   /// GeV resolution there to the earlier paper's form.
0593   inline Particle MUON_SMEAR_ATLAS_RUN2(const Particle& m) {
0594     // Low-pT res limit:
0595     double mres_pt = 0.015;
0596     // Hit sig_mm / m_mm ~ 0.025 at 100 GeV cf. Fig 12:
0597     if (m.pT() > 50*GeV) mres_pt = 0.015 + 0.01*(m.pT() - 50*GeV)/(50*GeV);
0598     // Hit sig(q/p) = 32% / sqrt(2) at pT = 2500 cf. Fig 2, degrade linearly:
0599     if (m.pT() > 100*GeV) mres_pt = 0.025 + 0.0084*(m.pT() - 100*GeV)/(100*GeV);
0600     const double ptres_pt = SQRT2 * mres_pt; //< from Eq (10)
0601     const double resolution = (m.abseta() < 1.5 ? 1.0 : 1.25) * ptres_pt;
0602     return Particle(m.pid(), P4_SMEAR_PT_GAUSS(m, resolution*m.pT()));
0603   }
0604 
0605   /// CMS Run 1 muon reco efficiency
0606   inline double MUON_EFF_CMS_RUN1(const Particle& m) {
0607     if (m.abspid() != PID::MUON) return 0;
0608     if (m.abseta() > 2.4) return 0;
0609     if (m.pT() < 10*GeV) return 0;
0610     return 0.95 * (m.abseta() < 1.5 ? 1 : exp(0.5 - 5e-4*m.pT()/GeV));
0611   }
0612   /// CMS Run 1 loose muon reconstruction efficiency
0613   /// @todo Add muon loose/medium/tight ID efficiencies? All around 95-98%... ignore?
0614   /// @todo Just an alias to generic: improve!
0615   inline double MUON_EFF_CMS_RUN1_LOOSE(const Particle& m) { return MUON_EFF_CMS_RUN1(m); }
0616   /// CMS Run 1 medium muon reconstruction efficiency
0617   /// @todo Add muon loose/medium/tight ID efficiencies? All around 95-98%... ignore?
0618   /// @todo Just an alias to generic: improve!
0619   inline double MUON_EFF_CMS_RUN1_MEDIUM(const Particle& m) { return MUON_EFF_CMS_RUN1(m); }
0620   /// CMS Run 1 tight muon reconstruction efficiency
0621   /// @todo Add muon loose/medium/tight ID efficiencies? All around 95-98%... ignore?
0622   /// @todo Just an alias to generic: improve!
0623   inline double MUON_EFF_CMS_RUN1_TIGHT(const Particle& m) { return MUON_EFF_CMS_RUN1(m); }
0624 
0625 
0626   /// CMS Run 2 muon reco efficiency
0627   ///
0628   /// @todo Currently just a copy of Run 1: fix!
0629   inline double MUON_EFF_CMS_RUN2(const Particle& m) {
0630     return MUON_EFF_CMS_RUN1(m);
0631   }
0632   /// CMS Run 2 loose muon reconstruction efficiency
0633   /// @todo Add muon loose/medium/tight ID efficiencies? All around 95-98%... ignore?
0634   /// @todo Just an alias to generic: improve!
0635   inline double MUON_EFF_CMS_RUN2_LOOSE(const Particle& m) { return MUON_EFF_CMS_RUN2(m); }
0636   /// CMS Run 2 medium muon reconstruction efficiency
0637   /// @todo Add muon loose/medium/tight ID efficiencies? All around 95-98%... ignore?
0638   /// @todo Just an alias to generic: improve!
0639   inline double MUON_EFF_CMS_RUN2_MEDIUM(const Particle& m) { return MUON_EFF_CMS_RUN2(m); }
0640   /// CMS Run 2 tight muon reconstruction efficiency
0641   /// @todo Add muon loose/medium/tight ID efficiencies? All around 95-98%... ignore?
0642   /// @todo Just an alias to generic: improve!
0643   inline double MUON_EFF_CMS_RUN2_TIGHT(const Particle& m) { return MUON_EFF_CMS_RUN2(m); }
0644 
0645 
0646 
0647   /// CMS Run 1 muon reco smearing
0648   inline Particle MUON_SMEAR_CMS_RUN1(const Particle& m) {
0649     // Calculate fractional resolution
0650     // for pT > 0.1 GeV, mom resolution = |eta| < 0.5 -> sqrt(0.01^2 + pt^2 * 2.0e-4^2)
0651     //                                    |eta| < 1.5 -> sqrt(0.02^2 + pt^2 * 3.0e-4^2)
0652     //                                    |eta| < 2.5 -> sqrt(0.05^2 + pt^2 * 2.6e-4^2)
0653     double resolution = 0;
0654     const double abseta = m.abseta();
0655     if (m.pT() > 0.1*GeV && abseta < 2.5) {
0656       if (abseta < 0.5) {
0657         resolution = add_quad(0.01, 2.0e-4 * m.pT()/GeV);
0658       } else if (abseta < 1.5) {
0659         resolution = add_quad(0.02, 3.0e-4 * m.pT()/GeV);
0660       } else { // still |eta| < 2.5... but isn't CMS' mu acceptance < 2.4?
0661         resolution = add_quad(0.05, 2.6e-4 * m.pT()/GeV);
0662       }
0663     }
0664 
0665     // Smear by a Gaussian centered on the current pT, with width given by the resolution
0666     // normal_distribution<> d(m.pT(), resolution*m.pT());
0667     // const double smeared_pt = max(d(gen), 0.);
0668     // const double mass = m.mass2() > 0 ? m.mass() : 0; //< numerical carefulness...
0669     // return Particle(m.pid(), FourMomentum::mkEtaPhiMPt(m.eta(), m.phi(), mass, smeared_pt));
0670     return Particle(m.pid(), P4_SMEAR_PT_GAUSS(m, resolution*m.pT()));
0671   }
0672 
0673   /// CMS Run 2 muon reco smearing
0674   ///
0675   /// @todo Currently just a copy of the Run 1 version: fix!
0676   inline Particle MUON_SMEAR_CMS_RUN2(const Particle& m) {
0677     return MUON_SMEAR_CMS_RUN1(m);
0678   }
0679 
0680   /// @}
0681 
0682 
0683 
0684   /// @defgroup smearing_tau Experiment-specific tau efficiency and smearing functions
0685   /// @{
0686 
0687   /// @brief ATLAS Run 1 8 TeV tau efficiencies (medium working point)
0688   ///
0689   /// Taken from http://arxiv.org/pdf/1412.7086.pdf
0690   ///   20-40 GeV 1-prong LMT eff|mis = 0.66|1/10, 0.56|1/20, 0.36|1/80
0691   ///   20-40 GeV 3-prong LMT eff|mis = 0.45|1/60, 0.38|1/100, 0.27|1/300
0692   ///   > 40 GeV 1-prong LMT eff|mis = 0.66|1/15, 0.56|1/25, 0.36|1/80
0693   ///   > 40 GeV 3-prong LMT eff|mis = 0.45|1/250, 0.38|1/400, 0.27|1/1300
0694   inline double TAU_EFF_ATLAS_RUN1_MEDIUM(const Particle& t) {
0695     if (t.abseta() > 2.5) return 0; //< hmm... mostly
0696     if (inRange(t.abseta(), 1.37, 1.52)) return 0; //< crack region
0697     double pThadvis = 0;
0698     Particles chargedhadrons;
0699     for (const Particle& p : t.children()) {
0700       if (p.isHadron()) {
0701         pThadvis += p.pT(); //< right definition? Paper is unclear
0702         if (p.charge3() != 0 && p.abseta() < 2.5 && p.pT() > 1*GeV) chargedhadrons += p;
0703       }
0704     }
0705     if (chargedhadrons.empty()) return 0; //< leptonic tau
0706     if (pThadvis < 20*GeV) return 0; //< below threshold
0707     if (pThadvis < 40*GeV) {
0708       if (chargedhadrons.size() == 1) return (t.abspid() == PID::TAU) ? 0.56 : 0; //1/20.;
0709       if (chargedhadrons.size() == 3) return (t.abspid() == PID::TAU) ? 0.38 : 0; //1/100.;
0710     } else {
0711       if (chargedhadrons.size() == 1) return (t.abspid() == PID::TAU) ? 0.56 : 0; //1/25.;
0712       if (chargedhadrons.size() == 3) return (t.abspid() == PID::TAU) ? 0.38 : 0; //1/400.;
0713     }
0714     return 0;
0715   }
0716   /// ATLAS Run 1 medium muon reconstruction efficiency
0717   /// @todo Just an alias to generic: improve!
0718   inline double TAU_EFF_ATLAS_RUN1(const Particle& t) { return TAU_EFF_ATLAS_RUN1_MEDIUM(t); }
0719   /// ATLAS Run 1 loose tau reconstruction efficiency
0720   /// @todo Just an alias to generic: improve!
0721   inline double TAU_EFF_ATLAS_RUN1_LOOSE(const Particle& t) { return TAU_EFF_ATLAS_RUN1(t); }
0722   /// ATLAS Run 1 tight muon reconstruction efficiency
0723   /// @todo Just an alias to generic: improve!
0724   inline double TAU_EFF_ATLAS_RUN1_TIGHT(const Particle& t) { return TAU_EFF_ATLAS_RUN1(t); }
0725 
0726 
0727   /// @brief ATLAS Run 1 8 TeV tau misID rates (medium working point)
0728   ///
0729   /// Taken from http://arxiv.org/pdf/1412.7086.pdf
0730   ///   20-40 GeV 1-prong LMT eff|mis = 0.66|1/10, 0.56|1/20, 0.36|1/80
0731   ///   20-40 GeV 3-prong LMT eff|mis = 0.45|1/60, 0.38|1/100, 0.27|1/300
0732   ///   > 40 GeV 1-prong LMT eff|mis = 0.66|1/15, 0.56|1/25, 0.36|1/80
0733   ///   > 40 GeV 3-prong LMT eff|mis = 0.45|1/250, 0.38|1/400, 0.27|1/1300
0734   inline double TAUJET_EFF_ATLAS_RUN1(const Jet& j) {
0735     if (j.abseta() > 2.5) return 0; //< hmm... mostly
0736     if (inRange(j.abseta(), 1.37, 1.52)) return 0; //< crack region
0737     double pThadvis = 0;
0738     Particles chargedhadrons;
0739     for (const Particle& p : j.particles()) {
0740       if (p.isHadron()) {
0741         pThadvis += p.pT(); //< right definition? Paper is unclear
0742         if (p.charge3() != 0 && p.abseta() < 2.5 && p.pT() > 1*GeV) chargedhadrons += p;
0743       }
0744     }
0745 
0746     if (chargedhadrons.empty()) return 0; //< leptonic tau?
0747     if (pThadvis < 20*GeV) return 0; //< below threshold
0748 
0749     const Particles ttags = j.tauTags(Cuts::pT > 10*GeV);
0750     // MisID rates for jets without truth tau label
0751     if (ttags.empty()) {
0752       if (pThadvis < 40*GeV)
0753         return chargedhadrons.size() == 1 ? 1/20. : chargedhadrons.size() == 3 ? 1/100. : 0; //< fake rates
0754       else
0755         return chargedhadrons.size() == 1 ? 1/25. : chargedhadrons.size() == 3 ? 1/400. : 0; //< fake rates
0756     }
0757     // Efficiencies for jets with a truth tau label
0758     const Particles prongs = ttags[0].stableDescendants(Cuts::charge3 > 0 && Cuts::pT > 1*GeV && Cuts::abseta < 2.5);
0759     return prongs.size() == 1 ? 0.56 : 0.38;
0760   }
0761 
0762 
0763   /// @brief ATLAS Run 2 13 TeV tau efficiencies (medium working point)
0764   ///
0765   /// From https://atlas.web.cern.ch/Atlas/GROUPS/PHYSICS/PUBNOTES/ATL-PHYS-PUB-2015-045/ATL-PHYS-PUB-2015-045.pdf
0766   ///   LMT 1 prong efficiency/mistag = 0.6|1/30, 0.55|1/50, 0.45|1/120
0767   ///   LMT 3 prong efficiency/mistag = 0.5|1/30, 0.4|1/110, 0.3|1/300
0768   inline double TAU_EFF_ATLAS_RUN2_MEDIUM(const Particle& t) {
0769     if (t.abspid() != PID::TAU) return 0;
0770     if (t.abseta() > 2.5) return 0; //< hmm... mostly
0771     if (inRange(t.abseta(), 1.37, 1.52)) return 0; //< crack region
0772     double pThadvis = 0;
0773     Particles chargedhadrons;
0774     for (const Particle& p : t.children()) {
0775       if (p.isHadron()) {
0776         pThadvis += p.pT(); //< right definition? Paper is unclear
0777         if (p.charge3() != 0 && p.abseta() < 2.5 && p.pT() > 1*GeV) chargedhadrons += p;
0778       }
0779     }
0780     if (chargedhadrons.empty()) return 0; //< leptonic tau
0781     if (pThadvis < 20*GeV) return 0; //< below threshold
0782     if (chargedhadrons.size() == 1) return (t.abspid() == PID::TAU) ? 0.55 : 0; //1/50.;
0783     if (chargedhadrons.size() == 3) return (t.abspid() == PID::TAU) ? 0.40 : 0; //1/110.;
0784     return 0;
0785   }
0786   /// ATLAS Run 2 medium muon reconstruction efficiency
0787   /// @todo Just an alias to generic: improve!
0788   inline double TAU_EFF_ATLAS_RUN2(const Particle& t) { return TAU_EFF_ATLAS_RUN2_MEDIUM(t); }
0789   /// ATLAS Run 2 loose tau reconstruction efficiency
0790   /// @todo Just an alias to generic: improve!
0791   inline double TAU_EFF_ATLAS_RUN2_LOOSE(const Particle& t) { return TAU_EFF_ATLAS_RUN2(t); }
0792   /// ATLAS Run 2 tight muon reconstruction efficiency
0793   /// @todo Just an alias to generic: improve!
0794   inline double TAU_EFF_ATLAS_RUN2_TIGHT(const Particle& t) { return TAU_EFF_ATLAS_RUN2(t); }
0795 
0796 
0797   /// @brief ATLAS Run 2 13 TeV tau misID rate (medium working point)
0798   ///
0799   /// From https://atlas.web.cern.ch/Atlas/GROUPS/PHYSICS/PUBNOTES/ATL-PHYS-PUB-2015-045/ATL-PHYS-PUB-2015-045.pdf
0800   ///   LMT 1 prong efficiency/mistag = 0.6|1/30, 0.55|1/50, 0.45|1/120
0801   ///   LMT 3 prong efficiency/mistag = 0.5|1/30, 0.4|1/110, 0.3|1/300
0802   inline double TAUJET_EFF_ATLAS_RUN2(const Jet& j) {
0803     if (j.abseta() > 2.5) return 0; //< hmm... mostly
0804     if (inRange(j.abseta(), 1.37, 1.52)) return 0; //< crack region
0805     double pThadvis = 0;
0806     Particles chargedhadrons;
0807     for (const Particle& p : j.particles()) {
0808       if (p.isHadron()) {
0809         pThadvis += p.pT(); //< right definition? Paper is unclear
0810         if (p.charge3() != 0 && p.abseta() < 2.5 && p.pT() > 1*GeV) chargedhadrons += p;
0811       }
0812     }
0813     if (chargedhadrons.empty()) return 0;
0814     if (pThadvis < 20*GeV) return 0; //< below threshold
0815     const Particles ttags = j.tauTags(Cuts::pT > 10*GeV);
0816     // if (ttags.empty()) {
0817     //   if (pThadvis < 40*GeV)
0818     //     return chargedhadrons.size() == 1 ? 1/50. : 1/110.; //< fake rates
0819     //   else
0820     //     return chargedhadrons.size() == 1 ? 1/25. : 1/400.; //< fake rates
0821     // }
0822     if (ttags.empty()) return chargedhadrons.size() == 1 ? 1/50. : chargedhadrons.size() == 3 ? 1/110. : 0; //< fake rates
0823     const Particles prongs = ttags[0].stableDescendants(Cuts::charge3 > 0 && Cuts::pT > 1*GeV && Cuts::abseta < 2.5);
0824     return prongs.size() == 1 ? 0.55 : 0.40;
0825   }
0826 
0827 
0828   /// ATLAS Run 1 tau smearing
0829   ///
0830   /// @todo Currently a copy of the jet smearing
0831   inline Particle TAU_SMEAR_ATLAS_RUN1(const Particle& t) {
0832     // // Const fractional resolution for now
0833     // static const double resolution = 0.03;
0834 
0835     // // Smear by a Gaussian centered on 1 with width given by the (fractional) resolution
0836     // /// @todo Is this the best way to smear? Should we preserve the energy, or pT, or direction?
0837     // const double fsmear = max(randnorm(1., resolution), 0.);
0838     // const double mass = t.mass2() > 0 ? t.mass() : 0; //< numerical carefulness...
0839     // return Particle(t.pid(), FourMomentum::mkXYZM(t.px()*fsmear, t.py()*fsmear, t.pz()*fsmear, mass));
0840 
0841     // Jet energy resolution lookup
0842     //   Implemented by Matthias Danninger for GAMBIT, based roughly on
0843     //   https://atlas.web.cern.ch/Atlas/GROUPS/PHYSICS/CONFNOTES/ATLAS-CONF-2015-017/
0844     //   Parameterisation can be still improved, but eta dependence is minimal
0845     /// @todo Also need a JES uncertainty component?
0846     static const vector<double> binedges_pt = {0., 50., 70., 100., 150., 200., 1000., 10000.};
0847     static const vector<double> jer = {0.145, 0.115, 0.095, 0.075, 0.07, 0.05, 0.04, 0.04}; //< note overflow value
0848     const int ipt = binIndex(t.pT()/GeV, binedges_pt, true);
0849     if (ipt < 0) return t;
0850     const double resolution = jer.at(ipt);
0851 
0852     // Smear by a Gaussian centered on 1 with width given by the (fractional) resolution
0853     /// @todo Is this the best way to smear? Should we preserve the energy, or pT, or direction?
0854     const double fsmear = max(randnorm(1., resolution), 0.);
0855     const double mass = t.mass2() > 0 ? t.mass() : 0; //< numerical carefulness...
0856     Particle rtn(PID::TAU, FourMomentum::mkXYZM(t.px()*fsmear, t.py()*fsmear, t.pz()*fsmear, mass));
0857     //if (deltaPhi(t, rtn) > 0.01) cout << "jdphi: " << deltaPhi(t, rtn) << endl;
0858     return rtn;
0859   }
0860 
0861 
0862   /// ATLAS Run 2 tau smearing
0863   ///
0864   /// @todo Currently a copy of the Run 1 version
0865   inline Particle TAU_SMEAR_ATLAS_RUN2(const Particle& t) {
0866     return TAU_SMEAR_ATLAS_RUN1(t);
0867   }
0868 
0869 
0870   /// CMS Run 1 tau efficiency
0871   ///
0872   /// @todo Needs work; this is just a copy of the Run 2 version in Delphes 3.3.2
0873   inline double TAU_EFF_CMS_RUN1(const Particle& t) {
0874     if (t.abspid() != PID::TAU) return 0;
0875     return (t.abspid() == PID::TAU) ? 0.6 : 0;
0876   }
0877   /// CMS Run 1 loose tau reconstruction efficiency
0878   /// @todo Just an alias to generic: improve!
0879   inline double TAU_EFF_CMS_RUN1_LOOSE(const Particle& t) { return TAU_EFF_CMS_RUN1(t); }
0880   /// CMS Run 1 medium muon reconstruction efficiency
0881   /// @todo Just an alias to generic: improve!
0882   inline double TAU_EFF_CMS_RUN1_MEDIUM(const Particle& t) { return TAU_EFF_CMS_RUN1(t); }
0883   /// CMS Run 1 tight muon reconstruction efficiency
0884   /// @todo Just an alias to generic: improve!
0885   inline double TAU_EFF_CMS_RUN1_TIGHT(const Particle& t) { return TAU_EFF_CMS_RUN1(t); }
0886 
0887   /// CMS Run 2 tau efficiency
0888   ///
0889   /// @todo Needs work; this is the dumb version from Delphes 3.3.2
0890   inline double TAU_EFF_CMS_RUN2(const Particle& t) {
0891     if (t.abspid() != PID::TAU) return 0;
0892     return (t.abspid() == PID::TAU) ? 0.6 : 0;
0893   }
0894   /// CMS Run 2 loose tau reconstruction efficiency
0895   /// @todo Just an alias to generic: improve!
0896   inline double TAU_EFF_CMS_RUN2_LOOSE(const Particle& t) { return TAU_EFF_CMS_RUN2(t); }
0897   /// CMS Run 2 medium muon reconstruction efficiency
0898   /// @todo Just an alias to generic: improve!
0899   inline double TAU_EFF_CMS_RUN2_MEDIUM(const Particle& t) { return TAU_EFF_CMS_RUN2(t); }
0900   /// CMS Run 2 tight muon reconstruction efficiency
0901   /// @todo Just an alias to generic: improve!
0902   inline double TAU_EFF_CMS_RUN2_TIGHT(const Particle& t) { return TAU_EFF_CMS_RUN2(t); }
0903 
0904 
0905   /// CMS Run 1 tau smearing
0906   ///
0907   /// @todo Currently a copy of the crappy ATLAS one
0908   inline Particle TAU_SMEAR_CMS_RUN1(const Particle& t) {
0909     return TAU_SMEAR_ATLAS_RUN1(t);
0910   }
0911 
0912 
0913   /// CMS Run 2 tau smearing
0914   ///
0915   /// @todo Currently a copy of the Run 1 version
0916   inline Particle TAU_SMEAR_CMS_RUN2(const Particle& t) {
0917     return TAU_SMEAR_CMS_RUN1(t);
0918   }
0919 
0920   /// @}
0921 
0922 
0923 
0924   /// @defgroup smearing_jet Experiment-specific jet efficiency and smearing functions
0925   /// @{
0926 
0927   /// Return the ATLAS Run 1 jet flavour tagging efficiency for the given Jet, from Delphes
0928   inline double JET_BTAG_ATLAS_RUN1(const Jet& j) {
0929     /// @todo This form drops past ~100 GeV, asymptotically to zero efficiency... really?!
0930     if (j.abseta() > 2.5) return 0;
0931     const auto ftagsel = [&](const Particle& p){ return p.pT() > 5*GeV && deltaR(p,j) < 0.3; };
0932     if (j.bTagged(ftagsel)) return 0.80*tanh(0.003*j.pT()/GeV)*(30/(1+0.0860*j.pT()/GeV));
0933     if (j.cTagged(ftagsel)) return 0.20*tanh(0.020*j.pT()/GeV)*( 1/(1+0.0034*j.pT()/GeV));
0934     return 0.002 + 7.3e-6*j.pT()/GeV;
0935   }
0936   /// Alias for naming scheme
0937   inline double JET_BTAG_ATLAS_RUN1_XXX(const Jet& j) { return JET_BTAG_ATLAS_RUN1(j); }
0938 
0939   /// Return the ATLAS Run 2 MC2c20 77% WP jet flavour tagging efficiency for the given Jet
0940   inline double JET_BTAG_ATLAS_RUN2_MV2C20(const Jet& j) {
0941     if (j.abseta() > 2.5) return 0;
0942     if (j.bTagged(Cuts::pT > 5*GeV)) return 0.77;
0943     if (j.cTagged(Cuts::pT > 5*GeV)) return 1/4.5;
0944     return 1/140.;
0945   }
0946 
0947   /// Return the ATLAS Run 2 MC2c10 77% WP jet flavour tagging efficiency for the given Jet
0948   inline double JET_BTAG_ATLAS_RUN2_MV2C10(const Jet& j) {
0949     if (j.abseta() > 2.5) return 0;
0950     if (j.bTagged(Cuts::pT > 5*GeV)) return 0.77;
0951     if (j.cTagged(Cuts::pT > 5*GeV)) return 1/6.0;
0952     return 1/134.;
0953   }
0954 
0955   // pT-dependendent ATLAS Run 2 (2016 study) MV2c10 b-tagging efficiencies.
0956   // If high-pT b-jets are important in your analysis, be careful!
0957   // Based on ATL-PHYS-PUB-2016-012 fig 12
0958   inline double JET_BTAG_ATLAS_RUN2_2016_MV2C10_77(const Jet & j){
0959       if (j.bTagged()){
0960         // N.B. !!! There is no overflow bin.
0961         // There is a clear downward curve in efficiency as pT->inf, but the value to use for pT > 500
0962         // is super-unclear. The used value (0.69) is certainly a reasonable extrapolation, but it should
0963         // be noted that tuning this value can make a really significant difference to the bin counts O(10%)
0964         // HOWEVER: atlas really needs to start increasing the range of this sort of plot
0965         // The behaviour of jets pT>500 is much more interesting and significant than those in the range 20<pt<25.
0966         const static vector<double>binedges_pt = {0.00, 30.0, 40.00, 50.00, 60.0, 75.00, 90.0, 105., 150., 200., 500 };
0967         const static vector<double> eff_pt = {0.63, 0.705, 0.74, 0.76, 0.775, 0.785, 0.795, 0.80, 0.79, 0.75, 0.68};
0968 
0969         return eff_pt[binIndex(j.pt(), binedges_pt, true)];
0970       }
0971       else if (j.cTagged()){
0972         //n.b. there is also a pt/eta table for c mistags, but if that's significant I'll eat my hat.
0973         return 1./5.;
0974       }
0975       else if (j.tauTagged()){
0976         return 1./16.;
0977       }
0978       else return 1./110.;
0979   }
0980 
0981   // pT-dependendent ATLAS Run 2 DL1d b-tagging efficiencies.
0982   // If high-pT b-jets are important in your analysis, be careful!
0983   inline double JET_BTAG_ATLAS_RUN2_DL1d_77(const Jet &j){
0984       if (j.bTagged()){
0985         if (j.abseta() > 2.5) return 0.;
0986 
0987         // Original Values from FTAG-2023-01 (up to 400 GeV)
0988         // N.B. !!! There is no overflow bin.
0989         // There is a clear downward curve in efficiency as pT->inf, but the value to use for pT > 500
0990         // is super-unclear. The used value (0.69) is certainly a reasonable extrapolation, but it should
0991         // be noted that tuning this value can make a really significant difference to the bin counts O(10%)
0992         // HOWEVER: atlas really needs to start increasing the range of this sort of plot
0993         // The behaviour of jets pT>500 is much more interesting and significant than those in the range 20<pt<25.
0994         // N.B.2 Message above copied from my implementation of MV2c10. Plus ca change...
0995         // Better distributions available for DL1r, but not here.
0996         // I will use the DL1/DL1r plots in arXiv:2211.16345 to guess a good overflow value 
0997         // (the consistency between DL1, DL1r hopefully makes this not crazy)
0998         const static vector<double>binedges_pt = {0.00, 20., 30., 40., 60., 85., 110., 140., 175., 250., 400., 800., 1200., 2000.};
0999         const static vector<double> eff_pt = {0.68, 0.74, 0.77, 0.79, 0.8, 0.81, 0.81, 0.805, 0.78, 0.75, 0.7, 0.65, 0.5, 0.5};
1000 
1001         return eff_pt[binIndex(j.pt(), binedges_pt, true)];
1002       }
1003       else if (j.cTagged()){
1004         //n.b. there is also a pt/eta table for c mistags, shouldn't be too significant
1005         return 0.155;
1006       }
1007       // Nothing in the paper, going to guess something small just in case.
1008       else if (j.tauTagged()){
1009         return 1./36.;
1010       }
1011       else return 0.0038;
1012     }
1013 
1014     // Based on figs 11-12 of https://arxiv.org/pdf/2211.16345
1015     // As far as I can see, this is the only pre-GN2 b-tag paper
1016     // that includes efficiencies up to very high pt. Thankyou!!!
1017     // Though n.b. the 0-250 range is in ttbar and 250-3000 is in
1018     // simulated Z', hence the discontinuities going on.
1019     inline double JET_BTAG_ATLAS_RUN2_DL1r_77(const Jet & j){
1020       if (j.pt() <= 20*GeV || j.abseta() > 2.5) return 0.;
1021 
1022       const static vector<double>binedges_pt = {20., 30., 40., 60., 85., 110., 140., 175., 210., 250.,
1023                                                   400., 600., 800., 1000., 1200., 1400., 1500., 2000.0, 3000.0 };
1024 
1025       if (j.bTagged()){
1026         // TODO: for pT < 250 we also have eta dependence... include somehow?
1027         const static vector<double> eff_pt = {0.667, 0.731, 0.771, 0.791, 0.801, 0.805, 0.806, 0.798, 0.795,
1028                                                0.825, 0.802, 0.771, 0.741, 0.706, 0.679, 0.674, 0.634, 0.5, 0.5};
1029                                                // 0.5 repeated for overflow just in case.
1030 
1031         return eff_pt[binIndex(j.pt(), binedges_pt, true)];
1032       }
1033       else if (j.cTagged()){
1034         const static vector<double> c_rej_pt = {5.30, 5.23, 5.55, 5.91, 6.13, 6.30, 6.32, 6.50, 6.10, 
1035                                                3.04, 3.05, 3.21, 3.37, 3.616, 3.83, 3.96, 4.01, 2.90, 2.90};
1036                                                // 2.90 repeated for overflow just in case.
1037         return 1./c_rej_pt[binIndex(j.pt(), binedges_pt, true)];
1038       }
1039       else {
1040         const static vector<double> light_rej_pt = {143, 191, 239, 261, 235, 257, 248, 189, 158, 
1041                                                32.8, 19.1, 14.2, 12.4, 11.2, 11.0, 11.2, 12.4, 8.27, 8.27};
1042                                                // 8.27 repeated for overflow just in case.]
1043         return 1./light_rej_pt[binIndex(j.pt(), binedges_pt, true)];
1044       }
1045     }
1046 
1047 
1048   /// @brief ATLAS Run 1 jet smearing
1049   ///
1050   /// Implemented by Matthias Danninger for GAMBIT, based roughly on
1051   /// https://atlas.web.cern.ch/Atlas/GROUPS/PHYSICS/CONFNOTES/ATLAS-CONF-2015-017/
1052   inline Jet JET_SMEAR_ATLAS_RUN1(const Jet& j) {
1053     // Jet energy resolution lookup
1054     //   Parameterisation can be still improved, but eta dependence is minimal
1055     /// @todo Also need a JES uncertainty component?
1056     static const vector<double> binedges_pt = {0., 50., 70., 100., 150., 200., 1000., 10000.};
1057     static const vector<double> jer = {0.145, 0.115, 0.095, 0.075, 0.07, 0.05, 0.04, 0.04}; //< note overflow value
1058     const int ipt = binIndex(j.pT()/GeV, binedges_pt, true);
1059     if (ipt < 0) return j;
1060     const double resolution = jer.at(ipt);
1061 
1062     // Smear by a Gaussian centered on 1 with width given by the (fractional) resolution
1063     /// @todo Is this the best way to smear? Should we preserve the energy, or pT, or direction?
1064     const double fsmear = max(randnorm(1., resolution), 0.);
1065     const double mass = j.mass2() > 0 ? j.mass() : 0; //< numerical carefulness...
1066     Jet rtn(FourMomentum::mkXYZM(j.px()*fsmear, j.py()*fsmear, j.pz()*fsmear, mass));
1067     //if (deltaPhi(j, rtn) > 0.01) cout << "jdphi: " << deltaPhi(j, rtn) << endl;
1068     return rtn;
1069   }
1070 
1071   /// ATLAS Run 2 jet smearing
1072   ///
1073   /// @todo Just a copy of the Run 1 one: improve!!
1074   inline Jet JET_SMEAR_ATLAS_RUN2(const Jet& j) { return JET_SMEAR_ATLAS_RUN1(j); }
1075 
1076   /// CMS Run 2 jet smearing
1077   ///
1078   /// @todo Just a copy of the suboptimal ATLAS one: improve!!
1079   inline Jet JET_SMEAR_CMS_RUN1(const Jet& j) { return JET_SMEAR_ATLAS_RUN1(j); }
1080 
1081   /// CMS Run 2 jet smearing
1082   ///
1083   /// @todo Just a copy of the suboptimal ATLAS one: improve!!
1084   inline Jet JET_SMEAR_CMS_RUN2(const Jet& j) { return JET_SMEAR_CMS_RUN1(j); }
1085 
1086   /// @}
1087 
1088 
1089   /// @defgroup smearing_met Experiment-specific missing-ET smearing functions
1090   /// @{
1091 
1092   /// @brief ATLAS Run 1 ETmiss resolution
1093   ///
1094   /// Based on https://arxiv.org/pdf/1108.5602v2.pdf, Figs 14 and 15
1095   inline METSmearParams MET_SMEARPARAMS_ATLAS_RUN1(const Vector3& met, double set) {
1096     // Linearity offset (Fig 14)
1097     Vector3 smeared_met = met;
1098     if (met.mod() < 25*GeV) smeared_met *= 1.05;
1099     else if (met.mod() < 40*GeV) smeared_met *= (1.05 - (0.04/15)*(met.mod()/GeV - 25)); //< linear decrease
1100     else smeared_met *= 1.01;
1101 
1102     return {smeared_met, 0.45*sqrt(set/GeV)*GeV, 0.0};
1103   }
1104 
1105   /// @brief ATLAS Run 1 ETmiss smearing
1106   inline Vector3 MET_SMEAR_ATLAS_RUN1(const Vector3& met, double set) {
1107     return MET_SMEAR_NORM(MET_SMEARPARAMS_ATLAS_RUN1(met, set));
1108   }
1109 
1110 
1111   /// ATLAS Run 2 ETmiss resolution
1112   ///
1113   /// Based on https://arxiv.org/pdf/1802.08168.pdf, Figs 6-9
1114   inline METSmearParams MET_SMEARPARAMS_ATLAS_RUN2(const Vector3& met, double set) {
1115     // Linearity offset (Fig 6):
1116     //
1117     // Expn. approx to Fig 6 tt curve, flat below 25 GeV, suppressed
1118     // to fall between the W and tt curves (and match the flat) at 25 GeV,
1119     // and to approach -2% at high-MET.
1120     //
1121     /// @todo If we could access the whole event we could switch between W and tt curves based on jet/lep activity...
1122     const Vector3 smeared_met = met * (1 + 0.45*min(1.0, exp(-(met.mod() - 25*GeV)/(10*GeV))) - 0.02);
1123 
1124     // Resolution(sumEt) ~ 0.45 sqrt(sumEt) GeV above SET = 180 GeV,
1125     // and with linear trend from SET = 180 -> 0 to resolution = 0 (Fig 7)
1126     const double resolution1 = (set < 180*GeV ? set/180. : 1) * 0.45 * sqrt(max(set/GeV, 180)) * GeV;
1127     /// @todo Allow smearing function to access the whole event, since Njet also affects? Or assume encoded in SET?
1128 
1129     // Resolution(MET_true) (Fig 9)
1130     const double resolution2 = 15*GeV + 0.5*sqrt(met.mod()/GeV)*GeV;
1131 
1132     // Smearing width is given by the minimum resolution estimator (should mean low-MET events
1133     // with high SET do not get a large smearing, and will be dominated by the linearity effect).
1134     /// @todo Arguably should somehow take the max of this and the linearity... but this is re-used
1135     const double sigma = min(resolution1, resolution2);
1136 
1137     return {smeared_met, sigma, 0.0};
1138   }
1139 
1140   /// ATLAS Run 2 ETmiss smearing
1141   inline Vector3 MET_SMEAR_ATLAS_RUN2(const Vector3& met, double set) {
1142     return MET_SMEAR_NORM(MET_SMEARPARAMS_ATLAS_RUN2(met, set));
1143   }
1144 
1145 
1146   /// ATLAS Run 2 pflow ETmiss resolution
1147   ///
1148   /// @warning No explicitly encoded SET-dependence, uses average of tt, VBF H->WW, and W->munu curves at <mu>.
1149   ///
1150   /// Based on https://arxiv.org/pdf/2402.05858
1151   inline METSmearParams MET_SMEARPARAMS_ATLAS_RUN2_PFLOW(const Vector3& met, double /* set */) {
1152     // Mean response (Fig 7), expn approximation to the ttbar curve guessing 2 e-foldings in 40 GeV.
1153     /// @todo If we could access the whole event we could switch between W and tt curves based on jet/lep activity...
1154     const Vector3 smeared_met = met * (1 + 1.15*exp(-met.mod()/(20*GeV)) - 0.01);
1155 
1156     // Mean resolution ~ 15-25 GeV for mu ~ 33.7 (Run 2 avg), but significant dependence on event type (i.e. SET)
1157     // See Figures 4 and 5. The paper doesn't provide enough information for a general SET-dependence:
1158     // maybe prefer the older MET smearing. Not enough discrimination between WPs, mainly encoded vs mu or N_PV.
1159     const double resolution = 20*GeV;
1160 
1161     return {smeared_met, resolution, 0.0};
1162   }
1163 
1164   /// ATLAS Run 2 pflow ETmiss smearing
1165   inline Vector3 MET_SMEAR_ATLAS_RUN2_PFLOW(const Vector3& met, double set) {
1166     return MET_SMEAR_NORM(MET_SMEARPARAMS_ATLAS_RUN2_PFLOW(met, set));
1167   }
1168 
1169 
1170   /// CMS Run 1 ETmiss smearing params
1171   ///
1172   /// From https://arxiv.org/pdf/1411.0511.pdf Table 2, p16 (Z channels)
1173   inline METSmearParams MET_SMEARPARAMS_CMS_RUN1(const Vector3& met, double set) {
1174     // Calculate parallel and perpendicular resolutions and combine in quadrature (?)
1175     const double resolution_x = (1.1 + 0.6*sqrt(set/GeV)) * GeV;
1176     const double resolution_y = (1.4 + 0.6*sqrt(set/GeV)) * GeV;
1177     const double resolution = sqrt(sqr(resolution_x) + sqr(resolution_y));
1178     return {met, resolution,0.0};
1179   }
1180 
1181   /// CMS Run 1 ETmiss smearing
1182   inline Vector3 MET_SMEAR_CMS_RUN1(const Vector3& met, double set) {
1183     return MET_SMEAR_NORM(MET_SMEARPARAMS_CMS_RUN1(met, set));
1184   }
1185 
1186 
1187   /// @brief CMS Run 2 ETmiss smearing
1188   ///
1189   /// From http://inspirehep.net/record/1681214/files/JME-17-001-pas.pdf Table 3, p20
1190   inline METSmearParams MET_SMEARPARAMS_CMS_RUN2(const Vector3& met, double set) {
1191     // Calculate parallel and perpendicular resolutions and combine in quadrature (?)
1192     const double resolution_para = ( 2.0 + 0.64*sqrt(set/GeV)) * GeV;
1193     const double resolution_perp = (-1.5 + 0.68*sqrt(set/GeV)) * GeV;
1194     const double resolution = sqrt(sqr(resolution_para) + sqr(resolution_perp));
1195     return {met, resolution,0.0};
1196   }
1197 
1198   /// @brief CMS Run 2 ETmiss smearing
1199   inline Vector3 MET_SMEAR_CMS_RUN2(const Vector3& met, double set) {
1200     return MET_SMEAR_NORM(MET_SMEARPARAMS_CMS_RUN2(met, set));
1201   }
1202 
1203   /// @}
1204 
1205 
1206   /// @defgroup smearing_trk Experiment-specific tracking efficiency and smearing functions
1207   /// @{
1208 
1209   /// ATLAS Run 1 tracking efficiency
1210   inline double TRK_EFF_ATLAS_RUN1(const Particle& p) {
1211     if (p.charge3() == 0) return 0;
1212     if (p.abseta() > 2.5) return 0;
1213     if (p.pT() < 0.1*GeV) return 0;
1214 
1215     if (p.abspid() == PID::ELECTRON) {
1216       if (p.abseta() < 1.5) {
1217         if (p.pT() < 1*GeV) return 0.73;
1218         if (p.pT() < 100*GeV) return 0.95;
1219         return 0.99;
1220       } else {
1221         if (p.pT() < 1*GeV) return 0.50;
1222         if (p.pT() < 100*GeV) return 0.83;
1223         else return 0.90;
1224       }
1225     } else if (p.abspid() == PID::MUON) {
1226       if (p.abseta() < 1.5) {
1227         return (p.pT() < 1*GeV) ? 0.75 : 0.99;
1228       } else {
1229         return (p.pT() < 1*GeV) ? 0.70 : 0.98;
1230       }
1231     } else { // charged hadrons
1232       if (p.abseta() < 1.5) {
1233         return (p.pT() < 1*GeV) ? 0.70 : 0.95;
1234       } else {
1235         return (p.pT() < 1*GeV) ? 0.60 : 0.85;
1236       }
1237     }
1238   }
1239 
1240   /// ATLAS Run 2 tracking efficiency
1241   ///
1242   /// @todo Currently just a copy of Run 1: fix!
1243   inline double TRK_EFF_ATLAS_RUN2(const Particle& p) {
1244     return TRK_EFF_ATLAS_RUN1(p);
1245   }
1246 
1247 
1248   /// ATLAS Run 1 track smearing
1249   ///
1250   /// @todo Currently identity: fix!
1251   inline Particle TRK_SMEAR_ATLAS_RUN1(const Particle& t) {
1252     return PARTICLE_SMEAR_IDENTITY(t);
1253   }
1254   /// ATLAS Run 2 track smearing
1255   ///
1256   /// @todo Currently identity: fix!
1257   inline Particle TRK_SMEAR_ATLAS_RUN2(const Particle& t) {
1258     return PARTICLE_SMEAR_IDENTITY(t);
1259   }
1260 
1261 
1262   /// CMS Run 1 tracking efficiency
1263   inline double TRK_EFF_CMS_RUN1(const Particle& p) {
1264     if (p.charge3() == 0) return 0;
1265     if (p.abseta() > 2.5) return 0;
1266     if (p.pT() < 0.1*GeV) return 0;
1267 
1268     if (p.abspid() == PID::ELECTRON) {
1269       if (p.abseta() < 1.5) {
1270         if (p.pT() < 1*GeV) return 0.73;
1271         if (p.pT() < 100*GeV) return 0.95;
1272         return 0.99;
1273       } else {
1274         if (p.pT() < 1*GeV) return 0.50;
1275         if (p.pT() < 100*GeV) return 0.83;
1276         else return 0.90;
1277       }
1278     } else if (p.abspid() == PID::MUON) {
1279       if (p.abseta() < 1.5) {
1280         return (p.pT() < 1*GeV) ? 0.75 : 0.99;
1281       } else {
1282         return (p.pT() < 1*GeV) ? 0.70 : 0.98;
1283       }
1284     } else { // charged hadrons
1285       if (p.abseta() < 1.5) {
1286         return (p.pT() < 1*GeV) ? 0.70 : 0.95;
1287       } else {
1288         return (p.pT() < 1*GeV) ? 0.60 : 0.85;
1289       }
1290     }
1291   }
1292 
1293   /// CMS Run 2 tracking efficiency
1294   ///
1295   /// @todo Currently just a copy of Run 1: fix!
1296   inline double TRK_EFF_CMS_RUN2(const Particle& p) {
1297     return TRK_EFF_CMS_RUN1(p);
1298   }
1299 
1300 
1301   /// CMS Run 1 track smearing
1302   ///
1303   /// @todo Currently identity: fix!
1304   inline Particle TRK_SMEAR_CMS_RUN1(const Particle& t) {
1305     return PARTICLE_SMEAR_IDENTITY(t);
1306   }
1307   /// CMS Run 2 track smearing
1308   ///
1309   /// @todo Currently identity: fix!
1310   inline Particle TRK_SMEAR_CMS_RUN2(const Particle& t) {
1311     return PARTICLE_SMEAR_IDENTITY(t);
1312   }
1313 
1314   /// @brief Return the efficiency of the ATLAS JVT tagger at > 0.2 W.P.
1315   ///
1316   /// Values taken from fig 17, https://arxiv.org/pdf/1510.03823.pdf.
1317   /// Shockingly, its run1, but it's still the most recent ATLAS JVT public plots
1318   /// And is still cited by all the Run 2 papers
1319   /// Note! The exact translation of weak/medium/tight varies across runs/years
1320   /// If possible double check the score cut off used.
1321   inline double ATLAS_JVT_EFF_LOOSE(const Jet & j){
1322     if (j.abseta() > 2.4) return 1.0;
1323     // No data for jets pt < 20, hopefully not relevant:
1324     if (j.pt() <= 20*GeV || j.pt() >= 60*GeV) return 1.0;
1325 
1326     const static vector<double> binedges_pt = {20*GeV,25*GeV,30*GeV,35*GeV,40*GeV,45*GeV,50*GeV,55*GeV,60*GeV};
1327     const static vector<double> binvals = {0.9, 0.93, 0.94, 0.95, 0.96, 0.96, 0.97, 0.97};
1328 
1329     const size_t bini = binIndex(j.pt(), binedges_pt);
1330     return binvals[bini];
1331   }
1332 
1333   /// @brief Return the efficiency of the ATLAS JVT tagger at > 0.4 W.P.
1334   ///
1335   /// Values taken from fig 17, https://arxiv.org/pdf/1510.03823.pdf.
1336   /// Shockingly, its run1, but it's still the most recent ATLAS JVT public plots
1337   /// And is still cited by all the Run 2 papers
1338   /// Note! The exact translation of weak/medium/tight varies across runs/years
1339   /// If possible double check the score cut off used.
1340   inline double ATLAS_JVT_EFF_MEDIUM(const Jet & j){
1341     if (j.abseta() > 2.4) return 1.0;
1342     // No data for jets pt < 20, hopefully not relevant:
1343     if (j.pt() <= 20*GeV || j.pt() >= 60*GeV) return 1.0;
1344 
1345     const static vector<double> binedges_pt = {20*GeV,25*GeV,30*GeV,35*GeV,40*GeV,45*GeV,50*GeV,55*GeV,60*GeV};
1346     const static vector<double> binvals = {0.86, 0.9, 0.92, 0.93, 0.94, 0.95, 0.95, 0.96};
1347 
1348     const size_t bini = binIndex(j.pt(), binedges_pt);
1349     return binvals[bini];
1350   }
1351 
1352   /// @brief Return the efficiency of the ATLAS JVT tagger at > 0.7 W.P.
1353   ///
1354   /// Values taken from fig 17, https://arxiv.org/pdf/1510.03823.pdf.
1355   /// Shockingly, its run1, but it's still the most recent ATLAS JVT public plots
1356   /// And is still cited by all the Run 2 papers
1357   /// Note! The exact translation of weak/medium/tight varies across runs/years
1358   /// If possible double check the score cut off used.
1359   inline double ATLAS_JVT_EFF_TIGHT(const Jet & j){
1360     if (j.abseta() > 2.4) return 1.0;
1361     // No data for jets pt < 20, hopefully not relevant:
1362     if (j.pt() <= 20*GeV || j.pt() >= 60*GeV) return 1.0;
1363 
1364     const static vector<double> binedges_pt = {20*GeV,25*GeV,30*GeV,35*GeV,40*GeV,45*GeV,50*GeV,55*GeV,60*GeV};
1365     const static vector<double> binvals = {0.81, 0.84, 0.87, 0.90, 0.91, 0.92, 0.93, 0.94};
1366 
1367     const size_t bini = binIndex(j.pt(), binedges_pt);
1368     return binvals[bini];
1369   }
1370 
1371   /// @}
1372 
1373   /// @name A full family of identity smearing functions
1374   /// @{
1375 
1376   inline double ELECTRON_EFF_IDENTITY_LOOSE(const Particle& e) { return PARTICLE_EFF_ONE(e); }
1377   inline double ELECTRON_EFF_IDENTITY_MEDIUM(const Particle& e) { return PARTICLE_EFF_ONE(e); }
1378   inline double ELECTRON_EFF_IDENTITY_TIGHT(const Particle& e) { return PARTICLE_EFF_ONE(e); }
1379   inline double MUON_EFF_IDENTITY_LOOSE(const Particle& m) { return PARTICLE_EFF_ONE(m); }
1380   inline double MUON_EFF_IDENTITY_MEDIUM(const Particle& m) { return PARTICLE_EFF_ONE(m); }
1381   inline double MUON_EFF_IDENTITY_TIGHT(const Particle& m) { return PARTICLE_EFF_ONE(m); }
1382   inline double PHOTON_EFF_IDENTITY_LOOSE(const Particle& y) { return PARTICLE_EFF_ONE(y); }
1383   inline double PHOTON_EFF_IDENTITY_MEDIUM(const Particle& y) { return PARTICLE_EFF_ONE(y); }
1384   inline double PHOTON_EFF_IDENTITY_TIGHT(const Particle& y) { return PARTICLE_EFF_ONE(y); }
1385   inline double TAU_EFF_IDENTITY_LOOSE(const Particle& t) { return PARTICLE_EFF_ONE(t); }
1386   inline double TAU_EFF_IDENTITY_MEDIUM(const Particle& t) { return PARTICLE_EFF_ONE(t); }
1387   inline double TAU_EFF_IDENTITY_TIGHT(const Particle& t) { return PARTICLE_EFF_ONE(t); }
1388 
1389   inline Particle ELECTRON_SMEAR_IDENTITY(const Particle& e) { return PARTICLE_SMEAR_IDENTITY(e); }
1390   inline Particle MUON_SMEAR_IDENTITY(const Particle& m) { return PARTICLE_SMEAR_IDENTITY(m); }
1391   inline Particle PHOTON_SMEAR_IDENTITY(const Particle& y) { return PARTICLE_SMEAR_IDENTITY(y); }
1392   inline Particle TAU_SMEAR_IDENTITY(const Particle& t) { return PARTICLE_SMEAR_IDENTITY(t); }
1393 
1394   inline double TRK_EFF_IDENTITY_TIGHT(const Particle& trk) { return PARTICLE_EFF_ONE(trk); }
1395   inline Particle TRK_SMEAR_IDENTITY(const Particle& trk) { return PARTICLE_SMEAR_IDENTITY(trk); }
1396 
1397   inline double JET_BTAG_IDENTITY_IDENTITY(const Jet& j) { return JET_BTAG_IDENTITY(j); }
1398 
1399   // Already defined: JET_SMEAR_IDENTITY and MET_SMEAR_IDENTITY
1400 
1401   /// @}
1402 
1403   /// @}
1404 
1405 
1406 }
1407 
1408 #endif