Program Listing for File BasicEventSelection.cxx

Return to documentation for file (Root/BasicEventSelection.cxx)

// EL include(s):
#include <EventLoop/Job.h>
#include <EventLoop/Worker.h>
#include "EventLoop/OutputStream.h"

// EDM include(s):
#include "xAODEventInfo/EventInfo.h"
#include "xAODTracking/VertexContainer.h"
#include "xAODCutFlow/CutBookkeeper.h"
#include "xAODCutFlow/CutBookkeeperContainer.h"

// package include(s):
#include <xAODAnaHelpers/HelperFunctions.h>
#include <xAODAnaHelpers/BasicEventSelection.h>

#include "PATInterfaces/CorrectionCode.h"
//#include "AsgMessaging/StatusCode.h"

// tools
#include "GoodRunsLists/GoodRunsListSelectionTool.h"
#include "PileupReweighting/PileupReweightingTool.h"
#include "TrigConfxAOD/xAODConfigTool.h"
//#include "PMGTools/PMGSherpa22VJetsWeightTool.h"

// For reading metadata
#include "AsgTools/AsgMetadataTool.h"
#include "xAODMetaData/FileMetaData.h"

// ROOT include(s):
#include "TFile.h"
#include "TTree.h"
#include "TTreeFormula.h"
#include "TSystem.h"
#include "xAODCore/tools/IOStats.h"
#include "xAODCore/tools/ReadStats.h"

// this is needed to distribute the algorithm to the workers
ClassImp(BasicEventSelection)

BasicEventSelection :: BasicEventSelection () :
    Algorithm("BasicEventSelection")
{
}


EL::StatusCode BasicEventSelection :: setupJob (EL::Job& job)
{
  // Here you put code that sets up the job on the submission object
  // so that it is ready to work with your algorithm, e.g. you can
  // request the D3PDReader service or add output files.  Any code you
  // put here could instead also go into the submission script.  The
  // sole advantage of putting it here is that it gets automatically
  // activated/deactivated when you add/remove the algorithm from your
  // job, which may or may not be of value to you.
  //
  ANA_MSG_INFO( "Calling setupJob");

  job.useXAOD();
  // let's initialize the algorithm to use the xAODRootAccess package
  xAOD::Init("BasicEventSelection").ignore(); // call before opening first file

  EL::OutputStream outForCFlow(m_cutFlowStreamName);
  if(!job.outputHas(m_cutFlowStreamName) ){ job.outputAdd ( outForCFlow ); }

  EL::OutputStream outForMetadata(m_metaDataStreamName);
  if(!job.outputHas(m_metaDataStreamName) ){ job.outputAdd ( outForMetadata ); }

  EL::OutputStream outForDuplicates(m_duplicatesStreamName);
  if(!job.outputHas(m_duplicatesStreamName) ){ job.outputAdd ( outForDuplicates ); }

  return EL::StatusCode::SUCCESS;
}



EL::StatusCode BasicEventSelection :: histInitialize ()
{
  // Here you do everything that needs to be done at the very
  // beginning on each worker node, e.g. create histograms and output
  // trees.  This method gets called before any input files are
  // connected.

  ANA_MSG_INFO( "Calling histInitialize");
  ANA_CHECK( xAH::Algorithm::algInitialize());

  // write the metadata hist to this file so algos downstream can pick up the pointer
  TFile *fileMD = wk()->getOutputFile (m_metaDataStreamName);
  fileMD->cd();

  // event counts from meta data
  if ( !m_histEventCount ) {
    m_histEventCount = new TH1D("MetaData_EventCount", "MetaData_EventCount", 6, 0.5, 6.5);
    m_histEventCount -> GetXaxis() -> SetBinLabel(1, "nEvents initial");
    m_histEventCount -> GetXaxis() -> SetBinLabel(2, "nEvents selected");
    m_histEventCount -> GetXaxis() -> SetBinLabel(3, "sumOfWeights initial");
    m_histEventCount -> GetXaxis() -> SetBinLabel(4, "sumOfWeights selected");
    m_histEventCount -> GetXaxis() -> SetBinLabel(5, "sumOfWeightsSquared initial");
    m_histEventCount -> GetXaxis() -> SetBinLabel(6, "sumOfWeightsSquared selected");
  }

  TFile *fileSumW = wk()->getOutputFile (m_metaDataStreamName);
  fileSumW->cd();

  // event counts from meta data
  if ( !m_histSumW ) {
    m_histSumW = new TH1D("MetaData_SumW", "MetaData_SumW", 1, -0.5, 0.5);
    m_histSumW->SetCanExtend(TH1::kAllAxes);
  }

  ANA_MSG_INFO( "Creating histograms");

  // write the cutflows to this file so algos downstream can pick up the pointer
  //
  TFile *fileCF = wk()->getOutputFile (m_cutFlowStreamName);
  fileCF->cd();

  // Note: the following code is needed for anyone developing/running in ROOT 6.04.10+
  // Bin extension is not done anymore via TH1::SetBit(TH1::kCanRebin), but with TH1::SetCanExtend(TH1::kAllAxes)

  //initialise event cutflow, which will be picked ALSO by the algos downstream where an event selection is applied (or at least can be applied)
  //
  // use 1,1,2 so Fill(bin) and GetBinContent(bin) refer to the same bin
  //
  m_cutflowHist  = new TH1D("cutflow", "cutflow", 1, 1, 2);
  m_cutflowHist->SetCanExtend(TH1::kAllAxes);
  // use 1,1,2 so Fill(bin) and GetBinContent(bin) refer to the same bin
  //
  m_cutflowHistW = new TH1D("cutflow_weighted", "cutflow_weighted", 1, 1, 2);
  m_cutflowHistW->SetCanExtend(TH1::kAllAxes);

  // initialise object cutflows, which will be picked by the object selector algos downstream and filled.
  //
  m_el_cutflowHist_1     = new TH1D("cutflow_electrons_1", "cutflow_electrons_1", 1, 1, 2);
  m_el_cutflowHist_1->SetCanExtend(TH1::kAllAxes);
  m_el_cutflowHist_2     = new TH1D("cutflow_electrons_2", "cutflow_electrons_2", 1, 1, 2);
  m_el_cutflowHist_2->SetCanExtend(TH1::kAllAxes);
  m_mu_cutflowHist_1     = new TH1D("cutflow_muons_1", "cutflow_muons_1", 1, 1, 2);
  m_mu_cutflowHist_1->SetCanExtend(TH1::kAllAxes);
  m_mu_cutflowHist_2     = new TH1D("cutflow_muons_2", "cutflow_muons_2", 1, 1, 2);
  m_mu_cutflowHist_2->SetCanExtend(TH1::kAllAxes);
  m_ph_cutflowHist_1     = new TH1D("cutflow_photons_1", "cutflow_photons_1", 1, 1, 2);
  m_ph_cutflowHist_1->SetCanExtend(TH1::kAllAxes);
  m_tau_cutflowHist_1     = new TH1D("cutflow_taus_1", "cutflow_taus_1", 1, 1, 2);
  m_tau_cutflowHist_1->SetCanExtend(TH1::kAllAxes);
  m_tau_cutflowHist_2     = new TH1D("cutflow_taus_2", "cutflow_taus_2", 1, 1, 2);
  m_tau_cutflowHist_2->SetCanExtend(TH1::kAllAxes);
  m_jet_cutflowHist_1    = new TH1D("cutflow_jets_1", "cutflow_jets_1", 1, 1, 2);
  m_jet_cutflowHist_1->SetCanExtend(TH1::kAllAxes);
  m_trk_cutflowHist_1    = new TH1D("cutflow_trks_1", "cutflow_trks_1", 1, 1, 2);
  m_trk_cutflowHist_1->SetCanExtend(TH1::kAllAxes);
  m_truth_cutflowHist_1  = new TH1D("cutflow_truths_1", "cutflow_truths_1", 1, 1, 2);
  m_truth_cutflowHist_1->SetCanExtend(TH1::kAllAxes);

  // start labelling the bins for the event cutflow
  //
  m_cutflow_all  = m_cutflowHist->GetXaxis()->FindBin("all");
  m_cutflowHistW->GetXaxis()->FindBin("all");

  m_cutflow_init  = m_cutflowHist->GetXaxis()->FindBin("init");
  m_cutflowHistW->GetXaxis()->FindBin("init");

  // extra cutflows
  // create histograms when m_doRunByRunCutflows is set to true
  // later check if !isMC() for filling so that these only get
  // filled when running on data
  if (m_doRunByRunCutflows) {
    m_runByrun_beforeCuts  = new TH1D("runByrun_cutflow_beforeCuts", "Run-by-Run cutflow before cuts", 1, 1, 2);
    m_runByrun_beforeCuts->SetCanExtend(TH1::kAllAxes);

    m_runByrun_afterCuts  = new TH1D("runByrun_cutflow_afterCuts", "Run-by-Run cutflow after cuts", 1, 1, 2);
    m_runByrun_afterCuts->SetCanExtend(TH1::kAllAxes);
  }

  ANA_MSG_INFO( "Finished creating histograms");

  return EL::StatusCode::SUCCESS;
}


EL::StatusCode BasicEventSelection :: fileExecute ()
{
  // Here you do everything that needs to be done exactly once for every
  // single file, e.g. collect a list of all lumi-blocks processed

  ANA_MSG_INFO( "Calling fileExecute");

  // get TEvent and TStore - must be done here b/c we need to retrieve CutBookkeepers container from TEvent!
  //
  m_event = wk()->xaodEvent();
  m_store = wk()->xaodStore();

  // get the MetaData tree once a new file is opened, with
  //
  TTree* MetaData = dynamic_cast<TTree*>( wk()->inputFile()->Get("MetaData") );
  if ( !MetaData ) {
    ANA_MSG_ERROR( "MetaData tree not found! Exiting.");
    return EL::StatusCode::FAILURE;
  }
  MetaData->LoadTree(0);

  //---------------------------
  // Meta data - CutBookkepers
  //---------------------------
  //
  // Metadata for intial N (weighted) events are used to correctly normalise MC
  // if running on a MC DAOD which had some skimming applied at the derivation stage

  //check if file is from a DxAOD
  bool m_isDerivation = !MetaData->GetBranch("StreamAOD");

  if (  m_useMetaData ) {

      // Check for potential file corruption
      //
      // If there are some Incomplete CBK, throw a WARNING,
      // unless ALL of them have inputStream == "unknownStream"
      //
      const xAOD::CutBookkeeperContainer* incompleteCBC(nullptr);
      if ( !m_event->retrieveMetaInput(incompleteCBC, "IncompleteCutBookkeepers").isSuccess() ) {
      ANA_MSG_ERROR("Failed to retrieve IncompleteCutBookkeepers from MetaData! Exiting.");
      return EL::StatusCode::FAILURE;
      }
      bool allFromUnknownStream(true);
      if ( incompleteCBC->size() != 0 ) {

      std::string stream("");
      for ( auto cbk : *incompleteCBC ) {
          ANA_MSG_INFO("Incomplete cbk name: " << cbk->name() << " - stream: " << cbk->inputStream());
          if ( cbk->inputStream() != "unknownStream" ) {
          allFromUnknownStream = false;
          stream = cbk->inputStream();
          break;
          }
      }
      if ( !allFromUnknownStream ) { ANA_MSG_WARNING("Found incomplete CBK from stream: " << stream << ". This is not necessarily a sign of file corruption (incomplete CBK appear when 'maxevents' is set in the AOD jo, for instance), but you may still want to check input file for potential corruption..." ); }

      }

      // Now, let's find the actual information
      //
      const xAOD::CutBookkeeperContainer* completeCBC(nullptr);
      if ( !m_event->retrieveMetaInput(completeCBC, "CutBookkeepers").isSuccess() ) {
      ANA_MSG_ERROR("Failed to retrieve CutBookkeepers from MetaData! Exiting.");
      return EL::StatusCode::FAILURE;
      }

      // Find the smallest cycle number, the original first processing step/cycle
      int minCycle(10000);
      for ( auto cbk : *completeCBC ) {
      if ( !( cbk->name().empty() )  && ( minCycle > cbk->cycle() ) ){ minCycle = cbk->cycle(); }
      }

      // Now, let's actually find the right one that contains all the needed info...
      const xAOD::CutBookkeeper* allEventsCBK(nullptr);
      const xAOD::CutBookkeeper* DxAODEventsCBK(nullptr);

      if ( m_isDerivation ) {
    if(m_derivationName != ""){
      ANA_MSG_INFO("Override auto config to look at DAOD made by Derivation Algorithm: " << m_derivationName);
    }else{
      ANA_MSG_INFO("Will autoconfig to look at DAOD made by Derivation Algorithm.");
    }
      }

      int maxCycle(-1);
      for ( const xAOD::CutBookkeeper* cbk: *completeCBC )
    {
      // Find initial book keeper
      ANA_MSG_INFO("Complete cbk name: " << cbk->name() << " - stream: " << cbk->inputStream() );
      if( cbk->cycle() > maxCycle && cbk->name() == "AllExecutedEvents" && cbk->inputStream() == "StreamAOD" )
        {
          allEventsCBK = cbk;
          maxCycle = cbk->cycle();
        }

      // Find derivation book keeper
      if ( m_isDerivation )
        {

          if(m_derivationName != "")
        {
          if ( cbk->name() == m_derivationName )
            DxAODEventsCBK = cbk;
        }
          else if( cbk->name().find("Kernel") != std::string::npos )
        {
          ANA_MSG_INFO("Auto config found DAOD made by Derivation Algorithm: " << cbk->name());
          DxAODEventsCBK = cbk;
        }
        } // is derivation

      // Find and record AOD-level sumW information for all alternate weights
      //  The nominal AllExecutedEvents will be filled later, due to posibility of multiple CBK entries
      if(cbk->name().substr(0,37) == "AllExecutedEvents_NonNominalMCWeight_" && cbk->name().length()!=17 && cbk->inputStream() == "StreamAOD")
        {
          // Extract weight index from name
          int32_t idx=std::stoi(cbk->name().substr(37));

          // Fill histogram, making sure that there is space
          // Note will fill bin index = idx+1
          while(idx>=m_histSumW->GetNbinsX())
        m_histSumW->LabelsInflate("X");
          m_histSumW->Fill(idx, cbk->sumOfEventWeights());
        }
    }

      if(allEventsCBK == nullptr) {
        ANA_MSG_WARNING("No allEventsCBK found (this is expected for DataScouting, otherwise not). Event numbers set to 0.");
        m_MD_initialNevents     = 0;
        m_MD_initialSumW        = 0;
        m_MD_initialSumWSquared = 0;
      }
      else {
        m_MD_initialNevents     = allEventsCBK->nAcceptedEvents();
        m_MD_initialSumW        = allEventsCBK->sumOfEventWeights();
        m_MD_initialSumWSquared = allEventsCBK->sumOfEventWeightsSquared();
      }

      if ( m_isDerivation && !DxAODEventsCBK ) {
        ANA_MSG_ERROR( "No CutBookkeeper corresponds to the selected Derivation Framework algorithm name. Check it with your DF experts! Aborting.");
        return EL::StatusCode::FAILURE;
      }

      m_MD_finalNevents       = ( m_isDerivation ) ? DxAODEventsCBK->nAcceptedEvents() : m_MD_initialNevents;
      m_MD_finalSumW          = ( m_isDerivation ) ? DxAODEventsCBK->sumOfEventWeights() : m_MD_initialSumW;
      m_MD_finalSumWSquared   = ( m_isDerivation ) ? DxAODEventsCBK->sumOfEventWeightsSquared() : m_MD_initialSumWSquared;

      // Write metadata event bookkeepers to histogram
      //
      ANA_MSG_INFO( "Meta data from this file:");
      ANA_MSG_INFO( "Initial  events  = "                << static_cast<unsigned int>(m_MD_initialNevents) );
      ANA_MSG_INFO( "Selected events  = "                << static_cast<unsigned int>(m_MD_finalNevents) );
      ANA_MSG_INFO( "Initial  sum of weights = "         << m_MD_initialSumW);
      ANA_MSG_INFO( "Selected sum of weights = "         << m_MD_finalSumW);
      ANA_MSG_INFO( "Initial  sum of weights squared = " << m_MD_initialSumWSquared);
      ANA_MSG_INFO( "Selected sum of weights squared = " << m_MD_finalSumWSquared);

      m_cutflowHist ->Fill(m_cutflow_all, m_MD_initialNevents);
      m_cutflowHistW->Fill(m_cutflow_all, m_MD_initialSumW);

      m_histSumW->Fill(0., m_MD_initialSumW);

      m_histEventCount -> Fill(1, m_MD_initialNevents);
      m_histEventCount -> Fill(2, m_MD_finalNevents);
      m_histEventCount -> Fill(3, m_MD_initialSumW);
      m_histEventCount -> Fill(4, m_MD_finalSumW);
      m_histEventCount -> Fill(5, m_MD_initialSumWSquared);
      m_histEventCount -> Fill(6, m_MD_finalSumWSquared);
  }

  return EL::StatusCode::SUCCESS;

}

EL::StatusCode BasicEventSelection :: changeInput (bool /*firstFile*/)
{
  // Here you do everything you need to do when we change input files,
  // e.g. resetting branch addresses on trees.  If you are using
  // D3PDReader or a similar service this method is not needed.
  return EL::StatusCode::SUCCESS;
}



EL::StatusCode BasicEventSelection :: initialize ()
{
  // Here you do everything that you need to do after the first input
  // file has been connected and before the first event is processed,
  // e.g. create additional histograms based on which variables are
  // available in the input files.  You can also create all of your
  // histograms and trees in here, but be aware that this method
  // doesn't get called if no events are processed.  So any objects
  // you create here won't be available in the output if you have no
  // input events.

  ANA_MSG_INFO( "Initializing BasicEventSelection... ");

  // if truth level make sure parameters are set properly
  if( m_truthLevelOnly ) {
    ANA_MSG_INFO( "Truth only! Turn off trigger stuff");
    m_triggerSelection      = "";
    m_extraTriggerSelection = "";
    m_applyTriggerCut = m_storeTrigDecisions = m_storePassL1 = m_storePassHLT = m_storeTrigKeys = false;
    ANA_MSG_INFO( "Truth only! Turn off GRL");
    m_applyGRLCut = false;
    ANA_MSG_INFO( "Truth only! Turn off Pile-up Reweight");
    m_doPUreweighting = false;
    m_doPUreweightingSys = false;
  }

  // get TEvent and TStore
  //
  m_event = wk()->xaodEvent();
  m_store = wk()->xaodStore();

  const xAOD::EventInfo* eventInfo(nullptr);
  ANA_CHECK( HelperFunctions::retrieve(eventInfo, m_eventInfoContainerName, m_event, m_store, msg()) );

  ANA_MSG_DEBUG( "Is MC? " << isMC() );

  //Protection in case GRL does not apply to this run
  //
  if ( m_applyGRLCut ) {
    std::string runNumString = std::to_string(eventInfo->runNumber());
    if ( m_GRLExcludeList.find( runNumString ) != std::string::npos ) {
      ANA_MSG_INFO( "RunNumber is in GRLExclusion list, setting applyGRL to false");
      m_applyGRLCut = false;
    }
  }

  m_cleanPowheg = false;
  if ( eventInfo->runNumber() == 426005 ) { // Powheg+Pythia J5
    m_cleanPowheg = true;
    ANA_MSG_INFO( "This is J5 Powheg - cleaning that nasty huge weight event");
  }


  // https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/CentralMC15ProductionList#Sherpa_v2_2_0_V_jets_NJet_reweig
  //m_reweightSherpa22 = false;
  //if( isMC() &&
  //    ( (eventInfo->mcChannelNumber() >= 363331 && eventInfo->mcChannelNumber() <= 363483 ) ||
  //      (eventInfo->mcChannelNumber() >= 363102 && eventInfo->mcChannelNumber() <= 363122 ) ||
  //      (eventInfo->mcChannelNumber() >= 363361 && eventInfo->mcChannelNumber() <= 363435 ) ) ){
  //  ANA_MSG_INFO( "This is Sherpa 2.2 dataset and should be reweighted.  An extra weight will be saved to EventInfo called \"weight_Sherpa22\".");
  //  m_reweightSherpa22 = true;

  //  //Choose Jet Truth container, WZ has more information and is favored by the tool
  //  std::string pmg_TruthJetContainer = "";
  //  if( m_event->contains<xAOD::JetContainer>("AntiKt4TruthWZJets") ){
  //    pmg_TruthJetContainer = "AntiKt4TruthWZJets";
  //  } else if( m_event->contains<xAOD::JetContainer>("AntiKt4TruthJets") ){
  //    pmg_TruthJetContainer = "AntiKt4TruthJets";
  //  } else {
  //    ANA_MSG_WARNING( "No Truth Jet Container found for Sherpa 22 reweighting, weight_Sherpa22 will not be set.");
  //    m_reweightSherpa22 = false;
  //  }

  //  //Initialize Tool
  //  if( m_reweightSherpa22 ){

  //    ANA_CHECK( m_reweightSherpa22_tool_handle.setProperty( "TruthJetContainer", pmg_TruthJetContainer ));
  //    ANA_CHECK( m_reweightSherpa22_tool_handle.setProperty( "OutputLevel", msg().level() ));
  //    ANA_CHECK( m_reweightSherpa22_tool_handle.retrieve());
  //    ANA_MSG_DEBUG("Retrieved tool: " << m_reweightSherpa22_tool_handle);

  //  }
  //}//if isMC and a Sherpa 2.2 sample

  ANA_MSG_INFO( "Setting up histograms based on isMC()");

  if ( ( !isMC() && m_checkDuplicatesData ) || ( isMC() && m_checkDuplicatesMC ) ) {
    m_cutflow_duplicates  = m_cutflowHist->GetXaxis()->FindBin("Duplicates");
    m_cutflowHistW->GetXaxis()->FindBin("Duplicates");
  }

  if ( !isMC() ) {
    if ( m_applyGRLCut ) {
      m_cutflow_grl  = m_cutflowHist->GetXaxis()->FindBin("GRL");
      m_cutflowHistW->GetXaxis()->FindBin("GRL");
    }
    m_cutflow_lar  = m_cutflowHist->GetXaxis()->FindBin("LAr");
    m_cutflowHistW->GetXaxis()->FindBin("LAr");
    m_cutflow_tile = m_cutflowHist->GetXaxis()->FindBin("tile");
    m_cutflowHistW->GetXaxis()->FindBin("tile");
    m_cutflow_SCT = m_cutflowHist->GetXaxis()->FindBin("SCT");
    m_cutflowHistW->GetXaxis()->FindBin("SCT");
    m_cutflow_core = m_cutflowHist->GetXaxis()->FindBin("core");
    m_cutflowHistW->GetXaxis()->FindBin("core");
  }
  if ( m_applyJetCleaningEventFlag ) {
    m_cutflow_jetcleaning = m_cutflowHist->GetXaxis()->FindBin("JetCleaning");
    m_cutflowHistW->GetXaxis()->FindBin("JetCleaning");
  }
  if ( !isMC() ) {
    if ( m_applyIsBadBatmanFlag ) {
      m_cutflow_isbadbatman =  m_cutflowHist->GetXaxis()->FindBin("IsBadBatman");
      m_cutflowHistW->GetXaxis()->FindBin("IsBadBatman");
    }
  }
  m_cutflow_npv  = m_cutflowHist->GetXaxis()->FindBin("NPV");
  m_cutflowHistW->GetXaxis()->FindBin("NPV");
  if ( !m_triggerSelection.empty() && m_applyTriggerCut ) {
    m_cutflow_trigger  = m_cutflowHist->GetXaxis()->FindBin("Trigger");
    m_cutflowHistW->GetXaxis()->FindBin("Trigger");
  }

  ANA_MSG_INFO( "Histograms set up!");

  // -------------------------------------------------------------------------------------------------

  // Create TTree for bookeeeping duplicated events
  //
  TFile *fileDPL = wk()->getOutputFile (m_duplicatesStreamName);
  fileDPL->cd();

  m_duplicatesTree = new TTree("duplicates","Info on duplicated events");
  m_duplicatesTree->Branch("runNumber",    &m_duplRunNumber,      "runNumber/I");
  m_duplicatesTree->Branch("eventNumber",  &m_duplEventNumber,    "eventNumber/L");

  // -------------------------------------------------------------------------------------------------

  ANA_MSG_INFO( "Setting Up Tools");

  // 1.
  // initialize the GoodRunsListSelectionTool
  //

  if(m_applyGRLCut){
    std::vector<std::string> vecStringGRL;

    std::string grl;
    std::istringstream ss(m_GRLxml);
    while ( std::getline(ss, grl, ',') ) {
        std::string file = PathResolverFindCalibFile(grl);
        ANA_MSG_DEBUG("Found GRL: " << file);
        vecStringGRL.push_back(file);
    }

    ANA_CHECK( m_grl_handle.setProperty("GoodRunsListVec", vecStringGRL));
    ANA_CHECK( m_grl_handle.setProperty("PassThrough", false));
    ANA_CHECK( m_grl_handle.setProperty("OutputLevel", msg().level()));
    ANA_CHECK( m_grl_handle.retrieve());
    ANA_MSG_DEBUG("Retrieved tool: " << m_grl_handle);
  }

  // 2.
  // initialize the Trig::TrigDecisionTool
  //
  if( !m_triggerSelection.empty() || !m_extraTriggerSelection.empty() ||
      m_applyTriggerCut || m_storeTrigDecisions || m_storePassL1 || m_storePassHLT || m_storeTrigKeys ) {

    ANA_CHECK( m_trigConfTool_handle.setProperty("OutputLevel", msg().level()));
    ANA_CHECK( m_trigConfTool_handle.retrieve());
    ANA_MSG_DEBUG("Retrieved tool: " << m_trigConfTool_handle);

    ANA_CHECK( m_trigDecTool_handle.setProperty( "ConfigTool", m_trigConfTool_handle ));
    ANA_CHECK( m_trigDecTool_handle.setProperty( "TrigDecisionKey", "xTrigDecision" ));
    ANA_CHECK( m_trigDecTool_handle.setProperty( "OutputLevel", msg().level() ));

    if ( m_useRun3navigation )
    {
      ANA_CHECK( m_trigDecTool_handle.setProperty( "NavigationFormat", "TrigComposite") );
      ANA_CHECK( m_trigDecTool_handle.setProperty( "HLTSummary", m_HLTSummary) );
    } else {
      ANA_CHECK( m_trigDecTool_handle.setProperty( "NavigationFormat", "TriggerElement") );
    }
    ANA_CHECK( m_trigDecTool_handle.retrieve());
    ANA_MSG_DEBUG("Retrieved tool: " << m_trigDecTool_handle);

    // parse extra triggers list and split by comma
    std::string token;
    std::istringstream ss(m_extraTriggerSelection);
    while (std::getline(ss, token, ',')) {
      m_extraTriggerSelectionList.push_back(token);
    }
  }//end trigger configuration

  // 3.
  // initialize the CP::PileupReweightingTool
  //

  if ( m_doPUreweighting ) {

    std::vector<std::string> PRWFiles;
    std::vector<std::string> lumiCalcFiles;

    std::string tmp_lumiCalcFileNames = m_lumiCalcFileNames;
    std::string tmp_PRWFileNames      = m_PRWFileNames;

    // Parse all comma seperated files
    //
    while ( tmp_PRWFileNames.size() > 0) {
      size_t pos = tmp_PRWFileNames.find_first_of(',');
      if ( pos == std::string::npos ) {
        pos = tmp_PRWFileNames.size();
        PRWFiles.push_back(tmp_PRWFileNames.substr(0, pos));
        tmp_PRWFileNames.erase(0, pos);
      } else {
        PRWFiles.push_back(tmp_PRWFileNames.substr(0, pos));
        tmp_PRWFileNames.erase(0, pos+1);
      }
    }
    while ( tmp_lumiCalcFileNames.size() > 0) {
      size_t pos = tmp_lumiCalcFileNames.find_first_of(',');
      if ( pos == std::string::npos ) {
        pos = tmp_lumiCalcFileNames.size();
        lumiCalcFiles.push_back(tmp_lumiCalcFileNames.substr(0, pos));
        tmp_lumiCalcFileNames.erase(0, pos);
      } else {
        lumiCalcFiles.push_back(tmp_lumiCalcFileNames.substr(0, pos));
        tmp_lumiCalcFileNames.erase(0, pos+1);
      }
    }

    // Find trigger specific lumicalc files
    if ( !isMC() ) {
      for ( const std::string &lumiCalcFile : lumiCalcFiles) {
        size_t pos = lumiCalcFile.find_first_of(':');
        if ( pos != std::string::npos ) {
          m_triggerUnprescaleList.push_back(lumiCalcFile.substr(pos + 1));
        }
      }

      if ( !m_triggerUnprescaleList.empty() ) {
        ANA_MSG_INFO("*** Trigger chains used for data unprescaling:");
        for ( const std::string &trigger : m_triggerUnprescaleList ) {
          ANA_MSG_INFO( "\t " << trigger );
        }
      }
    }

    if(m_autoconfigPRW)
      { ANA_CHECK( autoconfigurePileupRWTool() ); }
    else
      {
        if ( isMC() ) {
          ANA_MSG_INFO( "Adding Pileup files for CP::PileupReweightingTool:");
          for( unsigned int i=0; i < PRWFiles.size(); ++i){
            ANA_MSG_INFO( "\t- " << PRWFiles.at(i).c_str());
          }
          ANA_CHECK( m_pileup_tool_handle.setProperty("ConfigFiles", PRWFiles));
        }

        ANA_MSG_INFO( "Adding LumiCalc files for CP::PileupReweightingTool:");
        for( unsigned int i=0; i < lumiCalcFiles.size(); ++i){
          ANA_MSG_INFO( "\t- " << lumiCalcFiles.at(i).c_str());
        }
        ANA_CHECK( m_pileup_tool_handle.setProperty("LumiCalcFiles", lumiCalcFiles));
      }
    ANA_CHECK( m_pileup_tool_handle.setProperty("UsePeriodConfig", m_periodConfig) );
    ANA_CHECK( m_pileup_tool_handle.setProperty("OutputLevel", msg().level() ));
    if ( !m_triggerUnprescaleList.empty() ) {
      // We need to make an instance of ITrigDecisionTool:
      asg::AnaToolHandle<Trig::ITrigDecisionTool> iTrigDecTool_handle {"Trig::TrigDecisionTool/TrigDecisionTool"};
      if ( !iTrigDecTool_handle.isUserConfigured() ) {
        ANA_MSG_FATAL("A configured " << iTrigDecTool_handle.typeAndName() << " must have been previously created!");
        return EL::StatusCode::FAILURE;
      }
      ANA_CHECK( iTrigDecTool_handle.retrieve() );
      ANA_CHECK( m_pileup_tool_handle.setProperty("TrigDecisionTool", iTrigDecTool_handle ));
    }
    ANA_CHECK( m_pileup_tool_handle.retrieve());
    ANA_MSG_DEBUG("Retrieved tool: " << m_pileup_tool_handle);
  }

  // As a check, let's see the number of events in our file (long long int)
  //
  ANA_MSG_INFO( "Number of events in file = " << m_event->getEntries());

  // Initialize counter for number of entries
  m_eventCounter   = 0;

  ANA_MSG_INFO( "BasicEventSelection succesfully initialized!");

  return EL::StatusCode::SUCCESS;
}



EL::StatusCode BasicEventSelection :: execute ()
{
  // Here you do everything that needs to be done on every single
  // events, e.g. read input variables, apply cuts, and fill
  // histograms and trees.  This is where most of your actual analysis
  // code will go.

  ANA_MSG_DEBUG( "Basic Event Selection");

  // Print every 1000 entries, so we know where we are:
  //
  if ( (m_eventCounter % 1000) == 0 ) {
    ANA_MSG_INFO( "Entry number = " << m_eventCounter);
    ANA_MSG_VERBOSE( "Store Content:");
    if(msgLvl(MSG::VERBOSE)) m_store->print();
    ANA_MSG_VERBOSE( "End Content");
  }

  //-----------------------------------------
  // Print triggers used for first entry only
  // and fill the trigger expression for
  // unprescaling data
  //-----------------------------------------

  std::string TriggerExpression = "";

  if ( !m_triggerSelection.empty() ) {
    if (m_eventCounter == 0) {
      if (m_eventCounter == 0) ANA_MSG_INFO( "*** Triggers used (in OR) are:\n");
      auto printingTriggerChainGroup = m_trigDecTool_handle->getChainGroup(m_triggerSelection);
      std::vector<std::string> triggersUsed = printingTriggerChainGroup->getListOfTriggers();
      for ( unsigned int iTrigger = 0; iTrigger < triggersUsed.size(); ++iTrigger ) {
        if (m_eventCounter == 0) ANA_MSG_INFO("\t" << triggersUsed.at(iTrigger).c_str());
        TriggerExpression.append(triggersUsed.at(iTrigger).c_str());
        if ( iTrigger != triggersUsed.size() - 1) TriggerExpression.append(" || ");
      }
    }
  }

  if ( m_eventCounter == 0 && !m_extraTriggerSelection.empty() ) {
    ANA_MSG_INFO( "*** Extra Trigger Info Saved are :\n");
    for ( const std::string &trigName : m_extraTriggerSelectionList ) {
      ANA_MSG_INFO("\t" << trigName.c_str());

      ANA_MSG_INFO("\tEvaluates to:");
      auto printingTriggerChainGroup = m_trigDecTool_handle->getChainGroup(trigName);
      std::vector<std::string> triggersUsed = printingTriggerChainGroup->getListOfTriggers();
      for ( unsigned int iTrigger = 0; iTrigger < triggersUsed.size(); ++iTrigger ) {
        ANA_MSG_INFO("\t- "<< triggersUsed.at(iTrigger).c_str());
      }
    }
  }



  ++m_eventCounter;

  //------------------
  // Grab event
  //------------------
  const xAOD::EventInfo* eventInfo(nullptr);
  ANA_CHECK( HelperFunctions::retrieve(eventInfo, m_eventInfoContainerName, m_event, m_store, msg()) );

  //------------------------------------------------------------------------------------------
  // Declare an 'eventInfo' decorator with the MC event weight
  //------------------------------------------------------------------------------------------
  static SG::AuxElement::Decorator< float > mcEvtWeightDecor("mcEventWeight");
  static SG::AuxElement::Accessor< float >  mcEvtWeightAcc("mcEventWeight");

  float mcEvtWeight(1.0);

  // Check if need to create xAH event weight
  //
  if ( !mcEvtWeightDecor.isAvailable(*eventInfo) ) {
    if ( isMC() ) {
      const std::vector< float > weights = eventInfo->mcEventWeights(); // The weight (and systs) of all the MC events used in the simulation
      if ( weights.size() > 0 ) mcEvtWeight = weights[0];

      //for ( auto& it : weights ) { ANA_MSG_INFO( "event weight: %2f.", it ); }

      // kill the powheg event with a huge weight
      if( m_cleanPowheg ) {
        if( eventInfo->eventNumber() == 1652845 ) {
          ANA_MSG_INFO("Dropping huge weight event. Weight should be 352220000");
          ANA_MSG_INFO("WEIGHT : " << mcEvtWeight);
          wk()->skipEvent();
          return EL::StatusCode::SUCCESS; // go to next event
        }
      }
    }
    // Decorate event with the *total* MC event weight
    //
    mcEvtWeightDecor(*eventInfo) = mcEvtWeight;
  } else {
    mcEvtWeight = mcEvtWeightAcc(*eventInfo);
  }

  //------------------------------------------------------------------------------------------
  // Fill cutflows for run-by-run checks on data before cuts
  //------------------------------------------------------------------------------------------
  uint32_t runNumberForCutflow = (uint32_t) eventInfo->runNumber();
  if (m_doRunByRunCutflows && !isMC()) {
    m_runByrun_beforeCuts->Fill(TString(std::to_string(runNumberForCutflow)), 1.);
  }

  //------------------------------------------------------------------------------------------
  // Declare an 'eventInfo' decorator with the Sherpa 2.2 reweight to multijet truth
  // https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/CentralMC15ProductionList#Sherpa_v2_2_0_V_jets_NJet_reweig
  //------------------------------------------------------------------------------------------

  //if ( m_reweightSherpa22 ){
  //  static SG::AuxElement::Decorator< float > weight_Sherpa22Decor("weight_Sherpa22");
  //  // Check if weight needs to be added
  //  if ( !weight_Sherpa22Decor.isAvailable(*eventInfo) ) {

  //    float weight_Sherpa22 = -999.;
  //    weight_Sherpa22 = m_reweightSherpa22_tool_handle->getWeight();
  //    weight_Sherpa22Decor( *eventInfo ) = weight_Sherpa22;
  //    ANA_MSG_DEBUG("Setting Sherpa 2.2 reweight to " << weight_Sherpa22);

  //  } // If not already decorated
  //} // if m_reweightSherpa22


  //------------------------------------------------------------------------------------------
  // Fill initial bin of cutflow
  //------------------------------------------------------------------------------------------

  if( !m_useMetaData )
    {
      m_cutflowHist ->Fill( m_cutflow_all, 1 );
      m_cutflowHistW->Fill( m_cutflow_all, mcEvtWeight);

      m_histEventCount -> Fill(1, 1);
      m_histEventCount -> Fill(2, 1);
      m_histEventCount -> Fill(3, mcEvtWeight);
      m_histEventCount -> Fill(4, mcEvtWeight);
      m_histEventCount -> Fill(5, mcEvtWeight*mcEvtWeight);
      m_histEventCount -> Fill(6, mcEvtWeight*mcEvtWeight);
    }

  m_cutflowHist ->Fill( m_cutflow_init, 1 );
  m_cutflowHistW->Fill( m_cutflow_init, mcEvtWeight);

  //--------------------------------------------------------------------------------------------------------
  // Check current event is not a duplicate
  // This is done by checking against the std::set of <runNumber,eventNumber> filled for all previous events
  //--------------------------------------------------------------------------------------------------------

  if ( ( !isMC() && m_checkDuplicatesData ) || ( isMC() && m_checkDuplicatesMC ) ) {

    std::pair<uint32_t,uint32_t> thispair = std::make_pair(eventInfo->runNumber(),eventInfo->eventNumber());

    if ( m_RunNr_VS_EvtNr.find(thispair) != m_RunNr_VS_EvtNr.end() ) {

      ANA_MSG_WARNING("Found duplicated event! runNumber = " << static_cast<uint32_t>(eventInfo->runNumber()) << ", eventNumber = " << static_cast<uint32_t>(eventInfo->eventNumber()) << ". Skipping this event");

      // Bookkeep info in duplicates TTree
      //
      m_duplRunNumber   = eventInfo->runNumber();
      m_duplEventNumber = eventInfo->eventNumber();

      m_duplicatesTree->Fill();

      wk()->skipEvent();
      return EL::StatusCode::SUCCESS; // go to next event
    }

    m_RunNr_VS_EvtNr.insert(thispair);

    m_cutflowHist ->Fill( m_cutflow_duplicates, 1 );
    m_cutflowHistW->Fill( m_cutflow_duplicates, mcEvtWeight);

  }

  //------------------------------------------------------------------------------------------
  // Update Pile-Up Reweighting
  //------------------------------------------------------------------------------------------
  if ( m_doPUreweighting ) {
    m_pileup_tool_handle->applySystematicVariation(CP::SystematicSet()).ignore();
    ANA_CHECK(m_pileup_tool_handle->apply( *eventInfo )); // NB: this call automatically decorates eventInfo with:
                                                 //  1.) the PU weight ("PileupWeight")
                                                 //  2.) the corrected mu ("corrected_averageInteractionsPerCrossing")
                                                 //  3.) the random run number ("RandomRunNumber")
                                                 //  4.) the random lumiblock number ("RandomLumiBlockNumber")
    // static SG::AuxElement::Decorator< float >  correctedAvgMu("corrected_averageInteractionsPerCrossing");
    static SG::AuxElement::Decorator< float >  correctedAndScaledAvgMu("correctedScaled_averageInteractionsPerCrossing");
    static SG::AuxElement::Decorator< float >  correctedMu("corrected_actualInteractionsPerCrossing");
    static SG::AuxElement::Decorator< float >  correctedAndScaledMu("correctedScaled_actualInteractionsPerCrossing");

    correctedAndScaledAvgMu( *eventInfo ) = m_pileup_tool_handle->getCorrectedAverageInteractionsPerCrossing( *eventInfo, true );
    correctedMu( *eventInfo ) = m_pileup_tool_handle->getCorrectedActualInteractionsPerCrossing( *eventInfo );
    correctedAndScaledMu( *eventInfo ) = m_pileup_tool_handle->getCorrectedActualInteractionsPerCrossing( *eventInfo, true );

      if ( isMC() && m_doPUreweightingSys ) {
        CP::SystematicSet tmpSet;tmpSet.insert(CP::SystematicVariation("PRW_DATASF",1));
        m_pileup_tool_handle->applySystematicVariation( tmpSet ).ignore();
    eventInfo->auxdecor< float >( "PileupWeight_UP" )= m_pileup_tool_handle->getCombinedWeight( *eventInfo );
    tmpSet.clear();tmpSet.insert(CP::SystematicVariation("PRW_DATASF",-1));
    m_pileup_tool_handle->applySystematicVariation( tmpSet ).ignore();
    eventInfo->auxdecor< float >( "PileupWeight_DOWN")= m_pileup_tool_handle->getCombinedWeight( *eventInfo );
      }
  }

  if ( m_actualMuMin > 0 ) {
      // apply minimum pile-up cut
      if ( eventInfo->actualInteractionsPerCrossing() < m_actualMuMin ) { // veto event
          wk()->skipEvent();
          return EL::StatusCode::SUCCESS;
      }
  }

  if ( m_actualMuMax > 0 ) {
      // apply maximum pile-up cut
      if ( eventInfo->actualInteractionsPerCrossing() > m_actualMuMax ) { // veto event
          wk()->skipEvent();
          return EL::StatusCode::SUCCESS;
      }
  }


  //------------------------------------------------------
  // If data, check if event passes GRL and event cleaning
  //------------------------------------------------------
  if ( !isMC() ) {

    // Get the streams that the event was put in
    if (m_checkStreams){
      const std::vector<  xAOD::EventInfo::StreamTag > streams = eventInfo->streamTags();

      for ( auto& stream : streams ) {
        ANA_MSG_DEBUG( "event has fired stream: " << stream.name() );
      }
    }

    // GRL
    if ( m_applyGRLCut ) {
      if ( !m_grl_handle->passRunLB( *eventInfo ) ) {
        wk()->skipEvent();
        return EL::StatusCode::SUCCESS; // go to next event
      }
      m_cutflowHist ->Fill( m_cutflow_grl, 1 );
      m_cutflowHistW->Fill( m_cutflow_grl, mcEvtWeight);
    }

    //------------------------------------------------------------
    // Apply event cleaning to remove events due to
    // problematic regions of the detector, and incomplete events.
    // Apply to data.
    //------------------------------------------------------------

    if ( m_applyEventCleaningCut && (eventInfo->errorState(xAOD::EventInfo::LAr)==xAOD::EventInfo::Error ) ) {
      wk()->skipEvent();
      return EL::StatusCode::SUCCESS;
    }
    m_cutflowHist ->Fill( m_cutflow_lar, 1 );
    m_cutflowHistW->Fill( m_cutflow_lar, mcEvtWeight);

    if ( m_applyEventCleaningCut && (eventInfo->errorState(xAOD::EventInfo::Tile)==xAOD::EventInfo::Error ) ) {
      wk()->skipEvent();
      return EL::StatusCode::SUCCESS;
    }
    m_cutflowHist ->Fill( m_cutflow_tile, 1 );
    m_cutflowHistW->Fill( m_cutflow_tile, mcEvtWeight);

    if ( m_applyEventCleaningCut && (eventInfo->errorState(xAOD::EventInfo::SCT)==xAOD::EventInfo::Error) ) {
      wk()->skipEvent();
      return EL::StatusCode::SUCCESS;
    }
    m_cutflowHist ->Fill( m_cutflow_SCT, 1 );
    m_cutflowHistW->Fill( m_cutflow_SCT, mcEvtWeight);

    if ( m_applyCoreFlagsCut && (eventInfo->isEventFlagBitSet(xAOD::EventInfo::Core, 18) ) ) {
      wk()->skipEvent();
      return EL::StatusCode::SUCCESS;
    }
    m_cutflowHist ->Fill( m_cutflow_core, 1 );
    m_cutflowHistW->Fill( m_cutflow_core, mcEvtWeight);

  }

  // more info: https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/HowToCleanJets2017
  if ( m_applyJetCleaningEventFlag && eventInfo->isAvailable<char>("DFCommonJets_eventClean_LooseBad") ) {
    if(eventInfo->auxdataConst<char>("DFCommonJets_eventClean_LooseBad")<1) {
    wk()->skipEvent();
    return EL::StatusCode::SUCCESS;
      }
  }
  m_cutflowHist ->Fill( m_cutflow_jetcleaning, 1 );
  m_cutflowHistW->Fill( m_cutflow_jetcleaning, mcEvtWeight);

  // n.b. this cut should only be applied in 2015+16 data, and not to MC!
  // details here: https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/HowToCleanJets2017#IsBadBatMan_Event_Flag_and_EMEC
  if ( m_applyIsBadBatmanFlag && eventInfo->isAvailable<char>("DFCommonJets_isBadBatman") &&  !isMC() ) {
    if(eventInfo->auxdataConst<char>("DFCommonJets_isBadBatman")>0) {
      wk()->skipEvent();
      return EL::StatusCode::SUCCESS;
    }
  }
  m_cutflowHist ->Fill( m_cutflow_isbadbatman, 1 );
  m_cutflowHistW->Fill( m_cutflow_isbadbatman, mcEvtWeight);

  //-----------------------------
  // Primary Vertex 'quality' cut
  //-----------------------------

  const xAOD::VertexContainer* vertices(nullptr);
  if ( !m_truthLevelOnly && m_applyPrimaryVertexCut ) {
    ANA_CHECK( HelperFunctions::retrieve(vertices, m_vertexContainerName, m_event, m_store, msg()) );

    if ( !HelperFunctions::passPrimaryVertexSelection( vertices, m_PVNTrack ) ) {
      wk()->skipEvent();
      return EL::StatusCode::SUCCESS;
    }
  }
  m_cutflowHist ->Fill( m_cutflow_npv, 1 );
  m_cutflowHistW->Fill( m_cutflow_npv, mcEvtWeight);

  //---------------------
  // Trigger decision cut
  //---------------------

  if ( !m_triggerSelection.empty() || m_storeTrigDecisions ) {

    auto triggerChainGroup = m_trigDecTool_handle->getChainGroup(m_triggerSelection);

    if ( m_applyTriggerCut ) {

      // additional DEBUG logging to validate conditional logic
      ANA_MSG_DEBUG("Applying trigger cut corresponding to chain group " << m_triggerSelection);
      ANA_MSG_DEBUG("Is Trigger-Level Analysis (TLA) data = " << int(m_isTLAData));
      ANA_MSG_DEBUG("Trigger chain group is passed = " << int(m_isTLAData ? triggerChainGroup->isPassed(TrigDefs::requireDecision) : triggerChainGroup->isPassed()));

      // different behaviour for isPassed depending on whether you are running on TLA data or not
      // if running on TLA data, we only store the HLT part of the trigger decision i.e. the L1 part
      // will always be "false", so we need to use TrigDefs::requireDecision to limit the decision
      // to being satisfied by the HLT leg(s) of the trigger chain
      // TODO: check performance of this method when using trigger chains with the SAME HLT leg but different L1 seed
      // e.g. HLT_j20_pf_ftf_L1J100 vs. HLT_j20_pf_ftf_L1HT190-J15s5pETA21
      if ( (m_isTLAData && !triggerChainGroup->isPassed(TrigDefs::requireDecision)) || (!m_isTLAData && !triggerChainGroup->isPassed()) ) {
      // if (!triggerChainGroup->isPassed(TrigDefs::requireDecision)) {
        wk()->skipEvent();
        return EL::StatusCode::SUCCESS;
      }
      m_cutflowHist ->Fill( m_cutflow_trigger, 1 );
      m_cutflowHistW->Fill( m_cutflow_trigger, mcEvtWeight);

    }

    // save passed triggers in eventInfo
    //
    if ( m_storeTrigDecisions ) {

      std::vector<std::string>  passedTriggers;
      std::vector<std::string>  disabledTriggers;
      std::vector<float>        triggerPrescales;
      std::vector<float>        triggerPrescalesLumi;
      std::vector<std::string>  isPassedBitsNames;
      std::vector<unsigned int> isPassedBits;

      // Save info for the triggers used to skim events
      //
      for ( auto &trigName : triggerChainGroup->getListOfTriggers() ) {
        auto trigChain = m_trigDecTool_handle->getChainGroup( trigName );
        if ( (m_isTLAData && trigChain->isPassed(TrigDefs::requireDecision)) || (!m_isTLAData && trigChain->isPassed()) ) {
          passedTriggers.push_back( trigName );
          triggerPrescales.push_back( trigChain->getPrescale() );

          bool doLumiPrescale = std::find(m_triggerUnprescaleList.begin(), m_triggerUnprescaleList.end(), trigName) != m_triggerUnprescaleList.end();
          if ( doLumiPrescale ) {
            triggerPrescalesLumi.push_back( m_pileup_tool_handle->getDataWeight( *eventInfo, trigName, true ) );
          } else {
            triggerPrescalesLumi.push_back( -1 );
          }
        }
        isPassedBitsNames.push_back( trigName );
        isPassedBits     .push_back( m_trigDecTool_handle->isPassedBits(trigName) );
          if(trigChain->getPrescale()<1) disabledTriggers.push_back( trigName );
      }

      // Save info for extra triggers
      //
      if ( !m_extraTriggerSelection.empty() ) {

        for ( const std::string &trigName : m_extraTriggerSelectionList ) {
          auto trigChain = m_trigDecTool_handle->getChainGroup( trigName );
          if ( (m_isTLAData && trigChain->isPassed(TrigDefs::requireDecision)) || (!m_isTLAData && trigChain->isPassed()) ) {
            passedTriggers.push_back( trigName );
            triggerPrescales.push_back( trigChain->getPrescale() );

            bool doLumiPrescale = true;
            for ( const std::string &trigPart : trigChain->getListOfTriggers() ) {
              if (std::find(m_triggerUnprescaleList.begin(), m_triggerUnprescaleList.end(), trigPart) == m_triggerUnprescaleList.end()) doLumiPrescale = false;
            }
            if ( doLumiPrescale ) {
              triggerPrescalesLumi.push_back( m_pileup_tool_handle->getDataWeight( *eventInfo, trigName, true ) );
            } else {
              triggerPrescalesLumi.push_back( -1 );
            }
          }

          isPassedBitsNames.push_back( trigName );
          isPassedBits     .push_back( m_trigDecTool_handle->isPassedBits(trigName) );
          if(trigChain->getPrescale()<1) disabledTriggers.push_back( trigName );
        }
      }

      static SG::AuxElement::Decorator< std::vector< std::string > >  dec_passedTriggers("passedTriggers");
      dec_passedTriggers  ( *eventInfo ) = passedTriggers;
      static SG::AuxElement::Decorator< std::vector< std::string > >  dec_disabledTriggers("disabledTriggers");
      dec_disabledTriggers( *eventInfo ) = disabledTriggers;
      static SG::AuxElement::Decorator< std::vector< float > >        dec_triggerPrescales("triggerPrescales");
      dec_triggerPrescales( *eventInfo ) = triggerPrescales;
      static SG::AuxElement::Decorator< std::vector< float > >        dec_triggerPrescalesLumi("triggerPrescalesLumi");
      dec_triggerPrescalesLumi( *eventInfo ) = triggerPrescalesLumi;
      static SG::AuxElement::Decorator< std::vector< unsigned int > > dec_isPassedBits("isPassedBits");
      dec_isPassedBits( *eventInfo ) = isPassedBits;
      static SG::AuxElement::Decorator< std::vector< std::string > >  dec_isPassedBitsNames("isPassedBitsNames");
      dec_isPassedBitsNames( *eventInfo ) = isPassedBitsNames;

    }

    if ( m_storePrescaleWeight ) {
      static SG::AuxElement::Decorator< float > weight_prescale("weight_prescale");
      weight_prescale(*eventInfo) = triggerChainGroup->getPrescale();
    }

    if ( m_storePassL1 ) {
      static SG::AuxElement::Decorator< int > passL1("passL1");
      passL1(*eventInfo)  = ( m_triggerSelection.find("L1_") != std::string::npos )  ? (int)m_trigDecTool_handle->isPassed(m_triggerSelection.c_str()) : -1;
    }
    if ( m_storePassHLT ) {
      static SG::AuxElement::Decorator< int > passHLT("passHLT");
      if (!m_isTLAData) {
        passHLT(*eventInfo) = ( m_triggerSelection.find("HLT_") != std::string::npos ) ? (int)m_trigDecTool_handle->isPassed(m_triggerSelection.c_str()) : -1;
      } else {
        passHLT(*eventInfo) = ( m_triggerSelection.find("HLT_") != std::string::npos ) ? (int)m_trigDecTool_handle->isPassed(m_triggerSelection.c_str(), TrigDefs::requireDecision) : -1;
      }
    }

  } // if giving a specific list of triggers to look at

  if ( m_storeTrigKeys ) {
    static SG::AuxElement::Decorator< int > masterKey("masterKey");
    masterKey(*eventInfo) = m_trigConfTool_handle->masterKey();
    static SG::AuxElement::Decorator< int > L1PSKey("L1PSKey");
    L1PSKey(*eventInfo) = m_trigConfTool_handle->lvl1PrescaleKey();
    static SG::AuxElement::Decorator< int > HLTPSKey("HLTPSKey");
    HLTPSKey(*eventInfo) = m_trigConfTool_handle->hltPrescaleKey();
  }

  // Calculate distance to previous empty BCID and previous unpaired BCID, and save as decorations
  if( m_calcBCIDInfo && !isMC() && m_trigConfTool_handle.isInitialized() ){
    //Distance to previous empty BCID
    for (int i = eventInfo->bcid() - 1; i >= 0; i--){
      //get the bunch group pattern for bunch crossing i
      uint16_t bgPattern = m_trigConfTool_handle->bunchGroupSet()->bgPattern()[i];
      bool isEmpty = (bgPattern >> 3) & 0x1;
      if (isEmpty){
        static SG::AuxElement::Decorator< int > DistEmptyBCID("DistEmptyBCID");
        DistEmptyBCID(*eventInfo) = eventInfo->bcid()-i;
        break;
      }
    }//for each bcid
    //Distance to previous unpaired crossing
    for (int i = eventInfo->bcid() - 1; i >= 0; i--){
      //get the bunch group pattern for bunch crossing i
      uint16_t bgPattern = m_trigConfTool_handle->bunchGroupSet()->bgPattern()[i];
      bool isUnpaired = !((bgPattern >> 1) & 0x1);
      if (isUnpaired){
        static SG::AuxElement::Decorator< int > DistLastUnpairedBCID("DistLastUnpairedBCID");
        DistLastUnpairedBCID(*eventInfo) = eventInfo->bcid()-i;
        break;
      }
    }//for each bcid
    //Distance to next unpaired crossing
    for (int i = eventInfo->bcid() + 1; i <= 3654; i++){
      //get the bunch group pattern for bunch crossing i
      uint16_t bgPattern = m_trigConfTool_handle->bunchGroupSet()->bgPattern()[i];
      bool isUnpaired = !((bgPattern >> 1) & 0x1);
      if (isUnpaired){
        static SG::AuxElement::Decorator< int > DistNextUnpairedBCID("DistNextUnpairedBCID");
        DistNextUnpairedBCID(*eventInfo) = i-eventInfo->bcid();
        break;
      }
    }//  for each bcid

  }//if data

  //------------------------------------------------------------------------------------------
  // Fill cutflows for run-by-run checks on data after cuts
  //------------------------------------------------------------------------------------------
  if (m_doRunByRunCutflows && !isMC()) {
    m_runByrun_afterCuts->Fill(TString(std::to_string(runNumberForCutflow)), 1.);
  }

  return EL::StatusCode::SUCCESS;
}

// "Borrowed" from SUSYTools
// https://gitlab.cern.ch/atlas/athena/blob/3be30397de7c6cfdc15de38f532fdb4b9f338297/PhysicsAnalysis/SUSYPhys/SUSYTools/Root/SUSYObjDef_xAOD.cxx#L700
StatusCode BasicEventSelection::autoconfigurePileupRWTool()
{

  // Don't do this if we aren't supposed to
  if (! (isMC() && m_autoconfigPRW ))
    return StatusCode::SUCCESS;

  const xAOD::EventInfo* eventInfo = 0;
  ANA_CHECK( m_event->retrieve( eventInfo, "EventInfo" ) );

  // Determine simulation flavour
  std::string SimulationFlavour = isFastSim() ? ( isAF3() ? "AF3" : "AFII" ) : "FS";

  // Extract campaign automatically from Run Number
  std::string mcCampaignMD = "";

  uint32_t runNum = eventInfo->runNumber();

  switch(runNum)
    {
    case 284500 :
      mcCampaignMD="mc20a";
      break;
    case 300000 :
      mcCampaignMD="mc20d";
      break;
    case 310000 :
      mcCampaignMD="mc20e";
      break;
    case 410000 :
      mcCampaignMD="mc23a";
      break;
    case 450000 :
      mcCampaignMD="mc23d";
      break;
    default :
      ANA_MSG_ERROR( "Could not determine mc campaign from run number! Impossible to autoconfigure PRW. Aborting." );
      return StatusCode::FAILURE;
      break;
    }

  std::string mcCampaignMD_v2 = "";
  const xAOD::FileMetaData* fmd(nullptr);
  if ( !m_event->retrieveMetaInput(fmd, "FileMetaData").isSuccess() || !fmd->value(xAOD::FileMetaData::mcCampaign, mcCampaignMD_v2) ) {
      ANA_MSG_WARNING("Failed to retrieve FileMetaData from MetaData! Using MC campaign from run number. PLEASE DOUBLE-CHECK this is the correct campaign for your samples!");
  } else {
      fmd->value(xAOD::FileMetaData::mcCampaign, mcCampaignMD_v2);

      if(mcCampaignMD!=mcCampaignMD_v2){
        std::string MetadataAndRunConflict("");
        MetadataAndRunConflict += "autoconfigurePileupRWTool(): access to FileMetaData indicates a " + mcCampaignMD_v2;
        MetadataAndRunConflict += " sample, but the run number indiciates " +mcCampaignMD;
        MetadataAndRunConflict += ". Prioritizing the value from FileMetaData. This could occur if you are using an MC campaign with outdated pile-up reweighting. PLEASE DOUBLE-CHECK your samples!";
        ANA_MSG_WARNING( MetadataAndRunConflict );
        mcCampaignMD=mcCampaignMD_v2;
      }
  }
  ANA_MSG_INFO( "Determined MC campaign to be " << mcCampaignMD);

  // Extract campaign from user configuration
  std::string tmp_mcCampaign = m_mcCampaign;
  std::vector<std::string> mcCampaignList;
  while ( tmp_mcCampaign.size() > 0) {
    size_t pos = tmp_mcCampaign.find_first_of(',');
    if ( pos == std::string::npos ) {
      pos = tmp_mcCampaign.size();
      mcCampaignList.push_back(tmp_mcCampaign.substr(0, pos));
      tmp_mcCampaign.erase(0, pos);
      }
    else {
      mcCampaignList.push_back(tmp_mcCampaign.substr(0, pos));
      tmp_mcCampaign.erase(0, pos+1);
      }
  }

  // Sanity checks
  bool mc2XX_GoodFromProperty = !mcCampaignList.empty();
  bool mc2XX_GoodFromMetadata = false;
  for(const auto& mcCampaignP : mcCampaignList) mc2XX_GoodFromProperty &= ( mcCampaignP == "mc20a" || mcCampaignP == "mc20d" || mcCampaignP == "mc20e" || mcCampaignP == "mc23a" || mcCampaignP == "mc23c" || mcCampaignP == "mc23d");
  if( mcCampaignMD == "mc20a" || mcCampaignMD == "mc20d" || mcCampaignMD == "mc20e" || mcCampaignMD == "mc23a" || mcCampaignMD == "mc23c" || mcCampaignMD == "mc23d") mc2XX_GoodFromMetadata = true;

  if( !mc2XX_GoodFromMetadata && !mc2XX_GoodFromProperty ) {
    // ::
    std::string MetadataAndPropertyBAD("");
    MetadataAndPropertyBAD += "autoconfigurePileupRWTool(): access to FileMetaData failed, but don't panic. You can try to manually set the 'mcCampaign' BasicEventSelection property to ";
    MetadataAndPropertyBAD += "'mc20a', 'mc20c', 'mc20d', 'mc20e', 'mc20f', 'mc23a', 'mc23c', or 'mc23d' and restart your job. If you set it to any other string, you will still incur in this error.";
    ANA_MSG_ERROR( MetadataAndPropertyBAD );
    return StatusCode::FAILURE;
    // ::
  }

  if ( mc2XX_GoodFromProperty && mc2XX_GoodFromMetadata) {
    bool MDinP=false;
    for(const auto& mcCampaignP : mcCampaignList) MDinP |= (mcCampaignMD==mcCampaignP);
    if( !MDinP ) {
      // ::
      std::string MetadataAndPropertyConflict("");
      MetadataAndPropertyConflict += "autoconfigurePileupRWTool(): access to FileMetaData indicates a " + mcCampaignMD;
      MetadataAndPropertyConflict += " sample, but the 'mcCampaign' property passed to BasicEventSelection is set to '" +m_mcCampaign;
      MetadataAndPropertyConflict += "'. Prioritizing the value set by user: PLEASE DOUBLE-CHECK the value you set the 'mcCampaign' property to!";
      ANA_MSG_WARNING( MetadataAndPropertyConflict );
      // ::
    }
    else {
      // ::
      std::string NoMetadataButPropertyOK("");
      NoMetadataButPropertyOK += "autoconfigurePileupRWTool(): access to FileMetaData succeeded, but the 'mcCampaign' property is passed to BasicEventSelection as '";
      NoMetadataButPropertyOK += m_mcCampaign;
      NoMetadataButPropertyOK += "'. Autoconfiguring PRW accordingly.";
      ANA_MSG_WARNING( NoMetadataButPropertyOK );
      // ::
    }
  }

  // ::
  // Retrieve the input file
  if(!mc2XX_GoodFromProperty) {
    mcCampaignList.clear();
    mcCampaignList.push_back(mcCampaignMD);
  }
  ANA_MSG_INFO( "Setting MC campgains for CP::PileupReweightingTool:");
  for(const auto& mcCampaign : mcCampaignList)
    ANA_MSG_INFO( "\t" << mcCampaign.c_str() );

  //
  float dsid = -999;
  dsid = eventInfo->mcChannelNumber();
  int DSID_INT = (int) dsid;

  std::vector<std::string> prwConfigFiles;
  for(const auto& mcCampaign : mcCampaignList)
    {
      std::string prwConfigFile;
      // If requested set the PRW file to common PRW file of the processed MC campaign
      if (m_useCommonPRWFiles) {
        if      (mcCampaign == "mc20a") {prwConfigFile = PathResolverFindCalibFile(m_commonPRWFileMC20a);}
        else if (mcCampaign == "mc20d") {prwConfigFile = PathResolverFindCalibFile(m_commonPRWFileMC20d);}
        else if (mcCampaign == "mc20e") {prwConfigFile = PathResolverFindCalibFile(m_commonPRWFileMC20e);}
        else if (mcCampaign == "mc23a") {prwConfigFile = PathResolverFindCalibFile(m_commonPRWFileMC23a);}
        else if (mcCampaign == "mc23c") {prwConfigFile = PathResolverFindCalibFile(m_commonPRWFileMC23c);}
        else if (mcCampaign == "mc23d") {prwConfigFile = PathResolverFindCalibFile(m_commonPRWFileMC23d);}
        else {
          ANA_MSG_ERROR("autoconfigurePileupRWTool(): no common PRW file known for MC campaign: " << mcCampaign);
          return StatusCode::FAILURE;
        }
      } else {
        prwConfigFile = PathResolverFindCalibFile("dev/PileupReweighting/share/DSID" + std::to_string(DSID_INT/1000) +"xxx/pileup_" + mcCampaign + "_dsid" + std::to_string(DSID_INT) + "_" + SimulationFlavour + ".root");
      }
      TFile testF(prwConfigFile.data(),"read");
      if(testF.IsZombie())
        {
          ANA_MSG_ERROR("autoconfigurePileupRWTool(): Missing PRW config file for DSID " << std::to_string(DSID_INT) << " in campaign " << mcCampaign);
          return StatusCode::FAILURE;
        }
      else
        prwConfigFiles.push_back( prwConfigFile );
    }

  // Add actualMu config files
  for(const auto& mcCampaign : mcCampaignList)
    {
      if( !m_prwActualMu2016File.empty() && mcCampaign == "mc20a" )
        prwConfigFiles.push_back(PathResolverFindCalibFile(m_prwActualMu2016File));
      if( !m_prwActualMu2017File.empty() && (mcCampaign == "mc20c" || mcCampaign=="mc20d") )
        prwConfigFiles.push_back(PathResolverFindCalibFile(m_prwActualMu2017File));
      if( !m_prwActualMu2018File.empty() && (mcCampaign == "mc20e" || mcCampaign=="mc20f") )
        prwConfigFiles.push_back(PathResolverFindCalibFile(m_prwActualMu2018File));
      if( !m_prwActualMu2022File.empty() && mcCampaign == "mc23a" )
        prwConfigFiles.push_back(PathResolverFindCalibFile(m_prwActualMu2022File));
      if( !m_prwActualMu2023File.empty() && (mcCampaign == "mc23c" || mcCampaign=="mc23d") )
        prwConfigFiles.push_back(PathResolverFindCalibFile(m_prwActualMu2023File));
    }

  // also need to handle lumicalc files: only use 2015+2016 with mc20a
  // and only use 2017 with mc20d and 2018 data with mc20e
  // according to instructions on https://twiki.cern.ch/twiki/bin/view/AtlasProtected/ExtendedPileupReweighting#Tool_Properties

  // Parse lumicalc file names
  std::vector<std::string> allLumiCalcFiles;
  std::string tmp_lumiCalcFileNames = m_lumiCalcFileNames;
  while ( tmp_lumiCalcFileNames.size() > 0) {
    size_t pos = tmp_lumiCalcFileNames.find_first_of(',');
    if ( pos == std::string::npos ) {
      pos = tmp_lumiCalcFileNames.size();
      allLumiCalcFiles.push_back(tmp_lumiCalcFileNames.substr(0, pos));
      tmp_lumiCalcFileNames.erase(0, pos);
    } else {
      allLumiCalcFiles.push_back(tmp_lumiCalcFileNames.substr(0, pos));
      tmp_lumiCalcFileNames.erase(0, pos+1);
    }
  }

  std::vector<std::string> lumiCalcFiles;
  for(const auto& mcCampaign : mcCampaignList)
    {
      for(const auto& filename : allLumiCalcFiles)
    {
      // looking for things of format "stuff/data15_13TeV/stuff" etc
      size_t pos = filename.find("data");
      std::string year = filename.substr(pos+4, 2);

      if (mcCampaign == "mc20a") {
        if (year == "15" || year == "16") {
          lumiCalcFiles.push_back(filename);
        }
      } else if (mcCampaign == "mc20d") {
        if (year == "17") {
          lumiCalcFiles.push_back(filename);
        }
      } else if (mcCampaign == "mc20e") {
        if (year == "18") {
          lumiCalcFiles.push_back(filename);
        }
      } else if (mcCampaign == "mc23a") {
        if (year == "22") {
          lumiCalcFiles.push_back(filename);
        }
      } else if (mcCampaign == "mc23c" || mcCampaign == "mc23d") {
        if (year == "23") {
          lumiCalcFiles.push_back(filename);
        }
      } else {
        ANA_MSG_ERROR( "No lumicalc file is suitable for your mc campaign!" );
      }
    }
    }

  // Set everything and report on it.
  ANA_MSG_INFO( "Adding Pileup files for CP::PileupReweightingTool:");
  for( unsigned int i=0; i < prwConfigFiles.size(); ++i) {
    ANA_MSG_INFO("\t- " << prwConfigFiles.at(i).c_str());
  }
  ANA_CHECK( m_pileup_tool_handle.setProperty("ConfigFiles", prwConfigFiles));

  ANA_MSG_INFO( "Adding LumiCalc files for CP::PileupReweightingTool:");
  for( unsigned int i=0; i < lumiCalcFiles.size(); ++i) {
    ANA_MSG_INFO("\t- " << lumiCalcFiles.at(i).c_str());
  }
  ANA_CHECK( m_pileup_tool_handle.setProperty("LumiCalcFiles", lumiCalcFiles));

  // Return gracefully
  return StatusCode::SUCCESS;
}


EL::StatusCode BasicEventSelection :: postExecute ()
{
  // Here you do everything that needs to be done after the main event
  // processing.  This is typically very rare, particularly in user
  // code.  It is mainly used in implementing the NTupleSvc.

  return EL::StatusCode::SUCCESS;
}



EL::StatusCode BasicEventSelection :: finalize ()
{
  // This method is the mirror image of initialize(), meaning it gets
  // called after the last event has been processed on the worker node
  // and allows you to finish up any objects you created in
  // initialize() before they are written to disk.  This is actually
  // fairly rare, since this happens separately for each worker node.
  // Most of the time you want to do your post-processing on the
  // submission node after all your histogram outputs have been
  // merged.  This is different from histFinalize() in that it only
  // gets called on worker nodes that processed input events.

  ANA_MSG_INFO( "Number of processed events \t= " << m_eventCounter);

  m_RunNr_VS_EvtNr.clear();

  if ( m_trigDecTool_handle.isInitialized() ){
    if (asg::ToolStore::contains<Trig::TrigDecisionTool>("ToolSvc.TrigDecisionTool") ){
      m_trigDecTool_handle->finalize();
      asg::ToolStore::remove("ToolSvc.TrigDecisionTool").ignore();
    }
  }

  //after execution loop
  if(m_printBranchList){
    xAOD::IOStats::instance().stats().printSmartSlimmingBranchList();
  }

  return EL::StatusCode::SUCCESS;
}



EL::StatusCode BasicEventSelection :: histFinalize ()
{
  // This method is the mirror image of histInitialize(), meaning it
  // gets called after the last event has been processed on the worker
  // node and allows you to finish up any objects you created in
  // histInitialize() before they are written to disk.  This is
  // actually fairly rare, since this happens separately for each
  // worker node.  Most of the time you want to do your
  // post-processing on the submission node after all your histogram
  // outputs have been merged.  This is different from finalize() in
  // that it gets called on all worker nodes regardless of whether
  // they processed input events.
  ANA_CHECK( xAH::Algorithm::algFinalize());
  return EL::StatusCode::SUCCESS;
}