diff --git a/DQM/HLTEvF/python/ScoutingDiMuonVertexMonitor_cfi.py b/DQM/HLTEvF/python/ScoutingDiMuonVertexMonitor_cfi.py new file mode 100644 index 0000000000000..9ad62e1a996fc --- /dev/null +++ b/DQM/HLTEvF/python/ScoutingDiMuonVertexMonitor_cfi.py @@ -0,0 +1,4 @@ +import FWCore.ParameterSet.Config as cms + +from HLTriggerOffline.Scouting.HLTScoutingDiMuonVertexMonitor_cfi import ScoutingDiMuonVertexMonitor as _ScoutingDiMuonVertexMonitor +ScoutingDiMuonVertexMonitorOnline = _ScoutingDiMuonVertexMonitor.clone(FolderName = cms.string('HLT/ScoutingOnline/DiMuon')) diff --git a/DQM/Integration/python/clients/ngt_dqm_sourceclient-live_cfg.py b/DQM/Integration/python/clients/ngt_dqm_sourceclient-live_cfg.py index 509dd7b73b5d3..a6e5d16021af9 100644 --- a/DQM/Integration/python/clients/ngt_dqm_sourceclient-live_cfg.py +++ b/DQM/Integration/python/clients/ngt_dqm_sourceclient-live_cfg.py @@ -72,6 +72,9 @@ process.load("DQM.HLTEvF.ScoutingDileptonMonitor_cfi") process.ScoutingDileptonMonitorOnline.OutputInternalPath = "NGT/ScoutingOnline/DiLepton" +process.load("DQM.HLTEvF.ScoutingDiMuonVertexMonitor_cfi") +process.ScoutingDiMuonVertexMonitorOnline.FolderName = "NGT/ScoutingOnline/DiMuon" + process.load("DQM.HLTEvF.ScoutingPi0Monitor_cfi") process.ScoutingPi0MonitorOnline.OutputInternalPath = "NGT/ScoutingOnline/PiZero" process.ScoutingPi0MonitorOnline.maxEta = 2.5 @@ -119,6 +122,7 @@ process.scoutingCollectionMonitor * process.ScoutingRecHitsMonitoring * process.ScoutingDileptonMonitorOnline * + process.ScoutingDiMuonVertexMonitorOnline * process.ScoutingMuonPropertiesMonitorOnline * process.ScoutingPi0MonitorOnline * process.hltResults * diff --git a/DQM/Integration/python/clients/scouting_dqm_sourceclient-live_cfg.py b/DQM/Integration/python/clients/scouting_dqm_sourceclient-live_cfg.py index 36986f59b0a65..7e33baa8ca1e4 100644 --- a/DQM/Integration/python/clients/scouting_dqm_sourceclient-live_cfg.py +++ b/DQM/Integration/python/clients/scouting_dqm_sourceclient-live_cfg.py @@ -60,6 +60,7 @@ process.load("DQM.HLTEvF.ScoutingRechitMonitoring_cff") process.load("DQM.HLTEvF.ScoutingDileptonMonitor_cfi") process.load("DQM.HLTEvF.ScoutingPi0Monitor_cfi") +process.load("DQM.HLTEvF.ScoutingDiMuonVertexMonitor_cfi") ## best electron track producer from PhysicsTools.Scouting.Run3ScoutingElectronBestTrackProducer_cfi import Run3ScoutingElectronBestTrackProducer as _Run3ScoutingElectronBestTrackProducer @@ -71,6 +72,7 @@ process.run3ScoutingElectronBestTrack * process.scoutingCollectionMonitor * process.ScoutingMuonMonitoring * + process.ScoutingDiMuonVertexMonitorOnline * process.ScoutingJetMonitoring * process.ScoutingElectronMonitoring * process.ScoutingRecHitsMonitoring * diff --git a/DQMOffline/HLTScouting/python/HLTScoutingDqmOffline_cff.py b/DQMOffline/HLTScouting/python/HLTScoutingDqmOffline_cff.py index 554f34db3b16b..c9b2e71fee507 100644 --- a/DQMOffline/HLTScouting/python/HLTScoutingDqmOffline_cff.py +++ b/DQMOffline/HLTScouting/python/HLTScoutingDqmOffline_cff.py @@ -34,6 +34,9 @@ ### DiLeptons monitoring from HLTriggerOffline.Scouting.HLTScoutingDileptonMonitor_cfi import * +### DiMuon Vertexing monitoring +from HLTriggerOffline.Scouting.HLTScoutingDiMuonVertexMonitor_cfi import * + ### Pi0 Monitoring from HLTriggerOffline.Scouting.HLTScoutingPi0Monitor_cfi import * @@ -54,6 +57,7 @@ hltScoutingCollectionMonitor = cms.Sequence(scoutingCollectionMonitor) hltScoutingDileptonMonitor = cms.Sequence(ScoutingDileptonMonitor) hltScoutingPi0Monitor = cms.Sequence(ScoutingPi0Monitor) +hltScoutingDiMuonVertexMonitor = cms.Sequence(ScoutingDiMuonVertexMonitor) hltScoutingDqmOffline = cms.Sequence(hltScoutingTrackMonitor + hltScoutingMuonDqmOffline + @@ -61,6 +65,7 @@ hltScoutingJetDqmOffline + run3ScoutingElectronBestTrack + hltScoutingDileptonMonitor + + hltScoutingDiMuonVertexMonitor + hltScoutingPi0Monitor + hltScoutingCollectionMonitor) diff --git a/HLTriggerOffline/Scouting/plugins/ScoutingDiMuonVertexMonitor.cc b/HLTriggerOffline/Scouting/plugins/ScoutingDiMuonVertexMonitor.cc new file mode 100644 index 0000000000000..fe4d38c43eb58 --- /dev/null +++ b/HLTriggerOffline/Scouting/plugins/ScoutingDiMuonVertexMonitor.cc @@ -0,0 +1,525 @@ +// -*- C++ -*- +// +// Package: HLTriggerOffline/Scouting +// Class: ScoutingDiMuonVertexMonitor +// +// Description: +// DQM monitor for di-muon vertices in Run 3 scouting data. +// Inspired by DQMOffline/Alignment/src/DiMuonVertexMonitor.cc but adapted +// for scouting objects: +// - Uses Run3ScoutingMuon (no reco::Track, no TransientTrackBuilder) +// - Uses Run3ScoutingVertex for both the pre-fitted di-muon secondary +// vertex and the primary vertex collection +// - Avoids KalmanVertexFitter (vertex already computed online) +// +// Per-pair quantities monitored: +// * Invariant mass (full range + Z and J/ψ windows) +// * SV probability, χ²/ndof +// * PV–SV distance (xy and 3D) and significance +// * cos φ (xy and 3D) between di-muon momentum and PV→SV displacement +// * Per-muon track d_xy and d_z w.r.t. the primary vertex +// +// Author: Auto-generated skeleton (based on ScoutingDileptonMonitor) + +// system includes +#include +#include +#include +#include +#include + +// CMSSW framework +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Utilities/interface/InputTag.h" + +// DQM +#include "DQMServices/Core/interface/DQMEDAnalyzer.h" +#include "DQMServices/Core/interface/DQMStore.h" + +// Scouting data formats +#include "DataFormats/Scouting/interface/Run3ScoutingMuon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingVertex.h" + +// Math +#include "DataFormats/Math/interface/LorentzVector.h" +#include "TMath.h" + +// Local utilities (ID selectors shared with ScoutingDileptonMonitor) +#include "ScoutingDQMUtils.h" + +// --------------------------------------------------------------------------- +// Helper: Lorentz vector from scouting muon +// --------------------------------------------------------------------------- +namespace { + inline math::PtEtaPhiMLorentzVector muonP4(const Run3ScoutingMuon& mu) { + return math::PtEtaPhiMLorentzVector(mu.pt(), mu.eta(), mu.phi(), scoutingDQMUtils::MUON_MASS); + } + + // Vertex position uncertainty in 2D (xy) using diagonal errors only. + // If the newer covariance accessors are available (xyCov etc.) they should + // be used instead; we fall back to quadratic sum of xError/yError here for + // maximum compatibility. + inline double vtxDistXY(float svX, + float svY, + float pvX, + float pvY, + float svXErr, + float svYErr, + float pvXErr, + float pvYErr, + double& dist, + double& distErr) { + const double dx = svX - pvX; + const double dy = svY - pvY; + dist = std::sqrt(dx * dx + dy * dy); + // Propagated uncertainty (ignoring off-diagonal covariance) + if (dist > 0.) { + distErr = + std::sqrt((dx * dx * (svXErr * svXErr + pvXErr * pvXErr) + dy * dy * (svYErr * svYErr + pvYErr * pvYErr)) / + (dist * dist)); + } else { + distErr = 0.; + } + return dist; + } + + inline double vtxDist3D(float svX, + float svY, + float svZ, + float pvX, + float pvY, + float pvZ, + float svXErr, + float svYErr, + float svZErr, + float pvXErr, + float pvYErr, + float pvZErr, + double& dist, + double& distErr) { + const double dx = svX - pvX; + const double dy = svY - pvY; + const double dz = svZ - pvZ; + dist = std::sqrt(dx * dx + dy * dy + dz * dz); + if (dist > 0.) { + distErr = + std::sqrt((dx * dx * (svXErr * svXErr + pvXErr * pvXErr) + dy * dy * (svYErr * svYErr + pvYErr * pvYErr) + + dz * dz * (svZErr * svZErr + pvZErr * pvZErr)) / + (dist * dist)); + } else { + distErr = 0.; + } + return dist; + } + + // Unit-safe cm → μm conversion factor + constexpr double cmToUm = 1.e4; +} // namespace + +// --------------------------------------------------------------------------- +// Monitor class +// --------------------------------------------------------------------------- +class ScoutingDiMuonVertexMonitor : public DQMEDAnalyzer { +public: + explicit ScoutingDiMuonVertexMonitor(const edm::ParameterSet&); + ~ScoutingDiMuonVertexMonitor() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions&); + + void bookHistograms(DQMStore::IBooker&, edm::Run const&, edm::EventSetup const&) override; + void analyze(edm::Event const&, edm::EventSetup const&) override; + +private: + // ---- configuration ------------------------------------------------------- + const std::string folderName_; + const std::string decayMotherName_; + + // Mass window determined by decayMotherName_ + std::pair massLimits_; + + // Muon selection + const bool applyMuonID_; + const double minPt_; + const double maxEta_; + + // Vertex quality cuts + const double minVtxProb_; + const double maxSVdistXY_; // μm, cut on PV–SV xy distance + + // Input tags / tokens + const edm::EDGetTokenT muonToken_; + const edm::EDGetTokenT pvToken_; + const edm::EDGetTokenT svToken_; // di-muon displaced vtx + + // ---- histograms ---------------------------------------------------------- + + // Vertex quality + MonitorElement* hSVProb_{nullptr}; + MonitorElement* hSVChi2_{nullptr}; + MonitorElement* hSVNormChi2_{nullptr}; + + // PV–SV distance + MonitorElement* hSVDistXY_{nullptr}; + MonitorElement* hSVDistXYErr_{nullptr}; + MonitorElement* hSVDistXYSig_{nullptr}; + MonitorElement* hSVDist3D_{nullptr}; + MonitorElement* hSVDist3DErr_{nullptr}; + MonitorElement* hSVDist3DSig_{nullptr}; + + // Pointing angle + MonitorElement* hCosPhi_{nullptr}; // cos φ in xy + MonitorElement* hCosPhi3D_{nullptr}; // cos φ in 3D + MonitorElement* hCosPhiInv_{nullptr}; + MonitorElement* hCosPhiInv3D_{nullptr}; + MonitorElement* hCosPhiUnbalance_{nullptr}; + MonitorElement* hCosPhi3DUnbalance_{nullptr}; + + // Invariant mass + MonitorElement* hInvMass_{nullptr}; + + // Per-muon track impact parameters w.r.t. the primary vertex + MonitorElement* hTrkDxy_{nullptr}; + MonitorElement* hTrkDz_{nullptr}; + MonitorElement* hTrkDxyErr_{nullptr}; + MonitorElement* hTrkDzErr_{nullptr}; + + // Number of muons and vertices per event (diagnostic) + MonitorElement* hNMuons_{nullptr}; + MonitorElement* hNSV_{nullptr}; +}; + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- +ScoutingDiMuonVertexMonitor::ScoutingDiMuonVertexMonitor(const edm::ParameterSet& iConfig) + : folderName_(iConfig.getParameter("FolderName")), + decayMotherName_(iConfig.getParameter("decayMotherName")), + applyMuonID_(iConfig.getParameter("applyMuonID")), + minPt_(iConfig.getParameter("minMuonPt")), + maxEta_(iConfig.getParameter("maxMuonEta")), + minVtxProb_(iConfig.getParameter("minVtxProb")), + maxSVdistXY_(iConfig.getParameter("maxSVdistXY")), + muonToken_(consumes(iConfig.getParameter("muons"))), + pvToken_(consumes(iConfig.getParameter("primaryVertices"))), + svToken_(consumes(iConfig.getParameter("secondaryVertices"))) { + // Determine mass window from decay mother name + if (decayMotherName_.find('Z') != std::string::npos) { + massLimits_ = {50., 120.}; + } else if (decayMotherName_.find("J/#psi") != std::string::npos || + decayMotherName_.find("J/psi") != std::string::npos) { + massLimits_ = {2.7, 3.4}; + } else if (decayMotherName_.find("#Upsilon") != std::string::npos || + decayMotherName_.find("Upsilon") != std::string::npos) { + massLimits_ = {8.9, 9.9}; + } else { + edm::LogWarning("ScoutingDiMuonVertexMonitor") + << "Unrecognised decay mother '" << decayMotherName_ << "'. Defaulting to Z window [50, 120] GeV."; + massLimits_ = {50., 120.}; + } +} + +// --------------------------------------------------------------------------- +// bookHistograms +// --------------------------------------------------------------------------- +void ScoutingDiMuonVertexMonitor::bookHistograms(DQMStore::IBooker& iBooker, edm::Run const&, edm::EventSetup const&) { + iBooker.setCurrentFolder(folderName_ + "/DiMuonVertexMonitor"); + + const std::string motName = decayMotherName_; + const std::string ps = "N(#mu#mu pairs)"; + const std::string histTit = motName + " #rightarrow #mu^{+}#mu^{-}"; + + // ---- vertex quality ---- + hSVProb_ = iBooker.book1D("VtxProb", ";" + motName + " vertex probability;" + ps, 100, 0., 1.); + + hSVChi2_ = iBooker.book1D( + "VtxChi2", ";#chi^{2} of the " + motName + " vertex;#chi^{2} of the " + motName + " vertex;" + ps, 200, 0., 200.); + + hSVNormChi2_ = iBooker.book1D("VtxNormChi2", ";#chi^{2}/ndof of the " + motName + " vertex;" + ps, 100, 0., 20.); + + // ---- PV–SV distance xy ---- + hSVDistXY_ = iBooker.book1D("VtxDistXY", histTit + ";PV-" + motName + "V xy distance [#mum];" + ps, 100, 0., 300.); + + hSVDistXYErr_ = + iBooker.book1D("VtxDistXYErr", histTit + ";PV-" + motName + "V xy distance error [#mum];" + ps, 100, 0., 1000.); + + hSVDistXYSig_ = + iBooker.book1D("VtxDistXYSig", histTit + ";PV-" + motName + "V xy distance significance;" + ps, 100, 0., 10.); + + // ---- PV–SV distance 3D ---- + hSVDist3D_ = iBooker.book1D("VtxDist3D", histTit + ";PV-" + motName + "V 3D distance [#mum];" + ps, 100, 0., 300.); + + hSVDist3DErr_ = + iBooker.book1D("VtxDist3DErr", histTit + ";PV-" + motName + "V 3D distance error [#mum];" + ps, 100, 0., 1000.); + + hSVDist3DSig_ = + iBooker.book1D("VtxDist3DSig", histTit + ";PV-" + motName + "V 3D distance significance;" + ps, 100, 0., 10.); + + // ---- pointing angle ---- + hCosPhi_ = iBooker.book1D("CosPhi", histTit + ";cos(#phi_{xy});" + ps, 50, -1., 1.); + + hCosPhi3D_ = iBooker.book1D("CosPhi3D", histTit + ";cos(#phi_{3D});" + ps, 50, -1., 1.); + + hCosPhiInv_ = iBooker.book1D("CosPhiInv", histTit + ";inverted cos(#phi_{xy});" + ps, 50, -1., 1.); + + hCosPhiInv3D_ = iBooker.book1D("CosPhiInv3D", histTit + ";inverted cos(#phi_{3D});" + ps, 50, -1., 1.); + + hCosPhiUnbalance_ = iBooker.book1D("CosPhiUnbalance", histTit + ";cos(#phi_{xy}) unbalance;#Delta" + ps, 50, -1., 1.); + + hCosPhi3DUnbalance_ = + iBooker.book1D("CosPhi3DUnbalance", histTit + ";cos(#phi_{3D}) unbalance;#Delta" + ps, 50, -1., 1.); + + // ---- invariant mass ---- + hInvMass_ = + iBooker.book1D("InvMass", histTit + ";M(#mu^{+}#mu^{-}) [GeV];" + ps, 70, massLimits_.first, massLimits_.second); + + // ---- track impact parameters ---- + hTrkDxy_ = iBooker.book1D("TrkDxy", histTit + ";muon track d_{xy}(PV) [#mum];muon tracks", 150, -300., 300.); + + hTrkDz_ = iBooker.book1D("TrkDz", histTit + ";muon track d_{z}(PV) [#mum];muon tracks", 150, -300., 300.); + + hTrkDxyErr_ = iBooker.book1D("TrkDxyErr", histTit + ";muon track err_{dxy} [#mum];muon tracks", 250, 0., 500.); + + hTrkDzErr_ = iBooker.book1D("TrkDzErr", histTit + ";muon track err_{dz} [#mum];muon tracks", 250, 0., 500.); + + // ---- diagnostics ---- + hNMuons_ = iBooker.book1D("NMuons", ";Number of selected muons;Events", 20, 0., 20.); + hNSV_ = iBooker.book1D("NSV", ";Number of di-muon secondary vertices;Events", 20, 0., 20.); +} + +// --------------------------------------------------------------------------- +// analyze +// --------------------------------------------------------------------------- +void ScoutingDiMuonVertexMonitor::analyze(edm::Event const& iEvent, edm::EventSetup const&) { + // ---- Muon collection ---- + edm::Handle muonHandle; + iEvent.getByToken(muonToken_, muonHandle); + if (!muonHandle.isValid()) { + edm::LogWarning("ScoutingDiMuonVertexMonitor") << "Invalid muon collection handle."; + return; + } + + // ---- Primary vertex collection ---- + edm::Handle pvHandle; + iEvent.getByToken(pvToken_, pvHandle); + if (!pvHandle.isValid() || pvHandle->empty()) { + edm::LogWarning("ScoutingDiMuonVertexMonitor") << "No primary vertices found; skipping event."; + return; + } + const Run3ScoutingVertex& pv = pvHandle->front(); // use the first (hardest) PV + + // ---- Secondary vertex collection (di-muon) ---- + edm::Handle svHandle; + iEvent.getByToken(svToken_, svHandle); + if (!svHandle.isValid()) { + edm::LogWarning("ScoutingDiMuonVertexMonitor") << "Invalid secondary vertex collection handle."; + return; + } + + hNSV_->Fill(static_cast(svHandle->size())); + + // ---- Muon selection ---- + std::vector selectedMuons; + selectedMuons.reserve(muonHandle->size()); + for (size_t i = 0; i < muonHandle->size(); ++i) { + const auto& mu = (*muonHandle)[i]; + if (mu.pt() < minPt_) + continue; + if (std::abs(mu.eta()) > maxEta_) + continue; + if (applyMuonID_ && !scoutingDQMUtils::scoutingMuonID(mu)) + continue; + selectedMuons.push_back(i); + } + + hNMuons_->Fill(static_cast(selectedMuons.size())); + + if (selectedMuons.size() < 2) + return; + + // ---- Loop over di-muon secondary vertices ---- + // Each SV in the displaced-vertex collection corresponds to a di-muon pair. + // We pick the two muons (closest in ΔR to the SV flight direction) that form + // an opposite-sign pair. When no per-SV muon index map is available (the + // scouting format does not store it), we fall back to the globally selected + // muon pair with the smallest |Δm – m_SV|, i.e. all pairs are tried and the + // best-matching one is chosen. + // + // For simplicity and maximum compatibility we adopt the safe approach: + // iterate over all OS muon pairs, reconstruct their kinematics, and then + // look for a matching SV (closest in 3D position to the di-muon flight path). + // If no dedicated SV collection is provided (svToken points to an empty + // collection) the pair still contributes to kinematic histograms via the + // primary-vertex information only. + + for (size_t ii = 0; ii < selectedMuons.size(); ++ii) { + for (size_t jj = ii + 1; jj < selectedMuons.size(); ++jj) { + const auto& mu_i = (*muonHandle)[selectedMuons[ii]]; + const auto& mu_j = (*muonHandle)[selectedMuons[jj]]; + + // Opposite sign + if (mu_i.charge() * mu_j.charge() >= 0) + continue; + + // Invariant mass + const auto p4pair = muonP4(mu_i) + muonP4(mu_j); + const double mass = p4pair.mass(); + hInvMass_->Fill(mass); + + // Di-muon momentum direction + const double pxDimu = p4pair.px(); + const double pyDimu = p4pair.py(); + const double pzDimu = p4pair.pz(); + + // ---- Per-muon impact parameters w.r.t. PV ---- + // Run3ScoutingMuon stores trk_dxy and trk_dz computed w.r.t. the + // beamspot / PV at HLT. We use them directly. + for (const auto* muPtr : {&mu_i, &mu_j}) { + hTrkDxy_->Fill(muPtr->trk_dxy() * cmToUm); + hTrkDz_->Fill(muPtr->trk_dz() * cmToUm); + hTrkDxyErr_->Fill(muPtr->trk_dxyError() * cmToUm); + hTrkDzErr_->Fill(muPtr->trk_dzError() * cmToUm); + } + + // ---- Find best matching secondary vertex ---- + // Strategy: among all SVs, pick the one with the minimum 3D distance + // to the midpoint of the two muon "reference point" vectors. + // As a proxy we use the SV position directly; the scouting displaced + // vertex collection is typically pre-matched to muon pairs by the HLT. + const Run3ScoutingVertex* bestSV = nullptr; + double minSVdist = 1e9; + for (const auto& sv : *svHandle) { + if (!sv.isValidVtx()) + continue; + const double dx = sv.x() - pv.x(); + const double dy = sv.y() - pv.y(); + const double dz = sv.z() - pv.z(); + const double d = std::sqrt(dx * dx + dy * dy + dz * dz); + if (d < minSVdist) { + minSVdist = d; + bestSV = &sv; + } + } + + if (bestSV == nullptr) + continue; // no SV available for this pair + + // ---- Vertex probability and χ² ---- + const double chi2 = bestSV->chi2(); + const int ndof = bestSV->ndof(); + const double prob = (ndof > 0) ? TMath::Prob(chi2, ndof) : 0.; + + hSVProb_->Fill(prob); + hSVChi2_->Fill(chi2); + if (ndof > 0) + hSVNormChi2_->Fill(chi2 / ndof); + + if (prob < minVtxProb_) + continue; + + // ---- PV–SV distances ---- + // Use diagonal position errors only (conservative); if the newer + // Run3ScoutingVertex with covariance is available, subclasses can + // override with more precise error propagation. + double distXY, distXYErr; + vtxDistXY(bestSV->x(), + bestSV->y(), + pv.x(), + pv.y(), + bestSV->xError(), + bestSV->yError(), + pv.xError(), + pv.yError(), + distXY, + distXYErr); + + double dist3D, dist3DErr; + vtxDist3D(bestSV->x(), + bestSV->y(), + bestSV->z(), + pv.x(), + pv.y(), + pv.z(), + bestSV->xError(), + bestSV->yError(), + bestSV->zError(), + pv.xError(), + pv.yError(), + pv.zError(), + dist3D, + dist3DErr); + + hSVDistXY_->Fill(distXY * cmToUm); + hSVDistXYErr_->Fill(distXYErr * cmToUm); + if (distXYErr > 0.) + hSVDistXYSig_->Fill(distXY / distXYErr); + + hSVDist3D_->Fill(dist3D * cmToUm); + hSVDist3DErr_->Fill(dist3DErr * cmToUm); + if (dist3DErr > 0.) + hSVDist3DSig_->Fill(dist3D / dist3DErr); + + // ---- Pointing angles ---- + // Displacement vector PV → SV + const double dVtxX = bestSV->x() - pv.x(); + const double dVtxY = bestSV->y() - pv.y(); + const double dVtxZ = bestSV->z() - pv.z(); + + // cos φ in xy plane + const double magPtDimu = std::sqrt(pxDimu * pxDimu + pyDimu * pyDimu); + const double magDVtxXY = std::sqrt(dVtxX * dVtxX + dVtxY * dVtxY); + const double magPDimu = std::sqrt(pxDimu * pxDimu + pyDimu * pyDimu + pzDimu * pzDimu); + const double magDVtx3D = std::sqrt(dVtxX * dVtxX + dVtxY * dVtxY + dVtxZ * dVtxZ); + + if (magPtDimu > 0. && magDVtxXY > 0.) { + const double cosphi = (pxDimu * dVtxX + pyDimu * dVtxY) / (magPtDimu * magDVtxXY); + hCosPhi_->Fill(cosphi); + hCosPhiInv_->Fill(-cosphi); + hCosPhiUnbalance_->Fill(cosphi, 1.); + hCosPhiUnbalance_->Fill(-cosphi, -1.); + } + + if (magPDimu > 0. && magDVtx3D > 0.) { + const double cosphi3D = (pxDimu * dVtxX + pyDimu * dVtxY + pzDimu * dVtxZ) / (magPDimu * magDVtx3D); + hCosPhi3D_->Fill(cosphi3D); + hCosPhiInv3D_->Fill(-cosphi3D); + hCosPhi3DUnbalance_->Fill(cosphi3D, 1.); + hCosPhi3DUnbalance_->Fill(-cosphi3D, -1.); + } + + // ---- Distance cut ---- + if (distXY * cmToUm > maxSVdistXY_) + continue; + + // Additional per-pair histograms after distance cut can be added here. + } + } +} + +// --------------------------------------------------------------------------- +// fillDescriptions +// --------------------------------------------------------------------------- +void ScoutingDiMuonVertexMonitor::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + + desc.add("FolderName", "HLT/ScoutingOffline/DiMuon"); + desc.add("decayMotherName", "Z"); + + desc.add("muons", edm::InputTag("hltScoutingMuonPackerVtx")); + desc.add("primaryVertices", edm::InputTag("hltScoutingPrimaryVertexPacker", "primaryVtx")); + desc.add("secondaryVertices", edm::InputTag("hltScoutingMuonPackerVtx", "displacedVtx")); + + desc.add("applyMuonID", true); + desc.add("minMuonPt", 3.0); + desc.add("maxMuonEta", 2.4); + desc.add("minVtxProb", 0.005); + desc.add("maxSVdistXY", 50.0); // μm + + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(ScoutingDiMuonVertexMonitor); diff --git a/HLTriggerOffline/Scouting/plugins/ScoutingTrackMonitor.cc b/HLTriggerOffline/Scouting/plugins/ScoutingTrackMonitor.cc index 65e88433965a1..2a49cc71ea8ca 100644 --- a/HLTriggerOffline/Scouting/plugins/ScoutingTrackMonitor.cc +++ b/HLTriggerOffline/Scouting/plugins/ScoutingTrackMonitor.cc @@ -132,6 +132,7 @@ class ScoutingTrackMonitor : public DQMEDAnalyzer { // tokens const edm::EDGetTokenT> tracksToken_; const edm::EDGetTokenT> verticesToken_; + const edm::EDGetTokenT beamSpotToken_; const std::string topFolderName_; // top folder name where to book histograms @@ -149,6 +150,8 @@ class ScoutingTrackMonitor : public DQMEDAnalyzer { MonitorElement* p_dz_phi; MonitorElement* p2_dz_eta_phi; + MonitorElement *bsX, *bsY, *bsZ, *bsSigmaZ, *bsDxdz, *bsDydz, *bsBeamWidthX, *bsBeamWidthY, *bsType; + // hit profiles configuration std::vector hitProfiles_; std::vector impactParameterProfiles_; @@ -195,11 +198,20 @@ ScoutingTrackMonitor::ScoutingTrackMonitor(const edm::ParameterSet& iConfig) : conf_(iConfig), tracksToken_{consumes>(iConfig.getParameter("tracks"))}, verticesToken_{consumes>(iConfig.getParameter("vertices"))}, - topFolderName_{iConfig.getParameter("topFolderName")} { + beamSpotToken_{consumes(iConfig.getParameter("beamSpotLabel"))}, + topFolderName_{iConfig.getParameter("topFolderName")}, + bsX(nullptr), + bsY(nullptr), + bsZ(nullptr), + bsSigmaZ(nullptr), + bsDxdz(nullptr), + bsDydz(nullptr), + bsBeamWidthX(nullptr), + bsBeamWidthY(nullptr), + bsType(nullptr) { hitProfiles_ = {{"nValidPixelHits", "nValidPixelHits", 0., 10.}, {"nTrackerLayersWithMeasurement", "nTrackerLayersWithMeasurement", 0., 20.}, {"nValidStripHits", "nValidStripHits", 0., 30.}}; - impactParameterProfiles_ = {{"dxy", "d_{xy}", -0.15 * cmToUm, 0.15 * cmToUm}, {"dz", "d_{z}", -0.35 * cmToUm, 0.35 * cmToUm}}; } @@ -435,6 +447,7 @@ void ScoutingTrackMonitor::bookHistograms(DQMStore::IBooker& ibooker, edm::Run c 0., 1., "")); + // initialize and book the monitors; dxy_pt1.varname_ = "xy"; dxy_pt1.pTcut_ = 1.f; @@ -451,6 +464,25 @@ void ScoutingTrackMonitor::bookHistograms(DQMStore::IBooker& ibooker, edm::Run c dz_pt10.varname_ = "z"; dz_pt10.pTcut_ = 10.f; dz_pt10.bookIPMonitor(ibooker, conf_); + + // BeamSpot + + auto vposx = conf_.getParameter("Xpos"); + auto vposy = conf_.getParameter("Ypos"); + + bsX = ibooker.book1D("bsX", "BeamSpot x0", 100, vposx - 0.1, vposx + 0.1); + bsY = ibooker.book1D("bsY", "BeamSpot y0", 100, vposy - 0.1, vposy + 0.1); + bsZ = ibooker.book1D("bsZ", "BeamSpot z0", 100, -2., 2.); + bsSigmaZ = ibooker.book1D("bsSigmaZ", "BeamSpot sigmaZ", 100, 0., 10.); + bsDxdz = ibooker.book1D("bsDxdz", "BeamSpot dxdz", 100, -0.0003, 0.0003); + bsDydz = ibooker.book1D("bsDydz", "BeamSpot dydz", 100, -0.0003, 0.0003); + bsBeamWidthX = ibooker.book1D("bsBeamWidthX", "BeamSpot BeamWidthX", 500, 0., 15.); + bsBeamWidthY = ibooker.book1D("bsBeamWidthY", "BeamSpot BeamWidthY", 500, 0., 15.); + bsType = ibooker.book1D("bsType", "BeamSpot type", 4, -1.5, 2.5); + bsType->setBinLabel(1, "Unknown"); + bsType->setBinLabel(2, "Fake"); + bsType->setBinLabel(3, "LHC"); + bsType->setBinLabel(4, "Tracker"); } void ScoutingTrackMonitor::IPMonitoring::bookIPMonitor(DQMStore::IBooker& iBooker, const edm::ParameterSet& config) { @@ -634,12 +666,16 @@ bool ScoutingTrackMonitor::getValidHandle(const edm::Event& iEvent, void ScoutingTrackMonitor::analyze(const edm::Event& iEvent, const edm::EventSetup&) { edm::Handle> primaryVerticesH; edm::Handle> tracksH; + edm::Handle beamSpotHandle; if (!getValidHandle(iEvent, verticesToken_, primaryVerticesH, "primary vertices") || - !getValidHandle(iEvent, tracksToken_, tracksH, "tracks")) { + !getValidHandle(iEvent, tracksToken_, tracksH, "tracks") || + !getValidHandle(iEvent, beamSpotToken_, beamSpotHandle, "beamspot")) { return; } + iEvent.getByToken(beamSpotToken_, beamSpotHandle); + // derefernce handles when it's safe to do so. auto const& tracks = *tracksH; auto const& vertices = *primaryVerticesH; @@ -829,6 +865,19 @@ void ScoutingTrackMonitor::analyze(const edm::Event& iEvent, const edm::EventSet dz_pt10.IPErrVsPt_->Fill(pt, dzErr); dz_pt10.IPErrVsEtaVsPhi_->Fill(eta, phi, dzErr); } + + // BeamSpot + reco::BeamSpot beamSpot = *beamSpotHandle; + + bsX->Fill(beamSpot.x0()); + bsY->Fill(beamSpot.y0()); + bsZ->Fill(beamSpot.z0()); + bsSigmaZ->Fill(beamSpot.sigmaZ()); + bsDxdz->Fill(beamSpot.dxdz()); + bsDydz->Fill(beamSpot.dydz()); + bsBeamWidthX->Fill(beamSpot.BeamWidthX() * cmToUm); + bsBeamWidthY->Fill(beamSpot.BeamWidthY() * cmToUm); + bsType->Fill(beamSpot.type()); } // helper: build reco::Track @@ -908,7 +957,10 @@ void ScoutingTrackMonitor::fillDescriptions(edm::ConfigurationDescriptions& desc edm::ParameterSetDescription desc; desc.add("tracks", edm::InputTag("hltScoutingTrackPacker")); desc.add("vertices", edm::InputTag("hltScoutingPrimaryVertexPacker", "primaryVtx")); + desc.add("beamSpotLabel", edm::InputTag("hltOnlineBeamSpot")); desc.add("topFolderName", "HLT/ScoutingOffline/Tracks"); + desc.add("Xpos", 0.1); + desc.add("Ypos", 0.0); desc.add("DxyBin", 100); desc.add("DxyMin", -5000.0); desc.add("DxyMax", 5000.0); diff --git a/HLTriggerOffline/Scouting/python/HLTScoutingDiMuonVertexMonitor_cfi.py b/HLTriggerOffline/Scouting/python/HLTScoutingDiMuonVertexMonitor_cfi.py new file mode 100644 index 0000000000000..91199009289f3 --- /dev/null +++ b/HLTriggerOffline/Scouting/python/HLTScoutingDiMuonVertexMonitor_cfi.py @@ -0,0 +1,14 @@ +import FWCore.ParameterSet.Config as cms +from DQMServices.Core.DQMEDAnalyzer import DQMEDAnalyzer + +ScoutingDiMuonVertexMonitor = DQMEDAnalyzer("ScoutingDiMuonVertexMonitor", + FolderName = cms.string('HLT/ScoutingOffline/DiMuon'), + decayMotherName = cms.string('Z'), + muons = cms.InputTag('hltScoutingMuonPackerVtx'), + primaryVertices = cms.InputTag('hltScoutingPrimaryVertexPacker', 'primaryVtx'), + secondaryVertices = cms.InputTag('hltScoutingMuonPackerVtx', 'displacedVtx'), + applyMuonID = cms.bool(True), + minMuonPt = cms.double(3), + maxMuonEta = cms.double(2.4), + minVtxProb = cms.double(0.005), + maxSVdistXY = cms.double(50)) diff --git a/HLTriggerOffline/Scouting/test/testHLTScoutingDQMAnalyzers.cc b/HLTriggerOffline/Scouting/test/testHLTScoutingDQMAnalyzers.cc index 1817433d80c5a..9221a582630ef 100644 --- a/HLTriggerOffline/Scouting/test/testHLTScoutingDQMAnalyzers.cc +++ b/HLTriggerOffline/Scouting/test/testHLTScoutingDQMAnalyzers.cc @@ -146,3 +146,9 @@ TEST_CASE("ScoutingTrackMonitor tests", "[ScoutingTrackMonitor]") { const std::string baseConfig = generateBaseConfig("scoutingTrackMonitor"); runTestForAnalyzer(baseConfig, "ScoutingTrackMonitor"); } + +//___________________________________________________________________________________________ +TEST_CASE("ScoutingDiMuonVertexMonitor tests", "[ScoutingDiMuonVertexMonitor]") { + const std::string baseConfig = generateBaseConfig("scoutingDiMuonVertexMonitor"); + runTestForAnalyzer(baseConfig, "ScoutingDiMuonVertexMonitor"); +}