diff --git a/Configuration/EventContent/python/EventContent_cff.py b/Configuration/EventContent/python/EventContent_cff.py index 9699c50059487..95c41acecd42b 100644 --- a/Configuration/EventContent/python/EventContent_cff.py +++ b/Configuration/EventContent/python/EventContent_cff.py @@ -741,6 +741,7 @@ def SwapKeepAndDrop(l): 'keep *_hltPFMET_*_*', 'keep *_hltPFPuppiMET_*_*', 'keep *_hltPFPuppiMETTypeOne_*_*', + 'keep *_hltHpsPFTauDeepTauProducer_*_*' ]) phase2_muon.toModify(FEVTDEBUGHLTEventContent, diff --git a/HLTriggerOffline/Common/python/HLTValidationHarvest_cff.py b/HLTriggerOffline/Common/python/HLTValidationHarvest_cff.py index 3adbd44b5ae6f..c5f4bf989d46e 100644 --- a/HLTriggerOffline/Common/python/HLTValidationHarvest_cff.py +++ b/HLTriggerOffline/Common/python/HLTValidationHarvest_cff.py @@ -43,7 +43,7 @@ # Temporary Phase-2 configuration # Exclude everything except JetMET for now -_phase2_hltpostvalidation = hltpostvalidation.copyAndExclude([HLTTauPostVal, +_phase2_hltpostvalidation = hltpostvalidation.copyAndExclude([#HLTTauPostVal, EgammaPostVal, heavyFlavorValidationHarvestingSequence, #HLTJetMETPostVal, diff --git a/HLTriggerOffline/Common/python/HLTValidation_cff.py b/HLTriggerOffline/Common/python/HLTValidation_cff.py index 179d38848094e..cbc873e814a99 100644 --- a/HLTriggerOffline/Common/python/HLTValidation_cff.py +++ b/HLTriggerOffline/Common/python/HLTValidation_cff.py @@ -75,6 +75,9 @@ # Add HGCal SimTracksters _phase2_hltassociation += hltTiclSimTrackstersSeq +# Add gentau reference for validation +_phase2_hltassociation += tauPreValidSeq + # Apply the modification phase2_common.toReplaceWith(hltassociation, _phase2_hltassociation) @@ -102,7 +105,7 @@ # Exclude everything except Muon and JetMET for now. Add HGCAL Hit Calibration _hltvalidationWithMC_Phase2 = hltvalidationWithMC.copyAndExclude([#HLTMuonVal, - HLTTauVal, + #HLTTauVal, egammaValidationSequence, heavyFlavorValidationSequence, #HLTJetMETValSeq, @@ -117,7 +120,7 @@ hltHCALNoiseRates]) _hltvalidationWithMC_Phase2.insert(-1, hgcalHitCalibrationHLT) _hltvalidationWithMC_Phase2.insert(-1, hltHgcalValidator) -_hltvalidationWithMC_Phase2.insert(-1, hltGENValidation) +_hltvalidationWithMC_Phase2.insert(0, hltGENValidation) phase2_common.toReplaceWith(hltvalidationWithMC, _hltvalidationWithMC_Phase2) hltvalidationWithData = cms.Sequence( diff --git a/HLTriggerOffline/Tau/python/Validation/HLTTauPostValidation_cfi.py b/HLTriggerOffline/Tau/python/Validation/HLTTauPostValidation_cfi.py index 22766b10c02d4..3866edf8ad294 100644 --- a/HLTriggerOffline/Tau/python/Validation/HLTTauPostValidation_cfi.py +++ b/HLTriggerOffline/Tau/python/Validation/HLTTauPostValidation_cfi.py @@ -1,15 +1,21 @@ import FWCore.ParameterSet.Config as cms from HLTriggerOffline.Tau.Validation.HLTTauValidation_cfi import * +from Validation.RecoTau.hltTauPostProcessor_cff import * import DQMOffline.Trigger.HLTTauPostProcessor_cfi as postProcessor (HLTTauValPostAnalysisMC, HLTTauValPostAnalysisMC2) = postProcessor.makePFTauAnalyzer(hltTauValIdealMonitorMC) (HLTTauValPostAnalysisPF, HLTTauValPostAnalysisPF2) = postProcessor.makePFTauAnalyzer(hltTauValIdealMonitorPF) (HLTTauValPostAnalysisPN, HLTTauValPostAnalysisPN2) = postProcessor.makePFTauAnalyzer(hltTauValIdealMonitorPNet) (HLTTauValPostAnalysisTP, HLTTauValPostAnalysisTP2) = postProcessor.makePFTauAnalyzer(hltTauValTagAndProbe) + HLTTauPostVal = cms.Sequence( HLTTauValPostAnalysisMC+HLTTauValPostAnalysisMC2+ HLTTauValPostAnalysisPF+HLTTauValPostAnalysisPF2+ HLTTauValPostAnalysisPN+HLTTauValPostAnalysisPN2+ HLTTauValPostAnalysisTP+HLTTauValPostAnalysisTP2 ) +HLTTauPostValPhase2 = cms.Sequence(hltTauPostProcessor) + +from Configuration.Eras.Modifier_phase2_common_cff import phase2_common +phase2_common.toReplaceWith(HLTTauPostVal, HLTTauPostValPhase2) diff --git a/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cff.py b/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cff.py index e0db353b448a3..9c044f09f0b29 100644 --- a/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cff.py +++ b/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cff.py @@ -2,5 +2,10 @@ from HLTriggerOffline.Tau.Validation.HLTTauReferences_cfi import * from HLTriggerOffline.Tau.Validation.HLTTauValidation_cfi import * +from Validation.RecoTau.hltTauValidation_cff import * -HLTTauVal = cms.Sequence(hltTauRef+hltTauValIdeal) +HLTTauVal = cms.Sequence(hltTauRef+hltTauValIdeal) +HLTTauValPhase2 = cms.Sequence(hltTauValidationSequence) + +from Configuration.Eras.Modifier_phase2_common_cff import phase2_common +phase2_common.toReplaceWith(HLTTauVal, HLTTauValPhase2) diff --git a/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cfi.py b/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cfi.py index 6dcd3d347e5d1..33b827bb1573a 100644 --- a/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cfi.py +++ b/HLTriggerOffline/Tau/python/Validation/HLTTauValidation_cfi.py @@ -100,4 +100,3 @@ #hltTauValIdeal = cms.Sequence(hltTauValIdealMonitorMC+hltTauValIdealMonitorPF) hltTauValIdeal = cms.Sequence(hltTauValIdealMonitorMC+hltTauValIdealMonitorPF+hltTauValIdealMonitorPNet+hltTauValTagAndProbe) - diff --git a/Validation/Configuration/python/autoValidation.py b/Validation/Configuration/python/autoValidation.py index 87656bf796783..9d7909dcce8eb 100644 --- a/Validation/Configuration/python/autoValidation.py +++ b/Validation/Configuration/python/autoValidation.py @@ -7,7 +7,7 @@ 'JetMETOnlyValidation' : ['globalPrevalidationJetMETOnly','globalValidationJetMETonly','postValidation_JetMET'], 'electronOnlyValidation' : ['', 'electronValidationSequence', 'electronPostValidationSequence'], 'photonOnlyValidation' : ['', 'photonValidationSequence', 'photonPostProcessor'], - 'tauOnlyValidation' : ['produceDenoms', 'pfTauRunDQMValidation', 'runTauEff'], + 'tauOnlyValidation' : ['globalPrevalidationTaus', 'globalValidationTaus', 'postValidationTaus'], 'ecalOnlyValidation' : ['globalPrevalidationECALOnly','globalValidationECALOnly','postValidation_ECAL'], 'hcalValidation' : ['globalPrevalidationHCAL','globalValidationHCAL','postValidation_HCAL'], 'hcalOnlyValidation' : ['globalPrevalidationHCALOnly','globalValidationHCALOnly','postValidation_HCAL'], diff --git a/Validation/Configuration/python/globalValidation_cff.py b/Validation/Configuration/python/globalValidation_cff.py index 9fcf1c848e41c..e23350a9c2916 100644 --- a/Validation/Configuration/python/globalValidation_cff.py +++ b/Validation/Configuration/python/globalValidation_cff.py @@ -38,6 +38,7 @@ from Validation.DTRecHits.DTRecHitQuality_cfi import * from Validation.CSCRecHits.cscRecHitValidation_cfi import * from Validation.RecoTau.DQMMCValidation_cfi import * +from Validation.RecoTau.RecoTauValidation_cff import * from Validation.L1T.L1Validator_cfi import * from Validation.SiPixelPhase1ConfigV.SiPixelPhase1OfflineDQM_sourceV_cff import * from DQMOffline.RecoB.dqmAnalyzer_cff import * @@ -152,6 +153,16 @@ + metPreValidSeq ) +globalPrevalidationTaus = cms.Sequence( + # produceDenoms + tauPreValidSeq +) + +globalValidationTaus = cms.Sequence( + # pfTauRunDQMValidation + recoTauValidationSequence +) + # ECAL local reconstruction globalPrevalidationECAL = cms.Sequence() globalPrevalidationECALOnly = cms.Sequence( diff --git a/Validation/Configuration/python/postValidation_cff.py b/Validation/Configuration/python/postValidation_cff.py index ce0ba28b1da00..d66a5c9beb192 100644 --- a/Validation/Configuration/python/postValidation_cff.py +++ b/Validation/Configuration/python/postValidation_cff.py @@ -17,6 +17,7 @@ from Validation.RecoParticleFlow.PFValidationClient_cff import * from Validation.RPCRecHits.postValidation_cfi import * from Validation.RecoTau.DQMMCValidation_cfi import * +from Validation.RecoTau.RecoTauPostProcessor_cff import * from Validation.RecoVertex.PostProcessorVertex_cff import * from Validation.RecoMET.METPostProcessor_cff import * from Validation.L1T.postProcessorL1Gen_cff import * @@ -47,7 +48,6 @@ + MuonCSCDigisPostProcessors ) -effPlotting = cms.Sequence(runTauEff + makeBetterPlots) #test from Configuration.Eras.Modifier_phase1Pixel_cff import phase1Pixel postValidation_preprod = cms.Sequence( @@ -95,6 +95,11 @@ METPostProcessor ) +postValidationTaus = cms.Sequence( + # runTauEff + RecoTauPostProcessor +) + postValidation_ECAL = cms.Sequence() postValidation_HCAL = cms.Sequence( diff --git a/Validation/RecoTau/plugins/TauValidator.cc b/Validation/RecoTau/plugins/TauValidator.cc new file mode 100644 index 0000000000000..4646dad77e6f5 --- /dev/null +++ b/Validation/RecoTau/plugins/TauValidator.cc @@ -0,0 +1,593 @@ +// Analyzer for validation histograms for tau objects at HLT/RECO +// E. Vernazza Apr. 10, 2026 + +#include "FWCore/Framework/interface/MakerMacros.h" +#include "DQMServices/Core/interface/DQMStore.h" +#include "DataFormats/Common/interface/Handle.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include +#include "DataFormats/TauReco/interface/PFTau.h" +#include "DataFormats/PatCandidates/interface/Tau.h" +#include "DataFormats/JetReco/interface/GenJetCollection.h" +#include "PhysicsTools/JetMCUtils/interface/JetMCTag.h" +#include "DataFormats/TauReco/interface/TauDiscriminatorContainer.h" +#include "DataFormats/Math/interface/deltaR.h" + +#define EDM_ML_DEBUG +#include "FWCore/MessageLogger/interface/MessageLogger.h" + +#include +#include +#include +#include +#include +#include + +using namespace edm; +using namespace reco; +using namespace std; + +class TauValidator : public DQMEDAnalyzer { +public: + TauValidator(const edm::ParameterSet&); + ~TauValidator() override; + + void analyze(const edm::Event&, const edm::EventSetup&) override; + void bookHistograms(DQMStore::IBooker&, edm::Run const&, edm::EventSetup const&) override; + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + std::string convertId(double cut); + bool passIdCut(const std::vector idValuesForTau, + const std::vector> wpValuesForTau, + const std::vector& validCutIDs_raw, + const std::vector& validCutIDs_wp, + bool use_raw, + bool use_wp); + +private: + edm::EDGetTokenT genTauToken_; + edm::EDGetTokenT recoTauToken_; + edm::EDGetTokenT patTauToken_; + std::vector> recoTauIDTokens_; + std::vector recoTauIDLabels_; + edm::InputTag recoTauCollection; + + const std::unordered_map> histoVars = { + {"pt", std::make_tuple(200, 0., 1000.)}, + {"eta", std::make_tuple(60, -4.0, 4.0)}, + {"phi", std::make_tuple(50, -3.5, 3.5)}, + {"mass", std::make_tuple(200, 0, 10.)}, + }; + + const std::unordered_map> histoVars2D = { + {"pt_eta", std::make_tuple(200, 0., 1000., 60, -4.0, 4.0)}, + {"pt_phi", std::make_tuple(200, 0., 1000., 50, -3.5, 3.5)}, + {"pt_mass", std::make_tuple(200, 0., 1000., 200, 0., 10.)}, + {"mass_eta", std::make_tuple(200, 0., 10., 60, -4.0, 4.0)}, + {"mass_phi", std::make_tuple(200, 0., 10., 50, -3.5, 3.5)}, + }; + + using UMap = std::unordered_map; + + UMap h_recoTau_; + UMap h_recoTauMatched_; + UMap h_recoTauMultiMatched_; + UMap h_genTau_; + UMap h_genTauMatched_; + UMap h_genTauMultiMatched_; + + UMap h2d_recoTau_; + UMap h2d_recoTauMatched_; + UMap h2d_recoTauMultiMatched_; + UMap h2d_genTau_; + UMap h2d_genTauMatched_; + UMap h2d_genTauMultiMatched_; + UMap h2d_responsePt_; + UMap h2d_responseMass_; + + std::vector cutIDs_wp; // Working-point indices (WP mode) + bool use_wp; + std::vector cutIDs_raw; // Raw discriminator value cuts (raw mode) + bool use_raw; + + bool isPatTaus; + float matchingDeltaR; + std::string outFolder; +}; + +std::string TauValidator::convertId(double cut) { + if (cut == 0.0) + return "0p0"; + std::ostringstream oss; + oss << std::fixed << std::setprecision(2) << cut; + std::string result = oss.str(); + for (char& c : result) { + if (c == '.') + c = 'p'; + } + return result; +} + +bool TauValidator::passIdCut(const std::vector idValuesForTau, + const std::vector> wpValuesForTau, + const std::vector& validCutIDs_raw, + const std::vector& validCutIDs_wp, + bool use_raw, + bool use_wp) { + if (use_raw) { + for (size_t i = 0; i < idValuesForTau.size(); ++i) { + if (validCutIDs_raw[i] > 0.0) { + if (idValuesForTau[i] < validCutIDs_raw[i]) { + return false; // Fails raw cut + } + } + } + return true; + } + + else if (use_wp) { + for (size_t i = 0; i < wpValuesForTau.size(); ++i) { + if (validCutIDs_wp[i] >= 0) { + if (validCutIDs_wp[i] > static_cast(wpValuesForTau[i].size())) { + return false; + } + if (wpValuesForTau[i][validCutIDs_wp[i]] == 0) { + return false; // Fails WP cut + } + } + } + return true; + } + + return false; +} + +TauValidator::TauValidator(const edm::ParameterSet& iConfig) { + genTauToken_ = consumes(iConfig.getParameter("genTauCollection")); + recoTauCollection = iConfig.getParameter("recoTauCollection"); + matchingDeltaR = iConfig.getParameter("minDeltaR"); + outFolder = iConfig.getParameter("outFolder"); + isPatTaus = iConfig.getUntrackedParameter("isPatTaus"); + + if (isPatTaus) { + patTauToken_ = consumes(recoTauCollection); + } else { + recoTauToken_ = consumes(recoTauCollection); + } + + std::vector idTags = iConfig.getParameter>("recoTauIDCollections"); + for (const auto& tag : idTags) { + // Extract label from the instance part of the InputTag (e.g., "VSjet" from "module:VSjet") + recoTauIDTokens_.push_back(consumes(tag)); + recoTauIDLabels_.push_back(tag.instance()); + } + + cutIDs_wp = iConfig.getParameter>("cutIDs_wp"); + cutIDs_raw = iConfig.getParameter>("cutIDs_raw"); + + constexpr double EPS = 1e-12; + use_wp = std::any_of(cutIDs_wp.begin(), cutIDs_wp.end(), [](int x) { return x >= 0; }); + use_raw = std::any_of(cutIDs_raw.begin(), cutIDs_raw.end(), [](double x) { return std::abs(x) > EPS; }); + + if (use_wp && use_raw) { + throw cms::Exception("Configuration") << "Specify either cutIDs_wp OR cutIDs_raw, not both"; + } + + if (!recoTauIDLabels_.empty()) { + if (cutIDs_wp.size() != recoTauIDLabels_.size()) { + cutIDs_wp.resize(recoTauIDLabels_.size(), -1); + LogDebug("TauValidator") << "Warning: cutIDs_wp size (" << cutIDs_wp.size() + << ") adjusted to match idLabels size (" << recoTauIDLabels_.size() << ")"; + } + if (cutIDs_raw.size() != recoTauIDLabels_.size()) { + cutIDs_raw.resize(recoTauIDLabels_.size(), 0.0); + LogDebug("TauValidator") << "Warning: cutIDs_raw size (" << cutIDs_raw.size() + << ") adjusted to match idLabels size (" << recoTauIDLabels_.size() << ")"; + } + } +} + +void TauValidator::bookHistograms(DQMStore::IBooker& ibooker, edm::Run const& iRun, edm::EventSetup const&) { + // ---------------------------- Book Summary Histograms ------------------------------- + + if (use_wp) { + outFolder += "/CutWP"; + for (size_t i = 0; i < recoTauIDLabels_.size(); ++i) { + if (cutIDs_wp[i] >= 0) { + outFolder += "_" + recoTauIDLabels_[i] + std::to_string(cutIDs_wp[i]); + } + } + } + if (use_raw) { + outFolder += "/CutID"; + for (size_t i = 0; i < recoTauIDLabels_.size(); ++i) { + if (cutIDs_raw[i] > 0) { + outFolder += "_" + recoTauIDLabels_[i] + convertId(cutIDs_raw[i]); + } + } + } + + ibooker.setCurrentFolder(outFolder); + + // Book 1D histograms for gen and reco tau kinematics + for (auto& hVar : histoVars) { + auto [nBins, hMin, hMax] = hVar.second; + h_recoTau_[hVar.first] = + ibooker.book1D("recoTau_" + hVar.first, "#tau^{reco};" + hVar.first + ";", nBins, hMin, hMax); + h_recoTauMatched_[hVar.first] = + ibooker.book1D("recoTauMatched_" + hVar.first, "#tau^{reco} (Matched);" + hVar.first + ";", nBins, hMin, hMax); + h_recoTauMultiMatched_[hVar.first] = ibooker.book1D( + "recoTauMultiMatched_" + hVar.first, "#tau^{reco} (Multi-Matched);" + hVar.first + ";", nBins, hMin, hMax); + h_genTau_[hVar.first] = ibooker.book1D("genTau_" + hVar.first, "#tau^{gen};" + hVar.first + ";", nBins, hMin, hMax); + h_genTauMatched_[hVar.first] = + ibooker.book1D("genTauMatched_" + hVar.first, "#tau^{gen} (Matched);" + hVar.first + ";", nBins, hMin, hMax); + h_genTauMultiMatched_[hVar.first] = ibooker.book1D( + "genTauMultiMatched_" + hVar.first, "#tau^{gen} (Multi-Matched);" + hVar.first + ";", nBins, hMin, hMax); + h2d_responsePt_[hVar.first] = ibooker.book2D("responsePt_" + hVar.first, + "#tau^{gen} (Matched);" + hVar.first + ";#tau Pt Response", + nBins, + hMin, + hMax, + 50, + 0., + 2.); + h2d_responseMass_[hVar.first] = ibooker.book2D("responseMass_" + hVar.first, + "#tau^{gen} (Matched);" + hVar.first + ";#tau Mass Response", + nBins, + hMin, + hMax, + 50, + 0., + 2.); + + // Book 2D histograms for reco tau ID discriminators vs kinematics (dynamic based on idLabels) + for (const auto& label : recoTauIDLabels_) { + std::string idName = "id" + label + "_" + hVar.first; + h2d_recoTau_[idName] = ibooker.book2D( + "recoTau_" + idName, "#tau^{reco}; ID" + label + ";" + hVar.first, 50, 0., 1., nBins, hMin, hMax); + h2d_recoTauMatched_[idName] = ibooker.book2D("recoTauMatched_" + idName, + "#tau^{reco} (Matched); ID" + label + ";" + hVar.first, + 50, + 0., + 1., + nBins, + hMin, + hMax); + h2d_recoTauMultiMatched_[idName] = ibooker.book2D("recoTauMultiMatched_" + idName, + "#tau^{reco} (Multi-Matched); ID" + label + ";" + hVar.first, + 50, + 0., + 1., + nBins, + hMin, + hMax); + } + } + + // Book 1D histograms for reco tau ID discriminators (dynamic based on idLabels) + for (const auto& label : recoTauIDLabels_) { + std::string idName = "id" + label; + h_recoTau_[idName] = ibooker.book1D("recoTau_" + idName, "#tau^{reco};" + idName + ";", 50, 0., 1.); + h_recoTauMatched_[idName] = + ibooker.book1D("recoTauMatched_" + idName, "#tau^{reco} (Matched);" + idName + ";", 50, 0., 1.); + h_recoTauMultiMatched_[idName] = + ibooker.book1D("recoTauMultiMatched_" + idName, "#tau^{reco} (Multi-Matched);" + idName + ";", 50, 0., 1.); + } + + // Book 2D histograms for gen and reco tau kinematics + for (auto& h2dVar : histoVars2D) { + auto [nBinsX, hMinX, hMaxX, nBinsY, hMinY, hMaxY] = h2dVar.second; + auto x_title = h2dVar.first.substr(0, h2dVar.first.find("_")); + auto y_title = h2dVar.first.substr(h2dVar.first.find("_") + 1); + h2d_recoTau_[h2dVar.first] = ibooker.book2D( + "recoTau_" + h2dVar.first, "#tau^{reco};" + x_title + ";" + y_title, nBinsX, hMinX, hMaxX, nBinsY, hMinY, hMaxY); + h2d_recoTauMatched_[h2dVar.first] = ibooker.book2D("recoTauMatched_" + h2dVar.first, + "#tau^{reco} (Matched);" + x_title + ";" + y_title, + nBinsX, + hMinX, + hMaxX, + nBinsY, + hMinY, + hMaxY); + h2d_recoTauMultiMatched_[h2dVar.first] = ibooker.book2D("recoTauMultiMatched_" + h2dVar.first, + "#tau^{reco} (Multi-Matched);" + x_title + ";" + y_title, + nBinsX, + hMinX, + hMaxX, + nBinsY, + hMinY, + hMaxY); + h2d_genTau_[h2dVar.first] = ibooker.book2D( + "genTau_" + h2dVar.first, "#tau^{gen};" + x_title + ";" + y_title, nBinsX, hMinX, hMaxX, nBinsY, hMinY, hMaxY); + h2d_genTauMatched_[h2dVar.first] = ibooker.book2D("genTauMatched_" + h2dVar.first, + "#tau^{gen} (Matched);" + x_title + ";" + y_title, + nBinsX, + hMinX, + hMaxX, + nBinsY, + hMinY, + hMaxY); + h2d_genTauMultiMatched_[h2dVar.first] = ibooker.book2D("genTauMultiMatched_" + h2dVar.first, + "#tau^{gen} (Multi-Matched);" + x_title + ";" + y_title, + nBinsX, + hMinX, + hMaxX, + nBinsY, + hMinY, + hMaxY); + } +} + +//------------------------------------------------------------------------------ +// ~TauValidator +//------------------------------------------------------------------------------ +TauValidator::~TauValidator() = default; + +//------------------------------------------------------------------------------ +// analyze +//------------------------------------------------------------------------------ +void TauValidator::analyze(const edm::Event& mEvent, const edm::EventSetup& mSetup) { + // --------------------------------- Gen Taus -------------------------------- + + auto genTaus = mEvent.getHandle(genTauToken_); + if (!genTaus.isValid()) { + LogDebug("TauValidator") << " Gen Tau collection not found while running TauValidator.cc "; + return; + } + + // std::cout << "Number of gen taus: " << genTaus->size() << std::endl; // [DEBUG] + + // --------------------------------- Tau IDs -------------------------------- + + std::vector validRecoTauIDs; + std::vector validRecoTauIDLabels; + std::vector validCutIDs_raw; + std::vector validCutIDs_wp; + for (size_t i = 0; i < recoTauIDTokens_.size(); ++i) { + auto recoTauID = mEvent.getHandle(recoTauIDTokens_[i]); + if (!recoTauID.isValid()) { + LogDebug("TauValidator") << "Reco Tau Identifier " << recoTauIDLabels_[i] + << " collection not found while running TauValidator.cc "; + continue; + } + validRecoTauIDs.push_back(recoTauID.product()); + validRecoTauIDLabels.push_back(recoTauIDLabels_[i]); + validCutIDs_raw.push_back(cutIDs_raw[i]); + validCutIDs_wp.push_back(cutIDs_wp[i]); + } + + bool plotId = !validRecoTauIDs.empty(); + bool applyIdCuts = plotId && (use_wp || use_raw); + + // --------------------------------- Reco Taus -------------------------------- + + std::vector recoTaus; + std::vector> recoTauIDValues; + std::vector>> recoTauWPValues; + + if (!isPatTaus) { + auto recoTausTmp = mEvent.getHandle(recoTauToken_); + if (!recoTausTmp.isValid()) { + LogDebug("TauValidator") << " Reco Tau collection not found while running TauValidator.cc "; + return; + } + for (unsigned itau = 0; itau < recoTausTmp->size(); ++itau) { + std::vector idValuesForTau; + std::vector> wpValuesForTau; + for (size_t i = 0; i < validRecoTauIDs.size(); ++i) { + reco::PFTauRef tauRef = reco::PFTauRef(recoTausTmp, itau); + const auto& disc = (*validRecoTauIDs[i])[tauRef]; + idValuesForTau.push_back(disc.rawValues.empty() ? -1.0 : disc.rawValues[0]); + wpValuesForTau.push_back(disc.workingPoints.empty() ? std::vector(1, false) : disc.workingPoints); + } + if (applyIdCuts && !passIdCut(idValuesForTau, wpValuesForTau, validCutIDs_raw, validCutIDs_wp, use_raw, use_wp)) { + continue; + } + recoTauIDValues.push_back(idValuesForTau); + recoTauWPValues.push_back(wpValuesForTau); + recoTaus.push_back(recoTausTmp->at(itau)); + } + } else { + auto patTaus = mEvent.getHandle(patTauToken_); + if (!patTaus.isValid()) { + LogDebug("TauValidator") << " PAT Tau collection not found while running TauValidator.cc "; + return; + } + for (unsigned itau = 0; itau < patTaus->size(); ++itau) { + reco::PFTau tauFromPat; + tauFromPat.setP4(patTaus->at(itau).p4()); + std::vector idValuesForTau; + std::vector> wpValuesForTau; + for (size_t i = 0; i < validRecoTauIDs.size(); ++i) { + pat::TauRef tauRef = pat::TauRef(patTaus, itau); + const auto& disc = (*validRecoTauIDs[i])[tauRef]; + idValuesForTau.push_back(disc.rawValues.empty() ? -1.0 : disc.rawValues[0]); + wpValuesForTau.push_back(disc.workingPoints.empty() ? std::vector(1, false) : disc.workingPoints); + } + if (applyIdCuts && !passIdCut(idValuesForTau, wpValuesForTau, validCutIDs_raw, validCutIDs_wp, use_raw, use_wp)) { + continue; + } + recoTauIDValues.push_back(idValuesForTau); + recoTauWPValues.push_back(wpValuesForTau); + recoTaus.push_back(tauFromPat); + } + } + + // std::cout << "Number of reco taus: " << recoTaus.size() << std::endl; // [DEBUG] + + // --------------------------------- Compute Metrics -------------------------------- + + // Loop for efficiency + for (unsigned itau = 0; itau < genTaus->size(); ++itau) { + h_genTau_["pt"]->Fill(genTaus->at(itau).pt()); + h_genTau_["eta"]->Fill(genTaus->at(itau).eta()); + h_genTau_["phi"]->Fill(genTaus->at(itau).phi()); + h_genTau_["mass"]->Fill(genTaus->at(itau).mass()); + h2d_genTau_["pt_eta"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).eta()); + h2d_genTau_["pt_phi"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).phi()); + h2d_genTau_["pt_mass"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).mass()); + h2d_genTau_["mass_eta"]->Fill(genTaus->at(itau).mass(), genTaus->at(itau).eta()); + h2d_genTau_["mass_phi"]->Fill(genTaus->at(itau).mass(), genTaus->at(itau).phi()); + + // Count how many reco taus are matched to the gen tau + int nRecoMatchedToOneGen = 0; + float bestDeltaR = 999.; + float ResponsePt_bestDeltaR = 0.; + float ResponseMass_bestDeltaR = 0.; + for (unsigned jtau = 0; jtau < recoTaus.size(); ++jtau) { + float deltaRValue = deltaR(genTaus->at(itau), recoTaus.at(jtau)); + if (deltaRValue < matchingDeltaR) { + nRecoMatchedToOneGen++; + if (deltaRValue < bestDeltaR) { + bestDeltaR = deltaRValue; + ResponsePt_bestDeltaR = recoTaus.at(jtau).pt() / genTaus->at(itau).pt(); + ResponseMass_bestDeltaR = recoTaus.at(jtau).mass() / genTaus->at(itau).mass(); + } + } + } + + // Fill histograms for gen taus matched to at least one reco tau + if (nRecoMatchedToOneGen > 0) { + // Fill gen tau histograms for matched taus + h_genTauMatched_["pt"]->Fill(genTaus->at(itau).pt()); + h_genTauMatched_["eta"]->Fill(genTaus->at(itau).eta()); + h_genTauMatched_["phi"]->Fill(genTaus->at(itau).phi()); + h_genTauMatched_["mass"]->Fill(genTaus->at(itau).mass()); + h2d_genTauMatched_["pt_eta"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).eta()); + h2d_genTauMatched_["pt_phi"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).phi()); + h2d_genTauMatched_["pt_mass"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).mass()); + h2d_genTauMatched_["mass_eta"]->Fill(genTaus->at(itau).mass(), genTaus->at(itau).eta()); + h2d_genTauMatched_["mass_phi"]->Fill(genTaus->at(itau).mass(), genTaus->at(itau).phi()); + // Fill response histograms for matched taus + h2d_responsePt_["pt"]->Fill(genTaus->at(itau).pt(), ResponsePt_bestDeltaR); + h2d_responsePt_["eta"]->Fill(genTaus->at(itau).eta(), ResponsePt_bestDeltaR); + h2d_responsePt_["phi"]->Fill(genTaus->at(itau).phi(), ResponsePt_bestDeltaR); + h2d_responsePt_["mass"]->Fill(genTaus->at(itau).mass(), ResponsePt_bestDeltaR); + h2d_responseMass_["pt"]->Fill(genTaus->at(itau).pt(), ResponseMass_bestDeltaR); + h2d_responseMass_["eta"]->Fill(genTaus->at(itau).eta(), ResponseMass_bestDeltaR); + h2d_responseMass_["phi"]->Fill(genTaus->at(itau).phi(), ResponseMass_bestDeltaR); + h2d_responseMass_["mass"]->Fill(genTaus->at(itau).mass(), ResponseMass_bestDeltaR); + + if (nRecoMatchedToOneGen > 1) { + // Fill gen tau histograms for multi-matched taus + h_genTauMultiMatched_["pt"]->Fill(genTaus->at(itau).pt()); + h_genTauMultiMatched_["eta"]->Fill(genTaus->at(itau).eta()); + h_genTauMultiMatched_["phi"]->Fill(genTaus->at(itau).phi()); + h_genTauMultiMatched_["mass"]->Fill(genTaus->at(itau).mass()); + h2d_genTauMultiMatched_["pt_eta"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).eta()); + h2d_genTauMultiMatched_["pt_phi"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).phi()); + h2d_genTauMultiMatched_["pt_mass"]->Fill(genTaus->at(itau).pt(), genTaus->at(itau).mass()); + h2d_genTauMultiMatched_["mass_eta"]->Fill(genTaus->at(itau).mass(), genTaus->at(itau).eta()); + h2d_genTauMultiMatched_["mass_phi"]->Fill(genTaus->at(itau).mass(), genTaus->at(itau).phi()); + } + } + } + + // Loop for fake rate + for (unsigned itau = 0; itau < recoTaus.size(); ++itau) { + h_recoTau_["pt"]->Fill(recoTaus.at(itau).pt()); + h_recoTau_["eta"]->Fill(recoTaus.at(itau).eta()); + h_recoTau_["phi"]->Fill(recoTaus.at(itau).phi()); + h_recoTau_["mass"]->Fill(recoTaus.at(itau).mass()); + h2d_recoTau_["pt_eta"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).eta()); + h2d_recoTau_["pt_phi"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).phi()); + h2d_recoTau_["pt_mass"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).mass()); + h2d_recoTau_["mass_eta"]->Fill(recoTaus.at(itau).mass(), recoTaus.at(itau).eta()); + h2d_recoTau_["mass_phi"]->Fill(recoTaus.at(itau).mass(), recoTaus.at(itau).phi()); + + if (plotId) { + for (size_t i = 0; i < validRecoTauIDLabels.size(); ++i) { + const double idRawValue = recoTauIDValues[itau][i]; + const std::string idName = "id" + validRecoTauIDLabels[i]; + h_recoTau_[idName]->Fill(idRawValue); + h2d_recoTau_[idName + "_pt"]->Fill(idRawValue, recoTaus.at(itau).pt()); + h2d_recoTau_[idName + "_eta"]->Fill(idRawValue, recoTaus.at(itau).eta()); + h2d_recoTau_[idName + "_phi"]->Fill(idRawValue, recoTaus.at(itau).phi()); + h2d_recoTau_[idName + "_mass"]->Fill(idRawValue, recoTaus.at(itau).mass()); + } + } + + // Count how many gen taus are matched to the reco tau + int nGenMatchedToOneReco = 0; + for (unsigned jtau = 0; jtau < genTaus->size(); ++jtau) { + if (deltaR(genTaus->at(jtau), recoTaus.at(itau)) < matchingDeltaR) { + nGenMatchedToOneReco++; + } + } + + // Fill histograms for reco taus matched to at least one gen tau + if (nGenMatchedToOneReco > 0) { + // Fill reco tau histograms for matched taus + h_recoTauMatched_["pt"]->Fill(recoTaus.at(itau).pt()); + h_recoTauMatched_["eta"]->Fill(recoTaus.at(itau).eta()); + h_recoTauMatched_["phi"]->Fill(recoTaus.at(itau).phi()); + h_recoTauMatched_["mass"]->Fill(recoTaus.at(itau).mass()); + h2d_recoTauMatched_["pt_eta"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).eta()); + h2d_recoTauMatched_["pt_phi"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).phi()); + h2d_recoTauMatched_["pt_mass"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).mass()); + h2d_recoTauMatched_["mass_eta"]->Fill(recoTaus.at(itau).mass(), recoTaus.at(itau).eta()); + h2d_recoTauMatched_["mass_phi"]->Fill(recoTaus.at(itau).mass(), recoTaus.at(itau).phi()); + + if (plotId) { + for (size_t i = 0; i < validRecoTauIDLabels.size(); ++i) { + const double idRawValue = recoTauIDValues[itau][i]; + const std::string idName = "id" + validRecoTauIDLabels[i]; + h_recoTauMatched_[idName]->Fill(idRawValue); + h2d_recoTauMatched_[idName + "_pt"]->Fill(idRawValue, recoTaus.at(itau).pt()); + h2d_recoTauMatched_[idName + "_eta"]->Fill(idRawValue, recoTaus.at(itau).eta()); + h2d_recoTauMatched_[idName + "_phi"]->Fill(idRawValue, recoTaus.at(itau).phi()); + h2d_recoTauMatched_[idName + "_mass"]->Fill(idRawValue, recoTaus.at(itau).mass()); + } + } + + if (nGenMatchedToOneReco > 1) { + // Fill reco tau histograms for multi-matched taus + h_recoTauMultiMatched_["pt"]->Fill(recoTaus.at(itau).pt()); + h_recoTauMultiMatched_["eta"]->Fill(recoTaus.at(itau).eta()); + h_recoTauMultiMatched_["phi"]->Fill(recoTaus.at(itau).phi()); + h_recoTauMultiMatched_["mass"]->Fill(recoTaus.at(itau).mass()); + h2d_recoTauMultiMatched_["pt_eta"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).eta()); + h2d_recoTauMultiMatched_["pt_phi"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).phi()); + h2d_recoTauMultiMatched_["pt_mass"]->Fill(recoTaus.at(itau).pt(), recoTaus.at(itau).mass()); + h2d_recoTauMultiMatched_["mass_eta"]->Fill(recoTaus.at(itau).mass(), recoTaus.at(itau).eta()); + h2d_recoTauMultiMatched_["mass_phi"]->Fill(recoTaus.at(itau).mass(), recoTaus.at(itau).phi()); + + if (plotId) { + for (size_t i = 0; i < validRecoTauIDLabels.size(); ++i) { + const double idRawValue = recoTauIDValues[itau][i]; + const std::string idName = "id" + validRecoTauIDLabels[i]; + h_recoTauMultiMatched_[idName]->Fill(idRawValue); + h2d_recoTauMultiMatched_[idName + "_pt"]->Fill(idRawValue, recoTaus.at(itau).pt()); + h2d_recoTauMultiMatched_[idName + "_eta"]->Fill(idRawValue, recoTaus.at(itau).eta()); + h2d_recoTauMultiMatched_[idName + "_phi"]->Fill(idRawValue, recoTaus.at(itau).phi()); + h2d_recoTauMultiMatched_[idName + "_mass"]->Fill(idRawValue, recoTaus.at(itau).mass()); + } + } + } + } + } +} + +//------------------------------------------------------------------------------ +// fill description +//------------------------------------------------------------------------------ +void TauValidator::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + // Default tau validation HLT + desc.add("genTauCollection", edm::InputTag("tauGenJets")); + desc.add("recoTauCollection", edm::InputTag("hltHpsPFTauProducer")); + desc.add>("recoTauIDCollections", + std::vector{edm::InputTag("hltHpsPFTauDeepTauProducer:VSjet"), + edm::InputTag("hltHpsPFTauDeepTauProducer:VSe"), + edm::InputTag("hltHpsPFTauDeepTauProducer:VSmu")}); + desc.add>("cutIDs_raw", std::vector{0.0, 0.0, 0.0}); + desc.add>("cutIDs_wp", std::vector{-1, -1, -1}); + desc.add("minDeltaR", 0.3); + desc.add("outFolder", "HLT/Tau/TauValidation"); + desc.addUntracked("isPatTaus", false); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(TauValidator); diff --git a/Validation/RecoTau/python/RecoTauPostProcessor_cff.py b/Validation/RecoTau/python/RecoTauPostProcessor_cff.py new file mode 100644 index 0000000000000..e7f822d32abaf --- /dev/null +++ b/Validation/RecoTau/python/RecoTauPostProcessor_cff.py @@ -0,0 +1,101 @@ +import FWCore.ParameterSet.Config as cms +from DQMServices.Core.DQMEDHarvester import DQMEDHarvester + +RecoTauPostProcessor = DQMEDHarvester("DQMGenericClient", + subDirs=cms.untracked.vstring("Tau/TauValidation/", + "Tau/TauValidation/Cut*", + # "Tau/TauValidation_DeltaR/DeltaR*", + # "Tau/TauValidation_DeltaR/DeltaR*/Cut*", + ), + efficiency = cms.vstring( + # Efficiency + "Eff_vs_pt_eta 'Efficiency vs p_{T},#eta' genTauMatched_pt_eta genTau_pt_eta", + "Eff_vs_pt_phi 'Efficiency vs p_{T},#phi' genTauMatched_pt_phi genTau_pt_phi", + "Eff_vs_pt_mass 'Efficiency vs p_{T},mass' genTauMatched_pt_mass genTau_pt_mass", + "Eff_vs_mass_eta 'Efficiency vs mass,#eta' genTauMatched_mass_eta genTau_mass_eta", + "Eff_vs_mass_phi 'Efficiency vs mass,#phi' genTauMatched_mass_phi genTau_mass_phi", + # Fake rate + "Fake_vs_pt_eta 'Fake Rate vs p_{T},#eta' recoTauMatched_pt_eta recoTau_pt_eta fake", + "Fake_vs_pt_phi 'Fake Rate vs p_{T},#phi' recoTauMatched_pt_phi recoTau_pt_phi fake", + "Fake_vs_pt_mass 'Fake Rate vs p_{T},mass' recoTauMatched_pt_mass recoTau_pt_mass fake", + "Fake_vs_mass_eta 'Fake Rate vs mass,#eta' recoTauMatched_mass_eta recoTau_mass_eta fake", + "Fake_vs_mass_phi 'Fake Rate vs mass,#phi' recoTauMatched_mass_phi recoTau_mass_phi fake", + "Fake_vs_idVSjet_pt 'Fake Rate vs ID vs Jet,p_{T}' recoTauMatched_idVSjet_pt recoTau_idVSjet_pt fake", + "Fake_vs_idVSjet_eta 'Fake Rate vs ID vs Jet,#eta' recoTauMatched_idVSjet_eta recoTau_idVSjet_eta fake", + "Fake_vs_idVSjet_phi 'Fake Rate vs ID vs Jet,#phi' recoTauMatched_idVSjet_phi recoTau_idVSjet_phi fake", + "Fake_vs_idVSjet_mass 'Fake Rate vs ID vs Jet,mass' recoTauMatched_idVSjet_mass recoTau_idVSjet_mass fake", + "Fake_vs_idVSe_pt 'Fake Rate vs ID vs Electron,p_{T}' recoTauMatched_idVSe_pt recoTau_idVSe_pt fake", + "Fake_vs_idVSe_eta 'Fake Rate vs ID vs Electron,#eta' recoTauMatched_idVSe_eta recoTau_idVSe_eta fake", + "Fake_vs_idVSe_phi 'Fake Rate vs ID vs Electron,#phi' recoTauMatched_idVSe_phi recoTau_idVSe_phi fake", + "Fake_vs_idVSe_mass 'Fake Rate vs ID vs Electron,mass' recoTauMatched_idVSe_mass recoTau_idVSe_mass fake", + "Fake_vs_idVSmu_pt 'Fake Rate vs ID vs Muon,p_{T}' recoTauMatched_idVSmu_pt recoTau_idVSmu_pt fake", + "Fake_vs_idVSmu_eta 'Fake Rate vs ID vs Muon,#eta' recoTauMatched_idVSmu_eta recoTau_idVSmu_eta fake", + "Fake_vs_idVSmu_phi 'Fake Rate vs ID vs Muon,#phi' recoTauMatched_idVSmu_phi recoTau_idVSmu_phi fake", + "Fake_vs_idVSmu_mass 'Fake Rate vs ID vs Muon,mass' recoTauMatched_idVSmu_mass recoTau_idVSmu_mass fake", + # Split rate + "Split_vs_pt_eta 'Split Rate vs p_{T},#eta' genTauMultiMatched_pt_eta genTau_pt_eta", + "Split_vs_pt_phi 'Split Rate vs p_{T},#phi' genTauMultiMatched_pt_phi genTau_pt_phi", + "Split_vs_pt_mass 'Split Rate vs p_{T},mass' genTauMultiMatched_pt_mass genTau_pt_mass", + "Split_vs_mass_eta 'Split Rate vs mass,#eta' genTauMultiMatched_mass_eta genTau_mass_eta", + "Split_vs_mass_phi 'Split Rate vs mass,#phi' genTauMultiMatched_mass_phi genTau_mass_phi", + # Duplicate rate + "Dup_vs_pt_eta 'Duplicate Rate vs p_{T},#eta' recoTauMultiMatched_pt_eta recoTau_pt_eta", + "Dup_vs_pt_phi 'Duplicate Rate vs p_{T},#phi' recoTauMultiMatched_pt_phi recoTau_pt_phi", + "Dup_vs_pt_mass 'Duplicate Rate vs p_{T},mass' recoTauMultiMatched_pt_mass recoTau_pt_mass", + "Dup_vs_mass_eta 'Duplicate Rate vs mass,#eta' recoTauMultiMatched_mass_eta recoTau_mass_eta", + "Dup_vs_mass_phi 'Duplicate Rate vs mass,#phi' recoTauMultiMatched_mass_phi recoTau_mass_phi", + "Dup_vs_idVSjet_pt 'Duplicate Rate vs ID vs Jet,p_{T}' recoTauMultiMatched_idVSjet_pt recoTau_idVSjet_pt", + "Dup_vs_idVSjet_eta 'Duplicate Rate vs ID vs Jet,#eta' recoTauMultiMatched_idVSjet_eta recoTau_idVSjet_eta", + "Dup_vs_idVSjet_phi 'Duplicate Rate vs ID vs Jet,#phi' recoTauMultiMatched_idVSjet_phi recoTau_idVSjet_phi", + "Dup_vs_idVSjet_mass 'Duplicate Rate vs ID vs Jet,mass' recoTauMultiMatched_idVSjet_mass recoTau_idVSjet_mass", + "Dup_vs_idVSe_pt 'Duplicate Rate vs ID vs Electron,p_{T}' recoTauMultiMatched_idVSe_pt recoTau_idVSe_pt", + "Dup_vs_idVSe_eta 'Duplicate Rate vs ID vs Electron,#eta' recoTauMultiMatched_idVSe_eta recoTau_idVSe_eta", + "Dup_vs_idVSe_phi 'Duplicate Rate vs ID vs Electron,#phi' recoTauMultiMatched_idVSe_phi recoTau_idVSe_phi", + "Dup_vs_idVSe_mass 'Duplicate Rate vs ID vs Electron,mass' recoTauMultiMatched_idVSe_mass recoTau_idVSe_mass", + "Dup_vs_idVSmu_pt 'Duplicate Rate vs ID vs Muon,p_{T}' recoTauMultiMatched_idVSmu_pt recoTau_idVSmu_pt", + "Dup_vs_idVSmu_eta 'Duplicate Rate vs ID vs Muon,#eta' recoTauMultiMatched_idVSmu_eta recoTau_idVSmu_eta", + "Dup_vs_idVSmu_phi 'Duplicate Rate vs ID vs Muon,#phi' recoTauMultiMatched_idVSmu_phi recoTau_idVSmu_phi", + "Dup_vs_idVSmu_mass 'Duplicate Rate vs ID vs Muon,mass' recoTauMultiMatched_idVSmu_mass recoTau_idVSmu_mass", + ), + efficiencyProfile = cms.untracked.vstring( # for smoother rebinning + # Efficiency + "Eff_vs_eta 'Efficiency vs #eta' genTauMatched_eta genTau_eta", + "Eff_vs_phi 'Efficiency vs #phi' genTauMatched_phi genTau_phi", + "Eff_vs_pt 'Efficiency vs p_{T}' genTauMatched_pt genTau_pt", + "Eff_vs_mass 'Efficiency vs mass' genTauMatched_mass genTau_mass", + # Fake rate (note: the flag 'fake' coputes 1 - ratio only for the globalEfficiency histogram) + "Fake_vs_eta 'Fake Rate vs #eta' recoTauMatched_eta recoTau_eta fake", + "Fake_vs_phi 'Fake Rate vs #phi' recoTauMatched_phi recoTau_phi fake", + "Fake_vs_pt 'Fake Rate vs p_{T}' recoTauMatched_pt recoTau_pt fake", + "Fake_vs_mass 'Fake Rate vs mass' recoTauMatched_mass recoTau_mass fake", + "Fake_vs_idVSjet 'Fake Rate vs ID vs Jet' recoTauMatched_idVSjet recoTau_idVSjet fake", + "Fake_vs_idVSe 'Fake Rate vs ID vs E' recoTauMatched_idVSe recoTau_idVSe fake", + "Fake_vs_idVSmu 'Fake Rate vs ID vs Mu' recoTauMatched_idVSmu recoTau_idVSmu fake", + # Split rate + "Split_vs_eta 'Split Rate vs #eta' genTauMultiMatched_eta genTau_eta", + "Split_vs_phi 'Split Rate vs #phi' genTauMultiMatched_phi genTau_phi", + "Split_vs_pt 'Split Rate vs p_{T}' genTauMultiMatched_pt genTau_pt", + "Split_vs_mass 'Split Rate vs mass' genTauMultiMatched_mass genTau_mass", + # Duplicate rate + "Dup_vs_eta 'Duplicate Rate vs #eta' recoTauMultiMatched_eta recoTau_eta", + "Dup_vs_phi 'Duplicate Rate vs #phi' recoTauMultiMatched_phi recoTau_phi", + "Dup_vs_pt 'Duplicate Rate vs p_{T}' recoTauMultiMatched_pt recoTau_pt", + "Dup_vs_mass 'Duplicate Rate vs mass' recoTauMultiMatched_mass recoTau_mass", + "Dup_vs_idVSjet 'Duplicate Rate vs ID vs Jet' recoTauMultiMatched_idVSjet recoTau_idVSjet", + "Dup_vs_idVSe 'Duplicate Rate vs ID vs E' recoTauMultiMatched_idVSe recoTau_idVSe", + "Dup_vs_idVSmu 'Duplicate Rate vs ID vs Mu' recoTauMultiMatched_idVSmu recoTau_idVSmu", + ), + resolution = cms.vstring(), + resolutionProfile = cms.untracked.vstring( + "ResponsePt_RecoOverGen_vs_pt 'Response RecoOverGen vs p_{T}^{gen}' responsePt_pt rms", + "ResponsePt_RecoOverGen_vs_eta 'Response RecoOverGen vs #eta^{gen}' responsePt_eta rms", + "ResponsePt_RecoOverGen_vs_phi 'Response RecoOverGen vs #phi^{gen}' responsePt_phi rms", + "ResponsePt_RecoOverGen_vs_mass 'Response RecoOverGen vs mass^{gen}' responsePt_mass rms", + "ResponseMass_RecoOverGen_vs_pt 'Response RecoOverGen vs p_{T}^{gen}' responseMass_pt rms", + "ResponseMass_RecoOverGen_vs_eta 'Response RecoOverGen vs #eta^{gen}' responseMass_eta rms", + "ResponseMass_RecoOverGen_vs_phi 'Response RecoOverGen vs #phi^{gen}' responseMass_phi rms", + "ResponseMass_RecoOverGen_vs_mass 'Response RecoOverGen vs mass^{gen}' responseMass_mass rms", + ), + verbose = cms.untracked.uint32(2), + outputFileName = cms.untracked.string("") +) diff --git a/Validation/RecoTau/python/RecoTauValidation_cff.py b/Validation/RecoTau/python/RecoTauValidation_cff.py index b791b536096e5..602c89984a9f6 100644 --- a/Validation/RecoTau/python/RecoTauValidation_cff.py +++ b/Validation/RecoTau/python/RecoTauValidation_cff.py @@ -1,4 +1,104 @@ import FWCore.ParameterSet.Config as cms + +from PhysicsTools.JetMCAlgos.TauGenJetsDecayModeSelectorAllHadrons_cfi import * +tauPreValidSeq = cms.Sequence(tauGenJetsSelectorAllHadrons) + +from Validation.RecoTau.TauValidator import TauValidator as _TauValidator + +# recoTauValidation = _TauValidator( +# recoTauCollection = "hpsPFTauProducer", +# genTauCollection = "tauGenJetsSelectorAllHadrons", # only GenTaus decaying hadronically +# recoTauIDCollections = [], # reco discriminators not available at RECO level (only at MINI) +# cutIDs_wp = [], # WP discriminator (disabled if < 0) +# cutIDs_raw = [], # raw discriminator value cuts (disabled if 0.0) +# minDeltaR = 0.3, +# outFolder = "Tau/TauValidation", +# isPatTaus = False +# ) + +recoTauValidation = _TauValidator( + recoTauCollection = "slimmedTausNoDeepIDs", + genTauCollection = "tauGenJetsSelectorAllHadrons", # only GenTaus decaying hadronically + recoTauIDCollections = ["deepTau2026v2p5ForMini:VSjet", "deepTau2026v2p5ForMini:VSe", "deepTau2026v2p5ForMini:VSmu"], + cutIDs_wp = [-1, -1, -1], # WP discriminator (disabled if < 0) + cutIDs_raw = [0.0, 0.0, 0.0], # raw discriminator value cuts (disabled if 0.0) + minDeltaR = 0.3, + outFolder = "Tau/TauValidation", + isPatTaus = True +) + +recoTauValidation_cutWPVsJet_0 = recoTauValidation.clone(cutIDs_wp = [0, -1, -1]) +recoTauValidation_cutWPVsJet_1 = recoTauValidation.clone(cutIDs_wp = [1, -1, -1]) +recoTauValidation_cutWPVsJet_2 = recoTauValidation.clone(cutIDs_wp = [2, -1, -1]) +recoTauValidation_cutWPVsJet_3 = recoTauValidation.clone(cutIDs_wp = [3, -1, -1]) + +recoTauValidation_cutIdVsJet_0p5 = recoTauValidation.clone(cutIDs_raw = [0.5, -1, -1]) +recoTauValidation_cutIdVsJet_0p7 = recoTauValidation.clone(cutIDs_raw = [0.7, -1, -1]) +recoTauValidation_cutIdVsJet_0p9 = recoTauValidation.clone(cutIDs_raw = [0.9, -1, -1]) +recoTauValidation_cutIdVsJet_0p95 = recoTauValidation.clone(cutIDs_raw = [0.95, -1, -1]) +recoTauValidation_cutIdVsJet_0p99 = recoTauValidation.clone(cutIDs_raw = [0.99, -1, -1]) + +recoTauValidation_deltaR0p3 = recoTauValidation.clone( + minDeltaR = 0.3, + outFolder = "Tau/TauValidation_DeltaR/DeltaR0p3", +) + +recoTauValidation_deltaR0p25 = recoTauValidation.clone( + minDeltaR = 0.25, + outFolder = "Tau/TauValidation_DeltaR/DeltaR0p25", +) + +recoTauValidation_deltaR0p2 = recoTauValidation.clone( + minDeltaR = 0.2, + outFolder = "Tau/TauValidation_DeltaR/DeltaR0p2", +) + +recoTauValidation_deltaR0p15 = recoTauValidation.clone( + minDeltaR = 0.15, + outFolder = "Tau/TauValidation_DeltaR/DeltaR0p15", +) + +recoTauValidation_deltaR0p1 = recoTauValidation.clone( + minDeltaR = 0.1, + outFolder = "Tau/TauValidation_DeltaR/DeltaR0p1", +) + +recoTauValidation_deltaR = cms.Sequence( + recoTauValidation_deltaR0p3 + + recoTauValidation_deltaR0p25 + + recoTauValidation_deltaR0p2 + + recoTauValidation_deltaR0p15 + + recoTauValidation_deltaR0p1 +) + +recoTauValidation_wp = cms.Sequence( + recoTauValidation_cutWPVsJet_0 + + recoTauValidation_cutWPVsJet_1 + + recoTauValidation_cutWPVsJet_2 + + recoTauValidation_cutWPVsJet_3 +) + +recoTauValidation_id = cms.Sequence( + recoTauValidation_cutIdVsJet_0p5 + + recoTauValidation_cutIdVsJet_0p7 + + recoTauValidation_cutIdVsJet_0p9 + + recoTauValidation_cutIdVsJet_0p95 + + recoTauValidation_cutIdVsJet_0p99 +) + + +recoTauValidationSequence = cms.Sequence( + recoTauValidation + # WP scanning + + recoTauValidation_wp + # ID scanning + + recoTauValidation_id + # DeltaR scanning + # + recoTauValidation_deltaR +) + +# Old Run-3 validation, not maintained for Phase-2 + from Validation.RecoTau.dataTypes.ValidateTausOnRealData_cff import * from Validation.RecoTau.dataTypes.ValidateTausOnRealElectronsData_cff import * from Validation.RecoTau.dataTypes.ValidateTausOnRealMuonsData_cff import * diff --git a/Validation/RecoTau/python/hltTauPostProcessor_cff.py b/Validation/RecoTau/python/hltTauPostProcessor_cff.py new file mode 100644 index 0000000000000..9417704ef4ed4 --- /dev/null +++ b/Validation/RecoTau/python/hltTauPostProcessor_cff.py @@ -0,0 +1,101 @@ +import FWCore.ParameterSet.Config as cms +from DQMServices.Core.DQMEDHarvester import DQMEDHarvester + +hltTauPostProcessor = DQMEDHarvester("DQMGenericClient", + subDirs=cms.untracked.vstring("HLT/Tau/TauValidation/", + "HLT/Tau/TauValidation/Cut*", + # "HLT/Tau/TauValidation_DeltaR/DeltaR*", + # "HLT/Tau/TauValidation_DeltaR/DeltaR*/Cut*", + ), + efficiency = cms.vstring( + # Efficiency + "Eff_vs_pt_eta 'Efficiency vs p_{T},#eta' genTauMatched_pt_eta genTau_pt_eta", + "Eff_vs_pt_phi 'Efficiency vs p_{T},#phi' genTauMatched_pt_phi genTau_pt_phi", + "Eff_vs_pt_mass 'Efficiency vs p_{T},mass' genTauMatched_pt_mass genTau_pt_mass", + "Eff_vs_mass_eta 'Efficiency vs mass,#eta' genTauMatched_mass_eta genTau_mass_eta", + "Eff_vs_mass_phi 'Efficiency vs mass,#phi' genTauMatched_mass_phi genTau_mass_phi", + # Fake rate + "Fake_vs_pt_eta 'Fake Rate vs p_{T},#eta' recoTauMatched_pt_eta recoTau_pt_eta fake", + "Fake_vs_pt_phi 'Fake Rate vs p_{T},#phi' recoTauMatched_pt_phi recoTau_pt_phi fake", + "Fake_vs_pt_mass 'Fake Rate vs p_{T},mass' recoTauMatched_pt_mass recoTau_pt_mass fake", + "Fake_vs_mass_eta 'Fake Rate vs mass,#eta' recoTauMatched_mass_eta recoTau_mass_eta fake", + "Fake_vs_mass_phi 'Fake Rate vs mass,#phi' recoTauMatched_mass_phi recoTau_mass_phi fake", + "Fake_vs_idVSjet_pt 'Fake Rate vs ID vs Jet,p_{T}' recoTauMatched_idVSjet_pt recoTau_idVSjet_pt fake", + "Fake_vs_idVSjet_eta 'Fake Rate vs ID vs Jet,#eta' recoTauMatched_idVSjet_eta recoTau_idVSjet_eta fake", + "Fake_vs_idVSjet_phi 'Fake Rate vs ID vs Jet,#phi' recoTauMatched_idVSjet_phi recoTau_idVSjet_phi fake", + "Fake_vs_idVSjet_mass 'Fake Rate vs ID vs Jet,mass' recoTauMatched_idVSjet_mass recoTau_idVSjet_mass fake", + "Fake_vs_idVSe_pt 'Fake Rate vs ID vs Electron,p_{T}' recoTauMatched_idVSe_pt recoTau_idVSe_pt fake", + "Fake_vs_idVSe_eta 'Fake Rate vs ID vs Electron,#eta' recoTauMatched_idVSe_eta recoTau_idVSe_eta fake", + "Fake_vs_idVSe_phi 'Fake Rate vs ID vs Electron,#phi' recoTauMatched_idVSe_phi recoTau_idVSe_phi fake", + "Fake_vs_idVSe_mass 'Fake Rate vs ID vs Electron,mass' recoTauMatched_idVSe_mass recoTau_idVSe_mass fake", + "Fake_vs_idVSmu_pt 'Fake Rate vs ID vs Muon,p_{T}' recoTauMatched_idVSmu_pt recoTau_idVSmu_pt fake", + "Fake_vs_idVSmu_eta 'Fake Rate vs ID vs Muon,#eta' recoTauMatched_idVSmu_eta recoTau_idVSmu_eta fake", + "Fake_vs_idVSmu_phi 'Fake Rate vs ID vs Muon,#phi' recoTauMatched_idVSmu_phi recoTau_idVSmu_phi fake", + "Fake_vs_idVSmu_mass 'Fake Rate vs ID vs Muon,mass' recoTauMatched_idVSmu_mass recoTau_idVSmu_mass fake", + # Split rate + "Split_vs_pt_eta 'Split Rate vs p_{T},#eta' genTauMultiMatched_pt_eta genTau_pt_eta", + "Split_vs_pt_phi 'Split Rate vs p_{T},#phi' genTauMultiMatched_pt_phi genTau_pt_phi", + "Split_vs_pt_mass 'Split Rate vs p_{T},mass' genTauMultiMatched_pt_mass genTau_pt_mass", + "Split_vs_mass_eta 'Split Rate vs mass,#eta' genTauMultiMatched_mass_eta genTau_mass_eta", + "Split_vs_mass_phi 'Split Rate vs mass,#phi' genTauMultiMatched_mass_phi genTau_mass_phi", + # Duplicate rate + "Dup_vs_pt_eta 'Duplicate Rate vs p_{T},#eta' recoTauMultiMatched_pt_eta recoTau_pt_eta", + "Dup_vs_pt_phi 'Duplicate Rate vs p_{T},#phi' recoTauMultiMatched_pt_phi recoTau_pt_phi", + "Dup_vs_pt_mass 'Duplicate Rate vs p_{T},mass' recoTauMultiMatched_pt_mass recoTau_pt_mass", + "Dup_vs_mass_eta 'Duplicate Rate vs mass,#eta' recoTauMultiMatched_mass_eta recoTau_mass_eta", + "Dup_vs_mass_phi 'Duplicate Rate vs mass,#phi' recoTauMultiMatched_mass_phi recoTau_mass_phi", + "Dup_vs_idVSjet_pt 'Duplicate Rate vs ID vs Jet,p_{T}' recoTauMultiMatched_idVSjet_pt recoTau_idVSjet_pt", + "Dup_vs_idVSjet_eta 'Duplicate Rate vs ID vs Jet,#eta' recoTauMultiMatched_idVSjet_eta recoTau_idVSjet_eta", + "Dup_vs_idVSjet_phi 'Duplicate Rate vs ID vs Jet,#phi' recoTauMultiMatched_idVSjet_phi recoTau_idVSjet_phi", + "Dup_vs_idVSjet_mass 'Duplicate Rate vs ID vs Jet,mass' recoTauMultiMatched_idVSjet_mass recoTau_idVSjet_mass", + "Dup_vs_idVSe_pt 'Duplicate Rate vs ID vs Electron,p_{T}' recoTauMultiMatched_idVSe_pt recoTau_idVSe_pt", + "Dup_vs_idVSe_eta 'Duplicate Rate vs ID vs Electron,#eta' recoTauMultiMatched_idVSe_eta recoTau_idVSe_eta", + "Dup_vs_idVSe_phi 'Duplicate Rate vs ID vs Electron,#phi' recoTauMultiMatched_idVSe_phi recoTau_idVSe_phi", + "Dup_vs_idVSe_mass 'Duplicate Rate vs ID vs Electron,mass' recoTauMultiMatched_idVSe_mass recoTau_idVSe_mass", + "Dup_vs_idVSmu_pt 'Duplicate Rate vs ID vs Muon,p_{T}' recoTauMultiMatched_idVSmu_pt recoTau_idVSmu_pt", + "Dup_vs_idVSmu_eta 'Duplicate Rate vs ID vs Muon,#eta' recoTauMultiMatched_idVSmu_eta recoTau_idVSmu_eta", + "Dup_vs_idVSmu_phi 'Duplicate Rate vs ID vs Muon,#phi' recoTauMultiMatched_idVSmu_phi recoTau_idVSmu_phi", + "Dup_vs_idVSmu_mass 'Duplicate Rate vs ID vs Muon,mass' recoTauMultiMatched_idVSmu_mass recoTau_idVSmu_mass", + ), + efficiencyProfile = cms.untracked.vstring( # for smoother rebinning + # Efficiency + "Eff_vs_eta 'Efficiency vs #eta' genTauMatched_eta genTau_eta", + "Eff_vs_phi 'Efficiency vs #phi' genTauMatched_phi genTau_phi", + "Eff_vs_pt 'Efficiency vs p_{T}' genTauMatched_pt genTau_pt", + "Eff_vs_mass 'Efficiency vs mass' genTauMatched_mass genTau_mass", + # Fake rate (note: the flag 'fake' coputes 1 - ratio only for the globalEfficiency histogram) + "Fake_vs_eta 'Fake Rate vs #eta' recoTauMatched_eta recoTau_eta fake", + "Fake_vs_phi 'Fake Rate vs #phi' recoTauMatched_phi recoTau_phi fake", + "Fake_vs_pt 'Fake Rate vs p_{T}' recoTauMatched_pt recoTau_pt fake", + "Fake_vs_mass 'Fake Rate vs mass' recoTauMatched_mass recoTau_mass fake", + "Fake_vs_idVSjet 'Fake Rate vs ID vs Jet' recoTauMatched_idVSjet recoTau_idVSjet fake", + "Fake_vs_idVSe 'Fake Rate vs ID vs E' recoTauMatched_idVSe recoTau_idVSe fake", + "Fake_vs_idVSmu 'Fake Rate vs ID vs Mu' recoTauMatched_idVSmu recoTau_idVSmu fake", + # Split rate + "Split_vs_eta 'Split Rate vs #eta' genTauMultiMatched_eta genTau_eta", + "Split_vs_phi 'Split Rate vs #phi' genTauMultiMatched_phi genTau_phi", + "Split_vs_pt 'Split Rate vs p_{T}' genTauMultiMatched_pt genTau_pt", + "Split_vs_mass 'Split Rate vs mass' genTauMultiMatched_mass genTau_mass", + # Duplicate rate + "Dup_vs_eta 'Duplicate Rate vs #eta' recoTauMultiMatched_eta recoTau_eta", + "Dup_vs_phi 'Duplicate Rate vs #phi' recoTauMultiMatched_phi recoTau_phi", + "Dup_vs_pt 'Duplicate Rate vs p_{T}' recoTauMultiMatched_pt recoTau_pt", + "Dup_vs_mass 'Duplicate Rate vs mass' recoTauMultiMatched_mass recoTau_mass", + "Dup_vs_idVSjet 'Duplicate Rate vs ID vs Jet' recoTauMultiMatched_idVSjet recoTau_idVSjet", + "Dup_vs_idVSe 'Duplicate Rate vs ID vs E' recoTauMultiMatched_idVSe recoTau_idVSe", + "Dup_vs_idVSmu 'Duplicate Rate vs ID vs Mu' recoTauMultiMatched_idVSmu recoTau_idVSmu", + ), + resolution = cms.vstring(), + resolutionProfile = cms.untracked.vstring( + "ResponsePt_RecoOverGen_vs_pt 'Response RecoOverGen vs p_{T}^{gen}' responsePt_pt rms", + "ResponsePt_RecoOverGen_vs_eta 'Response RecoOverGen vs #eta^{gen}' responsePt_eta rms", + "ResponsePt_RecoOverGen_vs_phi 'Response RecoOverGen vs #phi^{gen}' responsePt_phi rms", + "ResponsePt_RecoOverGen_vs_mass 'Response RecoOverGen vs mass^{gen}' responsePt_mass rms", + "ResponseMass_RecoOverGen_vs_pt 'Response RecoOverGen vs p_{T}^{gen}' responseMass_pt rms", + "ResponseMass_RecoOverGen_vs_eta 'Response RecoOverGen vs #eta^{gen}' responseMass_eta rms", + "ResponseMass_RecoOverGen_vs_phi 'Response RecoOverGen vs #phi^{gen}' responseMass_phi rms", + "ResponseMass_RecoOverGen_vs_mass 'Response RecoOverGen vs mass^{gen}' responseMass_mass rms", + ), + verbose = cms.untracked.uint32(2), + outputFileName = cms.untracked.string("") +) diff --git a/Validation/RecoTau/python/hltTauValidation_cff.py b/Validation/RecoTau/python/hltTauValidation_cff.py new file mode 100644 index 0000000000000..cb35d3f999c54 --- /dev/null +++ b/Validation/RecoTau/python/hltTauValidation_cff.py @@ -0,0 +1,83 @@ +import FWCore.ParameterSet.Config as cms + +from Validation.RecoTau.TauValidator import TauValidator as _TauValidator + +hltTauValidation = _TauValidator( + genTauCollection = "tauGenJetsSelectorAllHadrons", # only GenTaus decaying hadronically + recoTauCollection = "hltHpsPFTauProducer", + recoTauIDCollections = ["hltHpsPFTauDeepTauProducer:VSjet", "hltHpsPFTauDeepTauProducer:VSe", "hltHpsPFTauDeepTauProducer:VSmu"], + cutIDs_wp = [-1, -1, -1], # WP discriminator (disabled if < 0) + cutIDs_raw = [0.0, 0.0, 0.0], # raw discriminator value cuts (disabled if 0.0) + minDeltaR = 0.3, + outFolder = "HLT/Tau/TauValidation", + isPatTaus = False +) + +hltTauValidation_cutWPVsJet_0 = hltTauValidation.clone(cutIDs_wp = [0, -1, -1]) +hltTauValidation_cutWPVsJet_1 = hltTauValidation.clone(cutIDs_wp = [1, -1, -1]) +hltTauValidation_cutWPVsJet_2 = hltTauValidation.clone(cutIDs_wp = [2, -1, -1]) +hltTauValidation_cutWPVsJet_3 = hltTauValidation.clone(cutIDs_wp = [3, -1, -1]) + +hltTauValidation_cutIdVsJet_0p5 = hltTauValidation.clone(cutIDs_raw = [0.5, -1, -1]) +hltTauValidation_cutIdVsJet_0p7 = hltTauValidation.clone(cutIDs_raw = [0.7, -1, -1]) +hltTauValidation_cutIdVsJet_0p9 = hltTauValidation.clone(cutIDs_raw = [0.9, -1, -1]) +hltTauValidation_cutIdVsJet_0p95 = hltTauValidation.clone(cutIDs_raw = [0.95, -1, -1]) +hltTauValidation_cutIdVsJet_0p99 = hltTauValidation.clone(cutIDs_raw = [0.99, -1, -1]) + +hltTauValidation_deltaR0p3 = hltTauValidation.clone( + minDeltaR = 0.3, + outFolder = "HLT/Tau/TauValidation_DeltaR/DeltaR0p3", +) + +hltTauValidation_deltaR0p25 = hltTauValidation.clone( + minDeltaR = 0.25, + outFolder = "HLT/Tau/TauValidation_DeltaR/DeltaR0p25", +) + +hltTauValidation_deltaR0p2 = hltTauValidation.clone( + minDeltaR = 0.2, + outFolder = "HLT/Tau/TauValidation_DeltaR/DeltaR0p2", +) + +hltTauValidation_deltaR0p15 = hltTauValidation.clone( + minDeltaR = 0.15, + outFolder = "HLT/Tau/TauValidation_DeltaR/DeltaR0p15", +) + +hltTauValidation_deltaR0p1 = hltTauValidation.clone( + minDeltaR = 0.1, + outFolder = "HLT/Tau/TauValidation_DeltaR/DeltaR0p1", +) + +hltTauValidation_deltaR = cms.Sequence( + hltTauValidation_deltaR0p3 + + hltTauValidation_deltaR0p25 + + hltTauValidation_deltaR0p2 + + hltTauValidation_deltaR0p15 + + hltTauValidation_deltaR0p1 +) + +hltTauValidation_wp = cms.Sequence( + hltTauValidation_cutWPVsJet_0 + + hltTauValidation_cutWPVsJet_1 + + hltTauValidation_cutWPVsJet_2 + + hltTauValidation_cutWPVsJet_3 +) + +hltTauValidation_id = cms.Sequence( + hltTauValidation_cutIdVsJet_0p5 + + hltTauValidation_cutIdVsJet_0p7 + + hltTauValidation_cutIdVsJet_0p9 + + hltTauValidation_cutIdVsJet_0p95 + + hltTauValidation_cutIdVsJet_0p99 +) + +hltTauValidationSequence = cms.Sequence( + hltTauValidation + # WP scanning + + hltTauValidation_wp + # ID scanning + + hltTauValidation_id + # DeltaR scanning + # + hltTauValidation_deltaR +) diff --git a/Validation/RecoTau/scripts/dqm_plotting.py b/Validation/RecoTau/scripts/dqm_plotting.py new file mode 100644 index 0000000000000..9db3be3cb6d41 --- /dev/null +++ b/Validation/RecoTau/scripts/dqm_plotting.py @@ -0,0 +1,724 @@ +#!/usr/bin/env python3 + +import os, re +import ROOT +import array +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +from matplotlib import colors as _mcolors +import mplhep as hep + +# Set CMS style globally +plt.style.use(hep.style.CMS) + + +def split_csv(text): + return [x.strip() for x in text.split(",") if x.strip()] + + +def parse_limits(text): + if text is None: + return [None, None] + vals = [float(x.strip()) for x in text.split(",")] + if len(vals) != 2: + raise ValueError(f"Expected min,max but got: {text}") + return vals + + +def parse_rebin(text): + if text is None: + return None + if "," in text: + return [float(x.strip()) for x in text.split(",")] + return int(float(text)) + + +# From dqm-plot (class DQMPlotter is not importable) +class DQMPlotter: + def __init__(self, figsize=(10, 10), ratio_height=0.3): + """ + Initialize the plotter. + + Args: + figsize: Figure size (width, height) in inches + ratio_height: Fraction of figure height for ratio plot + """ + self.figsize = figsize + self.ratio_height = ratio_height + # Color palette from https://cms-analysis.docs.cern.ch/guidelines/plotting/colors/ + self.colors = [ + "#832db6", + "#3f8fda", + "#a96b59", + "#ffa90e", + "#e76300", + "#bd1f01", + "#b9ac70", + "#717581", + "#92dadd", + "#94a4a2", + ] + self.markers = [ + "o", # circle + "s", # square + "^", # triangle up + "D", # diamond + "v", # triangle down + "p", # pentagon + "*", # star + "h", # hexagon + "<", # triangle left + ">", # triangle right + ] + + def _clean_bin_label(self, label): + """Clean ROOT bin label.""" + return label.strip() if label else "" + + def _extend_color_palette(self, needed: int): + """Ensure self.colors has at least 'needed' distinct entries.""" + if needed <= len(self.colors): + return + + extra_needed = needed - len(self.colors) + new_colors = [] + + cmap = plt.colormaps["hsv"] + for i in range(max(extra_needed, 1)): + rgba = cmap(i / max(extra_needed, 1)) + hexcol = _mcolors.to_hex(rgba, keep_alpha=False) + if hexcol not in self.colors and hexcol not in new_colors: + new_colors.append(hexcol) + if len(new_colors) >= extra_needed: + break + + self.colors.extend(new_colors) + + def apply_rebin(self, hist, rebin, name): + if rebin is None: + return hist + + if isinstance(rebin, int): + h = hist.Rebin(rebin, name + "_rebin") + h.SetDirectory(0) + return h + + edges = array.array("d", rebin) + h = hist.Rebin(len(edges) - 1, name + "_rebin", edges) + h.SetDirectory(0) + return h + + def project_if_profile(self, hist, name): + if hist.InheritsFrom("TProfile"): + h = hist.ProjectionX(name + "_proj") + h.SetDirectory(0) + return h + return hist + + def load_hist(self, file_path, hist_name, rebin=None, project_profile=True, clone_suffix=""): + root_file = ROOT.TFile.Open(file_path, "READ") + + if not root_file or root_file.IsZombie(): + print(f"ERROR: could not open {file_path}") + return None + + hist = root_file.Get(hist_name) + + if not hist: + print(f"WARNING: histogram not found: {hist_name}") + root_file.Close() + return None + + name = hist_name.split("/")[-1] + clone_suffix + clone = hist.Clone(name + "_clone") + clone.SetDirectory(0) + + clone = self.apply_rebin(clone, rebin, name) + + if project_profile: + clone = self.project_if_profile(clone, name) + + root_file.Close() + return clone + + def invert_rate_hist(self, hist, empty_bins=None): + for ibin in range(1, hist.GetNbinsX() + 1): + val = hist.GetBinContent(ibin) + err = hist.GetBinError(ibin) + + # Empty original TProfile bin: no measurement + if empty_bins is not None and empty_bins[ibin - 1]: + hist.SetBinContent(ibin, 0.0) + hist.SetBinError(ibin, 0.0) + continue + + # Empty TH1-like bin: no measurement + if empty_bins is None and val == 0.0 and err == 0.0: + hist.SetBinContent(ibin, 0.0) + hist.SetBinError(ibin, 0.0) + continue + + hist.SetBinContent(ibin, 1.0 - val) + hist.SetBinError(ibin, err) + + def normalise(self, hist): + integral = hist.Integral() + if integral > 0: + hist.Scale(1.0 / integral) + + def root_to_numpy(self, hist): + """ + Convert ROOT histogram to numpy arrays. + + Args: + hist: ROOT histogram + + Returns: + tuple: (bin_centers, bin_contents, bin_errors, bin_edges, bin_labels, has_labels) + """ + n_bins = hist.GetNbinsX() + bin_edges = np.array([hist.GetBinLowEdge(i) for i in range(1, n_bins + 2)]) + bin_centers = np.array([hist.GetBinCenter(i) for i in range(1, n_bins + 1)]) + bin_contents = np.array([hist.GetBinContent(i) for i in range(1, n_bins + 1)]) + bin_errors = np.array([hist.GetBinError(i) for i in range(1, n_bins + 1)]) + + bin_labels = [] + for i in range(1, n_bins + 1): + label = hist.GetXaxis().GetBinLabel(i) + bin_labels.append(self._clean_bin_label(label) if label else "") + + # Only use extracted labels if meaningful + has_labels = any(label and not label.isdigit() for label in bin_labels) + + return bin_centers, bin_contents, bin_errors, bin_edges, bin_labels, has_labels + + def extract_labels_from_hist(self, hist): + """ + Extract title and axis labels from ROOT histogram. + + Args: + hist: ROOT histogram + + Returns: + tuple: (title, xlabel, ylabel) + """ + title = hist.GetTitle() + xlabel = title + ylabel = "Occurrences" + + # Match "vs", "vs.", and flexible spacing/periods between v and s; allow underscores or spaces as delimiters + vs_regex = re.compile(r'[_\s]+v\s*\.?\s*s\s*\.?[_\s]+', re.IGNORECASE) + if vs_regex.search(title): + # Detect explicit underscore-delimited form even with optional dots/spaces + used_underscore_delim = bool(re.search(r'_v\s*\.?\s*s\s*\.?_', title.lower())) + parts = vs_regex.split(title, maxsplit=1) + left, right = parts[0].strip(), parts[1].strip() + + if used_underscore_delim: + left = left.replace("_", " ") + right = right.replace("_", " ") + + if "#sigma(" in title.lower(): + core = left[left.find("(") + 1 : left.rfind(")")] + ylabel = r"$\delta$" + core + "/" + core + right_clean = right + if "Mean" in title: + ylabel = "<" + ylabel + ">" + right_clean = right_clean.replace("Mean", "") + elif "Sigma" in title: + ylabel = r"$\sigma$(" + ylabel + ")" + right_clean = right_clean.replace("Sigma", "") + xlabel = right_clean.strip() + elif "Mean" in title: + right_clean = right.replace("Mean", "").strip() + ylabel = "<" + left + ">" + xlabel = right_clean + elif "Sigma" in title: + right_clean = right.replace("Sigma", "").strip() + ylabel = r"$\sigma$<" + left + ">" + xlabel = right_clean + else: + # Default: "ylabel vs xlabel" + ylabel = left + xlabel = right + if hist.InheritsFrom("TProfile") and "mean " in ylabel: + ylabel = ylabel.replace("mean ", "<") + ">" + else: + # Pull plots + if "pull" not in title.lower(): + if "eta" in title.lower(): + xlabel = r"$\eta$" + elif "pt2" in title.lower(): + xlabel = r"$p_{\mathrm{T}}^2$" + elif "pt" in title.lower(): + xlabel = r"$p_{\mathrm{T}}$" + elif "phi" in title.lower(): + xlabel = r"$\phi$" + # Efficiency and turn-on plots + if "eff" in title.lower(): + ylabel = "Efficiency" + elif "fake" in title.lower(): + ylabel = "Fake rate" + elif "dup" in title.lower(): + ylabel = "Duplicate rate" + elif "split" in title.lower(): + ylabel = "Split rate" + elif "turn-on" in title.lower(): + ylabel = "Turn-On" + + return title, xlabel, ylabel + + def _plot_histogram_data(self, ax, bin_centers, bin_contents, bin_errors, bin_edges, label, color_idx): + """Plot histogram data either as histogram or error bars.""" + hep.histplot( + bin_contents, + bins=bin_edges, + yerr=bin_errors, + label=label, + color=self.colors[color_idx % len(self.colors)], + histtype="step", + linewidth=2, + ax=ax, + ) + ax.errorbar( + bin_centers, + bin_contents, + yerr=bin_errors, + label=label, + color=self.colors[color_idx % len(self.colors)], + fmt=self.markers[color_idx % len(self.markers)], + markersize=5, + capsize=2, + linewidth=1.5, + ) + + def _calculate_and_plot_ratio(self, ax_ratio, bin_edges, bin_centers, bin_contents, bin_errors, ref_centers, ref_contents, ref_errors, color_idx): + """Calculate and plot ratio between current and reference histogram.""" + if ax_ratio is None: + return [] + + tolerance = 1e-6 + matching_indices = [] + + for idx, center in enumerate(bin_centers): + ref_idx = np.argmin(np.abs(ref_centers - center)) + if np.abs(ref_centers[ref_idx] - center) < tolerance: + matching_indices.append((idx, ref_idx)) + + if not matching_indices: + return [] + + curr_idxs, ref_idxs = zip(*matching_indices) + matching_centers = bin_centers[list(curr_idxs)] + matching_ref_contents = ref_contents[list(ref_idxs)] + matching_contents = bin_contents[list(curr_idxs)] + matching_ref_errors = ref_errors[list(ref_idxs)] + matching_errors = bin_errors[list(curr_idxs)] + + ratio = np.divide(matching_contents, matching_ref_contents, out=np.zeros_like(matching_contents), where=matching_ref_contents != 0) + + ratio_errors = np.zeros_like(ratio) + mask = (matching_ref_contents != 0) & (matching_contents != 0) + ratio_errors[mask] = np.abs(ratio[mask]) * np.sqrt( + np.power(matching_errors[mask] / np.maximum(matching_contents[mask], 1e-10), 2) + + np.power(matching_ref_errors[mask] / np.maximum(matching_ref_contents[mask], 1e-10), 2) + ) + ratio_errors = np.nan_to_num(ratio_errors, nan=0.0, posinf=0.0, neginf=0.0) + + curr_idxs = np.array(curr_idxs) + + # build correct edges from selected bins + matching_edges = bin_edges[np.concatenate([curr_idxs, [curr_idxs[-1] + 1]])] + + ax_ratio.step( + matching_edges, + np.r_[ratio, ratio[-1]], # extend last value for step plot + where="post", + color=self.colors[color_idx % len(self.colors)], + linewidth=2, + label="ratio", + ) + + ax_ratio.errorbar( + matching_centers, + ratio, + yerr=ratio_errors, + color=self.colors[color_idx % len(self.colors)], + fmt=self.markers[color_idx % len(self.markers)], + markersize=5, + capsize=2, + linewidth=1.5, + ) + + return ratio[(ratio > 0) & np.isfinite(ratio)] + + def _wrap_legend_labels(self, labels, width=25): + """Soft-wrap legend labels at natural break points to reduce horizontal size.""" + wrapped = [] + for lab in labels: + # Explicit new lines + if "\\n" in lab: + wrapped.append("\n".join(lab.split("\\n"))) + continue + + # Preserve " - " separator (for overlay labels like "File - Collection") + if " - " in lab: + parts = lab.split(" - ", 1) # Split only on first occurrence + file_part = parts[0] + collection_part = parts[1] if len(parts) > 1 else "" + + # If the combined length is too long, put on separate lines + wrapped.append(f"{file_part}\n- {collection_part}" if len(lab) > width else lab) + continue + + # Automatic split using / or _ + parts = re.split(r'(/|_)+', lab) + tokens = [] + buffer = "" + for p in parts: + if not p: + continue + candidate = (buffer + p) if buffer else p + if len(candidate) > width and buffer: + tokens.append(buffer.rstrip("_/")) + buffer = p + else: + buffer = candidate + if buffer: + tokens.append(buffer.rstrip("_/")) + + # CamelCase and digit splitting + final_tokens = [] + for tok in tokens: + if len(tok) > width: + subtoks = re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?![a-z])|\d+', tok) + line = "" + for st in subtoks: + if len(line) + len(st) + 1 > width and line: + final_tokens.append(line) + line = st + else: + line = (line + st) if not line else (line + st) + if line: + final_tokens.append(line) + else: + final_tokens.append(tok) + + wrapped.append("\n".join(final_tokens) if final_tokens else lab) + + return wrapped + + def _configure_legend(self, ax, labels, legend_title, place_outside=False): + """Configure legend; wrap long entries and move outside if needed.""" + if not labels: + return + + wrapped_labels = self._wrap_legend_labels(labels) + + legend_columns = len(wrapped_labels) if len(wrapped_labels) <= 3 else 3 + legend_fontsize = "20" + if len(wrapped_labels) > 3: + legend_fontsize = "18" + if len(wrapped_labels) > 10: + legend_fontsize = "16" + + if place_outside: + y_min, y_max = ax.get_ylim() + y_range = y_max - y_min + ax.set_ylim(y_min, y_max + y_range * 0.1) + ax.figure.subplots_adjust(right=0.9) + ax.legend(wrapped_labels, loc="upper left", bbox_to_anchor=(1.01, 1.0), borderaxespad=0.0, title=legend_title, fontsize=legend_fontsize, title_fontsize=legend_fontsize, frameon=False) + return + + ax.legend(wrapped_labels, loc="upper center", ncols=legend_columns, title=legend_title, fontsize=legend_fontsize, title_fontsize=legend_fontsize, columnspacing=1.0, frameon=False) + + def _apply_custom_formatter(self, ax): + """Apply custom scientific notation formatter to y-axis.""" + if ax.get_yscale() != "log": + from matplotlib.ticker import ScalarFormatter + + class CustomScalarFormatter(ScalarFormatter): + def format_data_short(self, value): + if self.orderOfMagnitude != 0: + return f"×10$^{{{self.orderOfMagnitude}}}$" + return "" + + formatter = CustomScalarFormatter(useOffset=True, useMathText=True) + ax.yaxis.set_major_formatter(formatter) + + # Move y-axis scientific notation to avoid overlap with CMS label + ax.yaxis.get_offset_text().set_position((-0.01, 1.02)) + ax.yaxis.get_offset_text().set_horizontalalignment("right") + ax.yaxis.get_offset_text().set_verticalalignment("bottom") + + def plot_comparison(self, histograms, labels, output_path, + x_lim=[None, None], y_lim=[None, None], y_lim_ratio=[None, None], + xlabel=None, ylabel=None, leg_title="", logy=False, logx=False, cms_text="Preliminary", energy_text=""): + """ + Create comparison plot with ratio panel. + + Args: + histograms: List of ROOT histograms to compare + labels: List of labels for each histogram + output_path: Output file path + cms_text: CMS label text + energy_text: Custom energy text (if None, uses default) + """ + if len(histograms) == 0: + raise RuntimeError("No histograms to plot.") + if len(histograms) != len(labels): + raise ValueError("Number of histograms must match number of labels") + + self._extend_color_palette(len(histograms)) + + if xlabel is None or ylabel is None: + _, auto_x, auto_y = self.extract_labels_from_hist(histograms[0]) + xlabel = xlabel or auto_x + ylabel = ylabel or auto_y + + fig = plt.figure(figsize=self.figsize) + gs = gridspec.GridSpec(2, 1, height_ratios=[1 - self.ratio_height, self.ratio_height], hspace=0.10) + + ax_main = fig.add_subplot(gs[0]) + + # CMS styling + hep.cms.label(cms_text, data=False, ax=ax_main, rlabel=energy_text or "", fontsize=20) + + ax_ratio = fig.add_subplot(gs[1], sharex=ax_main) + + ref_centers = None + ref_contents = None + ref_errors = None + ref_labels = None + has_bin_labels = False + + all_ratios = [] + main_upper = [] + all_ratio_upper = [] + all_ratio_lower = [] + + for i, (hist, label) in enumerate(zip(histograms, labels)): + bin_centers, bin_contents, bin_errors, bin_edges, bin_labels, has_labels = self.root_to_numpy(hist) + + main_upper.extend(bin_contents + bin_errors) + + if i == 0: + ref_centers = bin_centers + ref_contents = bin_contents + ref_errors = bin_errors + ref_labels = bin_labels + has_bin_labels = has_labels + + self._plot_histogram_data(ax_main, bin_centers, bin_contents, bin_errors, bin_edges, label, i) + + if i > 0: + valid_ratios = self._calculate_and_plot_ratio(ax_ratio, bin_edges, bin_centers, bin_contents, bin_errors, ref_centers, ref_contents, ref_errors, i) + all_ratios.extend(valid_ratios) + + valid = ref_contents != 0 + ratio_values = np.divide(bin_contents, ref_contents, out=np.zeros_like(bin_contents, dtype=float), where=valid) + ratio_errors = np.zeros_like(ratio_values, dtype=float) + ratio_errors[valid] = np.sqrt((bin_errors[valid] / ref_contents[valid]) ** 2 + ((bin_contents[valid] / ref_contents[valid]) * (ref_errors[valid] / ref_contents[valid])) ** 2) + all_ratio_upper.extend(ratio_values[valid] + ratio_errors[valid]) + all_ratio_lower.extend(ratio_values[valid] - ratio_errors[valid]) + + # Set custom labels if available + if has_bin_labels: + ax_main.set_xticks(ref_centers) + ax_main.set_xticklabels(ref_labels, size="small" if len(ref_labels) < 10 else "xx-small", rotation=45, ha="right", va="top") + ax_main.tick_params(axis="x", which="minor", bottom=False) + else: + ax_main.set_xlabel(rf"{xlabel}", fontsize=20) + + ax_main.set_ylabel(rf"{ylabel}", fontsize=20) + + if x_lim[0] is not None and x_lim[1] is not None: + ax_main.set_xlim(x_lim) + + if y_lim[0] is not None and y_lim[1] is not None: + ax_main.set_ylim(y_lim) + else: + ymax = 1.6 * np.max(main_upper) if len(main_upper) and np.max(main_upper) > 0 else 1.0 + ymin = max(1e-3, 0.5 * np.min([x for x in main_upper if x > 0])) if logy and any(x > 0 for x in main_upper) else 0.0 + ax_main.set_ylim(ymin, ymax) + + if logy: + ax_main.set_yscale("log") + if logx: + ax_main.set_xscale("log") + + self._configure_legend(ax_main, labels, leg_title) + + ax_main.grid(True, alpha=0.75, linestyle="dashdot", linewidth=0.75) + + self._apply_custom_formatter(ax_main) + + # Ratio plot styling + ax_main.set_xlabel("") + ax_main.tick_params(axis="x", labelbottom=False) + + # Set ratio plot limits + if y_lim_ratio[0] is not None and y_lim_ratio[1] is not None: + ax_ratio.set_ylim(y_lim_ratio) + else: + ratio_min = np.min(all_ratio_lower) if len(all_ratio_lower) else 0.0 + ratio_max = np.max(all_ratio_upper) if len(all_ratio_upper) else 2.0 + span = ratio_max - ratio_min + scale = max(abs(ratio_min), abs(ratio_max), 1.0) + pad = max(0.20 * span, 0.05 * scale**0.5) + if span == 0: + pad = 0.10 * scale**0.5 + ax_ratio.set_ylim(ratio_min - pad, ratio_max + pad) + + if has_bin_labels: + ax_ratio.set_xticks(ref_centers) + ax_ratio.set_xticklabels(ref_labels, size="small" if len(ref_labels) < 10 else "xx-small", rotation=45, ha="right", va="top") + ax_ratio.tick_params(axis="x", which="minor", bottom=False) + else: + ax_ratio.set_xlabel(xlabel, fontsize=20) + + ax_ratio.set_ylabel(f"Ratio wrt {labels[0]}", fontsize=20) + ax_ratio.axhline(y=1, color="black", linestyle="--", alpha=0.7) + ax_ratio.grid(True, alpha=0.75, linestyle="dashdot", linewidth=0.75) + + outdir = os.path.dirname(output_path) + if outdir: + os.makedirs(outdir, exist_ok=True) + + print(output_path) + plt.savefig(output_path, dpi=300, bbox_inches="tight") + + pdf_path = output_path.rsplit(".", 1)[0] + ".pdf" + print(pdf_path) + plt.savefig(pdf_path, dpi=300, bbox_inches="tight") + + plt.close() + + def make_sigma_over_mean_hist(self, sigma_profile, mean_profile, name): + sigma = self.project_if_profile(sigma_profile, name + "_sigma") + mean = self.project_if_profile(mean_profile, name + "_mean") + out = sigma.Clone(name) + out.Reset("ICES") + out.SetDirectory(0) + for ibin in range(1, sigma.GetNbinsX() + 1): + s = sigma.GetBinContent(ibin) + m = mean.GetBinContent(ibin) + s_err = sigma.GetBinError(ibin) + m_err = mean.GetBinError(ibin) + if m == 0: # undefined ratio + out.SetBinContent(ibin, 0.0) + out.SetBinError(ibin, 0.0) + continue + value = s / m + err = np.sqrt((s_err / m) ** 2 + ((s / m) * (m_err / m)) ** 2) + out.SetBinContent(ibin, value) + out.SetBinError(ibin, err) + return out + + def plot_counts_and_rate(self, denominator, numerator, rate, output_path, denominator_label, numerator_label, rate_label, xlabel, ylabel_rate, cms_text="Preliminary", energy_text="", xlim=(None, None), right_ylim=(0.0, 1.25), right_log=False, text=None, leg_title=""): + centres, rate_values, rate_errors, edges, _, _ = self.root_to_numpy(rate) + _, den_values, den_errors, _, _, _ = self.root_to_numpy(denominator) + _, num_values, num_errors, _, _, _ = self.root_to_numpy(numerator) + + widths = np.diff(edges) + + fig, ax = plt.subplots(figsize=self.figsize) + + hep.cms.label(cms_text, data=False, ax=ax, rlabel=energy_text or "", fontsize=20) + + den_step = np.r_[den_values, den_values[-1]] + num_step = np.r_[num_values, num_values[-1]] + ax.step(edges, den_step, where="post", label=denominator_label, color="black", linewidth=2) + ax.step(edges, num_step, where="post", label=numerator_label, color="#9c9ca1", linestyle="-.", linewidth=2) + ax.fill_between(edges, num_step, step="post", alpha=0.3, color="#9c9ca1") + + # print("last den:", den_values[-1], "last num:", num_values[-1], "last rate:", rate_values[-1], "last edges:", edges[-2], edges[-1]) + + # Set an automatic ymax based on the max value + the up error + counts_upper = np.concatenate([den_values + den_errors, num_values + num_errors]) + ymax = 1.2 * np.max(counts_upper) if len(counts_upper) and np.max(counts_upper) > 0 else 1.0 + rate_upper = rate_values + rate_errors + rate_ymax = 1.25 * np.max(rate_upper) if len(rate_upper) and np.max(rate_upper) > 0 else right_ylim[1] + + if xlim[0] is not None and xlim[1] is not None: + ax.set_xlim(xlim) + ax.set_ylim(0.0, ymax) + ax.set_xlabel(xlabel, fontsize=20) + ax.set_ylabel("Entries", fontsize=20) + leg = ax.legend(loc="upper left", frameon=False, fontsize=18, title=leg_title, title_fontsize=18, + borderaxespad=0.5, handletextpad=0.8) + leg._legend_box.align = "left" + leg.get_title().set_ha("left") + + ax2 = ax.twinx() + ax2.set_ylabel(ylabel_rate, color="#bd1f01", fontsize=20) + if right_ylim[0] is not None and right_ylim[1] is not None: + ax2.set_ylim(right_ylim) + else: + ax2.set_ylim(0.0, rate_ymax) + + if right_log: + ax2.set_yscale("log") + + ax2.errorbar(centres, rate_values, xerr=0.5 * widths, yerr=rate_errors, fmt="o", color="#bd1f01", capsize=2, linewidth=1.5, label=rate_label) + # ax2.axhline(y=1.0, color="#bd1f01", linewidth=2, linestyle="--", alpha=0.7) + + ax.grid(True, axis="x", alpha=0.7, linestyle="dashdot") + ax2.grid(True, axis="y", alpha=0.7, linestyle="dashdot") + ax2.tick_params(axis="y", labelcolor="#bd1f01") + + if text: + ax.text(0.97, 0.97, text, transform=ax.transAxes, ha="right", va="top", fontsize=18) + + outdir = os.path.dirname(output_path) + if outdir: + os.makedirs(outdir, exist_ok=True) + + print(output_path) + plt.savefig(output_path, dpi=300, bbox_inches="tight") + + pdf_path = output_path.rsplit(".", 1)[0] + ".pdf" + print(pdf_path) + plt.savefig(pdf_path, dpi=300, bbox_inches="tight") + + plt.close() + + +_default_plotter = DQMPlotter() + + +def apply_rebin(hist, rebin, name): + return _default_plotter.apply_rebin(hist, rebin, name) + + +def project_if_profile(hist, name): + return _default_plotter.project_if_profile(hist, name) + + +def load_hist(file_path, hist_name, rebin=None, project_profile=True, clone_suffix=""): + return _default_plotter.load_hist(file_path, hist_name, rebin, project_profile, clone_suffix) + + +def invert_rate_hist(hist, empty_bins=None): + return _default_plotter.invert_rate_hist(hist, empty_bins) + + +def normalise(hist): + return _default_plotter.normalise(hist) + + +def hist_to_numpy(hist): + return _default_plotter.root_to_numpy(hist) + + +def make_sigma_over_mean_hist(sigma_profile, mean_profile, name): + return _default_plotter.make_sigma_over_mean_hist(sigma_profile, mean_profile, name) + + +def plot_comparison(histograms, labels, output_path, xlabel=None, ylabel=None, xlim=None, ylim=None, ylim_ratio=None, leg_title="", logx=False, logy=False, cms_text="Preliminary", energy_text=""): + return _default_plotter.plot_comparison(histograms, labels, output_path, x_lim=xlim or [None, None], y_lim=ylim or [None, None], y_lim_ratio=ylim_ratio or [None, None], xlabel=xlabel, ylabel=ylabel, leg_title=leg_title, logx=logx, logy=logy, cms_text=cms_text, energy_text=energy_text) + + +def plot_counts_and_rate(denominator, numerator, rate, output_path, denominator_label, numerator_label, rate_label, xlabel, ylabel_rate, cms_text="Preliminary", energy_text="", xlim=(None, None), right_ylim=(0.0, 1.25), right_log=False, text=None, leg_title=""): + return _default_plotter.plot_counts_and_rate(denominator, numerator, rate, output_path, denominator_label, numerator_label, rate_label, xlabel, ylabel_rate, cms_text, energy_text, xlim, right_ylim, right_log, text, leg_title) diff --git a/Validation/RecoTau/scripts/makeComparisonPlots.py b/Validation/RecoTau/scripts/makeComparisonPlots.py new file mode 100755 index 0000000000000..e749be131fb47 --- /dev/null +++ b/Validation/RecoTau/scripts/makeComparisonPlots.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse + +from dqm_plotting import split_csv, parse_limits, parse_rebin, load_hist, invert_rate_hist, normalise, plot_comparison + + +def main(): + parser = argparse.ArgumentParser(description="Make comparison plots.") + + parser.add_argument("--files", type=str, required=True, help="Comma-separated list of DQM ROOT files.") + parser.add_argument("--hists", type=str, required=True, help="Comma-separated list of histogram names.") + parser.add_argument("--labels", type=str, required=True, help="Comma-separated list of legend labels.") + parser.add_argument("--odir", type=str, default="ComparisonPlots", help="Path to the output directory.") + parser.add_argument("--name", type=str, default=None, help="Name of the output plot.") + parser.add_argument("--rebin", type=str, default=None, help="Rebin for histograms, factor or vector.") + parser.add_argument("--normalize", action="store_true", help="Normalize histograms to unit area.") + parser.add_argument("--xlim", type=str, default=None, help="X-axis limits, min,max.") + parser.add_argument("--ylim", type=str, default=None, help="Y-axis limits, min,max.") + parser.add_argument("--ylim-ratio", type=str, default=None, help="Y-axis limits for ratio plot, min,max.") + parser.add_argument("--leg-title", type=str, default="", help="Title for the legend.") + parser.add_argument("--logy", action="store_true", help="Use logarithmic scale for y-axis.") + parser.add_argument("--logx", action="store_true", help="Use logarithmic scale for x-axis.") + parser.add_argument("--xlabel", type=str, default=None, help="Custom x-axis label.") + parser.add_argument("--ylabel", type=str, default=None, help="Custom y-axis label.") + parser.add_argument("--cms-text", type=str, default="Preliminary", help="CMS label text.") + parser.add_argument("--energy-text", type=str, default=None, help="Custom energy text for CMS label.") + parser.add_argument("--inverted", action="store_true", help="Invert histograms. E.g. Purity -> Fake Rates.") + + args = parser.parse_args() + + files = split_csv(args.files) + hists = split_csv(args.hists) + labels = split_csv(args.labels) + + if not (len(files) == len(hists) == len(labels)): + print("ERROR: --files, --hists and --labels must have the same length.") + print(f"files : {len(files)}") + print(f"hists : {len(hists)}") + print(f"labels: {len(labels)}") + sys.exit(1) + + rebin = parse_rebin(args.rebin) + + histograms = [] + good_labels = [] + + for i, (file_path, hist_name, label) in enumerate(zip(files, hists, labels)): + h = load_hist(file_path=file_path, hist_name=hist_name, rebin=rebin, project_profile=False, clone_suffix=f"_{i}") + + if h is None: + continue + + empty_bins = None + if h.InheritsFrom("TProfile"): + empty_bins = [ h.GetBinEntries(ibin) == 0 for ibin in range(1, h.GetNbinsX() + 1) ] + + h = h.ProjectionX(h.GetName() + "_proj") if h.InheritsFrom("TProfile") else h + h.SetDirectory(0) + + if args.inverted: + invert_rate_hist(h, empty_bins=empty_bins) + + if args.normalize: + normalise(h) + + histograms.append(h) + good_labels.append(label) + + if not histograms: + print("ERROR: no valid histograms were loaded.") + sys.exit(1) + + plot_name = args.name or hists[0].split("/")[-1] + output_path = os.path.join(args.odir, plot_name + ".png") + + plot_comparison( + histograms=histograms, + labels=good_labels, + output_path=output_path, + xlabel=args.xlabel, + ylabel=args.ylabel, + xlim=parse_limits(args.xlim), + ylim=parse_limits(args.ylim), + ylim_ratio=parse_limits(args.ylim_ratio), + leg_title=args.leg_title, + logx=args.logx, + logy=args.logy, + cms_text=args.cms_text, + energy_text=args.energy_text, + ) + + +if __name__ == "__main__": + main() diff --git a/Validation/RecoTau/scripts/makeTauValidationPlots.py b/Validation/RecoTau/scripts/makeTauValidationPlots.py new file mode 100644 index 0000000000000..4654001790fbc --- /dev/null +++ b/Validation/RecoTau/scripts/makeTauValidationPlots.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse +import ROOT + +from dqm_plotting import split_csv, parse_limits, parse_rebin, load_hist, apply_rebin, plot_counts_and_rate, plot_comparison, make_sigma_over_mean_hist, invert_rate_hist + + +def load_root_object(file_path, hist_name, rebin=None, clone_suffix=""): + root_file = ROOT.TFile.Open(file_path, "READ") + + if not root_file or root_file.IsZombie(): + print(f"ERROR: could not open {file_path}") + return None + + obj = root_file.Get(hist_name) + + if not obj: + print(f"WARNING: histogram not found: {hist_name}") + root_file.Close() + return None + + name = hist_name.split("/")[-1] + clone_suffix + clone = obj.Clone(name + "_clone") + clone.SetDirectory(0) + + if rebin is not None: + clone = apply_rebin(clone, rebin, name) + + root_file.Close() + return clone + + +def make_summary_plot(args): + files = split_csv(args.files) + den_hists = split_csv(args.den_hists) + num_hists = split_csv(args.num_hists) + rate_hists = split_csv(args.rate_hists) + labels = split_csv(args.labels) + + if not (len(files) == len(den_hists) == len(num_hists) == len(rate_hists) == len(labels)): + print("ERROR: --files, --den-hists, --num-hists, --rate-hists and --labels must have the same length.") + print(f"files : {len(files)}") + print(f"den-hists : {len(den_hists)}") + print(f"num-hists : {len(num_hists)}") + print(f"rate-hists: {len(rate_hists)}") + print(f"labels : {len(labels)}") + return False + + rebin = parse_rebin(args.rebin) + + made_any = False + + for i, (file_path, den_name, num_name, rate_name, label) in enumerate(zip(files, den_hists, num_hists, rate_hists, labels)): + denominator = load_hist(file_path=file_path, hist_name=den_name, rebin=rebin, project_profile=True, clone_suffix=f"_den_{i}") + numerator = load_hist(file_path=file_path, hist_name=num_name, rebin=rebin, project_profile=True, clone_suffix=f"_num_{i}") + rate = load_hist(file_path=file_path, hist_name=rate_name, rebin=rebin, project_profile=True, clone_suffix=f"_rate_{i}") + + if denominator is None or numerator is None or rate is None: + continue + + empty_bins = None + if rate.InheritsFrom("TProfile"): empty_bins = [rate.GetBinEntries(ibin) == 0 for ibin in range(1, rate.GetNbinsX() + 1)] + + rate_inverted = rate.ProjectionX(rate.GetName() + "_proj") if rate.InheritsFrom("TProfile") else rate + rate_inverted.SetDirectory(0) + + if args.inverted: invert_rate_hist(rate_inverted, empty_bins=empty_bins) + + if len(labels) == 1: + output_name = args.name or rate_name.split("/")[-1] + else: + safe_label = label.replace("$", "").replace("\\", "").replace(" ", "_").replace("=", "").replace(".", "p") + output_name = args.name or f"{rate_name.split('/')[-1]}_{safe_label}" + + output_path = os.path.join(args.odir, output_name + ".png") + + plot_counts_and_rate( + denominator=denominator, + numerator=numerator, + rate=rate, + output_path=output_path, + denominator_label=args.den_label, + numerator_label=args.num_label, + rate_label=label, + xlabel=args.xlabel, + ylabel_rate=args.ylabel, + cms_text=args.cms_text, + energy_text=args.energy_text, + xlim=parse_limits(args.xlim), + right_ylim=parse_limits(args.ylim), + right_log=args.logy, + text=args.text, + leg_title=args.leg_title, + ) + + made_any = True + + return made_any + + +def make_response_plot(args): + files = split_csv(args.files) + mean_hists = split_csv(args.mean_hists) + sigma_hists = split_csv(args.sigma_hists) + labels = split_csv(args.labels) + + if not (len(files) == len(mean_hists) == len(sigma_hists) == len(labels)): + print("ERROR: --files, --mean-hists, --sigma-hists and --labels must have the same length.") + print(f"files : {len(files)}") + print(f"mean-hists : {len(mean_hists)}") + print(f"sigma-hists: {len(sigma_hists)}") + print(f"labels : {len(labels)}") + return False + + rebin = parse_rebin(args.rebin) + + histograms = [] + good_labels = [] + + for i, (file_path, mean_name, sigma_name, label) in enumerate(zip(files, mean_hists, sigma_hists, labels)): + mean = load_root_object(file_path=file_path, hist_name=mean_name, rebin=rebin, clone_suffix=f"_mean_{i}") + sigma = load_root_object(file_path=file_path, hist_name=sigma_name, rebin=rebin, clone_suffix=f"_sigma_{i}") + + if mean is None or sigma is None: + continue + + h = make_sigma_over_mean_hist(sigma, mean, f"sigmaOverMean_{i}") + h.SetTitle(args.title or "Response sigma/mean") + + histograms.append(h) + good_labels.append(label) + + if not histograms: + print("ERROR: no valid response histograms were loaded.") + return False + + output_name = args.name or "Response_sigmaOverMean" + output_path = os.path.join(args.odir, output_name + ".png") + + plot_comparison( + histograms=histograms, + labels=good_labels, + output_path=output_path, + xlabel=args.xlabel, + ylabel=args.ylabel, + xlim=parse_limits(args.xlim), + ylim=parse_limits(args.ylim), + ylim_ratio=parse_limits(args.ylim_ratio), + logx=args.logx, + logy=args.logy, + cms_text=args.cms_text, + energy_text=args.energy_text, + leg_title=args.leg_title, + ) + + return True + + +def main(): + parser = argparse.ArgumentParser(description="Make Tau validation plots.") + + parser.add_argument("--mode", type=str, choices=["summary", "response"], required=True, help="summary: denominator/numerator/rate plot. response: sigma/mean comparison plot.") + parser.add_argument("--files", type=str, required=True, help="Comma-separated list of input ROOT files.") + parser.add_argument("--labels", type=str, required=True, help="Comma-separated list of legend labels, one per input file.") + + # Summary mode + parser.add_argument("--den-hists", type=str, default=None, help="Comma-separated list of denominator histogram paths.") + parser.add_argument("--num-hists", type=str, default=None, help="Comma-separated list of numerator histogram paths.") + parser.add_argument("--rate-hists", type=str, default=None, help="Comma-separated list of precomputed rate histogram paths.") + parser.add_argument("--den-label", type=str, default="Denominator", help="Legend label for the denominator histogram.") + parser.add_argument("--num-label", type=str, default="Numerator", help="Legend label for the numerator histogram.") + parser.add_argument("--inverted", action="store_true", help="Invert histograms. E.g. Purity -> Fake Rates.") + + # Response mode + parser.add_argument("--mean-hists", type=str, default=None, help="Comma-separated list of mean TProfile paths.") + parser.add_argument("--sigma-hists", type=str, default=None, help="Comma-separated list of sigma TProfile paths.") + + # Common plotting options + parser.add_argument("--odir", type=str, default="TauValidationPlots", help="Output directory where plots will be saved.") + parser.add_argument("--name", type=str, default=None, help="Base name of the output plot. If omitted, a name is built automatically.") + parser.add_argument("--title", type=str, default=None, help="Optional plot title.") + parser.add_argument("--rebin", type=str, default=None, help="Histogram rebinning. Use an integer factor, e.g. '2', or comma-separated bin edges, e.g. '0,20,40,60,100'.") + parser.add_argument("--xlabel", type=str, required=True, help="X-axis label.") + parser.add_argument("--ylabel", type=str, required=True, help="Y-axis label.") + parser.add_argument("--xlim", type=str, default=None, help="X-axis range as 'xmin,xmax', e.g. '-2.5,2.5'.") + parser.add_argument("--ylim", type=str, default=None, help="Y-axis range as 'ymin,ymax', e.g. '0,1.2'.") + parser.add_argument("--ylim-ratio", type=str, default=None, help="Ratio-panel y-axis range as 'ymin,ymax', e.g. '0.5,1.5'.") + parser.add_argument("--logx", action="store_true", help="Use a logarithmic scale for the x-axis.") + parser.add_argument("--logy", action="store_true", help="Use a logarithmic scale for the y-axis.") + parser.add_argument("--cms-text", type=str, default="Preliminary", help="CMS label text, e.g. 'Preliminary', 'Simulation' or an empty string.") + parser.add_argument("--energy-text", type=str, default="", help="Additional CMS energy/luminosity text.") + parser.add_argument("--text", type=str, default=None, help="Additional annotation text drawn on the plot.") + parser.add_argument("--leg-title", type=str, default="", help="Title for the legend.") + + args = parser.parse_args() + + os.makedirs(args.odir, exist_ok=True) + + if args.mode == "summary": + if args.den_hists is None or args.num_hists is None or args.rate_hists is None: + print("ERROR: summary mode requires --den-hists, --num-hists and --rate-hists.") + sys.exit(1) + ok = make_summary_plot(args) + + elif args.mode == "response": + if args.mean_hists is None or args.sigma_hists is None: + print("ERROR: response mode requires --mean-hists and --sigma-hists.") + sys.exit(1) + ok = make_response_plot(args) + + else: + print(f"ERROR: unknown mode {args.mode}") + sys.exit(1) + + if not ok: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/Validation/RecoTau/scripts/run_tau_comparison_plots.sh b/Validation/RecoTau/scripts/run_tau_comparison_plots.sh new file mode 100755 index 0000000000000..b87df23de23e2 --- /dev/null +++ b/Validation/RecoTau/scripts/run_tau_comparison_plots.sh @@ -0,0 +1,339 @@ +#!/usr/bin/env bash + +: <<'COMMENT' + +Usage: run_tau_comparison_plots.sh [ID|WP|DeltaR] [HLT|RECO] + + ID - scan on the ID raw values (default) + WP - scan on the working point + DeltaR - scan on the DeltaR matching + + HLT - use HLT DQM path and labels (default) + RECO - use RECO DQM path and labels + +It assumes input DQM file, for DeltaR comparison the user needs to activate the hlt/recoTauValidation_deltaR sequence (de-activated by default) +Command example for HLT validation to obtain the DQM file (based on CMSSW_16_1_0_pre4): + +cmsDriver.py step2 -s L1P2GT,HLT:75e33,VALIDATION:@hltValidation -n -1 --nThreads 0 \ + --conditions auto:phase2_realistic_T35 --datatier GEN-SIM-DIGI-RAW,DQMIO \ + --customise SLHCUpgradeSimulations/Configuration/aging.customise_aging_1000 --eventcontent FEVTDEBUGHLT,DQMIO \ + --geometry ExtendedRun4D110 --era Phase2C17I13M9 --hltProcess HLTX --processName HLTX \ + --filein file:/eos/cms/store/relval/CMSSW_16_1_0_pre2/RelValTenTau_15_500/GEN-SIM-DIGI-RAW/PU_150X_mcRun4_realistic_v1_STD_Run4D110_PU-v1/2590000/01cbb197-f9d0-48a5-a634-c985f3b66373.root --fileout file:step2.root \ + --inputCommands="keep *, drop *_hlt*_*_HLT, drop triggerTriggerFilterObjectWithRefs_l1t*_*_HLT" + +cmsDriver.py step3 -s HARVESTING:@hltValidation -n -1 \ + --conditions auto:phase2_realistic_T35 --mc --geometry ExtendedRun4D110 --era Phase2C17I13M9 \ + --filetype DQM --scenario pp --hltProcess HLTX --filein file:step2_inDQM.root --fileout file:step3.root +COMMENT + +set -u + +mode="ID" +if [ "$#" -gt 0 ]; then + mode="$1" +fi + +step="HLT" +if [ "$#" -gt 1 ]; then + step="$2" +fi + +mode_upper="${mode^^}" +case "$mode_upper" in + ID) + PREFIX="RawId" + SUFFIX="" + DIRS_LIST=( + "" + "CutID_VSjet0p50" + "CutID_VSjet0p70" + "CutID_VSjet0p90" + "CutID_VSjet0p95" + "CutID_VSjet0p99" + ) + LABELS_LIST=( + 'No ID' + 'IDvsJet > 0.50' + 'IDvsJet > 0.70' + 'IDvsJet > 0.90' + 'IDvsJet > 0.95' + 'IDvsJet > 0.99' + ) + ;; + WP) + PREFIX="WP" + SUFFIX="" + DIRS_LIST=( + "" + "CutWP_VSjet0" + "CutWP_VSjet1" + "CutWP_VSjet2" + "CutWP_VSjet3" + ) + LABELS_LIST=( + 'No ID' + 'WPvsJet > 0' + 'WPvsJet > 1' + 'WPvsJet > 2' + 'WPvsJet > 3' + ) + ;; + DELTAR) + PREFIX="DeltaR" + SUFFIX="_DeltaR" + DIRS_LIST=( + "DeltaR0p3" + "DeltaR0p25" + "DeltaR0p2" + "DeltaR0p15" + "DeltaR0p1" + ) + LABELS_LIST=( + '$\Delta R=0.3$' + '$\Delta R=0.25$' + '$\Delta R=0.2$' + '$\Delta R=0.15$' + '$\Delta R=0.1$' + ) + ;; + *) + echo "Invalid mode: $mode" + ;; +esac + +step_upper="${step^^}" +case "$step_upper" in + HLT) + STEP="HLT" + BASE_DIR="DQMData/Run 1/HLT/Run summary/Tau/TauValidation${SUFFIX}" + ;; + RECO) + STEP="Reco" + BASE_DIR="DQMData/Run 1/Tau/Run summary/TauValidation${SUFFIX}" + ;; + *) + echo "Invalid step: $step" + ;; +esac + +# Change label according to the input file used +ENERGY_TEXT="Ten Tau (200 PU) | 14 TeV" + +DQM_FILE="DQM_V0001_R000000001__Global__CMSSW_X_Y_Z__RECO.root" +SCRIPT_DIR="${CMSSW_BASE}/src/Validation/RecoTau/scripts" + +MAKE_COMPARISON="${SCRIPT_DIR}/makeComparisonPlots.py" +MAKE_TAU_VALIDATION="${SCRIPT_DIR}/makeTauValidationPlots.py" + +OUTDIR_COMPARISON="TauValidationPlots/Comparison_${mode_upper}_${step_upper}" + +PT_REBIN="0,5,10,20,30,40,50,60,70,80,90,100,120,140,160,180,200,220,240,260,280,300" +ETA_REBIN="-2.4,-2.0,-1.6,-1.2,-0.8,-0.4,0.0,0.4,0.8,1.2,1.6,2.0,2.4" +PHI_REBIN="-3.5,-2.8,-2.1,-1.4,-0.7,0.0,0.7,1.4,2.1,2.8,3.5" + +has_hist() { + local hist="$1" + rootls "$DQM_FILE:$hist" >/dev/null 2>&1 +} + +join_by_comma() { + local IFS="," + echo "$*" +} + +run_cmd() { + echo + echo "Running:" + echo "$*" + "$@" +} + +make_comparison_plot() { + local hist_base="$1" + local name="$2" + local xlabel="$3" + local ylabel="$4" + local xlim="$5" + local ylim="$6" + local ylim_ratio="$7" + local rebin="$8" + local inverted="${9:-0}" + + local files=() + local hists=() + local labels=() + + for i in "${!DIRS_LIST[@]}"; do + local hist="${BASE_DIR}/${DIRS_LIST[$i]}/${hist_base}" + + if has_hist "$hist"; then + files+=("$DQM_FILE") + hists+=("$hist") + labels+=("${LABELS_LIST[$i]}") + else + echo "Skipping missing histogram: $hist" + fi + done + + if [ "${#hists[@]}" -eq 0 ]; then + echo "No valid histograms for ${hist_base}. Skipping." + return + fi + + local cmd=( + python3 "$MAKE_COMPARISON" + --files "$(join_by_comma "${files[@]}")" + --hists "$(join_by_comma "${hists[@]}")" + --labels "$(join_by_comma "${labels[@]}")" + --xlabel "$xlabel" + --ylabel "$ylabel" + --leg-title "$STEP Tau Performance" + --energy-text "$ENERGY_TEXT" + --odir "$OUTDIR_COMPARISON" + --name "$name" + ) + + if [ -n "$xlim" ]; then + cmd+=("--xlim=${xlim}") + fi + + if [ -n "$ylim" ]; then + cmd+=("--ylim=${ylim}") + fi + + if [ -n "$ylim_ratio" ]; then + cmd+=("--ylim-ratio=${ylim_ratio}") + fi + + if [ -n "$rebin" ]; then + cmd+=("--rebin=${rebin}") + fi + + if [ "$inverted" -eq 1 ]; then + cmd+=(--inverted) + fi + + run_cmd "${cmd[@]}" +} + +mkdir -p "$OUTDIR_COMPARISON" + +echo "Input file:" +echo "$DQM_FILE" + +echo +echo "Making comparison plots" + +make_comparison_plot "Eff_vs_pt" "Eff_vs_pt_${PREFIX}_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "Efficiency" "0,300" "0,1.4" "0,2" "$PT_REBIN" +make_comparison_plot "Eff_vs_mass" "Eff_vs_mass_${PREFIX}_comparison" 'GenVis $\tau$ mass [GeV]' "Efficiency" "0,2" "0,1.4" "0,2" "2" +make_comparison_plot "Eff_vs_eta" "Eff_vs_eta_${PREFIX}_comparison" 'GenVis $\tau$ $\eta$' "Efficiency" "-2.5,2.5" "0,1.4" "0.5,1.5" "$ETA_REBIN" +make_comparison_plot "Eff_vs_phi" "Eff_vs_phi_${PREFIX}_comparison" 'GenVis $\tau$ $\phi$' "Efficiency" "" "0,1.4" "0.5,1.5" "$PHI_REBIN" + +make_comparison_plot "Fake_vs_pt" "Fake_vs_pt_${PREFIX}_comparison" '$\tau$ $p_T$ [GeV]' "Fake rate" "0,300" "0,1.4" "0.5,1.5" "$PT_REBIN" 1 +make_comparison_plot "Fake_vs_mass" "Fake_vs_mass_${PREFIX}_comparison" '$\tau$ mass [GeV]' "Fake rate" "0,2" "0,1.4" "0.5,1.5" "2" 1 +make_comparison_plot "Fake_vs_eta" "Fake_vs_eta_${PREFIX}_comparison" '$\tau$ $\eta$' "Fake rate" "-2.5,2.5" "0,1.4" "0.5,1.5" "$ETA_REBIN" 1 +make_comparison_plot "Fake_vs_phi" "Fake_vs_phi_${PREFIX}_comparison" '$\tau$ $\phi$' "Fake rate" "" "0,1.4" "0.5,1.5" "$PHI_REBIN" 1 + +make_comparison_plot "Dup_vs_pt" "Dup_vs_pt_${PREFIX}_comparison" '$\tau$ $p_T$ [GeV]' "Duplicate rate" "0,300" "0,1" "0,2" "$PT_REBIN" +make_comparison_plot "Dup_vs_mass" "Dup_vs_mass_${PREFIX}_comparison" '$\tau$ $mass$ [GeV]' "Duplicate rate" "0,2" "0,1" "0,2" "2" +make_comparison_plot "Dup_vs_eta" "Dup_vs_eta_${PREFIX}_comparison" '$\tau$ $\eta$' "Duplicate rate" "-2.5,2.5" "0,0.1" "0,2" "$ETA_REBIN" +make_comparison_plot "Dup_vs_phi" "Dup_vs_phi_${PREFIX}_comparison" '$\tau$ $\phi$' "Duplicate rate" "" "0,0.1" "0,2" "$PHI_REBIN" + +make_comparison_plot "Split_vs_pt" "Split_vs_pt_${PREFIX}_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "Split rate" "0,300" "0,1" "0,2" "$PT_REBIN" +make_comparison_plot "Split_vs_mass" "Split_vs_mass_${PREFIX}_comparison" 'GenVis $\tau$ $mass$ [GeV]' "Split rate" "0,2" "0,1" "0,2" "2" +make_comparison_plot "Split_vs_eta" "Split_vs_eta_${PREFIX}_comparison" 'GenVis $\tau$ $\eta$' "Split rate" "-2.5,2.5" "0,1" "0,2" "$ETA_REBIN" +make_comparison_plot "Split_vs_phi" "Split_vs_phi_${PREFIX}_comparison" 'GenVis $\tau$ $\phi$' "Split rate" "" "0,1" "0,2" "$PHI_REBIN" + +make_comparison_plot "ResponsePt_RecoOverGen_vs_pt_Mean" "ScalePt_vs_pt_${PREFIX}_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "0,300" "0,2" "0,2" "$PT_REBIN" +make_comparison_plot "ResponsePt_RecoOverGen_vs_mass_Mean" "ScalePt_vs_mass_${PREFIX}_comparison" 'GenVis $\tau$ $mass$ [GeV]' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "0,2" "0,2" "0,2" "2" +make_comparison_plot "ResponsePt_RecoOverGen_vs_eta_Mean" "ScalePt_vs_eta_${PREFIX}_comparison" 'GenVis $\tau$ $\eta$' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "-2.5,2.5" "0,2" "0,2" "$ETA_REBIN" +make_comparison_plot "ResponsePt_RecoOverGen_vs_phi_Mean" "ScalePt_vs_phi_${PREFIX}_comparison" 'GenVis $\tau$ $\phi$' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "" "0,2" "0,2" "$PHI_REBIN" + +make_comparison_plot "ResponseMass_RecoOverGen_vs_pt_Mean" "ScaleMass_vs_pt_${PREFIX}_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "$\langle m^{reco}/m^{gen} \rangle$" "0,300" "0,2" "0,2" "$PT_REBIN" +make_comparison_plot "ResponseMass_RecoOverGen_vs_mass_Mean" "ScaleMass_vs_mass_${PREFIX}_comparison" 'GenVis $\tau$ $mass$ [GeV]' "$\langle m^{reco}/m^{gen} \rangle$" "0,2" "0,2" "0,2" "2" +make_comparison_plot "ResponseMass_RecoOverGen_vs_eta_Mean" "ScaleMass_vs_eta_${PREFIX}_comparison" 'GenVis $\tau$ $\eta$' "$\langle m^{reco}/m^{gen} \rangle$" "-2.5,2.5" "0,2" "0,2" "$ETA_REBIN" +make_comparison_plot "ResponseMass_RecoOverGen_vs_phi_Mean" "ScaleMass_vs_phi_${PREFIX}_comparison" 'GenVis $\tau$ $\phi$' "$\langle m^{reco}/m^{gen} \rangle$" "" "0,2" "0,2" "$PHI_REBIN" + +make_resolution_comparison() { + local base="$1" + local name="$2" + local xlabel="$3" + local ylabel="$4" + local xlim="$5" + local ylim="$6" + local ylim_ratio="$7" + local rebin="$8" + + local files=() + local mean_hists=() + local sigma_hists=() + local labels=() + + for i in "${!DIRS_LIST[@]}"; do + local mean_hist="${BASE_DIR}/${DIRS_LIST[$i]}/${base}_Mean" + local sigma_hist="${BASE_DIR}/${DIRS_LIST[$i]}/${base}_Sigma" + + if has_hist "$mean_hist" && has_hist "$sigma_hist"; then + files+=("$DQM_FILE") + mean_hists+=("$mean_hist") + sigma_hists+=("$sigma_hist") + labels+=("${LABELS_LIST[$i]}") + else + echo "Skipping missing response pair:" + echo " mean : $mean_hist" + echo " sigma: $sigma_hist" + fi + done + + if [ "${#mean_hists[@]}" -eq 0 ]; then + echo "No valid response histograms for ${base}. Skipping." + return + fi + + local cmd=( + python3 "$MAKE_TAU_VALIDATION" + --mode response + --files "$(join_by_comma "${files[@]}")" + --mean-hists "$(join_by_comma "${mean_hists[@]}")" + --sigma-hists "$(join_by_comma "${sigma_hists[@]}")" + --labels "$(join_by_comma "${labels[@]}")" + --xlabel "$xlabel" + --ylabel "$ylabel" + --energy-text "$ENERGY_TEXT" + --odir "$OUTDIR_COMPARISON" + --name "$name" + --title "$name" + --leg-title "$STEP Tau Performance" + ) + + if [ -n "$xlim" ]; then + cmd+=("--xlim=${xlim}") + fi + + if [ -n "$ylim" ]; then + cmd+=("--ylim=${ylim}") + fi + + if [ -n "$ylim_ratio" ]; then + cmd+=("--ylim-ratio=${ylim_ratio}") + fi + + if [ -n "$rebin" ]; then + cmd+=("--rebin=${rebin}") + fi + + run_cmd "${cmd[@]}" +} + +make_resolution_comparison "ResponsePt_RecoOverGen_vs_pt" "ResolutionPt_vs_pt_${PREFIX}_comparison" 'GenVis $\tau$ $p_T$ [GeV]' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "0,300" "0,0.7" "0.5,1.5" "$PT_REBIN" +make_resolution_comparison "ResponsePt_RecoOverGen_vs_mass" "ResolutionPt_vs_mass_${PREFIX}_comparison" 'GenVis $\tau$ $mass$ [GeV]' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "0,2" "0,0.7" "0.5,1.5" "2" +make_resolution_comparison "ResponsePt_RecoOverGen_vs_eta" "ResolutionPt_vs_eta_${PREFIX}_comparison" 'GenVis $\tau$ $\eta$' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "-2.5,2.5" "0,0.7" "0.5,1.5" "$ETA_REBIN" +make_resolution_comparison "ResponsePt_RecoOverGen_vs_phi" "ResolutionPt_vs_phi_${PREFIX}_comparison" 'GenVis $\tau$ $\phi$' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "" "0,0.7" "0.5,1.5" "$PHI_REBIN" + +make_resolution_comparison "ResponseMass_RecoOverGen_vs_pt" "ResolutionMass_vs_pt_${PREFIX}_comparison" 'GenVis $\tau$ $p_T$ [GeV]' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "0,300" "0,0.7" "0.5,1.5" "$PT_REBIN" +make_resolution_comparison "ResponseMass_RecoOverGen_vs_mass" "ResolutionMass_vs_mass_${PREFIX}_comparison" 'GenVis $\tau$ $mass$ [GeV]' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "0,2" "0,0.7" "0.5,1.5" "2" +make_resolution_comparison "ResponseMass_RecoOverGen_vs_eta" "ResolutionMass_vs_eta_${PREFIX}_comparison" 'GenVis $\tau$ $\eta$' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "-2.5,2.5" "0,0.7" "0.5,1.5" "$ETA_REBIN" +make_resolution_comparison "ResponseMass_RecoOverGen_vs_phi" "ResolutionMass_vs_phi_${PREFIX}_comparison" 'GenVis $\tau$ $\phi$' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "" "0,0.7" "0.5,1.5" "$PHI_REBIN" + +echo +echo "Done." \ No newline at end of file diff --git a/Validation/RecoTau/scripts/run_tau_validation_plots.sh b/Validation/RecoTau/scripts/run_tau_validation_plots.sh new file mode 100755 index 0000000000000..5aee4cd6cbeb8 --- /dev/null +++ b/Validation/RecoTau/scripts/run_tau_validation_plots.sh @@ -0,0 +1,365 @@ +#!/usr/bin/env bash + +: <<'COMMENT' + +Usage: run_tau_validation_plots.sh [HLT|RECO] + HLT - use HLT DQM path and labels + RECO - use RECO DQM path and labels + (no argument) - run both HLT and RECO + +It assumes input DQM file +Command example for HLT validation to obtain the DQM file (based on CMSSW_16_1_0_pre4): + +cmsDriver.py step2 -s L1P2GT,HLT:75e33,VALIDATION:@hltValidation -n -1 --nThreads 0 \ + --conditions auto:phase2_realistic_T35 --datatier GEN-SIM-DIGI-RAW,DQMIO \ + --customise SLHCUpgradeSimulations/Configuration/aging.customise_aging_1000 --eventcontent FEVTDEBUGHLT,DQMIO \ + --geometry ExtendedRun4D110 --era Phase2C17I13M9 --hltProcess HLTX --processName HLTX \ + --filein file:/eos/cms/store/relval/CMSSW_16_1_0_pre2/RelValTenTau_15_500/GEN-SIM-DIGI-RAW/PU_150X_mcRun4_realistic_v1_STD_Run4D110_PU-v1/2590000/01cbb197-f9d0-48a5-a634-c985f3b66373.root --fileout file:step2.root \ + --inputCommands="keep *, drop *_hlt*_*_HLT, drop triggerTriggerFilterObjectWithRefs_l1t*_*_HLT" + +cmsDriver.py step3 -s HARVESTING:@hltValidation -n -1 \ + --conditions auto:phase2_realistic_T35 --mc --geometry ExtendedRun4D110 --era Phase2C17I13M9 \ + --filetype DQM --scenario pp --hltProcess HLTX --filein file:step2_inDQM.root --fileout file:step3.root +COMMENT + +set -u + +# Determine which steps to run +if [ "$#" -gt 0 ]; then + STEPS=("$1") +else + STEPS=("HLT" "RECO") +fi + +# Common configuration +DQM_FILE="DQM_V0001_R000000001__Global__CMSSW_X_Y_Z__RECO.root" +SCRIPT_DIR="${CMSSW_BASE}/src/Validation/RecoTau/scripts" +MAKE_TAU_VALIDATION="${SCRIPT_DIR}/makeTauValidationPlots.py" +MAKE_COMPARISON="${SCRIPT_DIR}/makeComparisonPlots.py" +ENERGY_TEXT="Ten Tau (200 PU) | 14 TeV" + +####### Configuration for WP vs jet > 0: +SUB_DIR="CutWP_VSjet0" +OUTDIR_SUFFIX="CutWP_VSjet0" +LABEL_TEXT="Tau validation WP vs jet > 0" +####### Configuration for no cut: +# SUB_DIR="" +# OUTDIR_SUFFIX="NoCut" +# LABEL_TEXT="Tau validation (no cut)" +####### Configuration for ID vs jet > 0.5: +# SUB_DIR="CutID_VSjet0p50" +# OUTDIR_SUFFIX="CutID_VSjet0p50" +# LABEL_TEXT="Tau validation ID vs jet > 0.5" +####### Configuration for ID vs jet > 0.9: +# SUB_DIR="CutID_VSjet0p90" +# OUTDIR_SUFFIX="CutID_VSjet0p90" +# LABEL_TEXT="Tau validation ID vs jet > 0.9" + +OUTDIR_COMPARISON="TauValidationPlots/Summary_${OUTDIR_SUFFIX}_HLTvsRECO" + +PT_REBIN="0,10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180,190,200,220,240,260,280,300,320,340,360,380,400" +ETA_REBIN="-2.4,-2.0,-1.6,-1.2,-0.8,-0.4,0.0,0.4,0.8,1.2,1.6,2.0,2.4" +PHI_REBIN="-3.5,-2.8,-2.1,-1.4,-0.7,0.0,0.7,1.4,2.1,2.8,3.5" + +has_hist() { + local hist="$1" + rootls "$DQM_FILE:$hist" >/dev/null 2>&1 +} + +join_by_comma() { + local IFS="," + echo "$*" +} + +run_cmd() { + echo + echo "Running:" + echo "$*" + "$@" +} + +make_summary_plot() { + local variable="$1" + local den_hist="$2" + local num_hist="$3" + local rate_hist="$4" + local name="$5" + local xlabel="$6" + local ylabel="$7" + local xlim="${8}" + local ylim="${9}" + local rebin="${10}" + local rate_label="${11}" + local den_label="${12}" + local num_label="${13}" + local extra_args="${14:-}" + + local den="${SELECTED_DIR}/${den_hist}" + local num="${SELECTED_DIR}/${num_hist}" + local rate="${SELECTED_DIR}/${rate_hist}" + + if ! has_hist "$den"; then + echo "Skipping missing denominator: $den" + return + fi + + if ! has_hist "$num"; then + echo "Skipping missing numerator: $num" + return + fi + + if ! has_hist "$rate"; then + echo "Skipping missing rate: $rate" + return + fi + + local cmd=( + python3 "$MAKE_TAU_VALIDATION" + --mode summary + --files "$DQM_FILE" + --den-hists "$den" + --num-hists "$num" + --rate-hists "$rate" + --labels "$rate_label" + --den-label "$den_label" + --num-label "$num_label" + --xlabel "$xlabel" + --ylabel "$ylabel" + --ylim "$ylim" + --energy-text "$ENERGY_TEXT" + --leg-title "${STEP} ${LABEL_TEXT}" + --odir "${OUTDIR_SUMMARY}" + --name "$name" + ) + + if [ -n "$xlim" ]; then + cmd+=("--xlim=${xlim}") + fi + + if [ -n "$rebin" ]; then + cmd+=("--rebin=${rebin}") + fi + + if [ -n "$extra_args" ]; then + cmd+=("$extra_args") + fi + + run_cmd "${cmd[@]}" +} + +# Loop through each step +for step in "${STEPS[@]}"; do + step_upper="${step^^}" + case "$step_upper" in + HLT) + STEP="HLT" + BASE_DIR="DQMData/Run 1/HLT/Run summary/Tau/TauValidation" + ;; + RECO) + STEP="Reco" + BASE_DIR="DQMData/Run 1/Tau/Run summary/TauValidation" + ;; + *) + echo "Invalid step: $step" + continue + ;; + esac + + # Setup paths for this step + OUTDIR_SUMMARY="TauValidationPlots/Summary_${OUTDIR_SUFFIX}_${step_upper}" + SELECTED_DIR="${BASE_DIR}/${SUB_DIR}" + + mkdir -p "$OUTDIR_SUMMARY" + + echo "Input file:" + echo "$DQM_FILE" + + echo + echo "Making summary plots for ${step_upper}" + + make_summary_plot "pt" "genTau_pt" "genTauMatched_pt" "Eff_vs_pt" "Efficiency_pt" 'GenVis $\tau$ $p_T$ [GeV]' "${STEP} Tau Efficiency" "0,400" "0,1.2" "2" "Efficiency" "Gen $\tau$'s" "Gen $\tau$'s matched to reco $\tau$'s" + make_summary_plot "eta" "genTau_eta" "genTauMatched_eta" "Eff_vs_eta" "Efficiency_eta" 'GenVis $\tau$ $\eta$' "${STEP} Tau Efficiency" "-2.5,2.5" "0,1.2" "2" "Efficiency" "Gen $\tau$'s" "Gen $\tau$'s matched to reco $\tau$'s" + make_summary_plot "phi" "genTau_phi" "genTauMatched_phi" "Eff_vs_phi" "Efficiency_phi" 'GenVis $\tau$ $\phi$' "${STEP} Tau Efficiency" "" "0,1.2" "2" "Efficiency" "Gen $\tau$'s" "Gen $\tau$'s matched to reco $\tau$'s" + make_summary_plot "mass" "genTau_mass" "genTauMatched_mass" "Eff_vs_mass" "Efficiency_mass" 'GenVis $\tau$ $mass$ [GeV]' "${STEP} Tau Efficiency" "0,2" "0,1.2" "" "Efficiency" "Gen $\tau$'s" "Gen $\tau$'s matched to reco $\tau$'s" + + make_summary_plot "pt" "recoTau_pt" "recoTauMatched_pt" "Fake_vs_pt" "FakeRate_pt" '$\tau$ $p_T$ [GeV]' "${STEP} Tau Fake Rate" "0,400" "0,1.2" "2" "Fake rate" "Reco $\tau$'s" "Reco $\tau$'s matched to gen $\tau$'s" "--inverted" + make_summary_plot "eta" "recoTau_eta" "recoTauMatched_eta" "Fake_vs_eta" "FakeRate_eta" '$\tau$ $\eta$' "${STEP} Tau Fake Rate" "-2.5,2.5" "0,1.2" "2" "Fake rate" "Reco $\tau$'s" "Reco $\tau$'s matched to gen $\tau$'s" "--inverted" + make_summary_plot "phi" "recoTau_phi" "recoTauMatched_phi" "Fake_vs_phi" "FakeRate_phi" '$\tau$ $\phi$' "${STEP} Tau Fake Rate" "" "0,1.2" "2" "Fake rate" "Reco $\tau$'s" "Reco $\tau$'s matched to gen $\tau$'s" "--inverted" + make_summary_plot "mass" "recoTau_mass" "recoTauMatched_mass" "Fake_vs_mass" "FakeRate_mass" '$\tau$ $mass$ [GeV]' "${STEP} Tau Fake Rate" "0,2" "0,1.2" "2" "Fake rate" "Reco $\tau$'s" "Reco $\tau$'s matched to gen $\tau$'s" "--inverted" + + make_summary_plot "pt" "genTau_pt" "genTauMultiMatched_pt" "Split_vs_pt" "SplitRate_pt" 'GenVis $\tau$ $p_T$ [GeV]' "${STEP} Tau Split Rate" "0,400" "0,1.2" "2" "Split rate" "Gen $\tau$'s" "Gen $\tau$'s matched to multiple reco $\tau$'s" + make_summary_plot "eta" "genTau_eta" "genTauMultiMatched_eta" "Split_vs_eta" "SplitRate_eta" 'GenVis $\tau$ $\eta$' "${STEP} Tau Split Rate" "-2.5,2.5" "0,1.2" "2" "Split rate" "Gen $\tau$'s" "Gen $\tau$'s matched to multiple reco $\tau$'s" + make_summary_plot "phi" "genTau_phi" "genTauMultiMatched_phi" "Split_vs_phi" "SplitRate_phi" 'GenVis $\tau$ $\phi$' "${STEP} Tau Split Rate" "" "0,1.2" "2" "Split rate" "Gen $\tau$'s" "Gen $\tau$'s matched to multiple reco $\tau$'s" + make_summary_plot "mass" "genTau_mass" "genTauMultiMatched_mass" "Split_vs_mass" "SplitRate_mass" 'GenVis $\tau$ $mass$ [GeV]' "${STEP} Tau Split Rate" "0,2" "0,1.2" "2" "Split rate" "Gen $\tau$'s" "Gen $\tau$'s matched to multiple reco $\tau$'s" + + make_summary_plot "pt" "recoTau_pt" "recoTauMultiMatched_pt" "Dup_vs_pt" "DupRate_pt" '$\tau$ $p_T$ [GeV]' "${STEP} Tau Duplicate Rate" "0,400" "0,1.2" "2" "Duplicate rate" "Reco $\tau$'s" "Reco $\tau$'s matched to multiple gen $\tau$'s" + make_summary_plot "eta" "recoTau_eta" "recoTauMultiMatched_eta" "Dup_vs_eta" "DupRate_eta" '$\tau$ $\eta$' "${STEP} Tau Duplicate Rate" "-2.5,2.5" "0,1.2" "2" "Duplicate rate" "Reco $\tau$'s" "Reco $\tau$'s matched to multiple gen $\tau$'s" + make_summary_plot "phi" "recoTau_phi" "recoTauMultiMatched_phi" "Dup_vs_phi" "DupRate_phi" '$\tau$ $\phi$' "${STEP} Tau Duplicate Rate" "" "0,1.2" "2" "Duplicate rate" "Reco $\tau$'s" "Reco $\tau$'s matched to multiple gen $\tau$'s" + make_summary_plot "mass" "recoTau_mass" "recoTauMultiMatched_mass" "Dup_vs_mass" "DupRate_mass" '$\tau$ $mass$ [GeV]' "${STEP} Tau Duplicate Rate" "0,2" "0,1.2" "2" "Duplicate rate" "Reco $\tau$'s" "Reco $\tau$'s matched to multiple gen $\tau$'s" + + echo + echo "Done with ${step_upper}" +done + +echo +echo "All steps complete." + +make_hlt_vs_reco_plot() { + local hist="$1" + local name="$2" + local xlabel="$3" + local ylabel="$4" + local xlim="${5}" + local ylim="${6}" + local ylim_ratio="${7}" + local rebin="${8}" + local inverted="${9:-0}" + + HIST_HLT="DQMData/Run 1/HLT/Run summary/Tau/TauValidation/${SUB_DIR}/${hist}" + HIST_RECO="DQMData/Run 1/Tau/Run summary/TauValidation/${SUB_DIR}/${hist}" + + local cmd=( + python3 "$MAKE_COMPARISON" + --files "${DQM_FILE},${DQM_FILE}" + --hists "$HIST_HLT,$HIST_RECO" + --labels "HLT,RECO" + --xlabel "$xlabel" + --ylabel "$ylabel" + --leg-title "${LABEL_TEXT}" + --energy-text "$ENERGY_TEXT" + --odir "$OUTDIR_COMPARISON" + --name "$name" + ) + + if [ -n "$xlim" ]; then + cmd+=("--xlim=${xlim}") + fi + + if [ -n "$ylim" ]; then + cmd+=("--ylim=${ylim}") + fi + + if [ -n "$ylim_ratio" ]; then + cmd+=("--ylim-ratio=${ylim_ratio}") + fi + + if [ -n "$rebin" ]; then + cmd+=("--rebin=${rebin}") + fi + + if [ "$inverted" -eq 1 ]; then + cmd+=(--inverted) + fi + + run_cmd "${cmd[@]}" +} + +make_hlt_vs_reco_resolution_comparison() { + local base="$1" + local name="$2" + local xlabel="$3" + local ylabel="$4" + local xlim="$5" + local ylim="$6" + local ylim_ratio="$7" + local rebin="$8" + + local files=() + local mean_hists=() + local sigma_hists=() + local labels=() + + HIST_HLT_MEAN="DQMData/Run 1/HLT/Run summary/Tau/TauValidation/${SUB_DIR}/${base}_Mean" + HIST_HLT_SIGMA="DQMData/Run 1/HLT/Run summary/Tau/TauValidation/${SUB_DIR}/${base}_Sigma" + HIST_RECO_MEAN="DQMData/Run 1/Tau/Run summary/TauValidation/${SUB_DIR}/${base}_Mean" + HIST_RECO_SIGMA="DQMData/Run 1/Tau/Run summary/TauValidation/${SUB_DIR}/${base}_Sigma" + + local cmd=( + python3 "$MAKE_TAU_VALIDATION" + --mode response + --files "${DQM_FILE},${DQM_FILE}" + --mean-hists "${HIST_HLT_MEAN},${HIST_RECO_MEAN}" + --sigma-hists "${HIST_HLT_SIGMA},${HIST_RECO_SIGMA}" + --labels "HLT,RECO" + --xlabel "$xlabel" + --ylabel "$ylabel" + --energy-text "$ENERGY_TEXT" + --odir "$OUTDIR_COMPARISON" + --name "$name" + --title "$name" + --leg-title "$LABEL_TEXT" + ) + + if [ -n "$xlim" ]; then + cmd+=("--xlim=${xlim}") + fi + + if [ -n "$ylim" ]; then + cmd+=("--ylim=${ylim}") + fi + + if [ -n "$ylim_ratio" ]; then + cmd+=("--ylim-ratio=${ylim_ratio}") + fi + + if [ -n "$rebin" ]; then + cmd+=("--rebin=${rebin}") + fi + + run_cmd "${cmd[@]}" +} + + +# If the user selected both steps, make comparison plots +if [[ " ${STEPS[*]} " == *" HLT "* ]] && [[ " ${STEPS[*]} " == *" RECO "* ]]; then + echo + echo "Making HLT vs RECO comparison plots" + + mkdir -p "$OUTDIR_COMPARISON" + + make_hlt_vs_reco_plot "Eff_vs_pt" "Eff_vs_pt_HLT_vs_RECO_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "Efficiency" "0,400" "0,1.3" "0,2" "$PT_REBIN" + make_hlt_vs_reco_plot "Eff_vs_eta" "Eff_vs_eta_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\eta$' "Efficiency" "-2.5,2.5" "0,1.3" "0,2" "$ETA_REBIN" + make_hlt_vs_reco_plot "Eff_vs_phi" "Eff_vs_phi_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\phi$' "Efficiency" "" "0,1.3" "0,2" "$PHI_REBIN" + make_hlt_vs_reco_plot "Eff_vs_mass" "Eff_vs_mass_HLT_vs_RECO_comparison" 'GenVis $\tau$ $mass$ [GeV]' "Efficiency" "0,2" "0,1.3" "0,2" "2" + + make_hlt_vs_reco_plot "Fake_vs_pt" "Fake_vs_pt_HLT_vs_RECO_comparison" '$\tau$ $p_T$ [GeV]' "Fake rate" "0,400" "0,1.3" "0,2" "$PT_REBIN" "1" + make_hlt_vs_reco_plot "Fake_vs_eta" "Fake_vs_eta_HLT_vs_RECO_comparison" '$\tau$ $\eta$' "Fake rate" "-2.5,2.5" "0,1.3" "0,2" "$ETA_REBIN" "1" + make_hlt_vs_reco_plot "Fake_vs_phi" "Fake_vs_phi_HLT_vs_RECO_comparison" '$\tau$ $\phi$' "Fake rate" "" "0,1.3" "0,2" "$PHI_REBIN" "1" + make_hlt_vs_reco_plot "Fake_vs_mass" "Fake_vs_mass_HLT_vs_RECO_comparison" '$\tau$ $mass$ [GeV]' "Fake rate" "0,2" "0,1.3" "0,2" "2" "1" + + make_hlt_vs_reco_plot "Split_vs_pt" "Split_vs_pt_HLT_vs_RECO_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "Split rate" "0,400" "0,1.3" "0,2" "$PT_REBIN" + make_hlt_vs_reco_plot "Split_vs_eta" "Split_vs_eta_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\eta$' "Split rate" "-2.5,2.5" "0,1.3" "0,2" "$ETA_REBIN" + make_hlt_vs_reco_plot "Split_vs_phi" "Split_vs_phi_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\phi$' "Split rate" "" "0,1.3" "0,2" "$PHI_REBIN" + make_hlt_vs_reco_plot "Split_vs_mass" "Split_vs_mass_HLT_vs_RECO_comparison" 'GenVis $\tau$ $mass$ [GeV]' "Split rate" "0,2" "0,1.3" "0,2" "2" + + make_hlt_vs_reco_plot "Dup_vs_pt" "Dup_vs_pt_HLT_vs_RECO_comparison" '$\tau$ $p_T$ [GeV]' "Duplicate rate" "0,400" "0,1.3" "0,2" "$PT_REBIN" + make_hlt_vs_reco_plot "Dup_vs_eta" "Dup_vs_eta_HLT_vs_RECO_comparison" '$\tau$ $\eta$' "Duplicate rate" "-2.5,2.5" "0,1.3" "0,2" "$ETA_REBIN" + make_hlt_vs_reco_plot "Dup_vs_phi" "Dup_vs_phi_HLT_vs_RECO_comparison" '$\tau$ $\phi$' "Duplicate rate" "" "0,1.3" "0,2" "$PHI_REBIN" + make_hlt_vs_reco_plot "Dup_vs_mass" "Dup_vs_mass_HLT_vs_RECO_comparison" '$\tau$ $mass$ [GeV]' "Duplicate rate" "0,2" "0,1.3" "0,2" "2" + + make_hlt_vs_reco_plot "ResponsePt_RecoOverGen_vs_pt_Mean" "ScalePt_vs_pt_HLT_vs_RECO_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "0,400" "0,2" "0,2" "$PT_REBIN" + make_hlt_vs_reco_plot "ResponsePt_RecoOverGen_vs_eta_Mean" "ScalePt_vs_eta_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\eta$' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "-2.5,2.5" "0,2" "0,2" "$ETA_REBIN" + make_hlt_vs_reco_plot "ResponsePt_RecoOverGen_vs_phi_Mean" "ScalePt_vs_phi_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\phi$' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "" "0,2" "0,2" "$PHI_REBIN" + make_hlt_vs_reco_plot "ResponsePt_RecoOverGen_vs_mass_Mean" "ScalePt_vs_mass_HLT_vs_RECO_comparison" 'GenVis $\tau$ $mass$ [GeV]' "$\langle p_T^{reco}/p_T^{gen} \rangle$" "0,2" "0,2" "0,2" "2" + + make_hlt_vs_reco_plot "ResponseMass_RecoOverGen_vs_pt_Mean" "ScaleMass_vs_pt_HLT_vs_RECO_comparison" 'GenVis $\tau$ $p_T$ [GeV]' "$\langle m^{reco}/m^{gen} \rangle$" "0,400" "0,2" "0,2" "$PT_REBIN" + make_hlt_vs_reco_plot "ResponseMass_RecoOverGen_vs_eta_Mean" "ScaleMass_vs_eta_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\eta$' "$\langle m^{reco}/m^{gen} \rangle$" "-2.5,2.5" "0,2" "0,2" "$ETA_REBIN" + make_hlt_vs_reco_plot "ResponseMass_RecoOverGen_vs_phi_Mean" "ScaleMass_vs_phi_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\phi$' "$\langle m^{reco}/m^{gen} \rangle$" "" "0,2" "0,2" "$PHI_REBIN" + make_hlt_vs_reco_plot "ResponseMass_RecoOverGen_vs_mass_Mean" "ScaleMass_vs_mass_HLT_vs_RECO_comparison" 'GenVis $\tau$ $mass$ [GeV]' "$\langle m^{reco}/m^{gen} \rangle$" "0,2" "0,2" "0,2" "2" + + make_hlt_vs_reco_resolution_comparison "ResponsePt_RecoOverGen_vs_pt" "ResolutionPt_vs_pt_HLT_vs_RECO_comparison" 'GenVis $\tau$ $p_T$ [GeV]' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "0,400" "0,0.7" "0.5,1.5" "$PT_REBIN" + make_hlt_vs_reco_resolution_comparison "ResponsePt_RecoOverGen_vs_eta" "ResolutionPt_vs_eta_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\eta$' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "-2.5,2.5" "0,0.7" "0.5,1.5" "$ETA_REBIN" + make_hlt_vs_reco_resolution_comparison "ResponsePt_RecoOverGen_vs_phi" "ResolutionPt_vs_phi_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\phi$' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "" "0,0.7" "0.5,1.5" "$PHI_REBIN" + make_hlt_vs_reco_resolution_comparison "ResponsePt_RecoOverGen_vs_mass" "ResolutionPt_vs_mass_HLT_vs_RECO_comparison" 'GenVis $\tau$ $mass$ [GeV]' '$\sigma(p_T^{reco}/p_T^{gen}) / \langle p_T^{reco}/p_T^{gen} \rangle$' "0,2" "0,0.7" "0.5,1.5" "2" + + make_hlt_vs_reco_resolution_comparison "ResponseMass_RecoOverGen_vs_pt" "ResolutionMass_vs_pt_HLT_vs_RECO_comparison" 'GenVis $\tau$ $p_T$ [GeV]' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "0,400" "0,0.7" "0.5,1.5" "$PT_REBIN" + make_hlt_vs_reco_resolution_comparison "ResponseMass_RecoOverGen_vs_eta" "ResolutionMass_vs_eta_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\eta$' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "-2.5,2.5" "0,0.7" "0.5,1.5" "$ETA_REBIN" + make_hlt_vs_reco_resolution_comparison "ResponseMass_RecoOverGen_vs_phi" "ResolutionMass_vs_phi_HLT_vs_RECO_comparison" 'GenVis $\tau$ $\phi$' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "" "0,0.7" "0.5,1.5" "$PHI_REBIN" + make_hlt_vs_reco_resolution_comparison "ResponseMass_RecoOverGen_vs_mass" "ResolutionMass_vs_mass_HLT_vs_RECO_comparison" 'GenVis $\tau$ $mass$ [GeV]' '$\sigma(m^{reco}/m^{gen}) / \langle m^{reco}/m^{gen} \rangle$' "0,2" "0,0.7" "0.5,1.5" "2" + + echo + echo "Comparison plots complete" +fi + +echo +echo "All plots complete."