diff --git a/Configuration/Applications/python/ConfigBuilder.py b/Configuration/Applications/python/ConfigBuilder.py index 83dc0655c033e..c76eb2bee9dd5 100644 --- a/Configuration/Applications/python/ConfigBuilder.py +++ b/Configuration/Applications/python/ConfigBuilder.py @@ -1830,6 +1830,47 @@ def prepare_RECOBEFMIX(self, stepSpec = "reconstruction"): def prepare_PAT(self, stepSpec = "patTask"): ''' Enrich the schedule with PAT ''' + + # Handle @-prefixed flavors (e.g., @Scout for scouting MiniAOD) + if '@' in stepSpec: + from PhysicsTools.PatFromScouting.autoPAT import autoPAT, expandPATMapping + + _patSeq = stepSpec.split('+') + _patCustoms = stepSpec.split('+') + expandPATMapping(_patSeq, autoPAT, 'sequence') + expandPATMapping(_patCustoms, autoPAT, 'customize') + + # Remove duplicates while preserving order + _patSeq = list(sorted(set(_patSeq), key=_patSeq.index)) + _patCustoms = list(sorted(set(_patCustoms), key=_patCustoms.index)) + # Remove empty strings + _patSeq = [s for s in _patSeq if s] + _patCustoms = [c for c in _patCustoms if c] + + # Load and schedule sequences + _seqToSchedule = [] + for _subSeq in _patSeq: + if '.' in _subSeq: + _cff, _seq = _subSeq.rsplit('.', 1) + print(f"PAT: scheduling: {_seq} from {_cff}") + self.loadAndRemember(_cff) + _seqToSchedule.append(_seq) + elif '/' in _subSeq: + self.loadAndRemember(_subSeq) + + if _seqToSchedule: + self.scheduleSequence('+'.join(_seqToSchedule), 'patMiniAOD_step') + + # Add customizations + for custom in _patCustoms: + self._options.customisation_file.append(custom) + + # cpu efficiency boost when running PAT by itself + if self.stepKeys[0] == 'PAT': + self._customise_coms.append( 'process.source.delayReadingEventProducts = cms.untracked.bool(False)') + + return + _,pat_sequence,pat_cff = self.loadDefaultOrSpecifiedCFF(stepSpec,self.PATDefaultCFF) ## handle the noise filters as Flag_* path that were loaded for existing_path,path_ in self.process.paths_().items(): @@ -1874,6 +1915,12 @@ def prepare_NANO(self, stepSpec = '' ): print(_nanoSeq) # create full specified sequence using autoNANO from PhysicsTools.NanoAOD.autoNANO import autoNANO, expandNanoMapping + # Extend with scouting-specific NANO flavors if available + try: + from PhysicsTools.PatFromScouting.autoPAT import autoNANO_scouting + autoNANO.update(autoNANO_scouting) + except ImportError: + pass # PatFromScouting not available # if not a autoNANO mapping, load an empty customization, which later will be converted into the default. _nanoCustoms = _nanoSeq.split('+') if '@' in stepSpec else [''] _nanoSeq = _nanoSeq.split('+') diff --git a/DataFormats/MuonReco/interface/Muon.h b/DataFormats/MuonReco/interface/Muon.h index 6f19b0fa784d8..c0b70c30b14e8 100644 --- a/DataFormats/MuonReco/interface/Muon.h +++ b/DataFormats/MuonReco/interface/Muon.h @@ -294,6 +294,7 @@ namespace reco { static const unsigned int RPCMuon = 1 << 6; static const unsigned int GEMMuon = 1 << 7; static const unsigned int ME0Muon = 1 << 8; + static const unsigned int ScoutingMuon = 1 << 9; void setType(unsigned int type) { type_ = type; } unsigned int type() const { return type_; } @@ -308,6 +309,7 @@ namespace reco { bool isRPCMuon() const { return type_ & RPCMuon; } bool isGEMMuon() const { return type_ & GEMMuon; } bool isME0Muon() const { return type_ & ME0Muon; } + bool isScoutingMuon() const { return type_ & ScoutingMuon; } private: /// check overlap with another candidate diff --git a/DataFormats/PatCandidates/BuildFile.xml b/DataFormats/PatCandidates/BuildFile.xml index 0b876ed1a519b..39a90ce086873 100644 --- a/DataFormats/PatCandidates/BuildFile.xml +++ b/DataFormats/PatCandidates/BuildFile.xml @@ -23,6 +23,7 @@ + diff --git a/DataFormats/PatCandidates/interface/Electron.h b/DataFormats/PatCandidates/interface/Electron.h index 177fa0bd7fc13..d1928ee1029a4 100644 --- a/DataFormats/PatCandidates/interface/Electron.h +++ b/DataFormats/PatCandidates/interface/Electron.h @@ -31,6 +31,9 @@ #include "DataFormats/PatCandidates/interface/PackedCandidate.h" #include "DataFormats/Common/interface/AtomicPtrCache.h" +// Forward declaration for scouting +class Run3ScoutingElectron; + // Define typedefs for convenience namespace pat { class Electron; @@ -61,6 +64,8 @@ namespace pat { Electron(const edm::RefToBase& anElectronRef); /// constructor from a Ptr to a reco::GsfElectron Electron(const edm::Ptr& anElectronRef); + /// constructor from Run3ScoutingElectron + Electron(const Run3ScoutingElectron& scoutingElectron); /// destructor ~Electron() override; @@ -269,6 +274,29 @@ namespace pat { associatedPackedFCandidateIndices_.insert(associatedPackedFCandidateIndices_.end(), beginIndexItr, endIndexItr); } + // ---- Scouting flag ---- + bool isScoutingElectron() const { return isScoutingElectron_; } + + // ---- Universal accessors with scouting dispatch ---- + // Track-related: dispatches to gsfTrack() for standard, scouting members for scouting + float trkEta() const; + float trkPhi() const; + float trkpMode() const; + float trketaMode() const; + float trkphiMode() const; + float trkqoverpModeError() const; + + // ECAL crystal-level: dispatches to superCluster() for standard, scouting members for scouting + uint32_t seedId() const; + uint32_t nClusters() const; + uint32_t nCrystals() const; + + // ---- Scouting-only accessors (no standard equivalent) ---- + std::vector const& scoutingEnergyMatrix() const { return scoutingEnergyMatrix_; } + std::vector const& scoutingTimingMatrix() const { return scoutingTimingMatrix_; } + std::vector const& scoutingDetIds() const { return scoutingDetIds_; } + bool scoutingRechitZeroSuppression() const { return scoutingRechitZeroSuppression_; } + friend class PATElectronSlimmer; friend class PATElectronCandidatesRekeyer; @@ -377,6 +405,22 @@ namespace pat { // ---- link to PackedPFCandidates edm::RefProd packedPFCandidates_; std::vector associatedPackedFCandidateIndices_; + + // ---- scouting-specific members ---- + bool isScoutingElectron_{false}; + std::vector scoutingTrkEta_; + std::vector scoutingTrkPhi_; + std::vector scoutingTrkpMode_; + std::vector scoutingTrketaMode_; + std::vector scoutingTrkphiMode_; + std::vector scoutingTrkqoverpModeError_; + uint32_t scoutingSeedId_{0}; + uint32_t scoutingNClusters_{0}; + uint32_t scoutingNCrystals_{0}; + std::vector scoutingEnergyMatrix_; + std::vector scoutingTimingMatrix_; + std::vector scoutingDetIds_; + bool scoutingRechitZeroSuppression_{false}; }; } // namespace pat diff --git a/DataFormats/PatCandidates/interface/Jet.h b/DataFormats/PatCandidates/interface/Jet.h index 57c6e2a491d7f..bbfe49e07e58a 100644 --- a/DataFormats/PatCandidates/interface/Jet.h +++ b/DataFormats/PatCandidates/interface/Jet.h @@ -48,6 +48,9 @@ #include +// Forward declaration for scouting +class Run3ScoutingPFJet; + // Define typedefs for convenience namespace pat { class Jet; @@ -95,6 +98,8 @@ namespace pat { Jet(const edm::RefToBase& aJetRef); /// constructure from ref to pat::Jet Jet(const edm::Ptr& aJetRef); + /// constructor from Run3ScoutingPFJet + Jet(const Run3ScoutingPFJet& scoutingJet); /// destructor ~Jet() override; /// required reimplementation of the Candidate's clone method diff --git a/DataFormats/PatCandidates/interface/Muon.h b/DataFormats/PatCandidates/interface/Muon.h index a9054c8927350..032e644512c4f 100644 --- a/DataFormats/PatCandidates/interface/Muon.h +++ b/DataFormats/PatCandidates/interface/Muon.h @@ -25,12 +25,15 @@ #include "DataFormats/MuonReco/interface/MuonTimeExtra.h" #include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/TrackReco/interface/HitPattern.h" #include "DataFormats/PatCandidates/interface/Lepton.h" #include "DataFormats/ParticleFlowCandidate/interface/IsolatedPFCandidateFwd.h" #include "DataFormats/ParticleFlowCandidate/interface/IsolatedPFCandidate.h" #include "DataFormats/MuonReco/interface/MuonSimInfo.h" // Define typedefs for convenience +class Run3ScoutingMuon; + namespace pat { class Muon; typedef std::vector MuonCollection; @@ -59,6 +62,8 @@ namespace pat { Muon(const edm::RefToBase& aMuonRef); /// constructor from a Ptr to a reco muon Muon(const edm::Ptr& aMuonRef); + /// constructor from a scouting muon + Muon(const Run3ScoutingMuon& aMuon); /// destructor ~Muon() override; @@ -344,6 +349,46 @@ namespace pat { } bool triggered(const char* pathName) const { return triggerObjectMatchByPath(pathName, true, true) != nullptr; } + // ---- Methods that hide reco::Muon methods for scouting muon compatibility ---- + // For ScoutingMuon, these return cached values; otherwise delegate to reco::Muon + + /// Number of chambers (hides reco::Muon::numberOfChambers) + int numberOfChambers() const; + /// Number of chambers CSC or DT only (hides reco::Muon::numberOfChambersCSCorDT) + int numberOfChambersCSCorDT() const; + /// Number of muon stations with matches (hides reco::Muon::numberOfMatches) + int numberOfMatches(reco::Muon::ArbitrationType type = reco::Muon::SegmentAndTrackArbitration) const; + /// Number of matched stations (hides reco::Muon::numberOfMatchedStations) + int numberOfMatchedStations(reco::Muon::ArbitrationType type = reco::Muon::SegmentAndTrackArbitration) const; + /// Expected number of matched stations (hides reco::Muon::expectedNnumberOfMatchedStations) + unsigned int expectedNnumberOfMatchedStations(float minDistanceFromEdge = 10.0) const; + /// Muon station mask (hides reco::Muon::stationMask) + unsigned int stationMask(reco::Muon::ArbitrationType type = reco::Muon::SegmentAndTrackArbitration) const; + /// Number of matched RPC layers (hides reco::Muon::numberOfMatchedRPCLayers) + int numberOfMatchedRPCLayers(reco::Muon::ArbitrationType type = reco::Muon::RPCHitAndTrackArbitration) const; + /// RPC layer mask (hides reco::Muon::RPClayerMask) + unsigned int RPClayerMask(reco::Muon::ArbitrationType type = reco::Muon::RPCHitAndTrackArbitration) const; + + // ---- Hit information accessors ---- + // For scouting muons return cached values; for standard muons compute from tracks + + /// Number of valid muon hits (from global track hitPattern for standard muons) + int numberOfValidMuonHits() const; + /// Number of valid standalone muon hits + int numberOfValidStandAloneMuonHits() const; + /// Number of standalone muon matched stations + int numberOfStandAloneMuonMatchedStations() const; + /// Number of valid pixel hits (from inner track hitPattern) + int numberOfValidPixelHits() const; + /// Number of valid strip hits (from inner track hitPattern) + int numberOfValidStripHits() const; + /// Number of pixel layers with measurement + int numberOfPixelLayersWithMeasurement() const; + /// Number of tracker layers with measurement + int numberOfTrackerLayersWithMeasurement() const; + /// Track hit pattern (from inner track for standard muons, from scouting data for scouting muons) + reco::HitPattern const& trkHitPattern() const; + protected: // ---- for content embedding ---- @@ -439,6 +484,22 @@ namespace pat { float simEta_; float simPhi_; float simMatchQuality_; + + private: + // ---- Cached values for scouting muons ---- + // Filled by constructor from Run3ScoutingMuon; accessors check isScoutingMuon() + int scoutingNChambers_{0}; + int scoutingNChambersCSCorDT_{0}; + int scoutingNMatches_{0}; + int scoutingNMatchedStations_{0}; + unsigned int scoutingExpectedMatchedStations_{0}; + unsigned int scoutingStationMask_{0}; + int scoutingNMatchedRPCLayers_{0}; + unsigned int scoutingRPCLayerMask_{0}; + int scoutingNValidMuonHits_{0}; + int scoutingNValidStandAloneMuonHits_{0}; + int scoutingNStandAloneMuonMatchedStations_{0}; + reco::HitPattern scoutingTrkHitPattern_; }; } // namespace pat diff --git a/DataFormats/PatCandidates/interface/Photon.h b/DataFormats/PatCandidates/interface/Photon.h index 4ce2d99821825..0989de01ec99b 100644 --- a/DataFormats/PatCandidates/interface/Photon.h +++ b/DataFormats/PatCandidates/interface/Photon.h @@ -17,6 +17,7 @@ \author Steven Lowette, Giovanni Petrucciani, Frederic Ronga */ +#include #include "DataFormats/PatCandidates/interface/PATObject.h" #include "DataFormats/EgammaCandidates/interface/Photon.h" #include "DataFormats/EgammaReco/interface/SuperClusterFwd.h" @@ -26,6 +27,9 @@ #include "DataFormats/EcalRecHit/interface/EcalRecHitCollections.h" #include "DataFormats/Common/interface/AtomicPtrCache.h" +// Forward declaration for scouting +class Run3ScoutingPhoton; + // Define typedefs for convenience namespace pat { class Photon; @@ -56,6 +60,8 @@ namespace pat { Photon(const edm::RefToBase& aPhotonRef); /// constructor from a Ptr to a reco photon Photon(const edm::Ptr& aPhotonRef); + /// constructor from Run3ScoutingPhoton + Photon(const Run3ScoutingPhoton& scoutingPhoton); /// destructor ~Photon() override; @@ -328,6 +334,21 @@ namespace pat { /// get the source candidate pointer with index i reco::CandidatePtr sourceCandidatePtr(size_type i) const override; + // ---- Scouting flag ---- + bool isScoutingPhoton() const { return isScoutingPhoton_; } + + // ---- Universal accessors with scouting dispatch ---- + // ECAL crystal-level: dispatches to superCluster() for standard, scouting members for scouting + uint32_t seedId() const; + uint32_t nClusters() const; + uint32_t nCrystals() const; + + // ---- Scouting-only accessors (no standard equivalent) ---- + std::vector const& scoutingEnergyMatrix() const { return scoutingEnergyMatrix_; } + std::vector const& scoutingTimingMatrix() const { return scoutingTimingMatrix_; } + std::vector const& scoutingDetIds() const { return scoutingDetIds_; } + bool scoutingRechitZeroSuppression() const { return scoutingRechitZeroSuppression_; } + friend class PATPhotonSlimmer; friend class PATPhotonCandidatesRekeyer; @@ -404,6 +425,16 @@ namespace pat { // ---- link to PackedPFCandidates edm::RefProd packedPFCandidates_; std::vector associatedPackedFCandidateIndices_; + + // ---- scouting-specific members ---- + bool isScoutingPhoton_{false}; + uint32_t scoutingSeedId_{0}; + uint32_t scoutingNClusters_{0}; + uint32_t scoutingNCrystals_{0}; + std::vector scoutingEnergyMatrix_; + std::vector scoutingTimingMatrix_; + std::vector scoutingDetIds_; + bool scoutingRechitZeroSuppression_{false}; }; } // namespace pat diff --git a/DataFormats/PatCandidates/interface/ScoutingDataHandling.h b/DataFormats/PatCandidates/interface/ScoutingDataHandling.h new file mode 100644 index 0000000000000..2b63161237659 --- /dev/null +++ b/DataFormats/PatCandidates/interface/ScoutingDataHandling.h @@ -0,0 +1,28 @@ +#ifndef DataFormats_PatCandidates_ScoutingDataHandling_h +#define DataFormats_PatCandidates_ScoutingDataHandling_h + +#include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/VertexReco/interface/Vertex.h" +#include "DataFormats/Scouting/interface/Run3ScoutingTrack.h" +#include "DataFormats/PatCandidates/interface/Muon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingMuon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingVertex.h" + +namespace pat { + typedef reco::Candidate::LorentzVector LorentzVector; + typedef reco::Candidate::PolarLorentzVector PolarLorentzVector; + + // Tracks + reco::Track makeRecoTrack(const Run3ScoutingTrack&); + reco::Track makeRecoTrack(const Run3ScoutingMuon&); + Run3ScoutingTrack makeScoutingTrack(const reco::Track&); + PolarLorentzVector makePolarLorentzVector(const Run3ScoutingTrack&, float mass); + + // Muons + pat::Muon makePatMuon(const Run3ScoutingMuon&); + + // Vertex + reco::Vertex makeRecoVertex(const Run3ScoutingVertex&); +} // namespace pat + +#endif diff --git a/DataFormats/PatCandidates/src/Electron.cc b/DataFormats/PatCandidates/src/Electron.cc index b115c3e7af54d..5ed4e19000907 100644 --- a/DataFormats/PatCandidates/src/Electron.cc +++ b/DataFormats/PatCandidates/src/Electron.cc @@ -2,10 +2,12 @@ // #include "DataFormats/PatCandidates/interface/Electron.h" +#include "DataFormats/Scouting/interface/Run3ScoutingElectron.h" #include "FWCore/Utilities/interface/Exception.h" #include "DataFormats/Common/interface/RefToPtr.h" #include +#include using namespace pat; @@ -81,6 +83,104 @@ Electron::Electron(const edm::Ptr& anElectronRef) initImpactParameters(); } +/// constructor from Run3ScoutingElectron +Electron::Electron(const Run3ScoutingElectron& sElec) + : Lepton(), + embeddedGsfElectronCore_(false), + embeddedGsfTrack_(false), + embeddedSuperCluster_(false), + embeddedPflowSuperCluster_(false), + embeddedTrack_(false), + embeddedSeedCluster_(false), + embeddedRecHits_(false), + embeddedPFCandidate_(false), + ecalDrivenMomentum_(Candidate::LorentzVector(0., 0., 0., 0.)), + ecalRegressionEnergy_(0.0), + ecalTrackRegressionEnergy_(0.0), + ecalRegressionError_(0.0), + ecalTrackRegressionError_(0.0), + ecalScale_(-99999.), + ecalSmear_(-99999.), + ecalRegressionScale_(-99999.), + ecalRegressionSmear_(-99999.), + ecalTrackRegressionScale_(-99999.), + ecalTrackRegressionSmear_(-99999.), + packedPFCandidates_(), + associatedPackedFCandidateIndices_() { + initImpactParameters(); + isScoutingElectron_ = true; + + // Set kinematics + float px = sElec.pt() * std::cos(sElec.phi()); + float py = sElec.pt() * std::sin(sElec.phi()); + float pz = sElec.pt() * std::sinh(sElec.eta()); + float energy = std::sqrt(px * px + py * py + pz * pz + sElec.m() * sElec.m()); + reco::GsfElectron::LorentzVector p4(px, py, pz, energy); + + // Get charge from first track + int charge = 0; + if (!sElec.trkcharge().empty()) { + charge = sElec.trkcharge()[0]; + } + + this->setCharge(charge); + this->setP4(p4); + this->setVertex(math::XYZPoint(0, 0, 0)); + + // Store shower shape variables as userFloats (GsfElectron native members not writable) + this->addUserFloat("sigmaIetaIeta", sElec.sigmaIetaIeta()); + this->addUserFloat("hOverE", sElec.hOverE()); + this->addUserFloat("r9", sElec.r9()); + this->addUserFloat("sMin", sElec.sMin()); + this->addUserFloat("sMaj", sElec.sMaj()); + + // Store ID variables + this->addUserFloat("dEtaIn", sElec.dEtaIn()); + this->addUserFloat("dPhiIn", sElec.dPhiIn()); + this->addUserFloat("ooEMOop", sElec.ooEMOop()); + this->addUserInt("missingHits", sElec.missingHits()); + + // Store track variables + this->addUserFloat("trackfbrem", sElec.trackfbrem()); + if (!sElec.trkd0().empty()) { + this->addUserFloat("trkd0", sElec.trkd0()[0]); + } + if (!sElec.trkdz().empty()) { + this->addUserFloat("trkdz", sElec.trkdz()[0]); + } + if (!sElec.trkpt().empty()) { + this->addUserFloat("trkpt", sElec.trkpt()[0]); + } + if (!sElec.trkchi2overndf().empty()) { + this->addUserFloat("trkchi2overndf", sElec.trkchi2overndf()[0]); + } + + // Store energy variables + this->addUserFloat("rawEnergy", sElec.rawEnergy()); + this->addUserFloat("preshowerEnergy", sElec.preshowerEnergy()); + this->addUserFloat("corrEcalEnergyError", sElec.corrEcalEnergyError()); + + // Store isolation + this->addUserFloat("ecalIso", sElec.ecalIso()); + this->addUserFloat("hcalIso", sElec.hcalIso()); + this->addUserFloat("trackIso", sElec.trackIso()); + + // Store scouting-specific track and ECAL information + scoutingTrkEta_ = sElec.trketa(); + scoutingTrkPhi_ = sElec.trkphi(); + scoutingTrkpMode_ = sElec.trkpMode(); + scoutingTrketaMode_ = sElec.trketaMode(); + scoutingTrkphiMode_ = sElec.trkphiMode(); + scoutingTrkqoverpModeError_ = sElec.trkqoverpModeError(); + scoutingSeedId_ = sElec.seedId(); + scoutingNClusters_ = sElec.nClusters(); + scoutingNCrystals_ = sElec.nCrystals(); + scoutingEnergyMatrix_ = sElec.energyMatrix(); + scoutingTimingMatrix_ = sElec.timingMatrix(); + scoutingDetIds_ = sElec.detIds(); + scoutingRechitZeroSuppression_ = sElec.rechitZeroSuppression(); +} + /// destructor Electron::~Electron() {} @@ -430,3 +530,80 @@ edm::RefVector Electron::associatedPackedPFCandi } return ret; } + +// ---- Universal accessors with scouting dispatch ---- + +float Electron::trkEta() const { + if (isScoutingElectron_) { + return !scoutingTrkEta_.empty() ? scoutingTrkEta_[0] : 0.f; + } + auto trk = gsfTrack(); + return trk.isNonnull() ? trk->eta() : 0.f; +} + +float Electron::trkPhi() const { + if (isScoutingElectron_) { + return !scoutingTrkPhi_.empty() ? scoutingTrkPhi_[0] : 0.f; + } + auto trk = gsfTrack(); + return trk.isNonnull() ? trk->phi() : 0.f; +} + +float Electron::trkpMode() const { + if (isScoutingElectron_) { + return !scoutingTrkpMode_.empty() ? scoutingTrkpMode_[0] : 0.f; + } + auto trk = gsfTrack(); + return trk.isNonnull() ? trk->pMode() : 0.f; +} + +float Electron::trketaMode() const { + if (isScoutingElectron_) { + return !scoutingTrketaMode_.empty() ? scoutingTrketaMode_[0] : 0.f; + } + auto trk = gsfTrack(); + return trk.isNonnull() ? trk->etaMode() : 0.f; +} + +float Electron::trkphiMode() const { + if (isScoutingElectron_) { + return !scoutingTrkphiMode_.empty() ? scoutingTrkphiMode_[0] : 0.f; + } + auto trk = gsfTrack(); + return trk.isNonnull() ? trk->phiMode() : 0.f; +} + +float Electron::trkqoverpModeError() const { + if (isScoutingElectron_) { + return !scoutingTrkqoverpModeError_.empty() ? scoutingTrkqoverpModeError_[0] : 0.f; + } + auto trk = gsfTrack(); + return trk.isNonnull() ? trk->qoverpModeError() : 0.f; +} + +uint32_t Electron::seedId() const { + if (isScoutingElectron_) { + return scoutingSeedId_; + } + auto sc = superCluster(); + if (sc.isNonnull() && sc->seed().isNonnull()) { + return sc->seed()->seed().rawId(); + } + return 0; +} + +uint32_t Electron::nClusters() const { + if (isScoutingElectron_) { + return scoutingNClusters_; + } + auto sc = superCluster(); + return sc.isNonnull() ? sc->clustersSize() : 0; +} + +uint32_t Electron::nCrystals() const { + if (isScoutingElectron_) { + return scoutingNCrystals_; + } + auto sc = superCluster(); + return sc.isNonnull() ? sc->size() : 0; +} diff --git a/DataFormats/PatCandidates/src/Jet.cc b/DataFormats/PatCandidates/src/Jet.cc index e3e7d790aaed3..7c4a62ced70a2 100644 --- a/DataFormats/PatCandidates/src/Jet.cc +++ b/DataFormats/PatCandidates/src/Jet.cc @@ -2,10 +2,13 @@ // #include "DataFormats/PatCandidates/interface/Jet.h" +#include "DataFormats/Scouting/interface/Run3ScoutingPFJet.h" #include "DataFormats/RecoCandidate/interface/RecoCaloTowerCandidate.h" #include "DataFormats/ParticleFlowCandidate/interface/PFCandidate.h" #include "FWCore/MessageLogger/interface/MessageLogger.h" +#include + using namespace pat; /// default constructor @@ -38,6 +41,62 @@ Jet::Jet(const edm::RefToBase& aJetRef) : Jet(*aJetRef) { /// constructure from ref to pat::Jet Jet::Jet(const edm::Ptr& aJetRef) : Jet(*aJetRef) { refToOrig_ = aJetRef; } +/// constructor from Run3ScoutingPFJet +Jet::Jet(const Run3ScoutingPFJet& sJet) + : PATObject(reco::Jet()), embeddedCaloTowers_(false), embeddedPFCandidates_(false), jetCharge_(0.) { + // Set kinematics + float px = sJet.pt() * std::cos(sJet.phi()); + float py = sJet.pt() * std::sin(sJet.phi()); + float pz = sJet.pt() * std::sinh(sJet.eta()); + float energy = std::sqrt(px * px + py * py + pz * pz + sJet.m() * sJet.m()); + reco::Particle::LorentzVector p4(px, py, pz, energy); + + this->setP4(p4); + this->setVertex(math::XYZPoint(0, 0, 0)); + this->setJetArea(sJet.jetArea()); + + // Build PFJet::Specific + reco::PFJet::Specific pfSpecific; + pfSpecific.mChargedHadronEnergy = sJet.chargedHadronEnergy(); + pfSpecific.mNeutralHadronEnergy = sJet.neutralHadronEnergy(); + pfSpecific.mPhotonEnergy = sJet.photonEnergy(); + pfSpecific.mElectronEnergy = sJet.electronEnergy(); + pfSpecific.mMuonEnergy = sJet.muonEnergy(); + pfSpecific.mHFHadronEnergy = sJet.HFHadronEnergy(); + pfSpecific.mHFEMEnergy = sJet.HFEMEnergy(); + + pfSpecific.mChargedHadronMultiplicity = sJet.chargedHadronMultiplicity(); + pfSpecific.mNeutralHadronMultiplicity = sJet.neutralHadronMultiplicity(); + pfSpecific.mPhotonMultiplicity = sJet.photonMultiplicity(); + pfSpecific.mElectronMultiplicity = sJet.electronMultiplicity(); + pfSpecific.mMuonMultiplicity = sJet.muonMultiplicity(); + pfSpecific.mHFHadronMultiplicity = sJet.HFHadronMultiplicity(); + pfSpecific.mHFEMMultiplicity = sJet.HFEMMultiplicity(); + + pfSpecific.mHOEnergy = sJet.HOEnergy(); + + pfSpecific.mChargedEmEnergy = sJet.electronEnergy(); + pfSpecific.mChargedMuEnergy = sJet.muonEnergy(); + pfSpecific.mNeutralEmEnergy = sJet.photonEnergy() + sJet.HFEMEnergy(); + + int chargedMultiplicity = sJet.chargedHadronMultiplicity() + sJet.electronMultiplicity() + sJet.muonMultiplicity(); + int neutralMultiplicity = sJet.neutralHadronMultiplicity() + sJet.photonMultiplicity() + sJet.HFHadronMultiplicity() + + sJet.HFEMMultiplicity(); + + pfSpecific.mChargedMultiplicity = chargedMultiplicity; + pfSpecific.mNeutralMultiplicity = neutralMultiplicity; + + specificPF_.push_back(pfSpecific); + + // Add b-tagging discriminators + this->addBDiscriminatorPair(std::make_pair("pfCombinedSecondaryVertexV2BJetTags", sJet.csv())); + this->addBDiscriminatorPair(std::make_pair("pfDeepCSVJetTags:probb", sJet.mvaDiscriminator())); + + // Also store as userFloats for convenience + this->addUserFloat("csv", sJet.csv()); + this->addUserFloat("mvaDiscriminator", sJet.mvaDiscriminator()); +} + std::ostream& reco::operator<<(std::ostream& out, const pat::Jet& obj) { if (!out) return out; diff --git a/DataFormats/PatCandidates/src/Muon.cc b/DataFormats/PatCandidates/src/Muon.cc index cfdaa2253d900..73d2d71a8df1c 100644 --- a/DataFormats/PatCandidates/src/Muon.cc +++ b/DataFormats/PatCandidates/src/Muon.cc @@ -5,6 +5,9 @@ #include "DataFormats/MuonReco/interface/MuonSelectors.h" #include "FWCore/Utilities/interface/Exception.h" #include "DataFormats/Common/interface/RefToPtr.h" +#include "DataFormats/Scouting/interface/Run3ScoutingMuon.h" +#include "DataFormats/PatCandidates/interface/ScoutingDataHandling.h" +#include "DataFormats/TrackReco/interface/HitPattern.h" #include using namespace pat; @@ -129,6 +132,86 @@ Muon::Muon(const edm::Ptr& aMuonRef) initSimInfo(); } +/// constructor from reco::Muon +Muon::Muon(const Run3ScoutingMuon& aMuon) + : Lepton(), + embeddedMuonBestTrack_(false), + embeddedTunePMuonBestTrack_(false), + embeddedTrack_(false), + embeddedStandAloneMuon_(false), + embeddedCombinedMuon_(false), + embeddedTCMETMuonCorrs_(false), + embeddedCaloMETMuonCorrs_(false), + embeddedPickyMuon_(false), + embeddedTpfmsMuon_(false), + embeddedDytMuon_(false), + embeddedPFCandidate_(false), + pfCandidateRef_(), + cachedNormChi2_(false), + normChi2_(0.0), + cachedNumberOfValidHits_(false), + numberOfValidHits_(0), + pfEcalEnergy_(0), + jetPtRatio_(0), + jetPtRel_(0), + mvaIDValue_(0), + softMvaValue_(0), + inverseBeta_(0), + inverseBetaErr_(0) { + initImpactParameters(); + initSimInfo(); + + reco::Candidate::PolarLorentzVector p4(aMuon.pt(), aMuon.eta(), aMuon.phi(), aMuon.m()); + auto track = makeRecoTrack(aMuon); + + this->setCharge(aMuon.charge()); + this->setP4(reco::Particle::LorentzVector(p4)); + this->setVertex(track.vertex()); + + // Set muon type from scouting flags - always set ScoutingMuon bit + unsigned int muonType = reco::Muon::ScoutingMuon; + if (aMuon.isGlobalMuon()) + muonType |= reco::Muon::GlobalMuon; + if (aMuon.isTrackerMuon()) + muonType |= reco::Muon::TrackerMuon; + this->setType(muonType); + + // Directly embed the track without creating a temporary TrackRef + // This avoids the transient reference issue that prevents serialization + combinedMuon_.clear(); + combinedMuon_.push_back(track); + embeddedCombinedMuon_ = true; + this->setBestTrack(reco::Muon::CombinedTrack); + + // Set isolation from scouting muon (call reco::Muon method directly) + reco::MuonIsolation isolation; + isolation.sumPt = aMuon.trackIso(); + isolation.emEt = aMuon.ecalIso(); + isolation.hadEt = aMuon.hcalIso(); + reco::Muon::setIsolation(isolation, isolation); + + this->setMiniPFIsolation(pat::PFIsolation()); + + // Store scouting-specific muon information directly + scoutingNChambers_ = aMuon.nRecoMuonChambers(); + scoutingNChambersCSCorDT_ = aMuon.nRecoMuonChambersCSCorDT(); + scoutingNMatches_ = aMuon.nRecoMuonMatches(); + scoutingNMatchedStations_ = aMuon.nRecoMuonMatchedStations(); + scoutingExpectedMatchedStations_ = aMuon.nRecoMuonExpectedMatchedStations(); + scoutingStationMask_ = aMuon.recoMuonStationMask(); + scoutingNMatchedRPCLayers_ = aMuon.nRecoMuonMatchedRPCLayers(); + scoutingRPCLayerMask_ = aMuon.recoMuonRPClayerMask(); + scoutingNValidMuonHits_ = aMuon.nValidRecoMuonHits(); + scoutingNValidStandAloneMuonHits_ = aMuon.nValidStandAloneMuonHits(); + scoutingNStandAloneMuonMatchedStations_ = aMuon.nStandAloneMuonMatchedStations(); + // Cache normalizedChi2 from scouting muon + cachedNormChi2_ = true; + normChi2_ = aMuon.normalizedChi2(); + + // Store hit pattern + scoutingTrkHitPattern_ = reco::HitPattern(aMuon.trk_hitPattern()); +} + /// destructor Muon::~Muon() {} @@ -546,3 +629,119 @@ bool Muon::isMediumMuon() const { return muon::isMediumMuon(*this); } bool Muon::isSoftMuon(const reco::Vertex& vtx) const { return muon::isSoftMuon(*this, vtx); } bool Muon::isHighPtMuon(const reco::Vertex& vtx) const { return muon::isHighPtMuon(*this, vtx); } + +// ---- Methods that hide reco::Muon for scouting muon compatibility ---- + +int Muon::numberOfChambers() const { + if (isScoutingMuon()) { + return scoutingNChambers_; + } + return reco::Muon::numberOfChambers(); +} + +int Muon::numberOfChambersCSCorDT() const { + if (isScoutingMuon()) { + return scoutingNChambersCSCorDT_; + } + return reco::Muon::numberOfChambersCSCorDT(); +} + +int Muon::numberOfMatches(reco::Muon::ArbitrationType type) const { + if (isScoutingMuon()) { + return scoutingNMatches_; + } + return reco::Muon::numberOfMatches(type); +} + +int Muon::numberOfMatchedStations(reco::Muon::ArbitrationType type) const { + if (isScoutingMuon()) { + return scoutingNMatchedStations_; + } + return reco::Muon::numberOfMatchedStations(type); +} + +unsigned int Muon::stationMask(reco::Muon::ArbitrationType type) const { + if (isScoutingMuon()) { + return scoutingStationMask_; + } + return reco::Muon::stationMask(type); +} + +unsigned int Muon::expectedNnumberOfMatchedStations(float minDistanceFromEdge) const { + if (isScoutingMuon()) { + return scoutingExpectedMatchedStations_; + } + return reco::Muon::expectedNnumberOfMatchedStations(minDistanceFromEdge); +} + +int Muon::numberOfMatchedRPCLayers(reco::Muon::ArbitrationType type) const { + if (isScoutingMuon()) { + return scoutingNMatchedRPCLayers_; + } + return reco::Muon::numberOfMatchedRPCLayers(type); +} + +unsigned int Muon::RPClayerMask(reco::Muon::ArbitrationType type) const { + if (isScoutingMuon()) { + return scoutingRPCLayerMask_; + } + return reco::Muon::RPClayerMask(type); +} + +// ---- Hit information accessors ---- + +int Muon::numberOfValidMuonHits() const { + if (isScoutingMuon()) { + return scoutingNValidMuonHits_; + } + // For standard muons, get from global track hit pattern + reco::TrackRef glbTrack = globalTrack(); + if (glbTrack.isNonnull()) { + return glbTrack->hitPattern().numberOfValidMuonHits(); + } + return 0; +} + +int Muon::numberOfValidStandAloneMuonHits() const { + if (isScoutingMuon()) { + return scoutingNValidStandAloneMuonHits_; + } + // For standard muons, get from outer track hit pattern + reco::TrackRef saTrack = standAloneMuon(); + if (saTrack.isNonnull()) { + return saTrack->hitPattern().numberOfValidMuonHits(); + } + return 0; +} + +int Muon::numberOfStandAloneMuonMatchedStations() const { + if (isScoutingMuon()) { + return scoutingNStandAloneMuonMatchedStations_; + } + // For standard muons, get from outer track hit pattern + reco::TrackRef saTrack = standAloneMuon(); + if (saTrack.isNonnull()) { + return saTrack->hitPattern().muonStationsWithValidHits(); + } + return 0; +} + +int Muon::numberOfValidPixelHits() const { return trkHitPattern().numberOfValidPixelHits(); } + +int Muon::numberOfValidStripHits() const { return trkHitPattern().numberOfValidStripHits(); } + +int Muon::numberOfPixelLayersWithMeasurement() const { return trkHitPattern().pixelLayersWithMeasurement(); } + +int Muon::numberOfTrackerLayersWithMeasurement() const { return trkHitPattern().trackerLayersWithMeasurement(); } + +reco::HitPattern const& Muon::trkHitPattern() const { + if (isScoutingMuon()) { + return scoutingTrkHitPattern_; + } + const reco::Track* trk = bestTrack(); + if (trk) { + return trk->hitPattern(); + } + static const reco::HitPattern empty; + return empty; +} diff --git a/DataFormats/PatCandidates/src/Photon.cc b/DataFormats/PatCandidates/src/Photon.cc index cb72aeca7c9e7..a1496f462320f 100644 --- a/DataFormats/PatCandidates/src/Photon.cc +++ b/DataFormats/PatCandidates/src/Photon.cc @@ -2,8 +2,11 @@ // #include "DataFormats/PatCandidates/interface/Photon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingPhoton.h" #include "DataFormats/Common/interface/RefToPtr.h" +#include + using pat::Photon; /// default constructor @@ -154,6 +157,90 @@ Photon::Photon(const edm::Ptr& aPhotonRef) iEta_(-999), iPhi_(-999) {} +// Helper to create reco::Photon from scouting photon +namespace { + reco::Photon makeRecoPhoton(const Run3ScoutingPhoton& sPhoton) { + float px = sPhoton.pt() * std::cos(sPhoton.phi()); + float py = sPhoton.pt() * std::sin(sPhoton.phi()); + float pz = sPhoton.pt() * std::sinh(sPhoton.eta()); + float energy = std::sqrt(px * px + py * py + pz * pz + sPhoton.m() * sPhoton.m()); + reco::Photon::LorentzVector p4(px, py, pz, energy); + reco::Photon::Point caloPos(0, 0, 0); + reco::Photon::Point vtx(0, 0, 0); + return reco::Photon(p4, caloPos, reco::PhotonCoreRef(), vtx); + } +} // namespace + +/// constructor from Run3ScoutingPhoton +Photon::Photon(const Run3ScoutingPhoton& sPhoton) + : PATObject(makeRecoPhoton(sPhoton)), + embeddedSuperCluster_(false), + embeddedSeedCluster_(false), + embeddedRecHits_(false), + passElectronVeto_(false), + hasPixelSeed_(false), + seedEnergy_(0.0), + eMax_(0.0), + e2nd_(0.0), + e3x3_(0.0), + eTop_(0.0), + eBottom_(0.0), + eLeft_(0.0), + eRight_(0.0), + see_(-999.), + spp_(-999.), + sep_(-999.), + maxDR_(-999.), + maxDRDPhi_(-999.), + maxDRDEta_(-999.), + maxDRRawEnergy_(-999.), + subClusRawE1_(-999.), + subClusRawE2_(-999.), + subClusRawE3_(-999.), + subClusDPhi1_(-999.), + subClusDPhi2_(-999.), + subClusDPhi3_(-999.), + subClusDEta1_(-999.), + subClusDEta2_(-999.), + subClusDEta3_(-999.), + cryEta_(-999.), + cryPhi_(-999), + iEta_(-999), + iPhi_(-999) { + isScoutingPhoton_ = true; + + // Store shower shape variables as userFloats + this->addUserFloat("sigmaIetaIeta", sPhoton.sigmaIetaIeta()); + this->addUserFloat("hOverE", sPhoton.hOverE()); + this->addUserFloat("r9", sPhoton.r9()); + this->addUserFloat("sMin", sPhoton.sMin()); + this->addUserFloat("sMaj", sPhoton.sMaj()); + + // Store energy variables + this->addUserFloat("rawEnergy", sPhoton.rawEnergy()); + this->addUserFloat("preshowerEnergy", sPhoton.preshowerEnergy()); + this->addUserFloat("corrEcalEnergyError", sPhoton.corrEcalEnergyError()); + + // Store isolation as userFloats (for NanoAOD access) + this->addUserFloat("ecalIso", sPhoton.ecalIso()); + this->addUserFloat("hcalIso", sPhoton.hcalIso()); + this->addUserFloat("trkIso", sPhoton.trkIso()); + + // Also set isolation using PAT isolation keys + this->setIsolation(pat::TrackIso, sPhoton.trkIso()); + this->setIsolation(pat::EcalIso, sPhoton.ecalIso()); + this->setIsolation(pat::HcalIso, sPhoton.hcalIso()); + + // Store scouting-specific ECAL crystal and rechit information + scoutingSeedId_ = sPhoton.seedId(); + scoutingNClusters_ = sPhoton.nClusters(); + scoutingNCrystals_ = sPhoton.nCrystals(); + scoutingEnergyMatrix_ = sPhoton.energyMatrix(); + scoutingTimingMatrix_ = sPhoton.timingMatrix(); + scoutingDetIds_ = sPhoton.detIds(); + scoutingRechitZeroSuppression_ = sPhoton.rechitZeroSuppression(); +} + /// destructor Photon::~Photon() {} @@ -305,3 +392,32 @@ reco::CandidatePtr Photon::sourceCandidatePtr(size_type i) const { edm::Ref(packedPFCandidates_, associatedPackedFCandidateIndices_[i]))); } } + +// ---- Universal accessors with scouting dispatch ---- + +uint32_t Photon::seedId() const { + if (isScoutingPhoton_) { + return scoutingSeedId_; + } + auto sc = superCluster(); + if (sc.isNonnull() && sc->seed().isNonnull()) { + return sc->seed()->seed().rawId(); + } + return 0; +} + +uint32_t Photon::nClusters() const { + if (isScoutingPhoton_) { + return scoutingNClusters_; + } + auto sc = superCluster(); + return sc.isNonnull() ? sc->clustersSize() : 0; +} + +uint32_t Photon::nCrystals() const { + if (isScoutingPhoton_) { + return scoutingNCrystals_; + } + auto sc = superCluster(); + return sc.isNonnull() ? sc->size() : 0; +} diff --git a/DataFormats/PatCandidates/src/ScoutingDataHandling.cc b/DataFormats/PatCandidates/src/ScoutingDataHandling.cc new file mode 100644 index 0000000000000..70eecf3102d8d --- /dev/null +++ b/DataFormats/PatCandidates/src/ScoutingDataHandling.cc @@ -0,0 +1,130 @@ +#include "DataFormats/PatCandidates/interface/ScoutingDataHandling.h" +#include "DataFormats/Math/interface/Error.h" +#include "DataFormats/Math/interface/Point3D.h" +#include "DataFormats/Math/interface/libminifloat.h" + +float reduce_precision(float f, int mantissaPrecision = 10) { + return MiniFloatConverter::reduceMantissaToNbitsRounding(f, mantissaPrecision); +} + +reco::Track pat::makeRecoTrack(const Run3ScoutingTrack& sTrack) { + reco::Track::Point v(sTrack.tk_vx(), sTrack.tk_vy(), sTrack.tk_vz()); + reco::Track::Vector p(math::RhoEtaPhiVector(sTrack.tk_pt(), sTrack.tk_eta(), sTrack.tk_phi())); + + reco::TrackBase::CovarianceMatrix cov; + cov(0, 0) = pow(sTrack.tk_qoverp_Error(), 2); + cov(0, 1) = sTrack.tk_qoverp_lambda_cov(); + cov(0, 2) = sTrack.tk_qoverp_phi_cov(); + cov(0, 3) = sTrack.tk_qoverp_dxy_cov(); + cov(0, 4) = sTrack.tk_qoverp_dsz_cov(); + cov(1, 1) = pow(sTrack.tk_lambda_Error(), 2); + cov(1, 2) = sTrack.tk_lambda_phi_cov(); + cov(1, 3) = sTrack.tk_lambda_dxy_cov(); + cov(1, 4) = sTrack.tk_lambda_dsz_cov(); + cov(2, 2) = pow(sTrack.tk_phi_Error(), 2); + cov(2, 3) = sTrack.tk_phi_dxy_cov(); + cov(2, 4) = sTrack.tk_phi_dsz_cov(); + cov(3, 3) = pow(sTrack.tk_dxy_Error(), 2); + cov(3, 4) = sTrack.tk_dxy_dsz_cov(); + cov(4, 4) = pow(sTrack.tk_dsz_Error(), 2); + + return reco::Track(sTrack.tk_chi2(), sTrack.tk_ndof(), v, p, sTrack.tk_charge(), cov); +} + +reco::Track pat::makeRecoTrack(const Run3ScoutingMuon& sMuon) { + reco::Track::Point vtx(sMuon.trk_vx(), sMuon.trk_vy(), sMuon.trk_vz()); + reco::Track::Vector p3(math::RhoEtaPhiVector(sMuon.trk_pt(), sMuon.trk_eta(), sMuon.trk_phi())); + + reco::TrackBase::CovarianceMatrix cov; + cov(0, 0) = pow(sMuon.trk_qoverpError(), 2); + cov(0, 1) = sMuon.trk_qoverp_lambda_cov(); + cov(0, 2) = sMuon.trk_qoverp_phi_cov(); + cov(0, 3) = sMuon.trk_qoverp_dxy_cov(); + cov(0, 4) = sMuon.trk_qoverp_dsz_cov(); + cov(1, 1) = pow(sMuon.trk_lambdaError(), 2); + cov(1, 2) = sMuon.trk_lambda_phi_cov(); + cov(1, 3) = sMuon.trk_lambda_dxy_cov(); + cov(1, 4) = sMuon.trk_lambda_dsz_cov(); + cov(2, 2) = pow(sMuon.trk_phiError(), 2); + cov(2, 3) = sMuon.trk_phi_dxy_cov(); + cov(2, 4) = sMuon.trk_phi_dsz_cov(); + cov(3, 3) = pow(sMuon.trk_dxyError(), 2); + cov(3, 4) = sMuon.trk_dxy_dsz_cov(); + cov(4, 4) = pow(sMuon.trk_dszError(), 2); + + return reco::Track(sMuon.trk_chi2(), sMuon.trk_ndof(), vtx, p3, sMuon.charge(), cov); +} + +Run3ScoutingTrack pat::makeScoutingTrack(const reco::Track& trk) { + return Run3ScoutingTrack(reduce_precision(trk.pt()), + reduce_precision(trk.eta()), + reduce_precision(trk.phi()), + reduce_precision(trk.chi2()), + trk.ndof(), + trk.charge(), + reduce_precision(trk.dxy()), + reduce_precision(trk.dz()), + trk.hitPattern().numberOfValidPixelHits(), + trk.hitPattern().trackerLayersWithMeasurement(), + trk.hitPattern().numberOfValidStripHits(), + reduce_precision(trk.qoverp()), + reduce_precision(trk.lambda()), + reduce_precision(trk.dxyError()), + reduce_precision(trk.dzError()), + reduce_precision(trk.qoverpError()), + reduce_precision(trk.lambdaError()), + reduce_precision(trk.phiError()), + reduce_precision(trk.dsz()), + reduce_precision(trk.dszError()), + reduce_precision(trk.covariance(0, 1)), + reduce_precision(trk.covariance(0, 2)), + reduce_precision(trk.covariance(0, 3)), + reduce_precision(trk.covariance(0, 4)), + reduce_precision(trk.covariance(1, 2)), + reduce_precision(trk.covariance(1, 3)), + reduce_precision(trk.covariance(1, 4)), + reduce_precision(trk.covariance(2, 3)), + reduce_precision(trk.covariance(2, 4)), + reduce_precision(trk.covariance(3, 4)), + 0, + reduce_precision(trk.vx()), + reduce_precision(trk.vy()), + reduce_precision(trk.vz())); +} + +pat::Muon pat::makePatMuon(const Run3ScoutingMuon& sMuon) { + reco::Candidate::PolarLorentzVector p4(sMuon.pt(), sMuon.eta(), sMuon.phi(), sMuon.m()); + + auto track = makeRecoTrack(sMuon); + + pat::Muon muon(reco::Muon(track.charge(), reco::Candidate::LorentzVector(p4), track.vertex())); + + muon.setType(sMuon.type()); + + std::vector tracks; + tracks.push_back(track); + muon.setGlobalTrack(reco::TrackRef(&tracks, 0)); + muon.embedCombinedMuon(); + muon.setBestTrack(reco::Muon::CombinedTrack); + + return muon; +} + +reco::Vertex pat::makeRecoVertex(const Run3ScoutingVertex& sVertex) { + reco::Vertex::Error err; + err(0, 0) = pow(sVertex.xError(), 2); + err(1, 1) = pow(sVertex.yError(), 2); + err(2, 2) = pow(sVertex.zError(), 2); + err(0, 1) = sVertex.xyCov(); + err(0, 2) = sVertex.xzCov(); + err(1, 2) = sVertex.yzCov(); + return reco::Vertex(reco::Vertex::Point(sVertex.x(), sVertex.y(), sVertex.z()), + err, + sVertex.chi2(), + sVertex.ndof(), + sVertex.tracksSize()); +} + +pat::PolarLorentzVector pat::makePolarLorentzVector(const Run3ScoutingTrack& trk, float mass) { + return PolarLorentzVector(trk.tk_pt(), trk.tk_eta(), trk.tk_phi(), mass); +} diff --git a/DataFormats/PatCandidates/src/classes_def_objects.xml b/DataFormats/PatCandidates/src/classes_def_objects.xml index 1be32aeba5dc0..7f6e91dbffe48 100644 --- a/DataFormats/PatCandidates/src/classes_def_objects.xml +++ b/DataFormats/PatCandidates/src/classes_def_objects.xml @@ -16,7 +16,8 @@ - + + @@ -63,7 +64,8 @@ - + + @@ -214,7 +216,8 @@ - + + diff --git a/PhysicsTools/PatFromScouting/README.md b/PhysicsTools/PatFromScouting/README.md new file mode 100644 index 0000000000000..d96a59fd12236 --- /dev/null +++ b/PhysicsTools/PatFromScouting/README.md @@ -0,0 +1,347 @@ +# Scouting to MiniAOD/NanoAOD Conversion + +This package converts Run3 scouting data to MiniAOD-compatible format, enabling NanoAOD production from scouting data. + +## Overview + +``` +Scouting RAW → Scouting MiniAOD → Scouting NanoAOD + (Step 1: PAT) (Step 2: NANO) +``` + +The workflow produces NanoAOD with standard branch naming (Muon_pt, Jet_eta, etc.) from scouting data, enabling analysis with standard NanoAOD tools. + +## Quick Start + +### Two-Step Production Workflow with cmsDriver + +**Step 1: Scouting HLTSCOUT → MiniAOD** +```bash +cmsDriver.py scoutMini \ + --scenario pp \ + --conditions auto:run3_data_prompt \ + --era Run3_2024 \ + --eventcontent MINIAOD \ + --datatier MINIAOD \ + --step PAT:@Scout \ + --filein file:scouting.root \ + --fileout file:scoutingMiniAOD.root \ + --data -n 100 +``` + +**Step 2: MiniAOD → NanoAOD** + +Use our custom NanoAOD tables that read from PAT objects: +```bash +cmsDriver.py scoutNano \ + --scenario pp \ + --conditions auto:run3_data_prompt \ + --era Run3_2024 \ + --eventcontent NANOAOD \ + --datatier NANOAOD \ + --step NANO:@ScoutMini \ + --filein file:scoutingMiniAOD.root \ + --fileout file:scoutingNanoAOD.root \ + --processName NANO \ + --data -n 100 +``` + +Note: +- Use `--era Run3_2025` for 2025 data +- The `--processName NANO` avoids conflicts with the MiniAOD process name +- `@Scout` and `@ScoutMini` flavors are defined in `autoPAT.py` +- For 2024+ data, the muon producer automatically switches to `hltScoutingMuonPackerVtx` via the `run3_scouting_2024` era modifier + +### Test Configurations (for development) + +```bash +# Step 1 test +cmsRun PhysicsTools/PatFromScouting/test/test_scoutingToMiniAOD_cfg.py + +# Step 2 test +cmsRun PhysicsTools/PatFromScouting/test/test_scoutingNanoAOD_cfg.py + +# Standard NanoAOD on scouting MiniAOD (with customizations) +cmsRun PhysicsTools/PatFromScouting/test/test_standardNanoAOD_cfg.py +``` + +## MiniAOD Collections + +| Collection Name | Type | Description | +|-----------------|------|-------------| +| `packedPFCandidates` | `pat::PackedCandidateCollection` | PF candidates from scouting particles | +| `offlineSlimmedPrimaryVertices` | `reco::VertexCollection` | Primary vertices | +| `slimmedMuons` | `pat::MuonCollection` | Muons with isolation and track info | +| `slimmedElectrons` | `pat::ElectronCollection` | Electrons with shower shape in userFloats | +| `slimmedPhotons` | `pat::PhotonCollection` | Photons with shower shape in userFloats | +| `slimmedJets` | `pat::JetCollection` | AK4 PF jets with b-tagging | +| `slimmedMETs` | `pat::METCollection` | MET from scouting PF data | +| `scoutingTracks` | `reco::TrackCollection` | Tracks with hit info as ValueMaps | +| `offlineBeamSpot` | `reco::BeamSpot` | From conditions database | +| `fixedGridRhoFastjetAll` | `double` | Rho copied from HLT scouting | +| `gtStage2Digis` | `GlobalAlgBlkBxCollection` | L1 trigger decisions (unpacked from raw) | +| `gmtStage2Digis` | `l1t::MuonBxCollection` | L1 muons (copied from gtStage2Digis) | +| `caloStage2Digis` | `l1t::Jet/EGamma/Tau/EtSumBxCollection` | L1 calo objects (copied from gtStage2Digis) | + +## NanoAOD Tables + +| Category | Key Variables | +|----------|---------------| +| **Muon** | pt, eta, phi, mass, charge, pdgId, dxy, dz, trkChi2, nValidHits, isGlobal, isTracker, isStandalone, isPF, nStations, nTrackerLayers, nPixelLayers, nChambers, nChambersCSCorDT, nValidMuonHits, nValidPixelHits, nValidStripHits, relIso, ecalIso, hcalIso, trkIso, tkRelIso | +| **Electron** | pt, eta, phi, mass, charge, pdgId, dxy, dz, sieie, hoe, dEtaIn, dPhiIn, pfRelIso03_all, ecalIso, hcalIso, trkIso | +| **Photon** | pt, eta, phi, mass, sieie, hoe, ecalIso, hcalIso | +| **Jet** | pt, eta, phi, mass, area, chHEF, neHEF, chEmEF, neEmEF, muEF, nConstituents, chMultiplicity, neMultiplicity, btagCSV, btagDeepB | +| **MET** | pt, phi, sumEt | +| **PV** | x, y, z, xErr, yErr, zErr, chi2, ndof | +| **Event** | fixedGridRhoFastjetAll | +| **HLT** | All HLT trigger decisions (HLT_*, DST_*) | +| **L1** | All L1 trigger seeds (L1_*) | + +## What's Available vs. Standard MiniAOD/NanoAOD + +### Available (Full Support) + +| Feature | Notes | +|---------|-------| +| Muon kinematics | pt, eta, phi, mass, charge | +| Muon track quality | chi2, nValidHits, dxy, dz | +| Muon ID flags | isGlobal, isTracker, isStandalone, isPF | +| Muon isolation | ecalIso, hcalIso, trkIso (calorimeter-based, not PF components) | +| Muon station/layer counts | nStations, nTrackerLayers, nPixelLayers, nChambers | +| Muon hit counts | nValidMuonHits, nValidPixelHits, nValidStripHits | +| Electron kinematics | pt, eta, phi, mass, charge | +| Electron shower shape | sigmaIetaIeta, H/E, dEtaIn, dPhiIn | +| Electron isolation | ecalIso, hcalIso, trackIso, PF relIso | +| Photon kinematics | pt, eta, phi | +| Photon shower shape | sigmaIetaIeta, H/E | +| Photon isolation | ecalIso, hcalIso | +| Jet kinematics | pt, eta, phi, mass, area | +| Jet composition | Energy fractions (chHEF, neHEF, etc.), multiplicities | +| Jet b-tagging | CSV, DeepCSV (pre-computed at HLT) | +| MET | pt, phi, sumEt | +| Primary vertices | Position, errors, chi2, ndof | +| HLT triggers | All trigger decisions as booleans | +| L1 triggers | All L1 seeds as booleans | +| L1 objects | Muons, jets, EGamma, taus, EtSum (via gmtStage2Digis/caloStage2Digis) | +| Pileup density | fixedGridRhoFastjetAll from HLT | +| Beam spot | From conditions database | + +### Not Available (Scouting Limitations) + +| Feature | Reason | +|---------|--------| +| Jet constituents | Not stored in scouting format | +| Pileup Jet ID | Requires iterating over jet daughters | +| Deep flavor b-tagging | Requires track-level constituent info | +| ParticleNet/DeepJet | Requires full constituent info | +| AK8/Fat jets | Not produced in scouting | +| Tau leptons | Not reconstructed in scouting | +| HLT trigger objects | hltTriggerSummaryAOD not saved in scouting | +| GenParticles | Data only (no MC scouting) | +| MET corrections | No Type-1 corrections available (raw MET only) | +| Electron/Photon MVA ID | Requires full shower info | + +### Partially Available + +| Feature | What Works | What Doesn't | +|---------|------------|--------------| +| Electron track | d0, dz, chi2 | No GSF track reference | +| Photon R9 | Stored if available | May be 0 for some photons | +| Jet corrections | Jets already corrected at HLT | No JEC uncertainties | + +## Data Mapping Details + +### Muons +- Uses `pat::Muon(const Run3ScoutingMuon&)` constructor from DataFormats/PatCandidates +- Track info embedded directly (fix applied for persistence) +- Isolation mapped to `reco::MuonIsolation` structure (ecalIso, hcalIso, trkIso) +- Station and hit count information derived from scouting muon data + +### Electrons +Variables stored as userFloats (direct PAT electron construction requires detector references): +``` +sigmaIetaIeta, hOverE, r9, sMin, sMaj, seedId +dEtaIn, dPhiIn, ooEMOop, missingHits +trkd0, trkdz, trkpt, trketa, trkphi, trkchi2overndf +rawEnergy, preshowerEnergy, corrEcalEnergyError, trackfbrem +ecalIso, hcalIso, trackIso +``` + +### Photons +Variables stored as userFloats: +``` +sigmaIetaIeta, hOverE, r9, sMin, sMaj, seedId +rawEnergy, preshowerEnergy, corrEcalEnergyError +ecalIso, hcalIso, trkIso +``` + +### Jets +- Full `reco::PFJet::Specific` populated (energy fractions, multiplicities) +- B-discriminators: `pfCombinedSecondaryVertexV2BJetTags` (csv), `pfDeepCSVJetTags:probb` (mva) +- Jet area preserved + +### Vertices +- Uses `pat::makeRecoVertex()` helper from DataFormats/PatCandidates +- Only valid vertices included (`isValidVtx() == true`) +- Full covariance matrix preserved + +### MET +- Uses precomputed MET from scouting data (pfMetPt, pfMetPhi) + +### Tracks +- Uses `pat::makeRecoTrack()` helper +- Hit pattern info stored as ValueMaps (nValidPixelHits, nValidStripHits, nTrackerLayersWithMeasurement) + +### L1 Objects +- `gtStage2Digis`: L1 trigger decisions unpacked from raw FED data (`hltFEDSelectorL1`) +- `gmtStage2Digis`: L1 muons copied from gtStage2Digis Muon collection +- `caloStage2Digis`: L1 jets, EGamma, taus, EtSum copied from gtStage2Digis + +## File Structure + +``` +PhysicsTools/PatFromScouting/ +├── plugins/ +│ ├── BuildFile.xml +│ ├── PatFromScoutingMuonProducer.cc +│ ├── PatFromScoutingElectronProducer.cc +│ ├── PatFromScoutingPhotonProducer.cc +│ ├── PatFromScoutingJetProducer.cc +│ ├── Run3ScoutingVertexToRecoVertexProducer.cc +│ ├── Run3ScoutingMETProducer.cc +│ ├── Run3ScoutingTrackToRecoTrackProducer.cc +│ ├── Run3ScoutingParticleToPackedCandidateProducer.cc +│ ├── Run3ScoutingL1MuonProducer.cc +│ ├── Run3ScoutingL1CaloProducer.cc +│ └── ScoutingRhoProducer.cc +├── python/ +│ ├── autoPAT.py # cmsDriver flavor definitions (@Scout, @ScoutMini) +│ ├── scoutingToMiniAOD_cff.py # Step 1: RAW → MiniAOD producers +│ ├── scoutingMiniAOD_cff.py # Alternative MiniAOD config with rho variants +│ ├── scoutingNanoAOD_cff.py # Step 2: MiniAOD → NanoAOD tables +│ ├── nanoAOD_scouting_cff.py # Customizations for standard NanoAOD on scouting MiniAOD +│ └── nanoAOD_customizations_cff.py # Additional NanoAOD customization helpers +├── test/ +│ ├── BuildFile.xml +│ ├── test_scoutingToMiniAOD_cfg.py # Step 1 test config +│ ├── test_scoutingNanoAOD_cfg.py # Step 2 test config +│ ├── test_standardNanoAOD_cfg.py # Standard NanoAOD on scouting MiniAOD test +│ ├── test_catch2_PatFromScouting.cc # Unit tests +│ └── test_catch2_main.cc # Catch2 test main +└── README.md +``` + +## Python Configuration Reference + +### autoPAT.py + +Defines `@`-prefixed flavor mappings for `cmsDriver --step PAT:@... / NANO:@...`: + +| Flavor | Step | Description | +|--------|------|-------------| +| `@Scout` | PAT | Scouting HLTSCOUT → MiniAOD | +| `@ScoutVtx` | PAT | Same as `@Scout` but with vertex-aware muons (2024+) | +| `@ScoutMini` | NANO | Scouting MiniAOD → NanoAOD | + +### scoutingToMiniAOD_cff.py + +Producers for Step 1 (Scouting RAW → MiniAOD): + +```python +from PhysicsTools.PatFromScouting.scoutingToMiniAOD_cff import scoutingToMiniAODTask + +# Task includes all producers with standard MiniAOD names: +# packedPFCandidates, offlineSlimmedPrimaryVertices, offlineBeamSpot, +# slimmedMuons, slimmedElectrons, slimmedPhotons, slimmedJets, slimmedMETs, +# scoutingTracks, fixedGridRhoFastjetAll, gtStage2Digis, gmtStage2Digis, caloStage2Digis +``` + +### scoutingNanoAOD_cff.py + +NanoAOD tables for Step 2: + +```python +from PhysicsTools.PatFromScouting.scoutingNanoAOD_cff import customiseScoutingNanoAOD +process = customiseScoutingNanoAOD(process) +``` + +### nanoAOD_scouting_cff.py + +Customizations for running **standard NanoAOD** on scouting MiniAOD (disables modules that require unavailable information): + +```python +from PhysicsTools.PatFromScouting.nanoAOD_scouting_cff import customiseNanoForScoutingMiniAOD +process = customiseNanoForScoutingMiniAOD(process) +``` + +## Input Tags (HLT Defaults) + +| Producer | Default Input Tag | +|----------|-------------------| +| PF Candidates | `hltScoutingPFPacker` | +| Vertices | `hltScoutingPrimaryVertexPacker:primaryVtx` | +| Muons | `hltScoutingMuonPacker` (2024+: `hltScoutingMuonPackerVtx` via era modifier) | +| Electrons | `hltScoutingEgammaPacker` | +| Photons | `hltScoutingEgammaPacker` | +| Jets | `hltScoutingPFPacker` | +| Tracks | `hltScoutingTrackPacker` | +| L1 Raw | `hltFEDSelectorL1` | +| Rho | `hltScoutingPFPacker:rho` | + +## Dependencies + +- `DataFormats/PatCandidates` +- `DataFormats/Scouting` +- `DataFormats/METReco` +- `DataFormats/JetReco` +- `DataFormats/VertexReco` +- `DataFormats/TrackReco` +- `DataFormats/MuonReco` +- `PhysicsTools/NanoAOD` +- `L1Trigger/L1TGlobal` (for L1TRawToDigi) + +## Testing + +```bash +# Build +cd $CMSSW_BASE/src +scram b -j8 + +# Run unit tests +scram b runtests + +# Run full workflow +cmsRun PhysicsTools/PatFromScouting/test/test_scoutingToMiniAOD_cfg.py +cmsRun PhysicsTools/PatFromScouting/test/test_scoutingNanoAOD_cfg.py + +# Verify output +python3 -c " +import ROOT +f = ROOT.TFile.Open('scoutingNanoAOD_test.root') +t = f.Get('Events') +print(f'Events: {t.GetEntries()}, Branches: {len(t.GetListOfBranches())}') +" +``` + +## Known Issues and Workarounds + +### Standard NanoAOD Compatibility + +Running standard NanoAOD (`cmsDriver --step NANO`) on scouting MiniAOD requires the customizations in `nanoAOD_scouting_cff.py` because: + +1. **Jet update chains** - Standard NanoAOD tries to recompute jets from constituents +2. **Deep taggers** - Require constituent-level information +3. **Tau reconstruction** - Not available in scouting +4. **PPS protons** - Not in scouting data + +The custom scouting NanoAOD (`NANO:@ScoutMini`) bypasses these issues by producing tables directly from the scouting MiniAOD collections. + +### Muon Persistence Fix + +A fix was applied to `DataFormats/PatCandidates/src/Muon.cc` to enable persistence of `pat::Muon` objects created from `Run3ScoutingMuon`. The original constructor created a `TrackRef` to a temporary local vector which could not be serialized. + +## References + +- [Run3 Scouting Data Formats](https://github.com/cms-sw/cmssw/tree/master/DataFormats/Scouting) +- [PAT Scouting Helpers](https://github.com/cms-sw/cmssw/blob/master/DataFormats/PatCandidates/interface/ScoutingDataHandling.h) +- [PhysicsTools/Scouting](https://github.com/cms-sw/cmssw/tree/master/PhysicsTools/Scouting) - Existing scouting tools diff --git a/PhysicsTools/PatFromScouting/plugins/BuildFile.xml b/PhysicsTools/PatFromScouting/plugins/BuildFile.xml new file mode 100644 index 0000000000000..8c3fea0e97ed3 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/BuildFile.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PhysicsTools/PatFromScouting/plugins/PatFromScoutingElectronProducer.cc b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingElectronProducer.cc new file mode 100644 index 0000000000000..f684e219ff6e0 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingElectronProducer.cc @@ -0,0 +1,75 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: PatFromScoutingElectronProducer +// +/**\class PatFromScoutingElectronProducer PatFromScoutingElectronProducer.cc PhysicsTools/PatFromScouting/plugins/PatFromScoutingElectronProducer.cc + + Description: Converts Run3ScoutingElectron to pat::Electron + + Implementation: + Uses the pat::Electron(const Run3ScoutingElectron&) constructor which sets: + - Kinematics (pt, eta, phi, mass, charge) + - Shower shape variables as userFloats (sigmaIetaIeta, hOverE, r9, sMin, sMaj) + - ID variables as userFloats (dEtaIn, dPhiIn, ooEMOop, missingHits) + - Track variables as userFloats (trkd0, trkdz, trkpt, trkchi2overndf, trackfbrem) + - Energy variables as userFloats (rawEnergy, preshowerEnergy, corrEcalEnergyError) + - Isolation as userFloats (ecalIso, hcalIso, trackIso) +*/ +// +// Original Author: Dmytro Kovalskyi +// Created: Thu, 05 Dec 2024 15:27:09 GMT +// +// + +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/PatCandidates/interface/Electron.h" +#include "DataFormats/Scouting/interface/Run3ScoutingElectron.h" + +class PatFromScoutingElectronProducer : public edm::stream::EDProducer<> { +public: + explicit PatFromScoutingElectronProducer(const edm::ParameterSet&); + ~PatFromScoutingElectronProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT electronToken_; +}; + +PatFromScoutingElectronProducer::PatFromScoutingElectronProducer(const edm::ParameterSet& iConfig) + : electronToken_(consumes(iConfig.getParameter("src"))) { + produces(); +} + +void PatFromScoutingElectronProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto patElectrons = std::make_unique(); + + const auto& scoutingElectrons = iEvent.get(electronToken_); + + for (const auto& sElec : scoutingElectrons) { + // Constructor now handles kinematics, shower shape, ID, and isolation + patElectrons->push_back(pat::Electron(sElec)); + } + + iEvent.put(std::move(patElectrons)); +} + +void PatFromScoutingElectronProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingEgammaPacker")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(PatFromScoutingElectronProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/PatFromScoutingJetProducer.cc b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingJetProducer.cc new file mode 100644 index 0000000000000..cfad49ece43f2 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingJetProducer.cc @@ -0,0 +1,90 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: PatFromScoutingJetProducer +// +/**\class PatFromScoutingJetProducer PatFromScoutingJetProducer.cc PhysicsTools/PatFromScouting/plugins/PatFromScoutingJetProducer.cc + + Description: Converts Run3ScoutingPFJet to pat::Jet + + Implementation: + Uses the pat::Jet(const Run3ScoutingPFJet&) constructor which sets: + - Kinematics (pt, eta, phi, mass) + - Jet area + - PFJet::Specific (energy fractions, multiplicities) + - B-tagging discriminators (CSV, DeepCSV) + + Constituent indices from the scouting jet are resolved to + CandidatePtr daughters pointing into the PackedCandidateCollection. +*/ +// +// Original Author: Dmytro Kovalskyi +// Created: Thu, 05 Dec 2024 15:27:09 GMT +// +// + +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/PatCandidates/interface/Jet.h" +#include "DataFormats/PatCandidates/interface/PackedCandidate.h" +#include "DataFormats/Scouting/interface/Run3ScoutingPFJet.h" + +class PatFromScoutingJetProducer : public edm::stream::EDProducer<> { +public: + explicit PatFromScoutingJetProducer(const edm::ParameterSet&); + ~PatFromScoutingJetProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT jetToken_; + const edm::EDGetTokenT pfCandToken_; +}; + +PatFromScoutingJetProducer::PatFromScoutingJetProducer(const edm::ParameterSet& iConfig) + : jetToken_(consumes(iConfig.getParameter("src"))), + pfCandToken_(consumes(iConfig.getParameter("pfCandidates"))) { + produces(); +} + +void PatFromScoutingJetProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto patJets = std::make_unique(); + + const auto& scoutingJets = iEvent.get(jetToken_); + auto pfCandHandle = iEvent.getHandle(pfCandToken_); + + for (const auto& sJet : scoutingJets) { + pat::Jet jet(sJet); + + // Resolve constituent indices to CandidatePtr daughters + for (int idx : sJet.constituents()) { + if (idx >= 0 && idx < static_cast(pfCandHandle->size())) { + reco::CandidatePtr ptr(pfCandHandle, idx); + jet.addDaughter(ptr); + } + } + + patJets->push_back(jet); + } + + iEvent.put(std::move(patJets)); +} + +void PatFromScoutingJetProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingPFPacker")); + desc.add("pfCandidates", edm::InputTag("packedPFCandidates")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(PatFromScoutingJetProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/PatFromScoutingMuonProducer.cc b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingMuonProducer.cc new file mode 100644 index 0000000000000..f9ae8ad8aceb8 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingMuonProducer.cc @@ -0,0 +1,72 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: PatFromScoutingMuonProducer +// +/**\class PatFromScoutingMuonProducer PatFromScoutingMuonProducer.cc PhysicsTools/PatFromScouting/plugins/PatFromScoutingMuonProducer.cc + + Description: Converts Run3ScoutingMuon to pat::Muon + + Implementation: + Uses the pat::Muon(const Run3ScoutingMuon&) constructor which sets: + - Kinematics (pt, eta, phi, mass, charge) + - Embedded track with vertex + - Isolation (trackIso, ecalIso, hcalIso -> isolationR03) +*/ +// +// Original Author: Dmytro Kovalskyi +// Created: Thu, 05 Dec 2024 15:27:09 GMT +// +// + +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/PatCandidates/interface/Muon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingMuon.h" + +class PatFromScoutingMuonProducer : public edm::stream::EDProducer<> { +public: + explicit PatFromScoutingMuonProducer(const edm::ParameterSet&); + ~PatFromScoutingMuonProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT muonToken_; +}; + +PatFromScoutingMuonProducer::PatFromScoutingMuonProducer(const edm::ParameterSet& iConfig) + : muonToken_(consumes(iConfig.getParameter("src"))) { + produces(); +} + +void PatFromScoutingMuonProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto patMuons = std::make_unique(); + + const auto& scoutingMuons = iEvent.get(muonToken_); + + for (const auto& sMuon : scoutingMuons) { + // Constructor now handles kinematics, track embedding, and isolation + patMuons->push_back(pat::Muon(sMuon)); + } + + iEvent.put(std::move(patMuons)); +} + +void PatFromScoutingMuonProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingMuonPacker")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(PatFromScoutingMuonProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/PatFromScoutingPhotonProducer.cc b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingPhotonProducer.cc new file mode 100644 index 0000000000000..b4ba287dc96d0 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/PatFromScoutingPhotonProducer.cc @@ -0,0 +1,73 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: PatFromScoutingPhotonProducer +// +/**\class PatFromScoutingPhotonProducer PatFromScoutingPhotonProducer.cc PhysicsTools/PatFromScouting/plugins/PatFromScoutingPhotonProducer.cc + + Description: Converts Run3ScoutingPhoton to pat::Photon + + Implementation: + Uses the pat::Photon(const Run3ScoutingPhoton&) constructor which sets: + - Kinematics (pt, eta, phi, mass) + - Shower shape variables as userFloats (sigmaIetaIeta, hOverE, r9, sMin, sMaj) + - Energy variables as userFloats (rawEnergy, preshowerEnergy, corrEcalEnergyError) + - Isolation as userFloats and PAT isolation keys (ecalIso, hcalIso, trkIso) +*/ +// +// Original Author: Dmytro Kovalskyi +// Created: Thu, 05 Dec 2024 15:27:09 GMT +// +// + +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/PatCandidates/interface/Photon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingPhoton.h" + +class PatFromScoutingPhotonProducer : public edm::stream::EDProducer<> { +public: + explicit PatFromScoutingPhotonProducer(const edm::ParameterSet&); + ~PatFromScoutingPhotonProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT photonToken_; +}; + +PatFromScoutingPhotonProducer::PatFromScoutingPhotonProducer(const edm::ParameterSet& iConfig) + : photonToken_(consumes(iConfig.getParameter("src"))) { + produces(); +} + +void PatFromScoutingPhotonProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto patPhotons = std::make_unique(); + + const auto& scoutingPhotons = iEvent.get(photonToken_); + + for (const auto& sPhoton : scoutingPhotons) { + // Constructor now handles kinematics, shower shape, and isolation + patPhotons->push_back(pat::Photon(sPhoton)); + } + + iEvent.put(std::move(patPhotons)); +} + +void PatFromScoutingPhotonProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingEgammaPacker")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(PatFromScoutingPhotonProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/Run3ScoutingL1CaloProducer.cc b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingL1CaloProducer.cc new file mode 100644 index 0000000000000..6467805fa9641 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingL1CaloProducer.cc @@ -0,0 +1,114 @@ +/** + * Run3ScoutingL1CaloProducer + * + * Copies L1 calorimeter objects (jets, EGamma, tau, EtSum) from gtStage2Digis + * to produce collections with the standard caloStage2Digis module label + * expected by downstream code. + * + * This allows scouting MiniAOD to be compatible with standard workflows + * that expect L1 calo objects from caloStage2Digis. + */ + +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "DataFormats/L1Trigger/interface/Jet.h" +#include "DataFormats/L1Trigger/interface/EGamma.h" +#include "DataFormats/L1Trigger/interface/Tau.h" +#include "DataFormats/L1Trigger/interface/EtSum.h" +#include "DataFormats/L1Trigger/interface/BXVector.h" + +class Run3ScoutingL1CaloProducer : public edm::stream::EDProducer<> { +public: + explicit Run3ScoutingL1CaloProducer(const edm::ParameterSet&); + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT> jetToken_; + const edm::EDGetTokenT> egammaToken_; + const edm::EDGetTokenT> tauToken_; + const edm::EDGetTokenT> etsumToken_; + + const edm::EDPutTokenT> jetPutToken_; + const edm::EDPutTokenT> egammaPutToken_; + const edm::EDPutTokenT> tauPutToken_; + const edm::EDPutTokenT> etsumPutToken_; +}; + +Run3ScoutingL1CaloProducer::Run3ScoutingL1CaloProducer(const edm::ParameterSet& iConfig) + : jetToken_(consumes>(iConfig.getParameter("jetSource"))), + egammaToken_(consumes>(iConfig.getParameter("egammaSource"))), + tauToken_(consumes>(iConfig.getParameter("tauSource"))), + etsumToken_(consumes>(iConfig.getParameter("etsumSource"))), + jetPutToken_(produces>("Jet")), + egammaPutToken_(produces>("EGamma")), + tauPutToken_(produces>("Tau")), + etsumPutToken_(produces>("EtSum")) {} + +void Run3ScoutingL1CaloProducer::produce(edm::Event& iEvent, const edm::EventSetup&) { + // Copy jets + { + auto const& jetsIn = iEvent.get(jetToken_); + BXVector jetsOut; + jetsOut.setBXRange(jetsIn.getFirstBX(), jetsIn.getLastBX()); + for (int bx = jetsIn.getFirstBX(); bx <= jetsIn.getLastBX(); ++bx) { + for (auto it = jetsIn.begin(bx); it != jetsIn.end(bx); ++it) { + jetsOut.push_back(bx, *it); + } + } + iEvent.emplace(jetPutToken_, std::move(jetsOut)); + } + + // Copy EGamma + { + auto const& egammaIn = iEvent.get(egammaToken_); + BXVector egammaOut; + egammaOut.setBXRange(egammaIn.getFirstBX(), egammaIn.getLastBX()); + for (int bx = egammaIn.getFirstBX(); bx <= egammaIn.getLastBX(); ++bx) { + for (auto it = egammaIn.begin(bx); it != egammaIn.end(bx); ++it) { + egammaOut.push_back(bx, *it); + } + } + iEvent.emplace(egammaPutToken_, std::move(egammaOut)); + } + + // Copy Tau + { + auto const& tauIn = iEvent.get(tauToken_); + BXVector tauOut; + tauOut.setBXRange(tauIn.getFirstBX(), tauIn.getLastBX()); + for (int bx = tauIn.getFirstBX(); bx <= tauIn.getLastBX(); ++bx) { + for (auto it = tauIn.begin(bx); it != tauIn.end(bx); ++it) { + tauOut.push_back(bx, *it); + } + } + iEvent.emplace(tauPutToken_, std::move(tauOut)); + } + + // Copy EtSum + { + auto const& etsumIn = iEvent.get(etsumToken_); + BXVector etsumOut; + etsumOut.setBXRange(etsumIn.getFirstBX(), etsumIn.getLastBX()); + for (int bx = etsumIn.getFirstBX(); bx <= etsumIn.getLastBX(); ++bx) { + for (auto it = etsumIn.begin(bx); it != etsumIn.end(bx); ++it) { + etsumOut.push_back(bx, *it); + } + } + iEvent.emplace(etsumPutToken_, std::move(etsumOut)); + } +} + +void Run3ScoutingL1CaloProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("jetSource", edm::InputTag("gtStage2Digis", "Jet")); + desc.add("egammaSource", edm::InputTag("gtStage2Digis", "EGamma")); + desc.add("tauSource", edm::InputTag("gtStage2Digis", "Tau")); + desc.add("etsumSource", edm::InputTag("gtStage2Digis", "EtSum")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(Run3ScoutingL1CaloProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/Run3ScoutingL1MuonProducer.cc b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingL1MuonProducer.cc new file mode 100644 index 0000000000000..8cb57d2a75cd6 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingL1MuonProducer.cc @@ -0,0 +1,54 @@ +/** + * Run3ScoutingL1MuonProducer + * + * Copies L1 muons from gtStage2Digis to produce collections with + * the standard gmtStage2Digis module label expected by downstream code. + * + * This allows scouting MiniAOD to be compatible with standard workflows + * that expect L1 muons from gmtStage2Digis. + */ + +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "DataFormats/L1Trigger/interface/Muon.h" +#include "DataFormats/L1Trigger/interface/BXVector.h" + +class Run3ScoutingL1MuonProducer : public edm::stream::EDProducer<> { +public: + explicit Run3ScoutingL1MuonProducer(const edm::ParameterSet&); + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT> muonToken_; + const edm::EDPutTokenT> muonPutToken_; +}; + +Run3ScoutingL1MuonProducer::Run3ScoutingL1MuonProducer(const edm::ParameterSet& iConfig) + : muonToken_(consumes>(iConfig.getParameter("muonSource"))), + muonPutToken_(produces>("Muon")) {} + +void Run3ScoutingL1MuonProducer::produce(edm::Event& iEvent, const edm::EventSetup&) { + auto const& muonsIn = iEvent.get(muonToken_); + BXVector muonsOut; + + muonsOut.setBXRange(muonsIn.getFirstBX(), muonsIn.getLastBX()); + for (int bx = muonsIn.getFirstBX(); bx <= muonsIn.getLastBX(); ++bx) { + for (auto it = muonsIn.begin(bx); it != muonsIn.end(bx); ++it) { + muonsOut.push_back(bx, *it); + } + } + + iEvent.emplace(muonPutToken_, std::move(muonsOut)); +} + +void Run3ScoutingL1MuonProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("muonSource", edm::InputTag("gtStage2Digis", "Muon")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(Run3ScoutingL1MuonProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/Run3ScoutingMETProducer.cc b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingMETProducer.cc new file mode 100644 index 0000000000000..eddd2f4146818 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingMETProducer.cc @@ -0,0 +1,90 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: Run3ScoutingMETProducer +// +/**\class Run3ScoutingMETProducer Run3ScoutingMETProducer.cc PhysicsTools/PatFromScouting/plugins/Run3ScoutingMETProducer.cc + + Description: Creates pat::MET from scouting MET (pt, phi) stored in event + + Implementation: + Reads precomputed MET pt and phi from hltScoutingPFPacker and creates pat::MET +*/ +// +// Original Author: Dmytro Kovalskyi +// Created: Thu, 05 Dec 2024 15:27:09 GMT +// +// + +#include +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/PatCandidates/interface/MET.h" + +class Run3ScoutingMETProducer : public edm::stream::EDProducer<> { +public: + explicit Run3ScoutingMETProducer(const edm::ParameterSet&); + ~Run3ScoutingMETProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT metPtToken_; + const edm::EDGetTokenT metPhiToken_; +}; + +Run3ScoutingMETProducer::Run3ScoutingMETProducer(const edm::ParameterSet& iConfig) + : metPtToken_(consumes(iConfig.getParameter("metPt"))), + metPhiToken_(consumes(iConfig.getParameter("metPhi"))) { + produces(); +} + +void Run3ScoutingMETProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto patMETs = std::make_unique(); + + double metPt = iEvent.get(metPtToken_); + double metPhi = iEvent.get(metPhiToken_); + + double metPx = metPt * std::cos(metPhi); + double metPy = metPt * std::sin(metPhi); + + // sumEt is not available in scouting, use metPt as approximation + double sumEt = metPt; + + reco::MET::LorentzVector p4(metPx, metPy, 0.0, metPt); + reco::MET::Point vtx(0.0, 0.0, 0.0); + + reco::MET recoMET(sumEt, p4, vtx); + pat::MET patMET(recoMET); + + // Initialize MET corrections to make NanoAOD happy + // Using the same values since scouting MET is already the "raw" PF MET + patMET.setCorShift(metPx, metPy, sumEt, pat::MET::None); + patMET.setCorShift(metPx, metPy, sumEt, pat::MET::T1); + patMET.setCorShift(metPx, metPy, sumEt, pat::MET::Calo); + patMET.setCorShift(metPx, metPy, sumEt, pat::MET::Chs); + patMET.setCorShift(metPx, metPy, sumEt, pat::MET::Trk); + + patMETs->push_back(patMET); + + iEvent.put(std::move(patMETs)); +} + +void Run3ScoutingMETProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("metPt", edm::InputTag("hltScoutingPFPacker", "pfMetPt")); + desc.add("metPhi", edm::InputTag("hltScoutingPFPacker", "pfMetPhi")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(Run3ScoutingMETProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/Run3ScoutingParticleToPackedCandidateProducer.cc b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingParticleToPackedCandidateProducer.cc new file mode 100644 index 0000000000000..f5c663aeea6a8 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingParticleToPackedCandidateProducer.cc @@ -0,0 +1,197 @@ +/** + * Run3ScoutingParticleToPackedCandidateProducer + * + * Converts Run3ScoutingParticle to pat::PackedCandidate for MiniAOD compatibility. + * Matches charged candidates to reco::Tracks to embed track details + * (hasTrackDetails() == true, dxyError/dzError/normalizedChi2/hit counts available). + * + * Requires vertices and scoutingTracks to be produced first. + */ + +#include +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/PatCandidates/interface/PackedCandidate.h" +#include "DataFormats/Scouting/interface/Run3ScoutingParticle.h" +#include "DataFormats/VertexReco/interface/Vertex.h" +#include "DataFormats/VertexReco/interface/VertexFwd.h" +#include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/TrackReco/interface/TrackFwd.h" +#include "DataFormats/Math/interface/LorentzVector.h" +#include "DataFormats/Math/interface/deltaR.h" + +#include "SimGeneral/HepPDTRecord/interface/ParticleDataTable.h" + +class Run3ScoutingParticleToPackedCandidateProducer : public edm::stream::EDProducer<> { +public: + explicit Run3ScoutingParticleToPackedCandidateProducer(const edm::ParameterSet&); + ~Run3ScoutingParticleToPackedCandidateProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT> particleToken_; + const edm::EDGetTokenT vertexToken_; + const edm::EDGetTokenT trackToken_; + const edm::ESGetToken pdtToken_; + const bool useCHS_; + const int covarianceVersion_; + const int covarianceSchema_; +}; + +Run3ScoutingParticleToPackedCandidateProducer::Run3ScoutingParticleToPackedCandidateProducer( + const edm::ParameterSet& iConfig) + : particleToken_(consumes>(iConfig.getParameter("src"))), + vertexToken_(consumes(iConfig.getParameter("vertices"))), + trackToken_(consumes(iConfig.getParameter("tracks"))), + pdtToken_(esConsumes()), + useCHS_(iConfig.getParameter("CHS")), + covarianceVersion_(iConfig.getParameter("covarianceVersion")), + covarianceSchema_(iConfig.getParameter("covarianceSchema")) { + produces(); +} + +void Run3ScoutingParticleToPackedCandidateProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto output = std::make_unique(); + + const auto& pdt = iSetup.getData(pdtToken_); + const auto& particles = iEvent.get(particleToken_); + + auto verticesHandle = iEvent.getHandle(vertexToken_); + const auto& vertices = *verticesHandle; + reco::VertexRefProd vertexRefProd(verticesHandle); + + const auto& tracks = iEvent.get(trackToken_); + + // Build a "used" flag so each reco::Track is matched at most once + std::vector trackUsed(tracks.size(), false); + + output->reserve(particles.size()); + + for (const auto& particle : particles) { + if (useCHS_ && particle.vertex() > 0) { + continue; + } + + const HepPDT::ParticleData* pdtData = pdt.particle(HepPDT::ParticleID(particle.pdgId())); + if (!pdtData) { + continue; + } + float mass = pdtData->mass().value(); + + float pt = particle.pt(); + float eta = particle.eta(); + float phi = particle.phi(); + float px = pt * std::cos(phi); + float py = pt * std::sin(phi); + float pz = pt * std::sinh(eta); + float energy = std::sqrt(px * px + py * py + pz * pz + mass * mass); + math::XYZTLorentzVector p4(px, py, pz, energy); + + bool relativeTrackVars = particle.relative_trk_vars(); + float trkPt = relativeTrackVars ? particle.trk_pt() + particle.pt() : particle.trk_pt(); + float trkEta = relativeTrackVars ? particle.trk_eta() + particle.eta() : particle.trk_eta(); + float trkPhi = relativeTrackVars ? particle.trk_phi() + particle.phi() : particle.trk_phi(); + + int vtxIdx = particle.vertex(); + reco::VertexRef::key_type pvKey = 0; + if (vtxIdx >= 0 && static_cast(vtxIdx) < vertices.size()) { + pvKey = static_cast(vtxIdx); + } + + math::XYZPoint pvPos(0, 0, 0); + if (pvKey < vertices.size()) { + pvPos = vertices[pvKey].position(); + } + + float dxy = particle.dxy(); + float dz = particle.dz(); + float sinPhi = std::sin(phi); + float cosPhi = std::cos(phi); + + math::XYZPoint vtxPos(pvPos.X() - dxy * sinPhi, pvPos.Y() + dxy * cosPhi, pvPos.Z() + dz); + + pat::PackedCandidate cand(p4, vtxPos, trkPt, trkEta, trkPhi, particle.pdgId(), vertexRefProd, pvKey); + + // Set lost inner hits + pat::PackedCandidate::LostInnerHits lostHits = pat::PackedCandidate::noLostInnerHits; + uint8_t scoutingLostHits = particle.lostInnerHits(); + if (scoutingLostHits == 0) { + lostHits = pat::PackedCandidate::noLostInnerHits; + } else if (scoutingLostHits == 1) { + lostHits = pat::PackedCandidate::oneLostInnerHit; + } else if (scoutingLostHits >= 2) { + lostHits = pat::PackedCandidate::moreLostInnerHits; + } + cand.setLostInnerHits(lostHits); + + // Set track quality + int quality = particle.quality(); + bool highPurity = (quality & 4); + cand.setTrackHighPurity(highPurity); + + // Set PV association quality + if (vtxIdx == 0) { + cand.setAssociationQuality(pat::PackedCandidate::UsedInFitTight); + } else if (vtxIdx > 0) { + cand.setAssociationQuality(pat::PackedCandidate::CompatibilityDz); + } else { + cand.setAssociationQuality(pat::PackedCandidate::NotReconstructedPrimary); + } + + // Match charged candidates to reco::Tracks and embed track details + if (particle.pdgId() != 22 && particle.pdgId() != 130 && abs(particle.pdgId()) > 2 && trkPt > 0) { + int bestIdx = -1; + float bestMetric = 999.f; + for (size_t iTk = 0; iTk < tracks.size(); ++iTk) { + if (trackUsed[iTk]) + continue; + const auto& tk = tracks[iTk]; + float dEta = trkEta - tk.eta(); + float dPhi = reco::deltaPhi(trkPhi, tk.phi()); + float dR2 = dEta * dEta + dPhi * dPhi; + float dPtRel = std::abs(trkPt - tk.pt()) / trkPt; + float metric = dR2 + dPtRel * dPtRel; + if (metric < bestMetric) { + bestMetric = metric; + bestIdx = iTk; + } + } + // Require reasonable match quality + if (bestIdx >= 0 && bestMetric < 0.01f) { + cand.setTrackProperties(tracks[bestIdx], covarianceSchema_, covarianceVersion_); + trackUsed[bestIdx] = true; + } + } + + output->push_back(cand); + } + + iEvent.put(std::move(output)); +} + +void Run3ScoutingParticleToPackedCandidateProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingPFPacker")) + ->setComment("Input scouting particle collection"); + desc.add("vertices", edm::InputTag("offlineSlimmedPrimaryVertices")) + ->setComment("Input vertex collection for vertex references"); + desc.add("tracks", edm::InputTag("scoutingTracks")) + ->setComment("Input reco::Track collection for embedding track details"); + desc.add("CHS", false)->setComment("Apply Charged Hadron Subtraction (skip vtx > 0)"); + desc.add("covarianceVersion", 1)->setComment("Covariance parameterization version (0=Phase0, 1=Phase1)"); + desc.add("covarianceSchema", 520)->setComment("Covariance packing schema"); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(Run3ScoutingParticleToPackedCandidateProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/Run3ScoutingTrackToRecoTrackProducer.cc b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingTrackToRecoTrackProducer.cc new file mode 100644 index 0000000000000..093467771fcbf --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingTrackToRecoTrackProducer.cc @@ -0,0 +1,124 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: Run3ScoutingTrackToRecoTrackProducer +// +/**\class Run3ScoutingTrackToRecoTrackProducer Run3ScoutingTrackToRecoTrackProducer.cc PhysicsTools/PatFromScouting/plugins/Run3ScoutingTrackToRecoTrackProducer.cc + + Description: Converts Run3ScoutingTrack to reco::Track with populated hit pattern + +*/ +// +// Original Author: Dmytro Kovalskyi +// Created: Thu, 05 Dec 2024 15:27:09 GMT +// +// + +#include +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/TrackReco/interface/TrackFwd.h" +#include "DataFormats/Common/interface/ValueMap.h" +#include "DataFormats/Common/interface/OrphanHandle.h" +#include "DataFormats/Scouting/interface/Run3ScoutingTrack.h" +#include "DataFormats/PatCandidates/interface/ScoutingDataHandling.h" +#include "DataFormats/SiPixelDetId/interface/PixelSubdetector.h" +#include "DataFormats/SiStripDetId/interface/StripSubdetector.h" +#include "DataFormats/TrackingRecHit/interface/TrackingRecHit.h" + +class Run3ScoutingTrackToRecoTrackProducer : public edm::stream::EDProducer<> { +public: + explicit Run3ScoutingTrackToRecoTrackProducer(const edm::ParameterSet&); + ~Run3ScoutingTrackToRecoTrackProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT trackToken_; + + std::vector vertexIndex_; +}; + +Run3ScoutingTrackToRecoTrackProducer::Run3ScoutingTrackToRecoTrackProducer(const edm::ParameterSet& iConfig) + : trackToken_(consumes(iConfig.getParameter("src"))) { + produces(); + produces>("vertexIndex"); +} + +void Run3ScoutingTrackToRecoTrackProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto recoTracks = std::make_unique(); + + vertexIndex_.clear(); + + const auto& scoutingTracks = iEvent.get(trackToken_); + + for (const auto& sTrack : scoutingTracks) { + reco::Track recoTrack = pat::makeRecoTrack(sTrack); + + // Populate hit pattern from scouting hit counts. + // Prefer pixLayers = min(nPixHits, 4) but adjust to guarantee: + // pixLayers + stripLayers = totalLayers, pixLayers <= nPixHits, stripLayers <= nStripHits + int nPixHits = sTrack.tk_nValidPixelHits(); + int nStripHits = sTrack.tk_nValidStripHits(); + int totalLayers = sTrack.tk_nTrackerLayersWithMeasurement(); + int lower = std::max(0, totalLayers - nStripHits); + int upper = nStripHits > 0 ? std::min(nPixHits, totalLayers - 1) : std::min(nPixHits, totalLayers); + int pixLayers = std::clamp(std::min(4, totalLayers), lower, upper); + int stripLayers = totalLayers - pixLayers; + + // Pixel: BPIX(1-4), FPIX(1-3), extras on BPIX layer 2 + for (int i = 0; i < pixLayers; ++i) { + if (i < 4) + recoTrack.appendTrackerHitPattern(PixelSubdetector::PixelBarrel, i + 1, 0, TrackingRecHit::valid); + else + recoTrack.appendTrackerHitPattern(PixelSubdetector::PixelEndcap, i - 3, 0, TrackingRecHit::valid); + } + for (int i = pixLayers; i < nPixHits; ++i) + recoTrack.appendTrackerHitPattern(PixelSubdetector::PixelBarrel, 2, 0, TrackingRecHit::valid); + + // Strip: TIB(1-4), TID(1-3), TOB(1-6), TEC(1-9), extras on TIB 1 + for (int i = 0; i < stripLayers; ++i) { + if (i < 4) + recoTrack.appendTrackerHitPattern(StripSubdetector::TIB, i + 1, 1, TrackingRecHit::valid); + else if (i < 7) + recoTrack.appendTrackerHitPattern(StripSubdetector::TID, i - 3, 1, TrackingRecHit::valid); + else if (i < 13) + recoTrack.appendTrackerHitPattern(StripSubdetector::TOB, i - 6, 1, TrackingRecHit::valid); + else + recoTrack.appendTrackerHitPattern(StripSubdetector::TEC, i - 12, 1, TrackingRecHit::valid); + } + for (int i = stripLayers; i < nStripHits; ++i) + recoTrack.appendTrackerHitPattern(StripSubdetector::TIB, 1, 1, TrackingRecHit::valid); + + recoTracks->push_back(recoTrack); + + vertexIndex_.push_back(sTrack.tk_vtxInd()); + } + + edm::OrphanHandle tracksHandle = iEvent.put(std::move(recoTracks)); + + auto vertexIndexMap = std::make_unique>(); + edm::ValueMap::Filler fillerVtx(*vertexIndexMap); + fillerVtx.insert(tracksHandle, vertexIndex_.begin(), vertexIndex_.end()); + fillerVtx.fill(); + iEvent.put(std::move(vertexIndexMap), "vertexIndex"); +} + +void Run3ScoutingTrackToRecoTrackProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingTrackPacker")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(Run3ScoutingTrackToRecoTrackProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/Run3ScoutingVertexToRecoVertexProducer.cc b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingVertexToRecoVertexProducer.cc new file mode 100644 index 0000000000000..6dd78a559f177 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/Run3ScoutingVertexToRecoVertexProducer.cc @@ -0,0 +1,75 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: Run3ScoutingVertexToRecoVertexProducer +// +/**\class Run3ScoutingVertexToRecoVertexProducer Run3ScoutingVertexToRecoVertexProducer.cc PhysicsTools/PatFromScouting/plugins/Run3ScoutingVertexToRecoVertexProducer.cc + + Description: Converts Run3ScoutingVertex to reco::Vertex + + Implementation: + Uses the existing pat::makeRecoVertex helper function +*/ +// +// Original Author: Dmytro Kovalskyi +// Created: Thu, 05 Dec 2024 15:27:09 GMT +// +// + +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/VertexReco/interface/Vertex.h" +#include "DataFormats/VertexReco/interface/VertexFwd.h" +#include "DataFormats/Scouting/interface/Run3ScoutingVertex.h" +#include "DataFormats/PatCandidates/interface/ScoutingDataHandling.h" + +class Run3ScoutingVertexToRecoVertexProducer : public edm::stream::EDProducer<> { +public: + explicit Run3ScoutingVertexToRecoVertexProducer(const edm::ParameterSet&); + ~Run3ScoutingVertexToRecoVertexProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT vertexToken_; +}; + +Run3ScoutingVertexToRecoVertexProducer::Run3ScoutingVertexToRecoVertexProducer(const edm::ParameterSet& iConfig) + : vertexToken_(consumes(iConfig.getParameter("src"))) { + produces(); +} + +void Run3ScoutingVertexToRecoVertexProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup) { + auto recoVertices = std::make_unique(); + + const auto& scoutingVertices = iEvent.get(vertexToken_); + + for (const auto& sVtx : scoutingVertices) { + if (!sVtx.isValidVtx()) { + continue; + } + + reco::Vertex recoVtx = pat::makeRecoVertex(sVtx); + recoVertices->push_back(recoVtx); + } + + iEvent.put(std::move(recoVertices)); +} + +void Run3ScoutingVertexToRecoVertexProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingPrimaryVertexPacker", "primaryVtx")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(Run3ScoutingVertexToRecoVertexProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/ScoutingDimuonVtxProducer.cc b/PhysicsTools/PatFromScouting/plugins/ScoutingDimuonVtxProducer.cc new file mode 100644 index 0000000000000..5d2a30e51f132 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/ScoutingDimuonVtxProducer.cc @@ -0,0 +1,131 @@ +// -*- C++ -*- +// +// Package: PhysicsTools/PatFromScouting +// Class: ScoutingDimuonVtxProducer +// +/**\class ScoutingDimuonVtxProducer ScoutingDimuonVtxProducer.cc + + Description: Converts scouting displaced dimuon vertices to + reco::VertexCompositePtrCandidate with CandidatePtr daughters + pointing into the pat::Muon collection. + + The association between muons and displaced vertices is read from + Run3ScoutingMuon::vtxIndx(), which stores the indices of the + displaced vertices each muon belongs to (filled by the HLT + scouting muon packer). +*/ +// +// Original Author: Dmytro Kovalskyi +// + +#include +#include +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "DataFormats/Candidate/interface/VertexCompositePtrCandidate.h" +#include "DataFormats/PatCandidates/interface/Muon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingMuon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingVertex.h" +#include "DataFormats/PatCandidates/interface/ScoutingDataHandling.h" + +class ScoutingDimuonVtxProducer : public edm::stream::EDProducer<> { +public: + explicit ScoutingDimuonVtxProducer(const edm::ParameterSet&); + ~ScoutingDimuonVtxProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT scoutMuonToken_; + const edm::EDGetTokenT scoutVtxToken_; + const edm::EDGetTokenT> patMuonToken_; +}; + +ScoutingDimuonVtxProducer::ScoutingDimuonVtxProducer(const edm::ParameterSet& iConfig) + : scoutMuonToken_(consumes(iConfig.getParameter("scoutingMuons"))), + scoutVtxToken_(consumes(iConfig.getParameter("scoutingVertices"))), + patMuonToken_(consumes>(iConfig.getParameter("patMuons"))) { + produces(); +} + +void ScoutingDimuonVtxProducer::produce(edm::Event& iEvent, const edm::EventSetup&) { + auto output = std::make_unique(); + + const auto& scoutMuons = iEvent.get(scoutMuonToken_); + const auto& scoutVtxs = iEvent.get(scoutVtxToken_); + auto patMuonHandle = iEvent.getHandle(patMuonToken_); + + // Build vertex → muon index map by inverting muon.vtxIndx() + // vtxIndx stores displaced vertex indices that each muon belongs to + std::unordered_map> vtxToMuons; + for (unsigned int iMu = 0; iMu < scoutMuons.size(); ++iMu) { + for (int vtxIdx : scoutMuons[iMu].vtxIndx()) { + vtxToMuons[vtxIdx].push_back(iMu); + } + } + + // Convert each scouting displaced vertex + for (unsigned int iVtx = 0; iVtx < scoutVtxs.size(); ++iVtx) { + const auto& sVtx = scoutVtxs[iVtx]; + if (!sVtx.isValidVtx()) + continue; + + // Build vertex covariance + reco::Vertex::Error err; + err(0, 0) = sVtx.xError() * sVtx.xError(); + err(1, 1) = sVtx.yError() * sVtx.yError(); + err(2, 2) = sVtx.zError() * sVtx.zError(); + err(0, 1) = sVtx.xyCov(); + err(0, 2) = sVtx.xzCov(); + err(1, 2) = sVtx.yzCov(); + + // Sum p4 of daughter muons and determine charge + reco::Candidate::LorentzVector p4; + int charge = 0; + auto it = vtxToMuons.find(iVtx); + if (it != vtxToMuons.end()) { + for (unsigned int iMu : it->second) { + if (iMu < patMuonHandle->size()) { + p4 += patMuonHandle->at(iMu).p4(); + charge += patMuonHandle->at(iMu).charge(); + } + } + } + + reco::VertexCompositePtrCandidate vtxCand( + charge, p4, reco::Candidate::Point(sVtx.x(), sVtx.y(), sVtx.z()), err, sVtx.chi2(), sVtx.ndof()); + + // Add daughter CandidatePtrs + if (it != vtxToMuons.end()) { + for (unsigned int iMu : it->second) { + if (iMu < patMuonHandle->size()) { + vtxCand.addDaughter(patMuonHandle->ptrAt(iMu)); + } + } + } + + output->push_back(vtxCand); + } + + iEvent.put(std::move(output)); +} + +void ScoutingDimuonVtxProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("scoutingMuons", edm::InputTag("hltScoutingMuonPackerVtx")); + desc.add("scoutingVertices", edm::InputTag("hltScoutingMuonPackerVtx", "displacedVtx")); + desc.add("patMuons", edm::InputTag("slimmedMuons")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(ScoutingDimuonVtxProducer); diff --git a/PhysicsTools/PatFromScouting/plugins/ScoutingRhoProducer.cc b/PhysicsTools/PatFromScouting/plugins/ScoutingRhoProducer.cc new file mode 100644 index 0000000000000..595b97b286096 --- /dev/null +++ b/PhysicsTools/PatFromScouting/plugins/ScoutingRhoProducer.cc @@ -0,0 +1,40 @@ +/** + * ScoutingRhoProducer + * + * Simple producer that copies the rho value from scouting data + * to produce it with standard module names expected by downstream code. + */ + +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" + +class ScoutingRhoProducer : public edm::stream::EDProducer<> { +public: + explicit ScoutingRhoProducer(const edm::ParameterSet&); + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::Event&, const edm::EventSetup&) override; + + const edm::EDGetTokenT rhoToken_; +}; + +ScoutingRhoProducer::ScoutingRhoProducer(const edm::ParameterSet& iConfig) + : rhoToken_(consumes(iConfig.getParameter("src"))) { + produces(); +} + +void ScoutingRhoProducer::produce(edm::Event& iEvent, const edm::EventSetup&) { + iEvent.put(std::make_unique(iEvent.get(rhoToken_))); +} + +void ScoutingRhoProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("hltScoutingPFPacker", "rho")); + descriptions.addWithDefaultLabel(desc); +} + +DEFINE_FWK_MODULE(ScoutingRhoProducer); diff --git a/PhysicsTools/PatFromScouting/python/autoPAT.py b/PhysicsTools/PatFromScouting/python/autoPAT.py new file mode 100644 index 0000000000000..29ed1ed73b009 --- /dev/null +++ b/PhysicsTools/PatFromScouting/python/autoPAT.py @@ -0,0 +1,65 @@ +""" +autoPAT - Automatic PAT/MiniAOD and NanoAOD configuration mappings for scouting. + +Defines flavors for PAT and NANO steps: + --step PAT:@Scout (HLTSCOUT -> MiniAOD) + --step NANO:@ScoutMini (Scouting MiniAOD -> NanoAOD) + +Usage: + cmsDriver.py ... --step PAT:@Scout ... + cmsDriver.py ... --step NANO:@ScoutMini ... +""" + +def expandPATMapping(seqList, mapping, key): + """Expand @-prefixed mappings in seqList using the mapping dictionary.""" + maxLevel = 30 + level = 0 + while '@' in repr(seqList) and level < maxLevel: + level += 1 + for specifiedCommand in seqList: + if specifiedCommand.startswith('@'): + location = specifiedCommand[1:] + if not location in mapping: + raise Exception("Impossible to map " + location + " from " + repr(mapping)) + mappedTo = mapping[location] + insertAt = seqList.index(specifiedCommand) + seqList.remove(specifiedCommand) + if key in mappedTo and mappedTo[key] is not None: + allToInsert = mappedTo[key].split('+') + for offset, toInsert in enumerate(allToInsert): + seqList.insert(insertAt + offset, toInsert) + break + if level == maxLevel: + raise Exception("Could not fully expand " + repr(seqList) + " from " + repr(mapping)) + + +# PAT flavor definitions for --step PAT:@... +autoPAT = { + # Standard MiniAOD (default) + 'PHYS': {'sequence': '', + 'customize': ''}, + + # Scouting MiniAOD from HLTSCOUT input + 'Scout': { + 'sequence': 'PhysicsTools/PatFromScouting/scoutingToMiniAOD_cff.scoutingToMiniAODTask', + 'customize': 'PhysicsTools/PatFromScouting/scoutingToMiniAOD_cff.customiseScoutingToMiniAOD' + }, + + # Scouting MiniAOD with vertex muons (2024+) + 'ScoutVtx': { + 'sequence': '@Scout', + 'customize': '@Scout+PhysicsTools/PatFromScouting/scoutingToMiniAOD_cff.customiseScoutingToMiniAOD_withMuonVtx' + }, +} + + +# NANO flavor definitions for --step NANO:@... +# These extend the central autoNANO with scouting-specific flavors +autoNANO_scouting = { + # NanoAOD from Scouting MiniAOD (PAT objects) + # This reads from slimmedMuons, slimmedJets, etc. produced by PAT:@Scout + 'ScoutMini': { + 'sequence': 'PhysicsTools/PatFromScouting/scoutingNanoAOD_cff.scoutingNanoAODTask', + 'customize': 'PhysicsTools/PatFromScouting/scoutingNanoAOD_cff.customiseScoutingNanoAOD' + }, +} diff --git a/PhysicsTools/PatFromScouting/python/nanoAOD_customizations_cff.py b/PhysicsTools/PatFromScouting/python/nanoAOD_customizations_cff.py new file mode 100644 index 0000000000000..37584cce333b0 --- /dev/null +++ b/PhysicsTools/PatFromScouting/python/nanoAOD_customizations_cff.py @@ -0,0 +1,161 @@ +""" +Customizations for running NanoAOD on scouting data converted to MiniAOD format. + +Scouting data has limited information compared to full reconstruction, +so some NanoAOD modules need to be disabled or modified. +""" + +import FWCore.ParameterSet.Config as cms + +def customizeNanoAODForScouting(process): + """ + Customize NanoAOD for scouting data input. + + Disables modules that require information not available in scouting: + - Pileup jet ID (requires jet constituents) + - Deep jet taggers (requires jet constituents) + - b-tag shape corrections + - AK8 jets (not available in scouting) + - PPS forward protons (not in scouting) + """ + + # Remove pileup jet ID modules (they crash without jet constituents) + modulesToRemove = [ + 'pileupJetId94X', + 'pileupJetIdPuppi', + 'pileupJetId', + 'pileupJetIdUpdated', + 'updatedJets', + 'updatedJetsWithUserData', + 'updatedJetsPuppi', + 'updatedJetsPuppiWithUserData', + # AK8 jets (not available in scouting) + 'fatJetTable', + 'subJetTable', + 'saTable', + 'saJetTable', + # Deep taggers that need constituents + 'btagDeepFlavPuppi', + 'btagDeepFlavCMVAPuppi', + # B-tag corrections + 'btagSFPuppi', + 'ctagSFPuppi', + ] + + # Remove modules from tasks + for moduleName in modulesToRemove: + if hasattr(process, moduleName): + module = getattr(process, moduleName) + for taskName in dir(process): + task = getattr(process, taskName) + if isinstance(task, cms.Task): + try: + task.remove(module) + except: + pass + + # Alternative approach: modify the slimmedJets producer to skip pileup jet ID + # by providing empty jet ID information + if hasattr(process, 'updatedPatJets'): + process.updatedPatJets.addJetID = cms.bool(False) + + if hasattr(process, 'updatedPatJetsPuppi'): + process.updatedPatJetsPuppi.addJetID = cms.bool(False) + + # Fix jet tables to not depend on pileup jet ID + if hasattr(process, 'jetTable'): + # Remove variables that depend on pileup jet ID + try: + del process.jetTable.variables.puId + del process.jetTable.variables.puIdDisc + except: + pass + + if hasattr(process, 'jetPuppiTable'): + try: + del process.jetPuppiTable.variables.puId + del process.jetPuppiTable.variables.puIdDisc + except: + pass + + # Disable PPS proton tables (not available in scouting) + # Remove from output and tasks instead of trying to configure empty sources + ppsModules = ['protonTable', 'multiRPTable', 'singleRPTable'] + for moduleName in ppsModules: + if hasattr(process, moduleName): + module = getattr(process, moduleName) + # Remove from tasks + for taskName in dir(process): + task = getattr(process, taskName) + if isinstance(task, cms.Task): + try: + task.remove(module) + except: + pass + # Also try removing from sequences + for seqName in dir(process): + seq = getattr(process, seqName) + if isinstance(seq, cms.Sequence): + try: + seq.remove(module) + except: + pass + + # Redirect jet modules that depend on updatedJetsPuppiWithUserData + # to use slimmedJets directly (scouting doesn't have separate Puppi jets) + if hasattr(process, 'corrT1METJetPuppiTable'): + process.corrT1METJetPuppiTable.src = cms.InputTag("slimmedJets") + # Remove variables that need user data + try: + del process.corrT1METJetPuppiTable.variables.muonSubtrFactor + del process.corrT1METJetPuppiTable.variables.muonSubtrDeltaEta + del process.corrT1METJetPuppiTable.variables.muonSubtrDeltaPhi + except: + pass + + # Redirect jetPuppiTable if it exists (scouting doesn't have separate Puppi jets) + if hasattr(process, 'jetPuppiTable'): + process.jetPuppiTable.src = cms.InputTag("slimmedJets") + + # Redirect jetTable to use slimmedJets + if hasattr(process, 'jetTable'): + process.jetTable.src = cms.InputTag("slimmedJets") + + # Disable modules that need data not available in scouting + modulesToDisable = [ + 'beamSpotTable', # No beam spot + 'l1bits', # L1 trigger bits (gtStage2Digis) + 'L1PreFiringWeight', # L1 prefiring + 'L1PreFiringWeightProducer', + # Additional L1/HLT related + 'l1TriggerPathTable', + 'triggerObjectTable', + 'TrigObjFlatTableProducer', + ] + + for moduleName in modulesToDisable: + if hasattr(process, moduleName): + module = getattr(process, moduleName) + for taskName in dir(process): + task = getattr(process, taskName) + if isinstance(task, cms.Task): + try: + task.remove(module) + except: + pass + + return process + + +def customizeNanoAODForScoutingMinimal(process): + """ + Minimal customization that only disables crashing modules. + """ + # Simply remove the pileup jet ID producers from all paths and tasks + toRemove = ['pileupJetIdPuppi', 'pileupJetId', 'pileupJetIdUpdated'] + + for name in toRemove: + if hasattr(process, name): + delattr(process, name) + + return process diff --git a/PhysicsTools/PatFromScouting/python/nanoAOD_scouting_cff.py b/PhysicsTools/PatFromScouting/python/nanoAOD_scouting_cff.py new file mode 100644 index 0000000000000..c72e0b4914ad2 --- /dev/null +++ b/PhysicsTools/PatFromScouting/python/nanoAOD_scouting_cff.py @@ -0,0 +1,375 @@ +""" +NanoAOD customizations for scouting MiniAOD input. + +This module provides era-based modifiers and customization functions to run +standard NanoAOD on MiniAOD produced from scouting data. + +Usage with cmsDriver: + + # Step 1: Scouting RAW -> MiniAOD + cmsDriver.py step1 --conditions auto:run3_data_prompt \ + --era Run3 --step MINI:PhysicsTools/PatFromScouting/scoutingMiniAOD_cff.scoutingMiniAODTask \ + --filein file:scouting.root --fileout file:scoutingMiniAOD.root + + # Step 2: MiniAOD -> NanoAOD with scouting modifier + cmsDriver.py NANO --conditions auto:run3_data_prompt \ + --era Run3 --step NANO \ + --customise PhysicsTools/PatFromScouting/nanoAOD_scouting_cff.customiseNanoForScoutingMiniAOD \ + --filein file:scoutingMiniAOD.root --fileout file:nanoAOD.root + +Scouting MiniAOD limitations handled: +- No jet constituents -> disable pileup jet ID, deep taggers +- No AK8 jets -> disable fat jet tables +- No PPS data -> disable proton tables +- Limited trigger objects -> use empty trigger object collection +- No beam spot constraint -> disable beam spot table +""" + +import FWCore.ParameterSet.Config as cms + + +def _removeModuleFromTasks(process, moduleName): + """Helper to remove a module from all tasks in the process.""" + if hasattr(process, moduleName): + module = getattr(process, moduleName) + for taskName in dir(process): + task = getattr(process, taskName, None) + if isinstance(task, cms.Task): + try: + task.remove(module) + except: + pass + + +def _removeModulesFromProcess(process, moduleNames): + """Remove multiple modules from tasks.""" + for name in moduleNames: + _removeModuleFromTasks(process, name) + + +def customiseNanoForScoutingMiniAOD(process): + """ + Main customization function for running standard NanoAOD on scouting MiniAOD. + + This is the recommended entry point for cmsDriver --customise flag. + """ + + # ============================================================ + # 1. Disable modules requiring jet constituents + # ============================================================ + + # Pileup Jet ID - requires iterating over jet daughters + pileupJetIdModules = [ + 'pileupJetId94X', + 'pileupJetIdPuppi', + 'pileupJetId', + 'pileupJetIdUpdated', + ] + _removeModulesFromProcess(process, pileupJetIdModules) + + # Jet update chain - these try to recompute jet properties + jetUpdateModules = [ + 'updatedJets', + 'updatedJetsWithUserData', + 'updatedJetsPuppi', + 'updatedJetsPuppiWithUserData', + # Full jet recreation chain + 'patJetsPuppi', + 'patJets', + 'selectedPatJetsPuppi', + 'selectedPatJets', + 'slimmedJetsPuppiNoDeepTags', + 'updatedPatJetsSlimmedDeepFlavour', + 'updatedPatJetsTransientCorrectedSlimmedDeepFlavour', + # Additional jet modules + 'tightJetId', + 'tightJetIdLepVeto', + 'jetCorrFactorsNano', + 'jetCorrFactorsAK8', + ] + _removeModulesFromProcess(process, jetUpdateModules) + + # Deep taggers requiring constituent information + deepTaggerModules = [ + 'btagDeepFlavPuppi', + 'btagDeepFlavCMVAPuppi', + 'btagSFPuppi', + 'ctagSFPuppi', + ] + _removeModulesFromProcess(process, deepTaggerModules) + + # ============================================================ + # 2. Disable AK8/fat jet modules (not available in scouting) + # ============================================================ + + fatJetModules = [ + 'fatJetTable', + 'subJetTable', + 'saTable', + 'saJetTable', + 'softActivityJetTable', + 'fatJetMCTable', + 'subJetMCTable', + ] + _removeModulesFromProcess(process, fatJetModules) + + # ============================================================ + # 2b. Disable tau modules (not available in scouting) + # ============================================================ + + tauModules = [ + 'slimmedTaus', + 'slimmedTausUpdated', + 'slimmedTausWithPNetCHS', + 'finalTaus', + 'tauTable', + 'tauMCTable', + 'boostedTauTable', + 'boostedTauMCTable', + 'tauIdMVAIsoTask', + 'tauIdDeepTauTask', + ] + _removeModulesFromProcess(process, tauModules) + + # ============================================================ + # 3. Disable PPS proton modules (not in scouting data) + # ============================================================ + + ppsModules = [ + 'protonTable', + 'multiRPTable', + 'singleRPTable', + ] + _removeModulesFromProcess(process, ppsModules) + + # ============================================================ + # 4. Redirect jet tables to use input jets directly + # ============================================================ + + # Skip the jet update chain, use slimmed jets directly + if hasattr(process, 'jetTable'): + process.jetTable.src = cms.InputTag("slimmedJets") + # Remove pileup jet ID variables + if hasattr(process.jetTable, 'variables'): + vars_to_remove = ['puId', 'puIdDisc'] + for var in vars_to_remove: + if hasattr(process.jetTable.variables, var): + delattr(process.jetTable.variables, var) + + if hasattr(process, 'jetPuppiTable'): + # Scouting doesn't have separate Puppi jets, use slimmedJets + process.jetPuppiTable.src = cms.InputTag("slimmedJets") + if hasattr(process.jetPuppiTable, 'variables'): + vars_to_remove = ['puId', 'puIdDisc'] + for var in vars_to_remove: + if hasattr(process.jetPuppiTable.variables, var): + delattr(process.jetPuppiTable.variables, var) + + # Redirect corrT1METJetPuppiTable and remove unavailable variables + if hasattr(process, 'corrT1METJetPuppiTable'): + # Scouting doesn't have separate Puppi jets, use slimmedJets + process.corrT1METJetPuppiTable.src = cms.InputTag("slimmedJets") + if hasattr(process.corrT1METJetPuppiTable, 'variables'): + vars_to_remove = ['muonSubtrFactor', 'muonSubtrDeltaEta', 'muonSubtrDeltaPhi'] + for var in vars_to_remove: + if hasattr(process.corrT1METJetPuppiTable.variables, var): + delattr(process.corrT1METJetPuppiTable.variables, var) + + # ============================================================ + # 5. Handle trigger-related modules + # ============================================================ + + # L1 prefiring weights not available + l1Modules = [ + 'L1PreFiringWeight', + 'L1PreFiringWeightProducer', + 'l1TriggerPathTable', + ] + _removeModulesFromProcess(process, l1Modules) + + # Trigger object table needs caloStage2Digis and gmtStage2Digis which we don't have + # We only have gtStage2Digis from scouting. Remove the trigger object table. + triggerModules = [ + 'triggerObjectTable', + 'l1MuTable', + 'l1EGTable', + 'l1TauTable', + 'l1JetTable', + 'l1EtSumTable', + ] + _removeModulesFromProcess(process, triggerModules) + + # Keep l1bits if gtStage2Digis is available (we produce it in scoutingToMiniAOD) + + # ============================================================ + # 6. Handle MET modules (we only have slimmedMETs, not Puppi variants) + # ============================================================ + + # Remove/redirect modules that need slimmedMETsPuppi + puppiMetModules = [ + 'rawPuppiMetTable', + 'puppiMetTable', + 'metPuppiTable', + 'corrT1METJetPuppiTable', + ] + _removeModulesFromProcess(process, puppiMetModules) + + # Redirect MET table to use slimmedMETs + if hasattr(process, 'metTable'): + process.metTable.src = cms.InputTag("slimmedMETs") + + # ============================================================ + # 7. Handle muons and vertices + # ============================================================ + + # Remove slimmedMuonsUpdated which might need additional info + _removeModuleFromTasks(process, 'slimmedMuonsUpdated') + + # Replace pvbsTable to use offlineSlimmedPrimaryVertices + # (we don't have offlineSlimmedPrimaryVerticesWithBS) + if hasattr(process, 'pvbsTable'): + process.pvbsTable = process.pvTable.clone( + src = cms.InputTag("offlineSlimmedPrimaryVertices"), + name = cms.string("PVBS"), + doc = cms.string("PV with beam spot (same as PV for scouting)") + ) + + # Redirect muon table to use slimmedMuons directly + if hasattr(process, 'muonTable'): + process.muonTable.src = cms.InputTag("slimmedMuons") + + # ============================================================ + # 8. Handle rho variables + # ============================================================ + + # Scouting MiniAOD has fixedGridRhoFastjetAll but NanoAOD also needs + # fixedGridRhoFastjetCentral. We need to either add it to MiniAOD + # or redirect the rho table. + + # Remove the rhoTable if it causes issues (it needs fixedGridRhoFastjetCentral) + # Alternative: add fixedGridRhoFastjetCentral to the process + if hasattr(process, 'rhoTable'): + # Check if fixedGridRhoFastjetCentral is being produced + if not hasattr(process, 'fixedGridRhoFastjetCentral'): + # Create the producer + process.fixedGridRhoFastjetCentral = cms.EDProducer("FixedGridRhoProducerFastjet", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + maxRapidity = cms.double(2.5), + gridSpacing = cms.double(0.55) + ) + # Add to any existing task that runs before nanoSequence + if hasattr(process, 'nanoSequence'): + # Insert at the beginning of nanoSequence + try: + process.nanoSequence.insert(0, process.fixedGridRhoFastjetCentral) + except: + pass + + # ============================================================ + # 9. Handle electron/photon updates + # ============================================================ + + # Remove electron updater that might need unavailable info + electronUpdateModules = [ + 'slimmedElectronsUpdated', + 'slimmedElectronsWithUserData', + ] + _removeModulesFromProcess(process, electronUpdateModules) + + # Redirect electron table to use slimmedElectrons + if hasattr(process, 'electronTable'): + process.electronTable.src = cms.InputTag("slimmedElectrons") + + # Same for photons + photonUpdateModules = [ + 'slimmedPhotonsUpdated', + 'slimmedPhotonsWithUserData', + ] + _removeModulesFromProcess(process, photonUpdateModules) + + # Replace photon table with a simpler version for scouting + # Our scouting photons don't have VID, MVA, or most standard variables + if hasattr(process, 'photonTable'): + from PhysicsTools.NanoAOD.common_cff import Var, P3Vars + process.photonTable = cms.EDProducer("SimplePATPhotonFlatTableProducer", + src = cms.InputTag("slimmedPhotons"), + cut = cms.string(""), + name = cms.string("Photon"), + doc = cms.string("Photons from scouting"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + P3Vars, + # Shower shape from userFloats (set in our producer) + sieie = Var("userFloat('sigmaIetaIeta')", float, doc="sigma_IetaIeta", precision=10), + hoe = Var("userFloat('hOverE')", float, doc="H/E", precision=8), + r9 = Var("userFloat('r9')", float, doc="R9", precision=10), + # Isolation from userFloats + ecalIso = Var("userFloat('ecalIso')", float, doc="ECAL isolation", precision=6), + hcalIso = Var("userFloat('hcalIso')", float, doc="HCAL isolation", precision=6), + trkIso = Var("userFloat('trkIso')", float, doc="track isolation", precision=6), + ) + ) + + # Replace electron table with a simpler version for scouting + if hasattr(process, 'electronTable'): + from PhysicsTools.NanoAOD.common_cff import Var, P4Vars + process.electronTable = cms.EDProducer("SimplePATElectronFlatTableProducer", + src = cms.InputTag("slimmedElectrons"), + cut = cms.string(""), + name = cms.string("Electron"), + doc = cms.string("Electrons from scouting"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + P4Vars, + charge = Var("charge", int, doc="charge"), + pdgId = Var("pdgId", int, doc="PDG ID"), + # Shower shape from userFloats + sieie = Var("userFloat('sigmaIetaIeta')", float, doc="sigma_IetaIeta", precision=10), + hoe = Var("userFloat('hOverE')", float, doc="H/E", precision=8), + r9 = Var("userFloat('r9')", float, doc="R9", precision=10), + # ID variables from userFloats + dEtaIn = Var("userFloat('dEtaIn')", float, doc="dEta(SC,track)", precision=10), + dPhiIn = Var("userFloat('dPhiIn')", float, doc="dPhi(SC,track)", precision=10), + ooEMOop = Var("userFloat('ooEMOop')", float, doc="1/E - 1/p", precision=10), + # Track from userFloats + dxy = Var("userFloat('trkd0')", float, doc="track d0", precision=10), + dz = Var("userFloat('trkdz')", float, doc="track dz", precision=10), + # Isolation from userFloats + ecalIso = Var("userFloat('ecalIso')", float, doc="ECAL isolation", precision=6), + hcalIso = Var("userFloat('hcalIso')", float, doc="HCAL isolation", precision=6), + trackIso = Var("userFloat('trackIso')", float, doc="track isolation", precision=6), + ) + ) + + # ============================================================ + # 10. Use TryToContinue for remaining missing products + # ============================================================ + + # This allows the job to continue if some products are missing + if hasattr(process, 'options'): + if not hasattr(process.options, 'TryToContinue'): + process.options.TryToContinue = cms.untracked.vstring() + process.options.TryToContinue.append('ProductNotFound') + + return process + + +def customiseNanoForScoutingMiniAOD_nol1bits(process): + """ + Customization that also removes L1 bits (for testing without L1 unpacking). + """ + process = customiseNanoForScoutingMiniAOD(process) + _removeModuleFromTasks(process, 'l1bits') + return process + + +def customiseNanoForScoutingMiniAOD_keepFatJets(process): + """ + Variant that keeps fat jet modules (if you recluster AK8 jets from PF candidates). + """ + process = customiseNanoForScoutingMiniAOD(process) + # Re-add fat jet modules that were removed + # (they would need reclustered AK8 jets as input) + return process diff --git a/PhysicsTools/PatFromScouting/python/scoutingMiniAOD_cff.py b/PhysicsTools/PatFromScouting/python/scoutingMiniAOD_cff.py new file mode 100644 index 0000000000000..bac88d641756f --- /dev/null +++ b/PhysicsTools/PatFromScouting/python/scoutingMiniAOD_cff.py @@ -0,0 +1,286 @@ +""" +Scouting to MiniAOD conversion configuration. + +This module converts Run3 scouting data to MiniAOD-compatible format with +standard collection names for downstream processing with standard NanoAOD. + +Output collections (matching standard MiniAOD names): +- packedPFCandidates: pat::PackedCandidateCollection from scouting particles +- offlineSlimmedPrimaryVertices: reco::VertexCollection from scouting vertices +- slimmedMuons: pat::MuonCollection from scouting muons +- slimmedMuonsNoVtx: pat::MuonCollection from displaced scouting muons (2024+) +- slimmedElectrons: pat::ElectronCollection from scouting electrons +- slimmedPhotons: pat::PhotonCollection from scouting photons +- slimmedJets: pat::JetCollection from scouting PF jets +- slimmedMETs: pat::METCollection computed from scouting PF candidates +- fixedGridRhoAll: double for pileup density +- fixedGridRhoFastjetAll: double for fastjet rho +- gtStage2Digis: L1 trigger unpacking from raw FED data +- scoutingTracks: reco::TrackCollection from scouting tracks + +Usage: + from PhysicsTools.PatFromScouting.scoutingMiniAOD_cff import scoutingMiniAODTask + process.scoutingMiniAODPath = cms.Path() + process.scoutingMiniAODPath.associate(scoutingMiniAODTask) + +Or via cmsDriver customization: + --customise PhysicsTools/PatFromScouting/scoutingMiniAOD_cff.customiseForScoutingMiniAOD +""" + +import FWCore.ParameterSet.Config as cms + +# ============================================================ +# Primary Vertices (must be produced before PF candidates) +# ============================================================ + +offlineSlimmedPrimaryVertices = cms.EDProducer("Run3ScoutingVertexToRecoVertexProducer", + src = cms.InputTag("hltScoutingPrimaryVertexPacker", "primaryVtx") +) + +# ============================================================ +# PF Candidates from scouting particles +# ============================================================ + +# Using pat::PackedCandidate for MiniAOD compatibility +# Requires vertices to be produced first for proper vertex references +packedPFCandidates = cms.EDProducer("Run3ScoutingParticleToPackedCandidateProducer", + src = cms.InputTag("hltScoutingPFPacker"), + vertices = cms.InputTag("offlineSlimmedPrimaryVertices"), + CHS = cms.bool(False) +) + +# ============================================================ +# Muons +# ============================================================ + +slimmedMuons = cms.EDProducer("PatFromScoutingMuonProducer", + src = cms.InputTag("hltScoutingMuonPacker") +) + +# Displaced muons (no vertex constraint) - available from 2024+ +slimmedMuonsNoVtx = cms.EDProducer("PatFromScoutingMuonProducer", + src = cms.InputTag("hltScoutingMuonPackerNoVtx") +) + +# ============================================================ +# Electrons +# ============================================================ + +slimmedElectrons = cms.EDProducer("PatFromScoutingElectronProducer", + src = cms.InputTag("hltScoutingEgammaPacker") +) + +# ============================================================ +# Photons +# ============================================================ + +slimmedPhotons = cms.EDProducer("PatFromScoutingPhotonProducer", + src = cms.InputTag("hltScoutingEgammaPacker") +) + +# ============================================================ +# Jets +# ============================================================ + +slimmedJets = cms.EDProducer("PatFromScoutingJetProducer", + src = cms.InputTag("hltScoutingPFPacker"), + pfCandidates = cms.InputTag("packedPFCandidates") +) + +# ============================================================ +# MET +# ============================================================ + +slimmedMETs = cms.EDProducer("Run3ScoutingMETProducer", + metPt = cms.InputTag("hltScoutingPFPacker", "pfMetPt"), + metPhi = cms.InputTag("hltScoutingPFPacker", "pfMetPhi") +) + +# ============================================================ +# Tracks +# ============================================================ + +scoutingTracks = cms.EDProducer("Run3ScoutingTrackToRecoTrackProducer", + src = cms.InputTag("hltScoutingTrackPacker") +) + +# ============================================================ +# Beam Spot (from conditions database) +# ============================================================ + +# BeamSpotProducer reads from conditions - works with GlobalTag +offlineBeamSpot = cms.EDProducer("BeamSpotProducer") + +# ============================================================ +# Pileup Density (rho) - all variants needed by NanoAOD +# ============================================================ + +# All eta range +fixedGridRhoAll = cms.EDProducer("FixedGridRhoProducer", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + EtaRegion = cms.string("All") +) + +fixedGridRhoFastjetAll = cms.EDProducer("FixedGridRhoProducerFastjet", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + maxRapidity = cms.double(5.0), + gridSpacing = cms.double(0.55) +) + +# Central region (|eta| < 2.5) +fixedGridRhoFastjetCentral = cms.EDProducer("FixedGridRhoProducerFastjet", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + maxRapidity = cms.double(2.5), + gridSpacing = cms.double(0.55) +) + +# Central "calo-like" - use PF candidates in central region +# (scouting doesn't have calo towers, so we approximate with PF) +fixedGridRhoFastjetCentralCalo = cms.EDProducer("FixedGridRhoProducerFastjet", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + maxRapidity = cms.double(2.5), + gridSpacing = cms.double(0.55) +) + +# Central charged-only (approximate - uses all PF candidates for scouting) +fixedGridRhoFastjetCentralChargedPileUp = cms.EDProducer("FixedGridRhoProducerFastjet", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + maxRapidity = cms.double(2.5), + gridSpacing = cms.double(0.55) +) + +# Neutral-only variant (approximate - uses all PF candidates for scouting) +fixedGridRhoFastjetCentralNeutral = cms.EDProducer("FixedGridRhoProducerFastjet", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + maxRapidity = cms.double(2.5), + gridSpacing = cms.double(0.55) +) + +# ============================================================ +# L1 Trigger Unpacking +# ============================================================ + +gtStage2Digis = cms.EDProducer("L1TRawToDigi", + InputLabel = cms.InputTag("hltFEDSelectorL1"), + Setup = cms.string("stage2::GTSetup"), + FedIds = cms.vint32(1404), +) + +# ============================================================ +# Task and Sequence Definitions +# ============================================================ + +# Core task - objects needed for standard NanoAOD +# Note: slimmedMuonsNoVtx is not included here; it is only available from 2024+ +# and should be added explicitly when processing 2024+ data +scoutingMiniAODCoreTask = cms.Task( + packedPFCandidates, + offlineSlimmedPrimaryVertices, + offlineBeamSpot, + slimmedMuons, + slimmedElectrons, + slimmedPhotons, + slimmedJets, + slimmedMETs, + # All rho variants needed by NanoAOD + fixedGridRhoAll, + fixedGridRhoFastjetAll, + fixedGridRhoFastjetCentral, + fixedGridRhoFastjetCentralCalo, + fixedGridRhoFastjetCentralChargedPileUp, + fixedGridRhoFastjetCentralNeutral, +) + +# Full task including L1 unpacking and tracks +scoutingMiniAODTask = cms.Task( + scoutingMiniAODCoreTask, + gtStage2Digis, + scoutingTracks, +) + +# Sequence for backward compatibility +scoutingMiniAODSequence = cms.Sequence(scoutingMiniAODTask) + + +# ============================================================ +# Customization Functions +# ============================================================ + +def customiseForScoutingMiniAOD(process): + """ + Customization function for cmsDriver to add scouting MiniAOD production. + + Usage: + cmsDriver.py MINI --customise PhysicsTools/PatFromScouting/scoutingMiniAOD_cff.customiseForScoutingMiniAOD + """ + + # Load particle data table (needed for PF candidate producer) + process.load("SimGeneral.HepPDTESSource.pdt_cfi") + + # Add all producers to the process + process.packedPFCandidates = packedPFCandidates.clone() + process.offlineSlimmedPrimaryVertices = offlineSlimmedPrimaryVertices.clone() + process.slimmedMuons = slimmedMuons.clone() + process.slimmedElectrons = slimmedElectrons.clone() + process.slimmedPhotons = slimmedPhotons.clone() + process.slimmedJets = slimmedJets.clone() + process.slimmedMETs = slimmedMETs.clone() + process.scoutingTracks = scoutingTracks.clone() + process.fixedGridRhoAll = fixedGridRhoAll.clone() + process.fixedGridRhoFastjetAll = fixedGridRhoFastjetAll.clone() + process.gtStage2Digis = gtStage2Digis.clone() + + # Create task and path + process.scoutingMiniAODTask = cms.Task( + process.packedPFCandidates, + process.offlineSlimmedPrimaryVertices, + process.slimmedMuons, + process.slimmedElectrons, + process.slimmedPhotons, + process.slimmedJets, + process.slimmedMETs, + process.scoutingTracks, + process.fixedGridRhoAll, + process.fixedGridRhoFastjetAll, + process.gtStage2Digis, + ) + + process.scoutingMiniAOD_step = cms.Path() + process.scoutingMiniAOD_step.associate(process.scoutingMiniAODTask) + + # Add to schedule if it exists + if hasattr(process, 'schedule') and process.schedule is not None: + process.schedule.insert(0, process.scoutingMiniAOD_step) + + return process + + +def customiseOutputForScoutingMiniAOD(process): + """ + Customize output module for scouting MiniAOD content. + """ + + outputCommands = cms.untracked.vstring( + 'drop *', + 'keep *_packedPFCandidates_*_*', + 'keep *_offlineSlimmedPrimaryVertices_*_*', + 'keep *_slimmedMuons_*_*', + 'keep *_slimmedElectrons_*_*', + 'keep *_slimmedPhotons_*_*', + 'keep *_slimmedJets_*_*', + 'keep *_slimmedMETs_*_*', + 'keep *_scoutingTracks_*_*', + 'keep *_TriggerResults_*_*', + 'keep *_fixedGridRhoAll_*_*', + 'keep *_fixedGridRhoFastjetAll_*_*', + 'keep *_gtStage2Digis_*_*', + ) + + # Find and configure output module + for name in ['MINIAODoutput', 'MINIAODSIMoutput', 'output', 'out']: + if hasattr(process, name): + output = getattr(process, name) + output.outputCommands = outputCommands + break + + return process diff --git a/PhysicsTools/PatFromScouting/python/scoutingNanoAOD_cff.py b/PhysicsTools/PatFromScouting/python/scoutingNanoAOD_cff.py new file mode 100644 index 0000000000000..3f8f10a90e603 --- /dev/null +++ b/PhysicsTools/PatFromScouting/python/scoutingNanoAOD_cff.py @@ -0,0 +1,338 @@ +""" +Scouting NanoAOD configuration. + +Produces NanoAOD flat tables directly from scouting MiniAOD collections, +bypassing the standard NanoAOD jet update chains that require full reconstruction. + +Tables produced: +- Muon: pt, eta, phi, mass, charge, isolation, ID flags +- MuonNoVtx: displaced muons without vertex constraint (2024+) +- Electron: pt, eta, phi, mass, charge, shower shape, isolation +- Photon: pt, eta, phi, mass, shower shape, isolation +- Jet: pt, eta, phi, mass, energy fractions, multiplicities, b-tags +- MET: pt, phi, sumEt +- PV: x, y, z, errors, chi2, ndof +- DimuonVtx: displaced dimuon vertices with muon index cross-references +- DimuonVtxNoVtx: displaced dimuon vertices from NoVtx muons (2024+) +- Event: fixedGridRhoAll + +To include HLT trigger decisions, add to outputCommands: + 'keep edmTriggerResults_*_*_*' +This will create HLT_* and DST_* boolean branches for all trigger paths. +""" + +import FWCore.ParameterSet.Config as cms +from Configuration.Eras.Modifier_run3_scouting_2024_cff import run3_scouting_2024 +from PhysicsTools.NanoAOD.common_cff import Var + +# Muon table +scoutingMuonTable = cms.EDProducer("SimplePATMuonFlatTableProducer", + src = cms.InputTag("slimmedMuons"), + cut = cms.string(""), + name = cms.string("Muon"), + doc = cms.string("Muons from scouting"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + pt = Var("pt", float, doc="pt", precision=10), + eta = Var("eta", float, doc="eta", precision=12), + phi = Var("phi", float, doc="phi", precision=12), + mass = Var("mass", float, doc="mass", precision=10), + charge = Var("charge", int, doc="charge"), + pdgId = Var("pdgId", int, doc="PDG ID"), + # Use global track dxy/dz - for scouting muons the track is embedded as combinedMuon + dxy = Var("? globalTrack.isNonnull ? globalTrack.dxy : 0", float, doc="dxy w.r.t. track reference point", precision=10), + dxyErr = Var("? globalTrack.isNonnull ? globalTrack.dxyError : 0", float, doc="dxy uncertainty", precision=10), + dz = Var("? globalTrack.isNonnull ? globalTrack.dz : 0", float, doc="dz w.r.t. track reference point", precision=10), + dzErr = Var("? globalTrack.isNonnull ? globalTrack.dzError : 0", float, doc="dz uncertainty", precision=10), + trkChi2 = Var("? globalTrack.isNonnull ? globalTrack.normalizedChi2 : -1", float, doc="track chi2/ndof", precision=6), + nValidHits = Var("? globalTrack.isNonnull ? globalTrack.numberOfValidHits : -1", int, doc="number of valid hits"), + # ID flags + isGlobal = Var("isGlobalMuon", bool, doc="is global muon"), + isTracker = Var("isTrackerMuon", bool, doc="is tracker muon"), + isStandalone = Var("isStandAloneMuon", bool, doc="is standalone muon"), + isPF = Var("isPFMuon", bool, doc="is PF muon"), + # Station and layer counts + nStations = Var("numberOfMatchedStations()", int, doc="number of matched stations with default arbitration"), + nTrackerLayers = Var("numberOfTrackerLayersWithMeasurement()", int, doc="number of tracker layers with measurement"), + nPixelLayers = Var("numberOfPixelLayersWithMeasurement()", int, doc="number of pixel layers with measurement"), + nChambers = Var("numberOfChambers()", int, doc="number of chambers"), + nChambersCSCorDT = Var("numberOfChambersCSCorDT()", int, doc="number of CSC or DT chambers"), + # Hit counts + nValidMuonHits = Var("numberOfValidMuonHits()", int, doc="number of valid muon hits"), + nValidPixelHits = Var("numberOfValidPixelHits()", int, doc="number of valid pixel hits"), + nValidStripHits = Var("numberOfValidStripHits()", int, doc="number of valid strip hits"), + # Isolation - scouting provides trackIso, ecalIso, hcalIso (not PF components) + # Use calorimeter isolation sum as proxy for relIso + relIso = Var("(isolationR03().emEt + isolationR03().hadEt)/pt", float, doc="relative calo isolation (ecal+hcal)/pt", precision=6), + ecalIso = Var("isolationR03().emEt", float, doc="ECAL isolation", precision=6), + hcalIso = Var("isolationR03().hadEt", float, doc="HCAL isolation", precision=6), + trkIso = Var("isolationR03().sumPt", float, doc="tracker isolation", precision=6), + tkRelIso = Var("isolationR03().sumPt/pt", float, doc="tracker relative isolation", precision=6), + ) +) + +# NoVtx muon table (2024+ only) +scoutingMuonNoVtxTable = scoutingMuonTable.clone( + src = cms.InputTag("slimmedMuonsNoVtx"), + name = cms.string("MuonNoVtx"), + doc = cms.string("Displaced muons from scouting (no vertex constraint)"), +) + +# Electron table +scoutingElectronTable = cms.EDProducer("SimplePATElectronFlatTableProducer", + src = cms.InputTag("slimmedElectrons"), + cut = cms.string(""), + name = cms.string("Electron"), + doc = cms.string("Electrons from scouting"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + pt = Var("pt", float, doc="pt", precision=10), + eta = Var("eta", float, doc="eta", precision=12), + phi = Var("phi", float, doc="phi", precision=12), + mass = Var("mass", float, doc="mass", precision=10), + charge = Var("charge", int, doc="charge"), + pdgId = Var("pdgId", int, doc="PDG ID"), + dxy = Var("dB('PV2D')", float, doc="dxy wrt PV", precision=10), + dz = Var("dB('PVDZ')", float, doc="dz wrt PV", precision=10), + # Shower shape + sieie = Var("full5x5_sigmaIetaIeta", float, doc="sigma_IetaIeta", precision=10), + hoe = Var("hadronicOverEm", float, doc="H/E", precision=8), + # ID + dEtaIn = Var("deltaEtaSuperClusterTrackAtVtx", float, doc="dEta(SC, track)", precision=10), + dPhiIn = Var("deltaPhiSuperClusterTrackAtVtx", float, doc="dPhi(SC, track)", precision=10), + # Isolation + pfRelIso03_all = Var("(pfIsolationVariables().sumChargedHadronPt + max(pfIsolationVariables().sumNeutralHadronEt + pfIsolationVariables().sumPhotonEt - 0.5*pfIsolationVariables().sumPUPt, 0.0))/pt", float, doc="PF relative isolation dR=0.3", precision=6), + ecalIso = Var("ecalPFClusterIso", float, doc="ECAL PF cluster isolation", precision=6), + hcalIso = Var("hcalPFClusterIso", float, doc="HCAL PF cluster isolation", precision=6), + trkIso = Var("trackIso", float, doc="tracker isolation", precision=6), + ) +) + +# Photon table +scoutingPhotonTable = cms.EDProducer("SimplePATPhotonFlatTableProducer", + src = cms.InputTag("slimmedPhotons"), + cut = cms.string(""), + name = cms.string("Photon"), + doc = cms.string("Photons from scouting"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + pt = Var("pt", float, doc="pt", precision=10), + eta = Var("eta", float, doc="eta", precision=12), + phi = Var("phi", float, doc="phi", precision=12), + mass = Var("mass", float, doc="mass", precision=10), + # Shower shape - use userFloats from our producer + sieie = Var("userFloat('sigmaIetaIeta')", float, doc="sigma_IetaIeta", precision=10), + hoe = Var("userFloat('hOverE')", float, doc="H/E", precision=8), + # Isolation from userFloats + ecalIso = Var("userFloat('ecalIso')", float, doc="ECAL isolation", precision=6), + hcalIso = Var("userFloat('hcalIso')", float, doc="HCAL isolation", precision=6), + ) +) + +# Jet table +scoutingJetTable = cms.EDProducer("SimplePATJetFlatTableProducer", + src = cms.InputTag("slimmedJets"), + cut = cms.string(""), + name = cms.string("Jet"), + doc = cms.string("Jets from scouting (AK4 PF jets)"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + pt = Var("pt", float, doc="pt", precision=10), + eta = Var("eta", float, doc="eta", precision=12), + phi = Var("phi", float, doc="phi", precision=12), + mass = Var("mass", float, doc="mass", precision=10), + area = Var("jetArea", float, doc="jet area", precision=6), + # Energy fractions + chHEF = Var("chargedHadronEnergyFraction", float, doc="charged hadron energy fraction", precision=6), + neHEF = Var("neutralHadronEnergyFraction", float, doc="neutral hadron energy fraction", precision=6), + chEmEF = Var("chargedEmEnergyFraction", float, doc="charged EM energy fraction", precision=6), + neEmEF = Var("neutralEmEnergyFraction", float, doc="neutral EM energy fraction", precision=6), + muEF = Var("muonEnergyFraction", float, doc="muon energy fraction", precision=6), + # Multiplicities + nConstituents = Var("numberOfDaughters", int, doc="number of constituents"), + chMultiplicity = Var("chargedMultiplicity", int, doc="charged multiplicity"), + neMultiplicity = Var("neutralMultiplicity", int, doc="neutral multiplicity"), + # B-tagging (from scouting) + btagCSV = Var("bDiscriminator('pfCombinedSecondaryVertexV2BJetTags')", float, doc="CSV b-tag discriminator", precision=10), + btagDeepB = Var("bDiscriminator('pfDeepCSVJetTags:probb')", float, doc="DeepCSV b-tag discriminator", precision=10), + ) +) + +# MET table +scoutingMETTable = cms.EDProducer("SimplePATMETFlatTableProducer", + src = cms.InputTag("slimmedMETs"), + name = cms.string("MET"), + doc = cms.string("MET from scouting PF candidates"), + singleton = cms.bool(True), + extension = cms.bool(False), + variables = cms.PSet( + pt = Var("pt", float, doc="MET pt", precision=10), + phi = Var("phi", float, doc="MET phi", precision=10), + sumEt = Var("sumEt", float, doc="scalar sum of Et", precision=10), + ) +) + +# Primary vertex table +scoutingPVTable = cms.EDProducer("SimpleVertexFlatTableProducer", + src = cms.InputTag("offlineSlimmedPrimaryVertices"), + cut = cms.string(""), + name = cms.string("PV"), + doc = cms.string("Primary vertices from scouting"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + x = Var("x", float, doc="x position", precision=10), + y = Var("y", float, doc="y position", precision=10), + z = Var("z", float, doc="z position", precision=10), + ndof = Var("ndof", float, doc="number of degrees of freedom", precision=6), + chi2 = Var("chi2", float, doc="chi2", precision=6), + # Error + xErr = Var("xError", float, doc="x position error", precision=10), + yErr = Var("yError", float, doc="y position error", precision=10), + zErr = Var("zError", float, doc="z position error", precision=10), + ) +) + +# L1 trigger bits conversion (GlobalAlgBlk -> TriggerResults) +# This converts the gtStage2Digis output to a format NanoAOD can use +l1bits = cms.EDProducer("L1TriggerResultsConverter", + src = cms.InputTag("gtStage2Digis"), + legacyL1 = cms.bool(False), + storeUnprefireableBits = cms.bool(True), +) + +# Event-level variables +scoutingEventTable = cms.EDProducer("GlobalVariablesTableProducer", + name = cms.string(""), + variables = cms.PSet( + fixedGridRhoFastjetAll = cms.PSet( + src = cms.InputTag("fixedGridRhoFastjetAll"), + doc = cms.string("rho from HLT scouting"), + type = cms.string("double"), + precision = cms.int32(8) + ), + ) +) + +# Dimuon displaced vertex table +scoutingDimuonVtxTable = cms.EDProducer("SimpleSecondaryVertexFlatTableProducer", + src = cms.InputTag("scoutingDimuonVertices"), + cut = cms.string(""), + name = cms.string("DimuonVtx"), + doc = cms.string("Displaced dimuon vertices from scouting"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + pt = Var("pt", float, doc="pt", precision=10), + eta = Var("eta", float, doc="eta", precision=12), + phi = Var("phi", float, doc="phi", precision=12), + mass = Var("mass", float, doc="dimuon invariant mass", precision=10), + x = Var("position().x()", float, doc="vertex X position, in cm", precision=10), + y = Var("position().y()", float, doc="vertex Y position, in cm", precision=10), + z = Var("position().z()", float, doc="vertex Z position, in cm", precision=14), + ndof = Var("vertexNdof()", float, doc="number of degrees of freedom", precision=8), + chi2 = Var("vertexNormalizedChi2()", float, doc="reduced chi2, i.e. chi/ndof", precision=8), + nMuons = Var("numberOfDaughters()", "uint8", doc="number of daughter muons"), + mu1Idx = Var("?numberOfDaughters()>0?daughterPtr(0).key():-1", "int16", doc="index of first muon in Muon collection"), + mu2Idx = Var("?numberOfDaughters()>1?daughterPtr(1).key():-1", "int16", doc="index of second muon in Muon collection"), + ), +) + +# Dimuon displaced vertex table for NoVtx muons (2024+ only) +scoutingDimuonVtxNoVtxTable = cms.EDProducer("SimpleSecondaryVertexFlatTableProducer", + src = cms.InputTag("scoutingDimuonVerticesNoVtx"), + cut = cms.string(""), + name = cms.string("DimuonVtxNoVtx"), + doc = cms.string("Displaced dimuon vertices from scouting (NoVtx muons)"), + singleton = cms.bool(False), + extension = cms.bool(False), + variables = cms.PSet( + pt = Var("pt", float, doc="pt", precision=10), + eta = Var("eta", float, doc="eta", precision=12), + phi = Var("phi", float, doc="phi", precision=12), + mass = Var("mass", float, doc="dimuon invariant mass", precision=10), + x = Var("position().x()", float, doc="vertex X position, in cm", precision=10), + y = Var("position().y()", float, doc="vertex Y position, in cm", precision=10), + z = Var("position().z()", float, doc="vertex Z position, in cm", precision=14), + ndof = Var("vertexNdof()", float, doc="number of degrees of freedom", precision=8), + chi2 = Var("vertexNormalizedChi2()", float, doc="reduced chi2, i.e. chi/ndof", precision=8), + nMuons = Var("numberOfDaughters()", "uint8", doc="number of daughter muons"), + mu1Idx = Var("?numberOfDaughters()>0?daughterPtr(0).key():-1", "int16", doc="index of first muon in MuonNoVtx collection"), + mu2Idx = Var("?numberOfDaughters()>1?daughterPtr(1).key():-1", "int16", doc="index of second muon in MuonNoVtx collection"), + ), +) + +# Scouting NanoAOD task - core tables +scoutingNanoAODTask = cms.Task( + scoutingMuonTable, + scoutingElectronTable, + scoutingPhotonTable, + scoutingJetTable, + scoutingMETTable, + scoutingPVTable, + scoutingEventTable, + scoutingDimuonVtxTable, + l1bits, +) + +# For 2024+, add NoVtx muon table and its dimuon vertex table +_scoutingNanoAODTask_2024 = scoutingNanoAODTask.copy() +_scoutingNanoAODTask_2024.add(scoutingMuonNoVtxTable) +_scoutingNanoAODTask_2024.add(scoutingDimuonVtxNoVtxTable) +run3_scouting_2024.toReplaceWith(scoutingNanoAODTask, _scoutingNanoAODTask_2024) + +scoutingNanoAODSequence = cms.Sequence(scoutingNanoAODTask) + + +# ============================================================ +# Customization function for cmsDriver +# ============================================================ + +def customiseScoutingNanoAOD(process): + """ + Customization function to add scouting NanoAOD production. + + Usage with cmsDriver (two-step workflow): + + Step 1 - Scouting RAW to MiniAOD: + cmsRun test_scoutingToMiniAOD_cfg.py + + Step 2 - MiniAOD to NanoAOD: + cmsDriver.py NANO --conditions auto:run3_data_prompt \\ + --era Run3 --step NANO --datatier NANOAOD \\ + --filein file:scoutingToMiniAOD_test.root \\ + --fileout file:scoutingNanoAOD.root \\ + --customise PhysicsTools/PatFromScouting/scoutingNanoAOD_cff.customiseScoutingNanoAOD \\ + -n 100 + + Or for standalone usage: + from PhysicsTools.PatFromScouting.scoutingNanoAOD_cff import customiseScoutingNanoAOD + process = customiseScoutingNanoAOD(process) + """ + + # When called via cmsDriver @ScoutMini, the task is already loaded + # and scheduled. Only add modules if they are not yet in the process. + if not hasattr(process, 'scoutingNanoAODTask'): + process.load('PhysicsTools.PatFromScouting.scoutingNanoAOD_cff') + + process.scoutingNanoAOD_step = cms.Path() + process.scoutingNanoAOD_step.associate(process.scoutingNanoAODTask) + + if hasattr(process, 'schedule') and process.schedule is not None: + process.schedule.insert(0, process.scoutingNanoAOD_step) + + # Configure output + if hasattr(process, 'NANOAODoutput'): + process.NANOAODoutput.outputCommands = cms.untracked.vstring( + 'drop *', + 'keep nanoaodFlatTable_*_*_*', + 'keep edmTriggerResults_*_*_*', + 'keep nanoaodMergeableCounterTable_*_*_*', + 'keep nanoaodUniqueString_*_*_*', + ) + + return process diff --git a/PhysicsTools/PatFromScouting/python/scoutingToMiniAOD_cff.py b/PhysicsTools/PatFromScouting/python/scoutingToMiniAOD_cff.py new file mode 100644 index 0000000000000..40f7e6923e080 --- /dev/null +++ b/PhysicsTools/PatFromScouting/python/scoutingToMiniAOD_cff.py @@ -0,0 +1,215 @@ +import FWCore.ParameterSet.Config as cms +from Configuration.Eras.Modifier_run3_scouting_2024_cff import run3_scouting_2024 + +# Use standard MiniAOD collection names for NanoAOD compatibility + +# Vertices - standard MiniAOD name (must be produced before PF candidates) +offlineSlimmedPrimaryVertices = cms.EDProducer("Run3ScoutingVertexToRecoVertexProducer", + src=cms.InputTag("hltScoutingPrimaryVertexPacker", "primaryVtx") +) + +# PF Candidates - using pat::PackedCandidate for MiniAOD compatibility +# Requires vertices to be produced first for proper vertex references +packedPFCandidates = cms.EDProducer("Run3ScoutingParticleToPackedCandidateProducer", + src=cms.InputTag("hltScoutingPFPacker"), + vertices=cms.InputTag("offlineSlimmedPrimaryVertices"), + tracks=cms.InputTag("scoutingTracks"), + CHS=cms.bool(False), + covarianceVersion=cms.int32(1), + covarianceSchema=cms.int32(520) +) + + +# Muons - standard MiniAOD name +# Note: From 2024, there are two muon collections (with/without vertex) +# Default uses hltScoutingMuonPacker, 2024+ uses hltScoutingMuonPackerVtx +slimmedMuons = cms.EDProducer("PatFromScoutingMuonProducer", + src=cms.InputTag("hltScoutingMuonPacker") +) +# For 2024 data, use hltScoutingMuonPackerVtx which includes vertex information +run3_scouting_2024.toModify(slimmedMuons, src="hltScoutingMuonPackerVtx") + +# Displaced muons (no vertex constraint) - only available from 2024+ +# Before 2024, there was only hltScoutingMuonPacker (no Vtx/NoVtx split) +slimmedMuonsNoVtx = cms.EDProducer("PatFromScoutingMuonProducer", + src=cms.InputTag("hltScoutingMuonPackerNoVtx") +) + +# Dimuon displaced vertices as VertexCompositePtrCandidate (like slimmedSecondaryVertices) +# with CandidatePtr daughters pointing into the corresponding muon collection. +# The muon-vertex link is derived from Run3ScoutingMuon::vtxIndx(). +# Default (pre-2024): single muon collection → single dimuon vertex collection +scoutingDimuonVertices = cms.EDProducer("ScoutingDimuonVtxProducer", + scoutingMuons=cms.InputTag("hltScoutingMuonPacker"), + scoutingVertices=cms.InputTag("hltScoutingMuonPacker", "displacedVtx"), + patMuons=cms.InputTag("slimmedMuons") +) +# For 2024+, Vtx muons have their own displaced vertices +run3_scouting_2024.toModify(scoutingDimuonVertices, + scoutingMuons="hltScoutingMuonPackerVtx", + scoutingVertices=cms.InputTag("hltScoutingMuonPackerVtx", "displacedVtx") +) + +# Dimuon vertices for NoVtx muons - only available from 2024+ +scoutingDimuonVerticesNoVtx = cms.EDProducer("ScoutingDimuonVtxProducer", + scoutingMuons=cms.InputTag("hltScoutingMuonPackerNoVtx"), + scoutingVertices=cms.InputTag("hltScoutingMuonPackerNoVtx", "displacedVtx"), + patMuons=cms.InputTag("slimmedMuonsNoVtx") +) + +# Electrons - standard MiniAOD name +slimmedElectrons = cms.EDProducer("PatFromScoutingElectronProducer", + src=cms.InputTag("hltScoutingEgammaPacker") +) + +# Photons - standard MiniAOD name +slimmedPhotons = cms.EDProducer("PatFromScoutingPhotonProducer", + src=cms.InputTag("hltScoutingEgammaPacker") +) + +# Jets - standard MiniAOD name +slimmedJets = cms.EDProducer("PatFromScoutingJetProducer", + src=cms.InputTag("hltScoutingPFPacker"), + pfCandidates=cms.InputTag("packedPFCandidates") +) + +# MET - standard MiniAOD name +# Uses precomputed MET from scouting data +slimmedMETs = cms.EDProducer("Run3ScoutingMETProducer", + metPt=cms.InputTag("hltScoutingPFPacker", "pfMetPt"), + metPhi=cms.InputTag("hltScoutingPFPacker", "pfMetPhi") +) + +# Tracks +scoutingTracks = cms.EDProducer("Run3ScoutingTrackToRecoTrackProducer", + src=cms.InputTag("hltScoutingTrackPacker") +) + +# Beam spot from conditions database +offlineBeamSpot = cms.EDProducer("BeamSpotProducer") + +# Rho (pileup density) - copy from HLT scouting data +fixedGridRhoFastjetAll = cms.EDProducer("ScoutingRhoProducer", + src=cms.InputTag("hltScoutingPFPacker", "rho") +) + +# L1 trigger unpacking from raw FED data +# Scouting stores L1 raw data in hltFEDSelectorL1 +gtStage2Digis = cms.EDProducer("L1TRawToDigi", + InputLabel = cms.InputTag("hltFEDSelectorL1"), + Setup = cms.string("stage2::GTSetup"), + FedIds = cms.vint32(1404), +) + +# L1 objects with standard module names for downstream compatibility +# Standard MiniAOD has L1 muons from gmtStage2Digis and L1 calo objects from caloStage2Digis +# These producers copy from gtStage2Digis to provide the expected module labels +gmtStage2Digis = cms.EDProducer("Run3ScoutingL1MuonProducer", + muonSource = cms.InputTag("gtStage2Digis", "Muon") +) + +caloStage2Digis = cms.EDProducer("Run3ScoutingL1CaloProducer", + jetSource = cms.InputTag("gtStage2Digis", "Jet"), + egammaSource = cms.InputTag("gtStage2Digis", "EGamma"), + tauSource = cms.InputTag("gtStage2Digis", "Tau"), + etsumSource = cms.InputTag("gtStage2Digis", "EtSum") +) + +# Task containing all producers +scoutingToMiniAODTask = cms.Task( + packedPFCandidates, + offlineSlimmedPrimaryVertices, + offlineBeamSpot, + slimmedMuons, + scoutingDimuonVertices, + slimmedElectrons, + slimmedPhotons, + slimmedJets, + slimmedMETs, + scoutingTracks, + fixedGridRhoFastjetAll, + gtStage2Digis, + gmtStage2Digis, + caloStage2Digis +) + +# For 2024+, add NoVtx muon collection and its dimuon vertices +_scoutingToMiniAODTask_2024 = scoutingToMiniAODTask.copy() +_scoutingToMiniAODTask_2024.add(slimmedMuonsNoVtx) +_scoutingToMiniAODTask_2024.add(scoutingDimuonVerticesNoVtx) +run3_scouting_2024.toReplaceWith(scoutingToMiniAODTask, _scoutingToMiniAODTask_2024) + +# Sequence for backward compatibility +scoutingToMiniAODSequence = cms.Sequence(scoutingToMiniAODTask) + + +# ============================================================ +# Customization function for cmsDriver +# ============================================================ + +def customiseScoutingToMiniAOD(process): + """ + Minimal customization for scouting to MiniAOD conversion. + + Usage with cmsDriver (for 2024 data): + + cmsDriver.py scoutMini \\ + --scenario pp \\ + --conditions auto:run3_data_prompt \\ + --era Run3_2024 \\ + --eventcontent MINIAOD \\ + --datatier MINIAOD \\ + --step USER:PhysicsTools/PatFromScouting/scoutingToMiniAOD_cff.scoutingToMiniAODTask \\ + --filein file:scouting.root \\ + --fileout file:scoutingMiniAOD.root \\ + --customise PhysicsTools/PatFromScouting/scoutingToMiniAOD_cff.customiseScoutingToMiniAOD \\ + --data --no_exec -n 100 + + For 2025 data, use --era Run3_2025 + + The customization only: + - Loads particle data table (needed for PackedCandidate producer) + - Extends output to include scoutingTracks and L1 collections + + The --step USER:...Task handles adding the producers. + The --eventcontent MINIAOD provides standard MiniAOD output commands. + """ + + # Load particle data table (needed for PackedCandidate producer) + process.load("SimGeneral.HepPDTESSource.pdt_cfi") + + # Handle missing collections gracefully (not all scouting triggers save all objects) + # This allows processing datasets where some events don't have egamma, etc. + if hasattr(process, 'options'): + if not hasattr(process.options, 'TryToContinue'): + process.options.TryToContinue = cms.untracked.vstring() + process.options.TryToContinue.append('ProductNotFound') + else: + process.options = cms.untracked.PSet( + TryToContinue = cms.untracked.vstring('ProductNotFound') + ) + + # Extend output commands to include scouting-specific collections + # Standard MINIAOD eventcontent already keeps slimmedMuons, slimmedJets, etc. + # We add scoutingTracks, L1 collections, rho, and drop raw scouting collections + for name in ['MINIAODoutput', 'MINIAODSIMoutput', 'output', 'out']: + if hasattr(process, name): + outputModule = getattr(process, name) + outputModule.outputCommands.extend([ + # Keep scouting-specific collections + 'keep patMuons_slimmedMuonsNoVtx_*_*', + 'keep *_scoutingDimuonVertices_*_*', + 'keep *_scoutingDimuonVerticesNoVtx_*_*', + 'keep recoTracks_scoutingTracks__*', + 'keep *_scoutingTracks_vertexIndex_*', + 'keep *_gtStage2Digis_*_*', + 'keep *_gmtStage2Digis_*_*', + 'keep *_caloStage2Digis_*_*', + 'keep *_fixedGridRhoFastjetAll_*_*', + # Drop raw scouting collections to save space + 'drop *_hltScouting*_*_*', + 'drop *_hltFEDSelectorL1_*_*', + ]) + break + + return process diff --git a/PhysicsTools/PatFromScouting/test/BuildFile.xml b/PhysicsTools/PatFromScouting/test/BuildFile.xml new file mode 100644 index 0000000000000..cecad47014a29 --- /dev/null +++ b/PhysicsTools/PatFromScouting/test/BuildFile.xml @@ -0,0 +1,4 @@ + + + + diff --git a/PhysicsTools/PatFromScouting/test/test_catch2_PatFromScouting.cc b/PhysicsTools/PatFromScouting/test/test_catch2_PatFromScouting.cc new file mode 100644 index 0000000000000..4106e2c39dd0b --- /dev/null +++ b/PhysicsTools/PatFromScouting/test/test_catch2_PatFromScouting.cc @@ -0,0 +1,130 @@ +#include "catch2/catch_all.hpp" +#include "FWCore/TestProcessor/interface/TestProcessor.h" +#include "FWCore/Utilities/interface/Exception.h" + +static constexpr auto s_tag = "[PatFromScouting]"; + +TEST_CASE("Standard checks of PatFromScoutingMuonProducer", s_tag) { + const std::string baseConfig{ + R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.toTest = cms.EDProducer("PatFromScoutingMuonProducer", + src = cms.InputTag("hltScoutingMuonPacker") +) +process.moduleToTest(process.toTest) +)_"}; + + edm::test::TestProcessor::Config config{baseConfig}; + SECTION("base configuration is OK") { REQUIRE_NOTHROW(edm::test::TestProcessor(config)); } + + SECTION("beginJob and endJob only") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testBeginAndEndJobOnly()); + } + + SECTION("Run with no LuminosityBlocks") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testRunWithNoLuminosityBlocks()); + } + + SECTION("LuminosityBlock with no Events") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testLuminosityBlockWithNoEvents()); + } +} + +TEST_CASE("Standard checks of PatFromScoutingElectronProducer", s_tag) { + const std::string baseConfig{ + R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.toTest = cms.EDProducer("PatFromScoutingElectronProducer", + src = cms.InputTag("hltScoutingEgammaPacker") +) +process.moduleToTest(process.toTest) +)_"}; + + edm::test::TestProcessor::Config config{baseConfig}; + SECTION("base configuration is OK") { REQUIRE_NOTHROW(edm::test::TestProcessor(config)); } + + SECTION("beginJob and endJob only") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testBeginAndEndJobOnly()); + } +} + +TEST_CASE("Standard checks of PatFromScoutingPhotonProducer", s_tag) { + const std::string baseConfig{ + R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.toTest = cms.EDProducer("PatFromScoutingPhotonProducer", + src = cms.InputTag("hltScoutingEgammaPacker") +) +process.moduleToTest(process.toTest) +)_"}; + + edm::test::TestProcessor::Config config{baseConfig}; + SECTION("base configuration is OK") { REQUIRE_NOTHROW(edm::test::TestProcessor(config)); } + + SECTION("beginJob and endJob only") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testBeginAndEndJobOnly()); + } +} + +TEST_CASE("Standard checks of PatFromScoutingJetProducer", s_tag) { + const std::string baseConfig{ + R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.toTest = cms.EDProducer("PatFromScoutingJetProducer", + src = cms.InputTag("hltScoutingPFPacker") +) +process.moduleToTest(process.toTest) +)_"}; + + edm::test::TestProcessor::Config config{baseConfig}; + SECTION("base configuration is OK") { REQUIRE_NOTHROW(edm::test::TestProcessor(config)); } + + SECTION("beginJob and endJob only") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testBeginAndEndJobOnly()); + } +} + +TEST_CASE("Standard checks of Run3ScoutingVertexToRecoVertexProducer", s_tag) { + const std::string baseConfig{ + R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.toTest = cms.EDProducer("Run3ScoutingVertexToRecoVertexProducer", + src = cms.InputTag("hltScoutingPrimaryVertexPacker", "primaryVtx") +) +process.moduleToTest(process.toTest) +)_"}; + + edm::test::TestProcessor::Config config{baseConfig}; + SECTION("base configuration is OK") { REQUIRE_NOTHROW(edm::test::TestProcessor(config)); } + + SECTION("beginJob and endJob only") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testBeginAndEndJobOnly()); + } +} + +TEST_CASE("Standard checks of Run3ScoutingMETProducer", s_tag) { + const std::string baseConfig{ + R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.toTest = cms.EDProducer("Run3ScoutingMETProducer", + metPt = cms.InputTag("hltScoutingPFPacker", "pfMetPt"), + metPhi = cms.InputTag("hltScoutingPFPacker", "pfMetPhi") +) +process.moduleToTest(process.toTest) +)_"}; + + edm::test::TestProcessor::Config config{baseConfig}; + SECTION("base configuration is OK") { REQUIRE_NOTHROW(edm::test::TestProcessor(config)); } + + SECTION("beginJob and endJob only") { + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testBeginAndEndJobOnly()); + } +} diff --git a/PhysicsTools/PatFromScouting/test/test_catch2_main.cc b/PhysicsTools/PatFromScouting/test/test_catch2_main.cc new file mode 100644 index 0000000000000..b3ea47c29c7a7 --- /dev/null +++ b/PhysicsTools/PatFromScouting/test/test_catch2_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch2/catch_all.hpp" diff --git a/PhysicsTools/PatFromScouting/test/test_scoutingNanoAOD_cfg.py b/PhysicsTools/PatFromScouting/test/test_scoutingNanoAOD_cfg.py new file mode 100644 index 0000000000000..1bfcbe5603958 --- /dev/null +++ b/PhysicsTools/PatFromScouting/test/test_scoutingNanoAOD_cfg.py @@ -0,0 +1,80 @@ +""" +Scouting NanoAOD production test. + +This produces NanoAOD from scouting MiniAOD using the custom scouting tables. + +Prerequisites: + First run test_scoutingToMiniAOD_cfg.py to create scoutingToMiniAOD_test.root + +Usage: + cmsRun test_scoutingNanoAOD_cfg.py + +Output: + scoutingNanoAOD_test.root - NanoAOD format with physics objects and triggers +""" + +import FWCore.ParameterSet.Config as cms + +process = cms.Process("NANO") + +process.load("FWCore.MessageService.MessageLogger_cfi") +process.MessageLogger.cerr.FwkReport.reportEvery = 10 + +# Load conditions for L1 trigger menu +process.load('Configuration.StandardSequences.FrontierConditions_GlobalTag_cff') +from Configuration.AlCa.GlobalTag import GlobalTag +process.GlobalTag = GlobalTag(process.GlobalTag, 'auto:run3_data_prompt', '') + +process.maxEvents = cms.untracked.PSet(input = cms.untracked.int32(100)) + +process.source = cms.Source("PoolSource", + fileNames = cms.untracked.vstring( + 'file:scoutingToMiniAOD_test.root' + ) +) + +# Load the scouting NanoAOD configuration +from PhysicsTools.PatFromScouting.scoutingNanoAOD_cff import ( + scoutingMuonTable, + scoutingElectronTable, + scoutingPhotonTable, + scoutingJetTable, + scoutingMETTable, + scoutingPVTable, + scoutingEventTable, + l1bits, +) + +process.scoutingMuonTable = scoutingMuonTable.clone() +process.scoutingElectronTable = scoutingElectronTable.clone() +process.scoutingPhotonTable = scoutingPhotonTable.clone() +process.scoutingJetTable = scoutingJetTable.clone() +process.scoutingMETTable = scoutingMETTable.clone() +process.scoutingPVTable = scoutingPVTable.clone() +process.scoutingEventTable = scoutingEventTable.clone() +process.l1bits = l1bits.clone() + +process.p = cms.Path( + process.scoutingMuonTable + + process.scoutingElectronTable + + process.scoutingPhotonTable + + process.scoutingJetTable + + process.scoutingMETTable + + process.scoutingPVTable + + process.scoutingEventTable + + process.l1bits +) + +# NanoAOD output +process.out = cms.OutputModule("NanoAODOutputModule", + fileName = cms.untracked.string('scoutingNanoAOD_test.root'), + outputCommands = cms.untracked.vstring( + 'drop *', + 'keep nanoaodFlatTable_*_*_*', + 'keep edmTriggerResults_*_*_*', + ), + compressionAlgorithm = cms.untracked.string('LZMA'), + compressionLevel = cms.untracked.int32(9), +) + +process.e = cms.EndPath(process.out) diff --git a/PhysicsTools/PatFromScouting/test/test_scoutingToMiniAOD_cfg.py b/PhysicsTools/PatFromScouting/test/test_scoutingToMiniAOD_cfg.py new file mode 100644 index 0000000000000..d75665093e99d --- /dev/null +++ b/PhysicsTools/PatFromScouting/test/test_scoutingToMiniAOD_cfg.py @@ -0,0 +1,98 @@ +import FWCore.ParameterSet.Config as cms +from FWCore.ParameterSet.VarParsing import VarParsing + +options = VarParsing('analysis') +options.inputFiles = ['file:/data/dmytro/data/store+data+Run2024C+ScoutingPFRun3+HLTSCOUT+v1+000+380+197+00000+05943107-3d39-4fcd-bea9-2b71ca2c3890.root'] +options.maxEvents = 100 +options.parseArguments() + +process = cms.Process("TEST") + +process.load("FWCore.MessageService.MessageLogger_cfi") +process.MessageLogger.cerr.FwkReport.reportEvery = 10 + +# Load particle data table (needed for PF candidate producer) +process.load("SimGeneral.HepPDTESSource.pdt_cfi") + +# Load conditions for beam spot +process.load('Configuration.StandardSequences.FrontierConditions_GlobalTag_cff') +from Configuration.AlCa.GlobalTag import GlobalTag +process.GlobalTag = GlobalTag(process.GlobalTag, 'auto:run3_data_prompt', '') + +process.maxEvents = cms.untracked.PSet(input = cms.untracked.int32(options.maxEvents)) + +process.source = cms.Source("PoolSource", + fileNames = cms.untracked.vstring(options.inputFiles) +) + +# Load the scouting to MiniAOD configuration +from PhysicsTools.PatFromScouting.scoutingToMiniAOD_cff import * + +# Use standard MiniAOD collection names +process.packedPFCandidates = packedPFCandidates +process.offlineSlimmedPrimaryVertices = offlineSlimmedPrimaryVertices +process.slimmedMuons = slimmedMuons.clone( + src = cms.InputTag("hltScoutingMuonPackerVtx") +) +process.slimmedMuonsNoVtx = slimmedMuonsNoVtx +process.scoutingDimuonVertices = scoutingDimuonVertices.clone( + scoutingMuons = cms.InputTag("hltScoutingMuonPackerVtx"), + scoutingVertices = cms.InputTag("hltScoutingMuonPackerVtx", "displacedVtx"), +) +process.scoutingDimuonVerticesNoVtx = scoutingDimuonVerticesNoVtx +process.slimmedElectrons = slimmedElectrons +process.slimmedPhotons = slimmedPhotons +process.slimmedJets = slimmedJets +process.slimmedMETs = slimmedMETs +process.scoutingTracks = scoutingTracks +process.offlineBeamSpot = offlineBeamSpot +process.fixedGridRhoFastjetAll = fixedGridRhoFastjetAll +process.gtStage2Digis = gtStage2Digis +process.gmtStage2Digis = gmtStage2Digis +process.caloStage2Digis = caloStage2Digis + +process.p = cms.Path( + process.offlineSlimmedPrimaryVertices + + process.scoutingTracks + + process.packedPFCandidates + + process.offlineBeamSpot + + process.slimmedMuons + + process.slimmedMuonsNoVtx + + process.scoutingDimuonVertices + + process.scoutingDimuonVerticesNoVtx + + process.slimmedElectrons + + process.slimmedPhotons + + process.slimmedJets + + process.slimmedMETs + + process.fixedGridRhoFastjetAll + + process.gtStage2Digis + + process.gmtStage2Digis + + process.caloStage2Digis +) + +# Output +process.out = cms.OutputModule("PoolOutputModule", + fileName = cms.untracked.string('scoutingToMiniAOD_test.root'), + outputCommands = cms.untracked.vstring( + 'drop *', + 'keep *_packedPFCandidates_*_*', + 'keep *_offlineSlimmedPrimaryVertices_*_*', + 'keep *_slimmedMuons_*_*', + 'keep *_slimmedMuonsNoVtx_*_*', + 'keep *_scoutingDimuonVertices_*_*', + 'keep *_scoutingDimuonVerticesNoVtx_*_*', + 'keep *_slimmedElectrons_*_*', + 'keep *_slimmedPhotons_*_*', + 'keep *_slimmedJets_*_*', + 'keep *_slimmedMETs_*_*', + 'keep *_scoutingTracks_*_*', + 'keep *_TriggerResults_*_*', + 'keep *_offlineBeamSpot_*_*', + 'keep *_fixedGridRho*_*_*', + 'keep *_gtStage2Digis_*_*', + 'keep *_gmtStage2Digis_*_*', + 'keep *_caloStage2Digis_*_*', + ) +) + +process.e = cms.EndPath(process.out) diff --git a/PhysicsTools/PatFromScouting/test/test_standardNanoAOD_cfg.py b/PhysicsTools/PatFromScouting/test/test_standardNanoAOD_cfg.py new file mode 100644 index 0000000000000..47068657174e5 --- /dev/null +++ b/PhysicsTools/PatFromScouting/test/test_standardNanoAOD_cfg.py @@ -0,0 +1,122 @@ +""" +Test standard NanoAOD production on scouting MiniAOD. + +This is the recommended production workflow: +1. First run test_scoutingToMiniAOD_cfg.py to create scoutingToMiniAOD_test.root +2. Then run this config to produce standard NanoAOD + +This uses cmsDriver-style configuration with our customizations. +""" + +import FWCore.ParameterSet.Config as cms + +from Configuration.Eras.Era_Run3_cff import Run3 +from Configuration.Eras.Modifier_run3_nanoAOD_pre142X_cff import run3_nanoAOD_pre142X + +process = cms.Process('NANO', Run3, run3_nanoAOD_pre142X) + +# ============================================================ +# Standard configurations +# ============================================================ + +process.load('Configuration.StandardSequences.Services_cff') +process.load('SimGeneral.HepPDTESSource.pythiapdt_cfi') +process.load('FWCore.MessageService.MessageLogger_cfi') +process.load('Configuration.EventContent.EventContent_cff') +process.load('Configuration.StandardSequences.GeometryRecoDB_cff') +process.load('Configuration.StandardSequences.MagneticField_cff') +process.load('PhysicsTools.NanoAOD.nano_cff') +process.load('Configuration.StandardSequences.EndOfProcess_cff') +process.load('Configuration.StandardSequences.FrontierConditions_GlobalTag_cff') + +process.MessageLogger.cerr.FwkReport.reportEvery = 10 + +process.maxEvents = cms.untracked.PSet( + input = cms.untracked.int32(100) +) + +# ============================================================ +# Input: Scouting MiniAOD +# ============================================================ + +process.source = cms.Source("PoolSource", + fileNames = cms.untracked.vstring('file:/work/CMSSW_15_0_18/src/scoutingToMiniAOD_test.root'), + secondaryFileNames = cms.untracked.vstring() +) + +process.options = cms.untracked.PSet( + TryToContinue = cms.untracked.vstring('ProductNotFound'), +) + +# ============================================================ +# Global Tag +# ============================================================ + +from Configuration.AlCa.GlobalTag import GlobalTag +process.GlobalTag = GlobalTag(process.GlobalTag, 'auto:run3_data_prompt', '') + +# ============================================================ +# Output +# ============================================================ + +process.NANOAODoutput = cms.OutputModule("NanoAODOutputModule", + compressionAlgorithm = cms.untracked.string('LZMA'), + compressionLevel = cms.untracked.int32(9), + dataset = cms.untracked.PSet( + dataTier = cms.untracked.string('NANOAOD'), + filterName = cms.untracked.string('') + ), + fileName = cms.untracked.string('file:standardNanoAOD_test.root'), + outputCommands = process.NANOAODEventContent.outputCommands +) + +# ============================================================ +# Paths +# ============================================================ + +process.nanoAOD_step = cms.Path(process.nanoSequence) +process.endjob_step = cms.EndPath(process.endOfProcess) +process.NANOAODoutput_step = cms.EndPath(process.NANOAODoutput) + +process.schedule = cms.Schedule( + process.nanoAOD_step, + process.endjob_step, + process.NANOAODoutput_step +) + +from PhysicsTools.PatAlgos.tools.helpers import associatePatAlgosToolsTask +associatePatAlgosToolsTask(process) + +# ============================================================ +# Apply customizations +# ============================================================ + +# Standard NanoAOD customization +from PhysicsTools.NanoAOD.nano_cff import nanoAOD_customizeCommon +process = nanoAOD_customizeCommon(process) + +# Scouting-specific customizations +from PhysicsTools.PatFromScouting.nanoAOD_scouting_cff import customiseNanoForScoutingMiniAOD +process = customiseNanoForScoutingMiniAOD(process) + +# ============================================================ +# Additional fixes for scouting MiniAOD +# ============================================================ + +# Add additional rho producers that NanoAOD expects +# (our MiniAOD has fixedGridRhoFastjetAll, but NanoAOD also needs fixedGridRhoFastjetCentral) +process.fixedGridRhoFastjetCentral = cms.EDProducer("FixedGridRhoProducerFastjet", + pfCandidatesTag = cms.InputTag("packedPFCandidates"), + maxRapidity = cms.double(2.5), # Central only + gridSpacing = cms.double(0.55) +) + +# Add to the beginning of nanoAOD path +process.nanoAOD_step.insert(0, process.fixedGridRhoFastjetCentral) + +print("=" * 60) +print("Standard NanoAOD on Scouting MiniAOD") +print("=" * 60) +print("Input: scoutingToMiniAOD_test.root") +print("Output: standardNanoAOD_test.root") +print("=" * 60)