file ColliderBit/ATLASEfficiencies.hpp

[No description available] More…

Namespaces

Name
Gambit
TODO: see if we can use this one:
Gambit::ColliderBit
Gambit::ColliderBit::ATLAS
ATLAS-specific efficiency and smearing functions for super fast detector simulation.

Detailed Description

Author:

  • Andy Buckley
  • Abram Krislock
  • Anders Kvellestad
  • Matthias Danninger
  • Rose Kudzman-Blais
  • Pat Scott
  • Tomas Gonzalo

Functions that do super fast ATLAS detector simulation based on four-vector smearing.


Authors (add name and date if you modify):


Source code

//   GAMBIT: Global and Modular BSM Inference Tool
//  *********************************************
///
///  \file
///  Functions that do super fast ATLAS detector
///  simulation based on four-vector smearing.
///
///  *********************************************
///
///  Authors (add name and date if you modify):
///
///  \author Andy Buckley
///  \author Abram Krislock
///  \author Anders Kvellestad
///  \author Matthias Danninger
///  \author Rose Kudzman-Blais
///  \author Pat Scott
///  \author Tomas Gonzalo
///
///  *********************************************


#pragma once

#include <cfloat>

#include "gambit/ColliderBit/Utils.hpp"
#include "gambit/Utils/threadsafe_rng.hpp"

#include "HEPUtils/MathUtils.h"
#include "HEPUtils/BinnedFn.h"
#include "HEPUtils/Event.h"

namespace Gambit
{

  namespace ColliderBit
  {


    /// ATLAS-specific efficiency and smearing functions for super fast detector simulation
    namespace ATLAS
    {

      /// @name ATLAS detector efficiency functions
      ///@{

        // /// Randomly filter the supplied particle list by parameterised electron tracking efficiency
        // /// @todo Remove? This is not the electron efficiency
        // inline void applyElectronTrackingEff(std::vector<const HEPUtils::Particle*>& electrons) {
        //   static HEPUtils::BinnedFn2D<double> _elTrackEff2d({{0, 1.5, 2.5, DBL_MAX}}, //< |eta|
        //                                                     {{0, 0.1, 1.0, 100, DBL_MAX}}, //< pT
        //                                                     {{0., 0.73, 0.95, 0.99,
        //                                                       0., 0.5,  0.83, 0.90,
        //                                                       0., 0.,   0.,   0.}});
        //   filtereff_etapt(electrons, _elTrackEff2d);
        // }


        /// Randomly filter the supplied particle list by parameterised electron efficiency
        /// @note Should be applied after the electron energy smearing
        inline void applyElectronEff(std::vector<const HEPUtils::Particle*>& electrons) {
          static HEPUtils::BinnedFn2D<double> _elEff2d({{0,1.5,2.5,DBL_MAX}}, //< |eta|
                                                       {{0,10.,DBL_MAX}}, //< pT
                                                       {{0., 0.95,
                                                         0., 0.85,
                                                         0., 0.}});
          filtereff_etapt(electrons, _elEff2d);
        }


        // /// Randomly filter the supplied particle list by parameterised muon tracking efficiency
        // /// @todo Remove? This is not the muon efficiency
        // inline void applyMuonTrackEff(std::vector<const HEPUtils::Particle*>& muons) {
        //   static HEPUtils::BinnedFn2D<double> _muTrackEff2d({{0,1.5,2.5,DBL_MAX}}, //< |eta|
        //                                                     {{0,0.1,1.0,DBL_MAX}}, //< pT
        //                                                     {{0., 0.75, 0.99,
        //                                                       0., 0.70, 0.98,
        //                                                       0., 0.,   0.}});
        //   filtereff_etapt(muons, _muTrackEff2d);
        // }


        /// Randomly filter the supplied particle list by parameterised muon efficiency
        inline void applyMuonEff(std::vector<const HEPUtils::Particle*>& muons) {
          static HEPUtils::BinnedFn2D<double> _muEff2d({{0,1.5,2.7,DBL_MAX}}, //< |eta|
                                                       {{0,10.0,DBL_MAX}}, //< pT
                                                       {{0., 0.95,
                                                         0., 0.85,
                                                         0., 0.}});
          filtereff_etapt(muons, _muEff2d);
        }


        /// Randomly filter the supplied particle list by parameterised muon efficiency
        inline void applyMuonEffR2(std::vector<const HEPUtils::Particle*>& muons) {
          static HEPUtils::BinnedFn2D<double> _muEff2d({0, 2.7, DBL_MAX}, //< |eta|
                                                       {0., 3.5, 4., 5., 6., 7., 8., 10., DBL_MAX}, //< pT
                                                       {0.00, 0.76, 0.94, 0.97, 0.98, 0.98, 0.98, 0.99,//
                                                        0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00});
          filtereff_etapt(muons, _muEff2d);
        }


        /// Randomly filter the supplied particle list by parameterised Run 1 tau efficiency
        /// @note From Delphes 3.1.2
        /// @todo Use https://cds.cern.ch/record/1233743/files/ATL-PHYS-PUB-2010-001.pdf -- it is more accurate and has pT-dependence
        inline void applyTauEfficiencyR1(std::vector<const HEPUtils::Particle*>& taus) {
          filtereff(taus, 0.40);
        }


        /// Randomly filter the supplied particle list by parameterised Run 2 tau efficiency
        /// @note From Delphes 3.3.2 & ATL-PHYS-PUB-2015-045, 60% for 1-prong, 70% for multi-prong: this is *wrong*!!
        /// @note No delete, because this should only ever be applied to copies of the Event Particle* vectors in Analysis routines
        inline void applyTauEfficiencyR2(std::vector<const HEPUtils::Particle*>& taus) {

          // Delphes 3.3.2 config:
          //   set DeltaR 0.2
          //   set DeltaRTrack 0.2
          //   set TrackPTMin 1.0
          //   set TauPTMin 1.0
          //   set TauEtaMax 2.5
          //   # instructions: {n-prongs} {eff}
          //   # 1 - one prong efficiency
          //   # 2 - two or more efficiency
          //   # -1 - one prong mistag rate
          //   # -2 - two or more mistag rate
          //   set BitNumber 0
          //   # taken from ATL-PHYS-PUB-2015-045 (medium working point)
          //   add EfficiencyFormula {1} {0.70}
          //   add EfficiencyFormula {2} {0.60}
          //   add EfficiencyFormula {-1} {0.02}
          //   add EfficiencyFormula {-2} {0.01}
          // filtereff(taus, 0.65);

          // Distributions from ATL-PHYS-PUB-2015-045, Fig 10
          const static std::vector<double> binedges_pt    = { 0.,  20.,  40.,   60.,   120.,  160.,   220.,   280.,   380.,    500.,  DBL_MAX };
          const static std::vector<double> bineffs_pt_1p  = { 0.,  .54,  .55,   .56,    .58,   .57,    .56,    .54,     .51,     0. };
          const static std::vector<double> bineffs_pt_3p  = { 0.,  .40,  .41,   .42,    .46,   .46,    .43,    .39,     .33,     0. };
          const static HEPUtils::BinnedFn1D<double> _eff_pt_1p(binedges_pt, bineffs_pt_1p);
          const static HEPUtils::BinnedFn1D<double> _eff_pt_3p(binedges_pt, bineffs_pt_3p);
          // 85% 1-prong, 15% >=3-prong
          const static std::vector<double> bineffs_pt_avg = { 0.,  .52,  .53,   .54,    .56,   .55,    .54,    .52,     .48,     0. };
          const static HEPUtils::BinnedFn1D<double> _eff_pt_avg(binedges_pt, bineffs_pt_avg);
          filtereff_pt(taus, _eff_pt_avg);

        }



        inline void applyPhotonEfficiencyR2(std::vector<const HEPUtils::Particle*>& photons) {

          const static std::vector<double> binedges_eta   = { 0., 0.6, 1.37, 1.52, 1.81, 2.37, DBL_MAX };
          const static std::vector<double> binedges_pt    = { 0., 10., 15., 20., 25., 30., 35., 40., 45., 50., 60., 80., 100., 125., 150., 175., 250., DBL_MAX };
          const static std::vector<double> bineffs_etapt  = { 0.00, 0.55, 0.70, 0.85, 0.89, 0.93, 0.95, 0.96, 0.96, 0.97, 0.97, 0.98, 0.97, 0.97, 0.97, 0.97, 0.97, //
                                                              0.00, 0.47, 0.66, 0.79, 0.86, 0.89, 0.94, 0.96, 0.97, 0.97, 0.98, 0.97, 0.98, 0.98, 0.98, 0.98, 0.98, //
                                                              0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, //
                                                              0.00, 0.54, 0.71, 0.84, 0.88, 0.92, 0.93, 0.94, 0.95, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, //
                                                              0.00, 0.61, 0.74, 0.83, 0.88, 0.91, 0.94, 0.95, 0.96, 0.97, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, //
                                                              0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 };
          const static HEPUtils::BinnedFn2D<double> _eff_etapt(binedges_eta, binedges_pt, bineffs_etapt);
          filtereff_etapt(photons, _eff_etapt);
        }


        /// Randomly smear the supplied electrons' momenta by parameterised resolutions
        inline void smearElectronEnergy(std::vector<HEPUtils::Particle*>& electrons) {
          // Function that mimics the DELPHES electron energy resolution
          // We need to smear E, then recalculate pT, then reset 4 vector

          static HEPUtils::BinnedFn2D<double> coeffE2({{0, 2.5, 3., 5.}}, //< |eta|
                                                      {{0, 0.1, 25., DBL_MAX}}, //< pT
                                                      {{0.,          0.015*0.015, 0.005*0.005,
                                                        0.005*0.005, 0.005*0.005, 0.005*0.005,
                                                        0.107*0.107, 0.107*0.107, 0.107*0.107}});

          static HEPUtils::BinnedFn2D<double> coeffE({{0, 2.5, 3., 5.}}, //< |eta|
                                                     {{0, 0.1, 25., DBL_MAX}}, //< pT
                                                     {{0.,        0.,        0.05*0.05,
                                                       0.05*0.05, 0.05*0.05, 0.05*0.05,
                                                       2.08*2.08, 2.08*2.08, 2.08*2.08}});

          static HEPUtils::BinnedFn2D<double> coeffC({{0, 2.5, 3., 5.}}, //< |eta|
                                                     {{0, 0.1, 25., DBL_MAX}}, //< pT
                                                     {{0.,       0.,       0.25*0.25,
                                                       0.25*0.25,0.25*0.25,0.25*0.25,
                                                       0.,       0.,       0.}});

          // Now loop over the electrons and smear the 4-vectors
          for (HEPUtils::Particle* e : electrons) {
            if (e->abseta() > 5) continue;

            // Look up / calculate resolution
            const double c1 = coeffE2.get_at(e->abseta(), e->pT());
            const double c2 = coeffE.get_at(e->abseta(), e->pT());
            const double c3 = coeffC.get_at(e->abseta(), e->pT());
            const double resolution = sqrt(c1*HEPUtils::sqr(e->E()) + c2*e->E() + c3);

            // Smear by a Gaussian centered on the current energy, with width given by the resolution
            std::normal_distribution<> d(e->E(), resolution);
            double smeared_E = d(Random::rng());
            if (smeared_E < e->mass()) smeared_E = 1.01*e->mass();
            // double smeared_pt = smeared_E/cosh(e->eta()); ///< @todo Should be cosh(|eta|)?
            e->set_mom(HEPUtils::P4::mkEtaPhiME(e->eta(), e->phi(), e->mass(), smeared_E));
          }
        }


        /// Randomly smear the supplied muons' momenta by parameterised resolutions
        inline void smearMuonMomentum(std::vector<HEPUtils::Particle*>& muons) {
          // Function that mimics the DELPHES muon momentum resolution
          // We need to smear pT, then recalculate E, then reset 4 vector

          static HEPUtils::BinnedFn2D<double> _muEff({{0,1.5,2.5}},
                                                     {{0,0.1,1.,10.,200.,DBL_MAX}},
                                                     {{0.,0.03,0.02,0.03,0.05,
                                                       0.,0.04,0.03,0.04,0.05}});

          // Now loop over the muons and smear the 4-vectors
          for (HEPUtils::Particle* mu : muons) {
            if (mu->abseta() > 2.5) continue;

            // Look up resolution
            const double resolution = _muEff.get_at(mu->abseta(), mu->pT());

            // Smear by a Gaussian centered on the current energy, with width given by the resolution
            std::normal_distribution<> d(mu->pT(), resolution*mu->pT());
            double smeared_pt = d(Random::rng());
            if (smeared_pt < 0) smeared_pt = 0;
            // const double smeared_E = smeared_pt*cosh(mu->eta()); ///< @todo Should be cosh(|eta|)?
            // std::cout << "Muon pt " << mu_pt << " smeared " << smeared_pt << endl;
            mu->set_mom(HEPUtils::P4::mkEtaPhiMPt(mu->eta(), mu->phi(), mu->mass(), smeared_pt));
          }
        }


        /// Randomly smear the supplied jets' momenta by parameterised resolutions
        inline void smearJets(std::vector<HEPUtils::Jet*>& jets) {
          // Function that mimics the DELPHES jet momentum resolution.
          // We need to smear pT, then recalculate E, then reset the 4-vector.
          // Const resolution for now
          //const double resolution = 0.03;

          // Matthias jet smearing implemented roughly from
          // https://atlas.web.cern.ch/Atlas/GROUPS/PHYSICS/CONFNOTES/ATLAS-CONF-2015-017/
          // Parameterisation can be still improved, but eta dependence is minimal
          const std::vector<double>  binedges_eta = {0,10.};
          const std::vector<double>  binedges_pt = {0,50.,70.,100.,150.,200.,1000.,10000.};
          const std::vector<double> JetsJER = {0.145,0.115,0.095,0.075,0.07,0.05,0.04};
          static HEPUtils::BinnedFn2D<double> _resJets2D(binedges_eta,binedges_pt,JetsJER);

          // Now loop over the jets and smear the 4-vectors
          for (HEPUtils::Jet* jet : jets) {
            const double resolution = _resJets2D.get_at(jet->abseta(), jet->pT());
            std::normal_distribution<> d(1., resolution);
            // Smear by a Gaussian centered on 1 with width given by the (fractional) resolution
            double smear_factor = d(Random::rng());
            /// @todo Is this the best way to smear? Should we preserve the mean jet energy, or pT, or direction?
            jet->set_mom(HEPUtils::P4::mkXYZM(jet->mom().px()*smear_factor, jet->mom().py()*smear_factor, jet->mom().pz()*smear_factor, jet->mass()));
          }
        }


        /// Randomly smear the MET vector by parameterised resolutions
        inline void smearMET(HEPUtils::P4& pmiss, double set) {
          // Smearing function from ATLAS Run 1 performance paper, converted from Rivet
          // cf. https://arxiv.org/pdf/1108.5602v2.pdf, Figs 14 and 15

          // Linearity offset (Fig 14)
          if (pmiss.pT() < 25) pmiss *= 1.05;
          else if (pmiss.pT() < 40) pmiss *= (1.05 - (0.04/15)*(pmiss.pT() - 25)); //< linear decrease
          else pmiss *= 1.01;

          // Smear by a Gaussian with width given by the resolution(sumEt) ~ 0.45 sqrt(sumEt) GeV
          const double resolution = 0.45 * sqrt(set);
          std::normal_distribution<> d(pmiss.pT(), resolution);
          const double smearedmet = std::max(d(Random::rng()), 0.);

          pmiss *= smearedmet / pmiss.pT();
        }


        /// Randomly smear the supplied taus' momenta by parameterised resolutions
        inline void smearTaus(std::vector<HEPUtils::Particle*>& taus) {
          // We need to smear pT, then recalculate E, then reset the 4-vector.
          // Same as for jets, but on a vector of particles. (?)
          // Const resolution for now
          const double resolution = 0.03;

          // Now loop over the jets and smear the 4-vectors
          std::normal_distribution<> d(1., resolution);
          for (HEPUtils::Particle* p : taus) {
            // Smear by a Gaussian centered on 1 with width given by the (fractional) resolution
            double smear_factor = d(Random::rng());
            /// @todo Is this the best way to smear? Should we preserve the mean jet energy, or pT, or direction?
            p->set_mom(HEPUtils::P4::mkXYZM(p->mom().px()*smear_factor, p->mom().py()*smear_factor, p->mom().pz()*smear_factor, p->mass()));
          }
        }


        /// Efficiency function for Loose ID electrons
        /// @note Numbers digitised from Fig 3 of 13 TeV note (ATL-PHYS-PUB-2015-041)
        /// @todo What about faking by jets or non-electrons?
        inline void applyLooseIDElectronSelectionR2(std::vector<const HEPUtils::Particle*>& electrons) {
          if (electrons.empty()) return;

          // Manually symmetrised eta eff histogram
          const static std::vector<double> binedges_eta = { 0.0,   0.1,   0.8,   1.37,  1.52,  2.01,  2.37,  2.47, DBL_MAX };
          const static std::vector<double> bineffs_eta  = { 0.950, 0.965, 0.955, 0.885, 0.950, 0.935, 0.90, 0 };
          const static HEPUtils::BinnedFn1D<double> _eff_eta(binedges_eta, bineffs_eta);
          // Et eff histogram (10-20 GeV bin added by hand)
          const static std::vector<double> binedges_et = { 10,   20,   25,   30,   35,   40,    45,    50,   60,  80, DBL_MAX };
          const static std::vector<double> bineffs_et  = { 0.90, 0.91, 0.92, 0.94, 0.95, 0.955, 0.965, 0.97, 0.98, 0.98 };
          const static HEPUtils::BinnedFn1D<double> _eff_et(binedges_et, bineffs_et);

          auto keptElectronsEnd = std::remove_if(electrons.begin(), electrons.end(),
                                                 [](const HEPUtils::Particle* electron) {
                                                   const double e_pt = electron->pT();
                                                   const double e_aeta = electron->abseta();
                                                   if (e_aeta > 2.47 || e_pt < 10) return true;
                                                   const double eff1 = _eff_eta.get_at(e_aeta), eff2 = _eff_et.get_at(e_pt);
                                                   const double eff = std::min(eff1 * eff2 / 0.95, 1.0); //< norm factor as approximate double differential
                                                   return random_bool(1-eff);
                                                 } );
          electrons.erase(keptElectronsEnd, electrons.end());
        }

        /// Alias to allow non-const particle vectors
        inline void applyLooseIDElectronSelectionR2(std::vector<HEPUtils::Particle*>& electrons) {
          applyLooseIDElectronSelectionR2(reinterpret_cast<std::vector<const HEPUtils::Particle*>&>(electrons));
        }

        /// Efficiency function for Loose ID electrons
        /// @note Numbers digitised from Fig 3 of 13 TeV note (ATL-PHYS-PUB-2015-041)
        inline void applyMediumIDElectronSelectionR2(std::vector<const HEPUtils::Particle*>& electrons) {
          if (electrons.empty()) return;

          // Manually symmetrised eta eff histogram
          const static std::vector<double> binedges_eta = { 0.0,   0.1,   0.8,   1.37,  1.52,  2.01,  2.37,  2.47, DBL_MAX };
          const static std::vector<double> bineffs_eta  = { 0.900, 0.930, 0.905, 0.830, 0.900, 0.880, 0.85, 0 };
          const static HEPUtils::BinnedFn1D<double> _eff_eta(binedges_eta, bineffs_eta);
          // Et eff histogram (10-20 GeV bin added by hand)
          const static std::vector<double> binedges_et = { 10,   20,   25,   30,   35,   40,    45,    50,   60,  80, DBL_MAX };
          const static std::vector<double> bineffs_et  = { 0.83, 0.845, 0.87, 0.89, 0.90, 0.91, 0.92, 0.93, 0.95, 0.95 };
          const static HEPUtils::BinnedFn1D<double> _eff_et(binedges_et, bineffs_et);

          auto keptElectronsEnd = std::remove_if(electrons.begin(), electrons.end(),
                                                 [](const HEPUtils::Particle* electron) {
                                                   const double e_pt = electron->pT();
                                                   const double e_aeta = electron->abseta();
                                                   if (e_aeta > 2.47 || e_pt < 10) return true;
                                                   const double eff1 = _eff_eta.get_at(e_aeta), eff2 = _eff_et.get_at(e_pt);
                                                   const double eff = std::min(eff1 * eff2 / 0.95, 1.0); //< norm factor as approximate double differential
                                                   return random_bool(1-eff);
                                                 } );
          electrons.erase(keptElectronsEnd, electrons.end());
        }

        /// Alias to allow non-const particle vectors
        inline void applyMediumIDElectronSelectionR2(std::vector<HEPUtils::Particle*>& electrons) {
          applyMediumIDElectronSelectionR2(reinterpret_cast<std::vector<const HEPUtils::Particle*>&>(electrons));
        }

        /// Efficiency function for Medium ID electrons
        /// @note Numbers digitised from 8 TeV note (ATLAS-CONF-2014-032)
        inline void applyMediumIDElectronSelection(std::vector<const HEPUtils::Particle*>& electrons) {
          if (electrons.empty()) return;

          const static std::vector<double> binedges_E10_15 = {0., 0.0494681, 0.453578, 1.10675, 1.46298, 1.78963, 2.2766, 2.5};
          const static std::vector<double> binvalues_E10_15 = makeBinValues({0.730435, 0.730435, 0.782609, 0.776812, 0.765217, 0.773913, 0.77971, 0.776812});
          const static HEPUtils::BinnedFn1D<double> _eff_E10_15(binedges_E10_15, binvalues_E10_15);

          const static std::vector<double> binedges_E15_20 = {0., 0.0533175, 0.456161, 1.1019, 1.46327, 1.78318, 2.26303, 2.5};
          const static std::vector<double> binvalues_E15_20 = makeBinValues({0.77971, 0.77971, 0.82029, 0.817391, 0.701449, 0.797101, 0.828986, 0.828986});
          const static HEPUtils::BinnedFn1D<double> _eff_E15_20(binedges_E15_20, binvalues_E15_20);

          const static std::vector<double> binedges_E20_25 = {-2.5, -2.45249, -2.21496, -1.94181, -1.6924, -1.46675, -1.26485, -0.991686, -0.682898, -0.350356, -0.0415677, 0.0653207, 0.362233, 0.718527, 0.97981, 1.2886, 1.45487, 1.68052, 1.94181, 2.23872, 2.45249, 2.5};
          const static std::vector<double> binvalues_E20_25 = makeBinValues({0.827811, 0.82572, 0.790414, 0.798585, 0.779843, 0.727974, 0.802447, 0.798192, 0.812561, 0.812423, 0.808153, 0.779115, 0.822483, 0.816123, 0.795304, 0.793105, 0.772326, 0.778446, 0.794906, 0.78857, 0.821617, 0.821593});
          const static HEPUtils::BinnedFn1D<double> _eff_E20_25(binedges_E20_25, binvalues_E20_25);

          const static std::vector<double> binedges_E25_30 = {-2.5, -2.45249, -2.22684, -1.92993, -1.6924, -1.46675, -1.26485, -0.97981, -0.694774, -0.33848, -0.0534442, 0.0771971, 0.33848, 0.74228, 1.00356, 1.26485, 1.46675, 1.6924, 1.94181, 2.22684, 2.45249, 2.5};
          const static std::vector<double> binvalues_E25_30 = makeBinValues({0.84095, 0.838892, 0.8286, 0.830801, 0.818436, 0.76037, 0.841463, 0.83535, 0.850008, 0.852233, 0.837812, 0.82748, 0.854592, 0.854759, 0.838251, 0.844591, 0.76782, 0.815688, 0.836563, 0.824219, 0.838853, 0.838877});
          const static HEPUtils::BinnedFn1D<double> _eff_E25_30(binedges_E25_30, binvalues_E25_30);

          const static std::vector<double> binedges_E30_35 = {-2.5, -2.44062, -2.21496, -1.92993, -1.68052, -1.46675, -1.27672, -0.991686, -0.706651, -0.350356, -0.0534442, 0.0771971, 0.350356, 0.706651, 0.97981, 1.2886, 1.47862, 1.68052, 1.94181, 2.23872, 2.44062, 2.5};
          const static std::vector<double> binvalues_E30_35 = makeBinValues({0.849263, 0.849233, 0.840831, 0.853176, 0.844763, 0.771974, 0.873676, 0.865249, 0.877593, 0.883677, 0.869013, 0.856496, 0.879231, 0.883238, 0.870661, 0.870533, 0.779059, 0.839213, 0.84949, 0.834827, 0.834743, 0.834718});
          const static HEPUtils::BinnedFn1D<double> _eff_E30_35(binedges_E30_35, binvalues_E30_35);

          const static std::vector<double> binedges_E35_40 = {-2.5, -2.44841, -2.23431, -1.94914, -1.69969, -1.46336, -1.28359, -0.998664, -0.713488, -0.357087, -0.0723338, 0.0580256, 0.343519, 0.699955, 1.0085, 1.26989, 1.45836, 1.685, 1.93451, 2.23118, 2.46818, 2.5};
          const static std::vector<double> binvalues_E35_40 = makeBinValues({0.836795, 0.84095, 0.859644, 0.867953, 0.87003, 0.799407, 0.894955, 0.888724, 0.897033, 0.903264, 0.886647, 0.87003, 0.897033, 0.905341, 0.890801, 0.897033, 0.805638, 0.863798, 0.87003, 0.85549, 0.824332, 0.826409});
          const static HEPUtils::BinnedFn1D<double> _eff_E35_40(binedges_E35_40, binvalues_E35_40);

          const static std::vector<double> binedges_E40_45 = {-2.5, -2.45261, -2.22749, -1.93128, -1.68246, -1.46919, -1.27962, -0.995261, -0.7109, -0.343602, -0.0592417, 0.0473934, 0.35545, 0.699052, 0.983412, 1.27962, 1.4455, 1.69431, 1.94313, 2.22749, 2.44076, 2.5};
          const static std::vector<double> binvalues_E40_45 = makeBinValues({0.836795, 0.836795, 0.87003, 0.882493, 0.897033, 0.84095, 0.911573, 0.89911, 0.907418, 0.909496, 0.89911, 0.888724, 0.907418, 0.91365, 0.89911, 0.907418, 0.843027, 0.890801, 0.882493, 0.87003, 0.816024, 0.816024});
          const static HEPUtils::BinnedFn1D<double> _eff_E40_45(binedges_E40_45, binvalues_E40_45);

          const static std::vector<double> binedges_E45_50 = {-2.5, -2.46086, -2.22192, -1.93675, -1.68709, -1.46211, -1.27124, -0.986416, -0.689328, -0.356822, -0.0482438, 0.0584337, 0.355838, 0.712203, 0.996992, 1.28217, 1.45947, 1.68576, 1.93499, 2.21988, 2.44378, 2.5};
          const static std::vector<double>  binvalues_E45_50 =  makeBinValues({0.807101, 0.807101, 0.889941, 0.898225, 0.912722, 0.873373, 0.923077, 0.910651, 0.921006, 0.918935, 0.906509, 0.894083, 0.923077, 0.927219, 0.912722, 0.921006, 0.871302, 0.90858, 0.898225, 0.889941, 0.786391, 0.786391});
          const static HEPUtils::BinnedFn1D<double> _eff_E45_50 = {binedges_E45_50, binvalues_E45_50};

          const static std::vector<double> binedges_E50_60 = {-2.5, -2.44076, -2.21564, -1.93128, -1.69431, -1.46919, -1.26777, -0.983412, -0.7109, -0.35545, -0.0592417, 0.0592417, 0.35545, 0.7109, 0.983412, 1.27962, 1.46919, 1.68246, 1.91943, 2.22749, 2.44076, 2.5};
          const static std::vector<double>  binvalues_E50_60 = makeBinValues({0.785417, 0.785417, 0.891667, 0.9, 0.916667, 0.877083, 0.927083, 0.91875, 0.91875, 0.922917, 0.90625, 0.9, 0.922917, 0.929167, 0.920833, 0.925, 0.885417, 0.9125, 0.904167, 0.885417, 0.7625, 0.7625});
          const static HEPUtils::BinnedFn1D<double> _eff_E50_60 = {binedges_E50_60, binvalues_E50_60};

          const static std::vector<double> binedges_E60_80 = {-2.5, -2.44933, -2.22119, -1.9353, -1.68491, -1.47115, -1.2682, -0.982628, -0.696912, -0.351494, -0.0423579, 0.0526683, 0.350856, 0.719871, 1.00552, 1.29137, 1.46938, 1.69596, 1.94572, 2.24305, 2.45479, 2.5};
          const static std::vector<double>  binvalues_E60_80 = makeBinValues({0.779163, 0.779188, 0.893866, 0.904402, 0.927423, 0.896262, 0.92968, 0.921466, 0.921585, 0.932145, 0.909357, 0.896897, 0.930355, 0.928425, 0.924377, 0.93283, 0.899571, 0.922582, 0.908102, 0.89156, 0.741648, 0.741678});
          const static HEPUtils::BinnedFn1D<double> _eff_E60_80 = {binedges_E60_80, binvalues_E60_80};

          const static std::vector<double> binedges_E80 = {-2.5, -2.45835, -2.22058, -1.94663, -1.68451, -1.44712, -1.27961, -0.970161, -0.708258, -0.351259, -0.0537477, 0.0532884, 0.351188, 0.72041, 0.994111, 1.29176, 1.4815, 1.70839, 1.93419, 2.21998, 2.45825, 2.5};
          const static std::vector<double>  binvalues_E80 = makeBinValues({0.951041, 0.948944, 0.930151, 0.938346, 0.9507, 0.909058, 0.95884, 0.954557, 0.954449, 0.945992, 0.939637, 0.933361, 0.949854, 0.960086, 0.953741, 0.955695, 0.911996, 0.953445, 0.930502, 0.934538, 0.944824, 0.9448});
          const static HEPUtils::BinnedFn1D<double> _eff_E80 = {binedges_E80, binvalues_E80};

          // Now loop over the electrons and only keep those that pass the random efficiency cut
          /// @note No delete is necessary, because this should only ever be applied to a copy of the Event Particle* vectors
          /// @todo This is an exact duplication of the below filtering code -- split into a single util fn (in unnamed namespace?) when binned fns are static
          auto keptElectronsEnd = std::remove_if(electrons.begin(), electrons.end(),
                                             [](const HEPUtils::Particle* electron) {
                                               const double e_pt = electron->pT();
                                               const double e_eta = electron->eta();
                                               if (!(fabs(e_eta) < 2.5 && e_pt >= 10)) return true;
                                               else if (HEPUtils::in_range(e_pt, 10, 15)) return !random_bool(_eff_E10_15, fabs(e_eta));
                                               else if (HEPUtils::in_range(e_pt, 15, 20)) return !random_bool(_eff_E15_20, fabs(e_eta));
                                               else if (HEPUtils::in_range(e_pt, 20, 25)) return !random_bool(_eff_E20_25, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 25, 30)) return !random_bool(_eff_E25_30, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 30, 35)) return !random_bool(_eff_E30_35, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 35, 40)) return !random_bool(_eff_E35_40, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 40, 45)) return !random_bool(_eff_E40_45, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 45, 50)) return !random_bool(_eff_E45_50, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 50, 60)) return !random_bool(_eff_E50_60, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 60, 80)) return !random_bool(_eff_E60_80, e_eta);
                                               else return !random_bool(_eff_E80, e_eta);
                                             } );
          electrons.erase(keptElectronsEnd, electrons.end());
        }


        /// Alias to allow non-const particle vectors
        inline void applyMediumIDElectronSelection(std::vector<HEPUtils::Particle*>& electrons) {
          applyMediumIDElectronSelection(reinterpret_cast<std::vector<const HEPUtils::Particle*>&>(electrons));
        }


        /// Efficiency function for Tight ID electrons
        /// @note Numbers digitised from 8 TeV note (ATLAS-CONF-2014-032)
        inline void applyTightIDElectronSelection(std::vector<const HEPUtils::Particle*>& electrons) {

          const static std::vector<double> binedges_E10_15 = {0., 0.0485903, 0.458914, 1.10009, 1.46117, 1.78881, 2.27013, 2.5};
          const static std::vector<double> binvalues_E10_15 = makeBinValues({0.57971, 0.582609, 0.681159, 0.655072, 0.46087, 0.634783, 0.689855, 0.689855});
          const static HEPUtils::BinnedFn1D<double> _eff_E10_15(binedges_E10_15, binvalues_E10_15);

          const static std::vector<double> binedges_E15_20 = {0., 0.0533175, 0.450237, 1.09597, 1.46327, 1.78318, 2.26896, 2.5};
          const static std::vector<double> binvalues_E15_20 = makeBinValues({0.631884, 0.628986, 0.727536, 0.701449, 0.565217, 0.666667, 0.733333, 0.733333});
          const static HEPUtils::BinnedFn1D<double> _eff_E15_20(binedges_E15_20, binvalues_E15_20);

          const static std::vector<double> binedges_E20_25 =  {-2.5, -2.44062, -2.22684, -1.92993, -1.66865, -1.45487, -1.26485, -0.967933, -0.706651, -0.350356, -0.0415677, 0.0653207, 0.362233, 0.718527, 0.991686, 1.27672, 1.47862, 1.6924, 1.92993, 2.22684, 2.46437, 2.5};
          const static std::vector<double> binvalues_E20_25 = makeBinValues({0.678698, 0.678674, 0.70965, 0.65361, 0.655573, 0.599567, 0.6844, 0.694632, 0.729731, 0.731654, 0.665254, 0.640358, 0.743785, 0.733282, 0.697962, 0.672992, 0.585926, 0.660394, 0.652011, 0.703663, 0.670429, 0.668338});
          const static HEPUtils::BinnedFn1D<double> _eff_E20_25(binedges_E20_25, binvalues_E20_25);

          const static std::vector<double> binedges_E25_30 = {-2.5, -2.44062, -2.22684, -1.91805, -1.68052, -1.45487, -1.27672, -0.97981, -0.706651, -0.350356, -0.0415677, 0.0771971, 0.362233, 0.718527, 0.991686, 1.30048, 1.47862, 1.6924, 1.94181, 2.22684, 2.46437, 2.5};
          const static std::vector<double> binvalues_E25_30 = makeBinValues({0.678932, 0.681034, 0.737205, 0.683328, 0.695889, 0.633669, 0.720983, 0.733569, 0.758609, 0.769142, 0.69657, 0.688311, 0.771515, 0.771663, 0.734388, 0.717899, 0.636964, 0.699368, 0.689086, 0.730747, 0.67684, 0.67686});
          const static HEPUtils::BinnedFn1D<double> _eff_E25_30(binedges_E25_30, binvalues_E25_30);

          const static std::vector<double> binedges_E30_35 = {-2.5, -2.45249, -2.21496, -1.94181, -1.68052, -1.47862, -1.27672, -0.97981, -0.706651, -0.33848, -0.0415677, 0.0534442, 0.362233, 0.718527, 1.00356, 1.27672, 1.46675, 1.68052, 1.95368, 2.23872, 2.45249, 2.5};
          const static std::vector<double> binvalues_E30_35 = makeBinValues({0.691395, 0.691375, 0.749436, 0.716089, 0.726366, 0.653582, 0.749047, 0.771772, 0.800739, 0.802663, 0.731916, 0.71526, 0.802372, 0.810532, 0.773025, 0.75214, 0.656512, 0.722892, 0.712393, 0.745509, 0.670643, 0.6727});
          const static HEPUtils::BinnedFn1D<double> _eff_E30_35(binedges_E30_35, binvalues_E30_35);

          const static std::vector<double> binedges_E35_40 = {-2.5, -2.46296, -2.22413, -1.93966, -1.7017, -1.47721, -1.28567, -0.988409, -0.714721, -0.334744, -0.0510125, 0.0437527, 0.342215, 0.710598, 0.971211, 1.27968, 1.45638, 1.68306, 1.94399, 2.21764, 2.44185, 2.5};
          const static std::vector<double> binvalues_E35_40 = makeBinValues({0.683086, 0.683086, 0.759941, 0.726706, 0.751632, 0.683086, 0.772404, 0.793175, 0.824332, 0.820178, 0.743323, 0.728783, 0.820178, 0.832641, 0.793175, 0.774481, 0.689318, 0.749555, 0.728783, 0.757864, 0.6727, 0.6727});
          const static HEPUtils::BinnedFn1D<double> _eff_E35_40(binedges_E35_40, binvalues_E35_40);

          const static std::vector<double> binedges_E40_45 = {-2.5, -2.45261, -2.21564, -1.94313, -1.69431, -1.45735, -1.27962, -0.983412, -0.7109, -0.35545, -0.0592417, 0.0473934, 0.35545, 0.699052, 0.983412, 1.26777, 1.45735, 1.67062, 1.93128, 2.20379, 2.45261, 2.5};
          const static std::vector<double> binvalues_E40_45 = makeBinValues({0.693472, 0.693472, 0.782789, 0.757864, 0.784866, 0.726706, 0.797329, 0.803561, 0.836795, 0.805638, 0.747478, 0.735015, 0.805638, 0.843027, 0.807715, 0.797329, 0.732938, 0.780712, 0.762018, 0.782789, 0.674777, 0.674777});
          const static HEPUtils::BinnedFn1D<double> _eff_E40_45(binedges_E40_45, binvalues_E40_45);

          const static std::vector<double> binedges_E45_50 = {-2.5, -2.46311, -2.22329, -1.93875, -1.70073, -1.47585, -1.273, -0.976015, -0.714205, -0.358403, -0.0625448, 0.0560444, 0.354151, 0.711078, 0.98364, 1.28045, 1.45768, 1.68407, 1.94493, 2.20653, 2.4415, 2.5};
          const static std::vector<double>  binvalues_E45_50 =  makeBinValues({0.674556, 0.674556, 0.809172, 0.780178, 0.809172, 0.763609, 0.819527, 0.823669, 0.854734, 0.82574, 0.763609, 0.753254, 0.823669, 0.860947, 0.82574, 0.819527, 0.76568, 0.809172, 0.78432, 0.802959, 0.651775, 0.651775});
          const static HEPUtils::BinnedFn1D<double> _eff_E45_50 = {binedges_E45_50, binvalues_E45_50};

          const static std::vector<double> binedges_E50_60 = {-2.5, -2.45261, -2.21564, -1.93128, -1.68246, -1.45735, -1.27962, -0.995261, -0.699052, -0.343602, -0.0592417, 0.0592417, 0.35545, 0.699052, 0.983412, 1.26777, 1.4455, 1.68246, 1.94313, 2.21564, 2.45261, 2.5};
          const static std::vector<double>  binvalues_E50_60 = makeBinValues({0.6625, 0.6625, 0.810417, 0.795833, 0.81875, 0.779167, 0.839583, 0.84375, 0.860417, 0.841667, 0.777083, 0.764583, 0.841667, 0.877083, 0.85, 0.839583, 0.785417, 0.816667, 0.8, 0.804167, 0.64375, 0.64375});
          const static HEPUtils::BinnedFn1D<double> _eff_E50_60 = {binedges_E50_60, binvalues_E50_60};

          const static std::vector<double> binedges_E60_80 = {-2.5, -2.46326, -2.22265, -1.93711, -1.69844, -1.47299, -1.28152, -0.995631, -0.709702, -0.364674, -0.0564949, 0.0504716, 0.349652, 0.707116, 0.980538, 1.27812, 1.46757, 1.69447, 1.94394, 2.24157, 2.45288, 2.5};
          const static std::vector<double>  binvalues_E60_80 = makeBinValues({0.660412, 0.660432, 0.808449, 0.798151, 0.831584, 0.787928, 0.846341, 0.856877, 0.869496, 0.85714, 0.778101, 0.767729, 0.859521, 0.87842, 0.855617, 0.853658, 0.79332, 0.835081, 0.803935, 0.804059, 0.629147, 0.629172});
          const static HEPUtils::BinnedFn1D<double> _eff_E60_80 = {binedges_E60_80, binvalues_E60_80};

          const static std::vector<double> binedges_E80 = {-2.5, -2.45987, -2.22149, -1.94797, -1.69748, -1.47206, -1.29251, -0.994818, -0.709105, -0.352212, -0.0558319, 0.0513809, 0.374044, 0.719562, 0.981359, 1.27873, 1.46843, 1.70723, 1.9449, 2.20712, 2.45676, 2.5};
          const static std::vector<double>  binvalues_E80 = makeBinValues({0.859652, 0.859627, 0.876145, 0.859415, 0.888391, 0.8426, 0.900685, 0.904716, 0.904597, 0.889909, 0.817086, 0.821195, 0.893762, 0.910235, 0.903895, 0.889231, 0.843455, 0.884899, 0.859875, 0.87846, 0.857585, 0.85756});
          const static HEPUtils::BinnedFn1D<double> _eff_E80 = {binedges_E80, binvalues_E80};

          // Now loop over the electrons and only keep those that pass the random efficiency cut
          /// @note No delete is necessary, because this should only ever be applied to a copy of the Event Particle* vectors
          /// @todo This is an exact duplication of the above filtering code -- split into a single util fn (in unnamed namespace?) when binned fns are static
          auto keptElectronsEnd = std::remove_if(electrons.begin(), electrons.end(),
                                             [](const HEPUtils::Particle* electron) {
                                               const double e_pt = electron->pT();
                                               const double e_eta = electron->eta();
                                               if (!(fabs(e_eta) < 2.5 && e_pt >= 10)) return true;
                                               else if (HEPUtils::in_range(e_pt, 10, 15)) return !random_bool(_eff_E10_15, fabs(e_eta));
                                               else if (HEPUtils::in_range(e_pt, 15, 20)) return !random_bool(_eff_E15_20, fabs(e_eta));
                                               else if (HEPUtils::in_range(e_pt, 20, 25)) return !random_bool(_eff_E20_25, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 25, 30)) return !random_bool(_eff_E25_30, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 30, 35)) return !random_bool(_eff_E30_35, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 35, 40)) return !random_bool(_eff_E35_40, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 40, 45)) return !random_bool(_eff_E40_45, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 45, 50)) return !random_bool(_eff_E45_50, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 50, 60)) return !random_bool(_eff_E50_60, e_eta);
                                               else if (HEPUtils::in_range(e_pt, 60, 80)) return !random_bool(_eff_E60_80, e_eta);
                                               else return !random_bool(_eff_E80, e_eta);
                                             } );
          electrons.erase(keptElectronsEnd, electrons.end());
        }


        /// Alias to allow non-const particle vectors
        inline void applyTightIDElectronSelection(std::vector<HEPUtils::Particle*>& electrons) {
          applyTightIDElectronSelection(reinterpret_cast<std::vector<const HEPUtils::Particle*>&>(electrons));
        }


        /// Electron 2019 ID efficiency functions from https://arxiv.org/pdf/1902.04655.pdf
        /// @note These efficiencies are 1D efficiencies so only pT is used
        inline void applyElectronIDEfficiency2019(std::vector<const HEPUtils::Particle*>& electrons, str operating_point)
        {

          // digitised from Fig 8
          const static std::vector<double> binedges_pt  = { 0.0, 6.668795911849248, 9.673354432217419, 14.643593391597225, 19.57318312476409, 24.71356813100665, 29.655352632037403, 34.594233616910074, 39.73636073284749, 44.68221015649952, 49.6292209866148, 59.52440405330856, 79.51859702099242, DBL_MAX};
          const static std::vector<double> bineffs_pt_loose  = { 0.9054376657824932, 0.9267904509283819, 0.8757294429708221, 0.8450928381962863, 0.8775862068965516, 0.889655172413793, 0.9035809018567638, 0.9193633952254641, 0.929575596816976, 0.9370026525198938, 0.942572944297082, 0.9509283819628646, 0.9592838196286471};
          const static std::vector<double> bineffs_pt_medium  = { 0.7355437665782492, 0.7912466843501325, 0.7986737400530503, 0.7717506631299733, 0.8135278514588858, 0.8348806366047744, 0.8525198938992041, 0.8692307692307691, 0.8822281167108752, 0.889655172413793, 0.902652519893899, 0.9230769230769229, 0.9407161803713526 };
          const static std::vector<double> bineffs_pt_tight  = { 0.5572944297082227, 0.6213527851458884, 0.6547745358090185, 0.6714854111405835, 0.699336870026525, 0.7299734748010609, 0.7559681697612731, 0.7754641909814322, 0.7921750663129972, 0.8079575596816975, 0.8311671087533155, 0.8710875331564986, 0.8989389920424402 };

          // select operating point
          std::vector<double> bineffs_pt;
          if (operating_point == "Loose" or operating_point == "VeryLoose")
            bineffs_pt = bineffs_pt_loose;
          else if (operating_point == "Medium")
            bineffs_pt = bineffs_pt_medium;
          else if (operating_point == "Tight")
            bineffs_pt = bineffs_pt_tight;
          else
            utils_error().raise(LOCAL_INFO, "Unknown operating point");
          const static HEPUtils::BinnedFn1D<double> _eff_pt(binedges_pt, bineffs_pt);

          // filter electrons
          filtereff_pt(electrons, _eff_pt);

        }

        /// Electron 2019 Isolation efficiency functions from https://arxiv.org/pdf/1902.04655.pdf
        /// @note These efficiencies are 1D efficiencies so only pT is used
        inline void applyElectronIsolationEfficiency2019(std::vector<const HEPUtils::Particle*>& electrons, str operating_point)
        {

          // digitised from Fig 12
          const static std::vector<double> binedges_pt = {0.0, 6.548307897301772, 9.706735099256047, 14.643593391597225, 19.611982283197417, 24.561829913760132, 29.71154676569653, 34.461525174885566, 39.61370954807349, 44.56047277707178, 49.5109372879474, 59.60803424919497, 79.4086585320716, DBL_MAX};
          const static std::vector<double> bineffs_pt_loose_trackonly = {0.9694027334287603, 0.9841898810834618, 0.9915715839022242, 0.9890807366218896, 0.9875756991852016, 0.9875509249064084, 0.9875261506276152, 0.9879947974014535, 0.9884634441752919, 0.9884386698964986, 0.9888959617925568, 0.9907953231667035, 0.9930404921823388};
          const static std::vector<double> bineffs_pt_loose = {0.9595332801145123, 0.9812303870292888, 0.9891055109006828, 0.9875994412023784, 0.9856020149746753, 0.9826167143800926, 0.9820985190486677, 0.9820737447698745, 0.9820489704910813, 0.9825186495265361, 0.9829749091609778, 0.9903008698524555, 0.9930394599207224};
          const static std::vector<double> bineffs_pt_gradient_loose = {0.8973632597445498, 0.9471843343977098, 0.9693676365338032, 0.9466465260955738, 0.947115172869412, 0.9485706617485136, 0.9539735190486678, 0.9593784408720547, 0.9642868448579609, 0.9706755120017618, 0.9780417308962784, 0.9843808494824929, 0.9851457553402335};
          const static std::vector<double> bineffs_pt_gradient = {0.8425935229024444, 0.9082030389781987, 0.944204195111209, 0.9007573359392205, 0.9081359419731337, 0.9145235768553183, 0.924368255890773, 0.9351987447698745, 0.9460292336489761, 0.9573531435807092, 0.9716251926888351, 0.9838874284298613, 0.9851457553402335};

          // select operating point
          std::vector<double> bineffs_pt;
          if (operating_point == "LooseTrackOnly")
            bineffs_pt = bineffs_pt_loose_trackonly;
          else if (operating_point == "Loose")
            bineffs_pt = bineffs_pt_loose;
          else if (operating_point == "GradientLoose")
            bineffs_pt = bineffs_pt_gradient_loose;
          else if (operating_point == "Gradient")
            bineffs_pt = bineffs_pt_gradient;
          else
           utils_error().raise(LOCAL_INFO, "Unknown operating point");
          const static HEPUtils::BinnedFn1D<double> _eff_pt(binedges_pt, bineffs_pt);

          // filter electrons
          filtereff_pt(electrons, _eff_pt);
        }
 

        /// Electron 2020 reconstruction efficiency functions in 1908.00005 using 81 fb^-1 of Run 2 data
        /// @note These efficiencies are 1D efficiencies so only dependence on p_T is used
        inline void applyElectronReconstructionEfficiency2020(std::vector<const HEPUtils::Particle*>& electrons, str operating_point){

          // Digitised from Fig 5
          const static std::vector<double> binedges_pt = {0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5, 16.0, 16.5, 17.0, 17.5, 18.0, 18.5, 19.0, 19.5, 20.0, 20.5, 21.0, 21.5, 22.0, 22.5, 23.0, 23.5, 24.0, 24.5, DBL_MAX};
          const static std::vector<double> bineffs_pt_candidate = {0.0, 0.0, 0.0, 0.0, 0.003218, 0.049709, 0.203532, 0.388353, 0.546803, 0.662459, 0.749662, 0.807719, 0.850278, 0.875487, 0.893757, 0.907169, 0.919424, 0.926128, 0.932831, 0.936759, 0.940224, 0.944846, 0.947386, 0.949695, 0.951078, 0.953849, 0.955695, 0.956847, 0.958924, 0.959845, 0.962154, 0.96238, 0.96492, 0.966766, 0.966762, 0.967914, 0.967678, 0.970912, 0.970676, 0.97229, 0.972286, 0.97205, 0.973664, 0.97366, 0.973655, 0.973419, 0.975496, 0.976417, 0.977106, 0.976639};
          
          // Select operating point
          std::vector<double> bineffs_pt;
          if (operating_point == "Candidate")
            bineffs_pt = bineffs_pt_candidate;
          else
           utils_error().raise(LOCAL_INFO, "Unknown operating point");
          const static HEPUtils::BinnedFn1D<double> _eff_pt(binedges_pt, bineffs_pt);

          // Filter muons
          filtereff_pt(electrons, _eff_pt);
        }


        
        /// Electron 2020 ID efficiency functions in 1908.00005 using 81 fb^-1 of Run 2 data
        /// @note These efficiencies are 1D efficiencies so only dependence on p_T is used
        inline void applyElectronIDEfficiency2020(std::vector<const HEPUtils::Particle*>& electrons, str operating_point){

          // Digitised from Fig 23a
          const static std::vector<double> binedges_pt = {4.5, 7.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0, 60.0, 80.0, DBL_MAX};
          const static std::vector<double> bineffs_pt_loose = {0.976333, 0.928653, 0.882698, 0.830078, 0.86466, 0.884306, 0.901769, 0.920381, 0.930032, 0.936581, 0.938994, 0.943589, 0.949449};
          const static std::vector<double> bineffs_pt_medium = {0.790671, 0.797679, 0.816062, 0.752183, 0.794807, 0.82801, 0.847541, 0.868796, 0.882008, 0.887868, 0.897518, 0.916475, 0.93233};
          const static std::vector<double> bineffs_pt_tight = {0.582835, 0.608686, 0.670726, 0.651999, 0.684283, 0.716567, 0.747358, 0.768038, 0.782399, 0.795381, 0.815832, 0.853631, 0.884536};
          
          // Select operating point
          std::vector<double> bineffs_pt;
          if (operating_point == "Loose")
            bineffs_pt = bineffs_pt_loose;
          else if (operating_point == "Medium")
            bineffs_pt = bineffs_pt_medium;
          else if (operating_point == "Tight")
            bineffs_pt = bineffs_pt_tight;
          else
           utils_error().raise(LOCAL_INFO, "Unknown operating point");
          const static HEPUtils::BinnedFn1D<double> _eff_pt(binedges_pt, bineffs_pt);

          // Filter muons
          filtereff_pt(electrons, _eff_pt);
        }


        /// Electron 2020 isolation efficiency functions in 1908.00005 using 81 fb^-1 of Run 2 data
        /// @note These efficiencies are 1D efficiencies so only dependence on p_T is used
        inline void applyElectronIsolationEfficiency2020(std::vector<const HEPUtils::Particle*>& electrons, str operating_point){

          // Digitised from Fig 23a
          const static std::vector<double> binedges_pt = {4.5, 7.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0, 60.0, 80.0, 150.0, 200.0, 250.0, 300.0, 350.0, DBL_MAX};
          const static std::vector<double> bineffs_pt_gradient = {0.800008, 0.880847, 0.927209, 0.879823, 0.888724, 0.895806, 0.908012, 0.9198, 0.929307, 0.941235, 0.960432, 0.979162, 0.982515, 0.993515, 0.994261, 0.995376, 0.993139, 0.992581};
          const static std::vector<double> bineffs_pt_loose = {0.740555, 0.826427, 0.905545, 0.951997, 0.972965, 0.983728, 0.990392, 0.994352, 0.996819, 0.997565, 0.997844, 0.998311, 0.99859, 0.99859, 0.999057, 0.999105, 0.996589, 0.997614};
          const static std::vector<double> bineffs_pt_tight = {0.458893, 0.541276, 0.617828, 0.698061, 0.769957, 0.822, 0.862863, 0.895528, 0.924554, 0.940538, 0.95321, 0.970449, 0.985547, 0.992351, 0.993187, 0.993606, 0.991326, 0.991326};
          const static std::vector<double> bineffs_pt_highptcaloonly = {0.982097, 0.975105, 0.969703, 0.967933, 0.966908, 0.965137, 0.965416, 0.966859, 0.970449, 0.970358, 0.967138, 0.962014, 0.950973, 0.926185, 0.904053, 0.908291, 0.925906, 0.929168};

          // Select operating point
          std::vector<double> bineffs_pt;
          if (operating_point == "Gradient")
            bineffs_pt = bineffs_pt_gradient;
          else if (operating_point == "Loose")
            bineffs_pt = bineffs_pt_loose;
          else if (operating_point == "Tight")
            bineffs_pt = bineffs_pt_tight;
          else if (operating_point == "HighPtCaloOnly")
            bineffs_pt = bineffs_pt_highptcaloonly;
          else
           utils_error().raise(LOCAL_INFO, "Unknown operating point");
          const static HEPUtils::BinnedFn1D<double> _eff_pt(binedges_pt, bineffs_pt);

          // Filter muons
          filtereff_pt(electrons, _eff_pt);
        }


        /// Muon 2020 identification efficiency functions from full Run2 dataset released in 2012.00578
        /// @note These efficiencies are 1D efficiencies so only dependence on p_T is used
        inline void applyMuonIDEfficiency2020(std::vector<const HEPUtils::Particle*>& muons, str operating_point){

          // Digitised from Fig 11a
          const static std::vector<double> binedges_pt = {3.0, 3.5, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 12.0, 14.0, 16.0, 18.0, DBL_MAX};
          const static std::vector<double> bineffs_pt_tight = {0.0, 0.0, 0.66948, 0.8143, 0.85466, 0.87816, 0.89246, 0.90421, 0.91418, 0.91877, 0.92031, 0.92669, 0.93972};
          const static std::vector<double> bineffs_pt_medium = {0.45262, 0.61328, 0.80766, 0.9387, 0.96245, 0.97063, 0.97165, 0.97216, 0.97292, 0.97292, 0.97216, 0.97114, 0.97522};
          const static std::vector<double> bineffs_pt_loose = {0.87075, 0.93129, 0.97241, 0.98851, 0.99157, 0.98851, 0.98799, 0.98799, 0.98799, 0.98748, 0.98672, 0.98748, 0.98927};
          
          // Select operating point
          std::vector<double> bineffs_pt;
          if (operating_point == "Tight")
            bineffs_pt = bineffs_pt_tight;
          else if (operating_point == "Medium")
            bineffs_pt = bineffs_pt_medium;
          else if (operating_point == "Loose")
            bineffs_pt = bineffs_pt_loose;
          else
           utils_error().raise(LOCAL_INFO, "Unknown operating point");
          const static HEPUtils::BinnedFn1D<double> _eff_pt(binedges_pt, bineffs_pt);

          // Filter muons
          filtereff_pt(muons, _eff_pt);
        }

        /// Muon 2020 isolation efficiency functions from full Run2 dataset released in 2012.00578
        /// @note These efficiencies are 1D efficiencies so only dependence on p_T is used
        inline void applyMuonIsolationEfficiency2020(std::vector<const HEPUtils::Particle*>& muons, str operating_point){

          // Digitised from Fig 19a
          const static std::vector<double> binedges_pt = {3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 15.0, 20.0, 30.0, 40.0, 55.0, 70.0, 90.0, 150.0, DBL_MAX};
          const static std::vector<double> bineffs_pt_tight = {0.56788, 0.63355, 0.66702, 0.68974, 0.70173, 0.71814, 0.7314, 0.75917, 0.79262, 0.84313, 0.9189, 0.96941, 0.9915, 0.99653, 0.99649, 0.99517};
          const static std::vector<double> bineffs_pt_loose = {0.655349, 0.725581, 0.765116, 0.788605, 0.82093, 0.851395, 0.877907, 0.92, 0.956279, 0.981163, 0.992558, 0.996744, 0.997442, 0.997442, 0.997674, 0.995116};

          // Select operating point
          std::vector<double> bineffs_pt;
          if (operating_point == "Tight")
            bineffs_pt = bineffs_pt_tight;
          else if (operating_point == "Loose")
            bineffs_pt = bineffs_pt_loose;
          else
           utils_error().raise(LOCAL_INFO, "Unknown operating point");
          const static HEPUtils::BinnedFn1D<double> _eff_pt(binedges_pt, bineffs_pt);

          // Filter muons
          filtereff_pt(muons, _eff_pt);
        }

        ///@}

      }
   }
}

Updated on 2023-06-26 at 21:36:56 +0000