From 35898d782d15dd1ccc421e29dd5a2d1b1b80c843 Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Sat, 7 Mar 2026 17:49:04 +0100 Subject: [PATCH 1/8] L1S(Nano): fix in SimpleOrbitFlatTableProducer for skipNonExistingSrc=True This changes the initialisation of the vector "selbxOffsets", which is used in case the parameter "skipNonExistingSrc" is set to True. Without this change, that vector is empty, and this leads to a segmentation fault at https://github.com/cms-sw/cmssw/blob/edd2f62bfb3fe43fdc5609ce38b82169e80251fb/DataFormats/NanoAOD/interface/OrbitFlatTable.h#L27 --- .../Utilities/plugins/SimpleOrbitFlatTableProducer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc b/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc index d7096bdb6e6cd..cf69b1c53cdd3 100644 --- a/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc +++ b/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc @@ -188,7 +188,7 @@ class SimpleOrbitFlatTableProducer : public edm::stream::EDProducer<> { std::vector selobjs; std::vector> selptrs; // for external variables - std::vector selbxOffsets; + std::vector selbxOffsets(l1ScoutingRun3::OrbitFlatTable::NBX + 2, 0); if (src.isValid() || !skipNonExistingSrc_) { if (singleton_) { From b6daf432d9bb7d9d24c8468e5137e6f4585306ab Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Sat, 7 Mar 2026 17:54:31 +0100 Subject: [PATCH 2/8] L1S(Nano): updates for CaloTowers in Run3_2026 era Updates to add CaloTowers' information to the output of "L1ScoutingNano" for 2026 workflows. This is done by adding a modifier named "run3_l1scouting_2026", and including it in the Era "Run3_2026". Various updates are made to custom_l1scoutingrun3_cff, mainly - using a dedicated Sequence (rather than overriding the regular NANO sequence), and - making the customisation of the relevant output module(s) more robust (covering both the NANOEDMAOD and NANOAOD data tiers, and not relying solely on the label of the output module). Co-Authored-By: Rocco Ardino --- .../Eras/python/Era_Run3_2026_cff.py | 3 +- .../Modifier_run3_l1scouting_2026_cff.py | 3 + ...outingCaloTowerPhysicalValueMapProducer.cc | 89 +++++++++ .../plugins/SimpleOrbitFlatTableProducer.cc | 4 + PhysicsTools/NanoAOD/python/autoNANO.py | 6 +- .../python/custom_l1scoutingrun3_cff.py | 189 +++++++++++------- .../NanoAOD/python/l1scoutingrun3_cff.py | 33 ++- 7 files changed, 253 insertions(+), 74 deletions(-) create mode 100644 Configuration/Eras/python/Modifier_run3_l1scouting_2026_cff.py create mode 100644 L1TriggerScouting/Utilities/plugins/L1ScoutingCaloTowerPhysicalValueMapProducer.cc diff --git a/Configuration/Eras/python/Era_Run3_2026_cff.py b/Configuration/Eras/python/Era_Run3_2026_cff.py index 6cda7445a1530..1f8c06722ad24 100644 --- a/Configuration/Eras/python/Era_Run3_2026_cff.py +++ b/Configuration/Eras/python/Era_Run3_2026_cff.py @@ -1,6 +1,7 @@ import FWCore.ParameterSet.Config as cms from Configuration.Eras.Era_Run3_2025_cff import Run3_2025 +from Configuration.Eras.Modifier_run3_l1scouting_2026_cff import run3_l1scouting_2026 from Configuration.ProcessModifiers.PixelCPEGeneric_cff import PixelCPEGeneric -Run3_2026 = cms.ModifierChain(Run3_2025, PixelCPEGeneric) +Run3_2026 = cms.ModifierChain(Run3_2025, PixelCPEGeneric, run3_l1scouting_2026) diff --git a/Configuration/Eras/python/Modifier_run3_l1scouting_2026_cff.py b/Configuration/Eras/python/Modifier_run3_l1scouting_2026_cff.py new file mode 100644 index 0000000000000..c32e54c03d61c --- /dev/null +++ b/Configuration/Eras/python/Modifier_run3_l1scouting_2026_cff.py @@ -0,0 +1,3 @@ +import FWCore.ParameterSet.Config as cms + +run3_l1scouting_2026 = cms.Modifier() diff --git a/L1TriggerScouting/Utilities/plugins/L1ScoutingCaloTowerPhysicalValueMapProducer.cc b/L1TriggerScouting/Utilities/plugins/L1ScoutingCaloTowerPhysicalValueMapProducer.cc new file mode 100644 index 0000000000000..0efd084eb0cf0 --- /dev/null +++ b/L1TriggerScouting/Utilities/plugins/L1ScoutingCaloTowerPhysicalValueMapProducer.cc @@ -0,0 +1,89 @@ +#include +#include +#include +#include + +#include "DataFormats/Common/interface/ValueMap.h" +#include "DataFormats/L1Scouting/interface/OrbitCollection.h" +#include "DataFormats/L1Scouting/interface/L1ScoutingCaloTower.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "L1TriggerScouting/Utilities/interface/conversion.h" + +class L1ScoutingCaloTowerPhysicalValueMapProducer : public edm::global::EDProducer<> { +public: + L1ScoutingCaloTowerPhysicalValueMapProducer(edm::ParameterSet const&); + ~L1ScoutingCaloTowerPhysicalValueMapProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::StreamID, edm::Event&, edm::EventSetup const&) const override; + + void putValueMap(edm::Event&, + edm::Handle const&, + std::vector const&, + std::string const&) const; + + edm::EDGetTokenT const src_; +}; + +L1ScoutingCaloTowerPhysicalValueMapProducer::L1ScoutingCaloTowerPhysicalValueMapProducer(edm::ParameterSet const& params) + : src_(consumes(params.getParameter("src"))) { + produces>("fEt"); + produces>("fEta"); + produces>("fPhi"); +} + +void L1ScoutingCaloTowerPhysicalValueMapProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src"); + descriptions.addWithDefaultLabel(desc); +} + +void L1ScoutingCaloTowerPhysicalValueMapProducer::produce(edm::StreamID, + edm::Event& iEvent, + edm::EventSetup const&) const { + auto const src_h = iEvent.getHandle(src_); + + std::vector outv_fEt{}; + std::vector outv_fEta{}; + std::vector outv_fPhi{}; + + if (src_h.isValid()) { + auto const& src = *src_h; + auto const nobjs = src.size(); + + outv_fEt.reserve(nobjs); + outv_fEta.reserve(nobjs); + outv_fPhi.reserve(nobjs); + + for (auto iobj = 0; iobj < nobjs; ++iobj) { + auto const& obj = src[iobj]; + outv_fEt.emplace_back(l1ScoutingRun3::calol1::fEt(obj.hwEt())); + outv_fEta.emplace_back(l1ScoutingRun3::calol1::fEta(obj.hwEta())); + outv_fPhi.emplace_back(l1ScoutingRun3::calol1::fPhi(obj.hwPhi())); + } + + putValueMap(iEvent, src_h, outv_fEt, "fEt"); + putValueMap(iEvent, src_h, outv_fEta, "fEta"); + putValueMap(iEvent, src_h, outv_fPhi, "fPhi"); + } +} + +void L1ScoutingCaloTowerPhysicalValueMapProducer::putValueMap( + edm::Event& iEvent, + edm::Handle const& handle, + std::vector const& values, + std::string const& label) const { + auto valuemap = std::make_unique>(); + edm::ValueMap::Filler filler(*valuemap); + filler.insert(handle, values.begin(), values.end()); + filler.fill(); + iEvent.put(std::move(valuemap), label); +} + +#include "FWCore/Framework/interface/MakerMacros.h" +DEFINE_FWK_MODULE(L1ScoutingCaloTowerPhysicalValueMapProducer); diff --git a/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc b/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc index cf69b1c53cdd3..eada7d15527f6 100644 --- a/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc +++ b/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc @@ -297,9 +297,13 @@ typedef SimpleOrbitFlatTableProducer SimpleL1ScoutingJetOrb #include "DataFormats/L1Scouting/interface/L1ScoutingBMTFStub.h" typedef SimpleOrbitFlatTableProducer SimpleL1ScoutingBMTFStubOrbitFlatTableProducer; +#include "DataFormats/L1Scouting/interface/L1ScoutingCaloTower.h" +typedef SimpleOrbitFlatTableProducer SimpleL1ScoutingCaloTowerOrbitFlatTableProducer; + #include "FWCore/Framework/interface/MakerMacros.h" DEFINE_FWK_MODULE(SimpleL1ScoutingMuonOrbitFlatTableProducer); DEFINE_FWK_MODULE(SimpleL1ScoutingEGammaOrbitFlatTableProducer); DEFINE_FWK_MODULE(SimpleL1ScoutingTauOrbitFlatTableProducer); DEFINE_FWK_MODULE(SimpleL1ScoutingJetOrbitFlatTableProducer); DEFINE_FWK_MODULE(SimpleL1ScoutingBMTFStubOrbitFlatTableProducer); +DEFINE_FWK_MODULE(SimpleL1ScoutingCaloTowerOrbitFlatTableProducer); diff --git a/PhysicsTools/NanoAOD/python/autoNANO.py b/PhysicsTools/NanoAOD/python/autoNANO.py index 26d2ac3b3cfc7..5f10b80766340 100644 --- a/PhysicsTools/NanoAOD/python/autoNANO.py +++ b/PhysicsTools/NanoAOD/python/autoNANO.py @@ -39,10 +39,10 @@ def expandNanoMapping(seqList, mapping, key): 'ScoutFromMini' : {'sequence': '@Scout', 'customize': '@Scout+PhysicsTools/NanoAOD/custom_run3scouting_cff.customiseScoutingNanoFromMini'}, # L1Scouting nano - 'L1Scout': {'sequence': 'PhysicsTools/NanoAOD/custom_l1scoutingrun3_cff', + 'L1Scout': {'sequence': 'PhysicsTools/NanoAOD/custom_l1scoutingrun3_cff.l1scoutingNanoSequence', 'customize': 'PhysicsTools/NanoAOD/custom_l1scoutingrun3_cff.customiseL1ScoutingNanoAOD'}, - 'L1ScoutSelect': {'sequence': '@L1Scout', - 'customize': '@L1Scout+PhysicsTools/NanoAOD/custom_l1scoutingrun3_cff.customiseL1ScoutingNanoAODSelection'}, + 'L1ScoutSelect': {'sequence': 'PhysicsTools/NanoAOD/custom_l1scoutingrun3_cff.l1scoutingNanoSequence', + 'customize': 'PhysicsTools/NanoAOD/custom_l1scoutingrun3_cff.customiseL1ScoutingNanoAODSelection'}, # BPH nano 'BPH' : {'sequence': '@PHYS', 'customize': '@PHYS+PhysicsTools/NanoAOD/custom_bph_cff.nanoAOD_customizeBPH'}, diff --git a/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py b/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py index 3a2f938124b7c..5dea8b253634f 100644 --- a/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py +++ b/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py @@ -1,13 +1,10 @@ import FWCore.ParameterSet.Config as cms + from PhysicsTools.NanoAOD.l1scoutingrun3_cff import * -######################### -# Default Configuration # -######################### +from Configuration.Eras.Modifier_run3_l1scouting_2026_cff import run3_l1scouting_2026 -# since L1ScoutingNano should be run standalone only, -# this replace task and sequences in standard NanoAOD -nanoTableTaskCommon = cms.Task( +l1scoutingNanoTask = cms.Task( l1scoutingMuonPhysicalValueMap, l1scoutingEGammaPhysicalValueMap, l1scoutingTauPhysicalValueMap, @@ -20,91 +17,133 @@ l1scoutingBMTFStubTable, ) -nanoSequenceCommon = cms.Sequence(l1scoutingJetPhysicalValueMap + cms.Sequence(nanoTableTaskCommon)) - -nanoSequence = cms.Sequence(nanoSequenceCommon) - -nanoSequenceMC = cms.Sequence(nanoSequenceCommon) +_l1scoutingNanoTask = l1scoutingNanoTask.copy() +_l1scoutingNanoTask.add(l1scoutingCaloTowerPhysicalValueMap) +_l1scoutingNanoTask.add(l1scoutingCaloTowerTable) +run3_l1scouting_2026.toReplaceWith(l1scoutingNanoTask, _l1scoutingNanoTask) + +l1scoutingNanoSequence = cms.Sequence(l1scoutingNanoTask) + +def _getNanoPoolOutputModuleLabels(process): + """This method is meant to catch the instances of PoolOutputModule used for NanoAOD EDM products. + - At present, these instances are identified by requiring the "dataset.dataTier" parameter (string) + of the output module to contain the word "NANO" (this requirement is not case-sensitive). + """ + ret = [] + for outModLabel, outMod in process.outputModules_().items(): + if outMod.type_() != "PoolOutputModule": + continue + try: + if "nano" in outMod.dataset.dataTier.value().lower(): + ret += [outModLabel] + except: + pass + return ret + +def _getNanoAODOutputModuleLabels(process): + return [outModLabel for outModLabel in process.outputModules_() \ + if process.outputModules_()[outModLabel].type_() == "NanoAODOutputModule"] + +def _getOrbitNanoAODOutputModuleLabels(process): + return [outModLabel for outModLabel in process.outputModules_() \ + if process.outputModules_()[outModLabel].type_() == "OrbitNanoAODOutputModule"] def customiseL1ScoutingNanoAOD(process): - # change OutputModule to OrbitNanoAODOutputModule - if hasattr(process, "NANOAODoutput"): - print("custom_l1scoutingrun3_cff: Change NANOAODoutput to OrbitNanoAODOutputModule") - setattr(process, "NANOAODoutput", - cms.OutputModule("OrbitNanoAODOutputModule", - # copy from standard NanoAOD - compressionAlgorithm = process.NANOAODoutput.compressionAlgorithm, - compressionLevel = process.NANOAODoutput.compressionLevel, - dataset = process.NANOAODoutput.dataset, - fileName = process.NANOAODoutput.fileName, - # change eventcontent - outputCommands = cms.untracked.vstring( - "drop *", - "keep l1ScoutingRun3OrbitFlatTable_*_*_*"), - # additional parameters for l1scouting - SelectEvents = cms.untracked.PSet( - SelectEvents = cms.vstring('nanoAOD_step') # l1scouting runs standalone only - ), - skipEmptyBXs = cms.bool(True), # drop empty bxs - ) - ) + """Customisation to run on the "L1Scouting" primary dataset + """ + # NANO: convert instances of NanoAODOutputModule to instances of OrbitNanoAODOutputModule + nanoAODOutputModuleLabels = _getNanoAODOutputModuleLabels(process) + for outModLabel in nanoAODOutputModuleLabels: + outMod = getattr(process, outModLabel) + setattr(process, outModLabel, cms.OutputModule("OrbitNanoAODOutputModule", + **outMod.parameters_(), + skipEmptyBXs = cms.bool(True), # drop empty BXs + selectedBx = cms.InputTag("") # not used if the InputTag's label is empty + )) + + # NANO and NANOEDM: customise the event content + nanoPoolOutputModuleLabels = _getNanoPoolOutputModuleLabels(process) + for outModLabel in (nanoAODOutputModuleLabels + nanoPoolOutputModuleLabels): + outMod = getattr(process, outModLabel) + outMod.outputCommands = [ + "drop *", + "keep l1ScoutingRun3OrbitFlatTable_*_*_*", + ] return process -################# -# Customisation # -################# -# these function are designed to be used with --customise flag in cmsDriver.py -# e.g. --customise PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.dropStub - -# configure to run with L1ScoutingSelection dataset -# should be used with default customiseL1ScoutingNanoAOD def customiseL1ScoutingNanoAODSelection(process): - # change sources - process.l1scoutingMuonPhysicalValueMap.src = cms.InputTag("FinalBxSelectorMuon", "Muon") - process.l1scoutingEGammaPhysicalValueMap.src = cms.InputTag("FinalBxSelectorEGamma", "EGamma") - process.l1scoutingJetPhysicalValueMap.src = cms.InputTag("FinalBxSelectorJet", "Jet") - - process.l1scoutingMuonTable.src = cms.InputTag("FinalBxSelectorMuon", "Muon") - process.l1scoutingEGammaTable.src = cms.InputTag("FinalBxSelectorEGamma", "EGamma") - process.l1scoutingJetTable.src = cms.InputTag("FinalBxSelectorJet", "Jet") - process.l1scoutingEtSumTable.src = cms.InputTag("FinalBxSelectorBxSums", "EtSum") - process.l1scoutingBMTFStubTable.src = cms.InputTag("FinalBxSelectorBMTFStub", "BMTFStub") + """Customisation to run on the "L1ScoutingSelection" primary dataset + """ + process = customiseL1ScoutingNanoAOD(process) + + # change input collections from the L1SCOUT data tier + process.l1scoutingMuonPhysicalValueMap.src = "FinalBxSelectorMuon:Muon" + process.l1scoutingEGammaPhysicalValueMap.src = "FinalBxSelectorEGamma:EGamma" + process.l1scoutingJetPhysicalValueMap.src = "FinalBxSelectorJet:Jet" + process.l1scoutingCaloTowerPhysicalValueMap.src = "FinalBxSelectorCaloTower:CaloTower" + + process.l1scoutingMuonTable.src = "FinalBxSelectorMuon:Muon" + process.l1scoutingEGammaTable.src = "FinalBxSelectorEGamma:EGamma" + process.l1scoutingJetTable.src = "FinalBxSelectorJet:Jet" + process.l1scoutingEtSumTable.src = "FinalBxSelectorBxSums:EtSum" + process.l1scoutingBMTFStubTable.src = "FinalBxSelectorBMTFStub:BMTFStub" + process.l1scoutingCaloTowerTable.src = "FinalBxSelectorCaloTower:CaloTower" # drop L1Tau - process.nanoTableTaskCommon.remove(process.l1scoutingTauTable) + process.l1scoutingNanoTask.remove(process.l1scoutingTauTable) + + # NANO: customise instances of OrbitNanoAODOutputModule + for outModLabel in _getOrbitNanoAODOutputModuleLabels(process): + outMod = getattr(process, outModLabel) + outMod.outputCommands += ["keep uints_*_SelBx_*"] # keep SelBx + outMod.selectedBx = "FinalBxSelector:SelBx" # use to select products - # change parameters in OrbitNanoAODOutputModule - process.NANOAODoutput.outputCommands += ["keep uints_*_SelBx_*"] # keep SelBx - process.NANOAODoutput.selectedBx = cms.InputTag("FinalBxSelector", "SelBx") # use to select products + # NANOEDM: modify outputCommands of PoolOutputModule instances + for outModLabel in _getNanoPoolOutputModuleLabels(process): + outMod = getattr(process, outModLabel) + outMod.outputCommands += ["keep uints_*_SelBx_*"] # keep SelBx return process +### +### Additional customisations +### +### - These functions are designed to be used with the --customise flag of cmsDriver.py, +### e.g. "--customise PhysicsTools/NanoAOD/custom_l1scoutingrun3_cff.addHardwareValues". +### def addHardwareValues(process): - # add hardware values to variables + """Customisation to add the hardware values of L1-Scouting objects to the NanoAOD output tables + """ process.l1scoutingMuonTable.variables = cms.PSet( - process.l1scoutingMuonTable.variables, - l1scoutingMuonUnconvertedVariables + process.l1scoutingMuonTable.variables, + l1scoutingMuonUnconvertedVariables ) process.l1scoutingEGammaTable.variables = cms.PSet( - process.l1scoutingEGammaTable.variables, - l1scoutingCaloObjectUnconvertedVariables + process.l1scoutingEGammaTable.variables, + l1scoutingCaloObjectUnconvertedVariables ) process.l1scoutingTauTable.variables = cms.PSet( - process.l1scoutingTauTable.variables, - l1scoutingCaloObjectUnconvertedVariables + process.l1scoutingTauTable.variables, + l1scoutingCaloObjectUnconvertedVariables ) process.l1scoutingJetTable.variables = cms.PSet( - process.l1scoutingJetTable.variables, - l1scoutingCaloObjectUnconvertedVariables + process.l1scoutingJetTable.variables, + l1scoutingCaloObjectUnconvertedVariables + ) + process.l1scoutingCaloTowerTable.variables = cms.PSet( + process.l1scoutingCaloTowerTable.variables, + l1scoutingCaloTowerUnconvertedVariables ) # EtSum uses dedicated EDProducer and can add hardware values by setting a boolean - process.l1scoutingEtSumTable.writeHardwareValues = cms.bool(True) + process.l1scoutingEtSumTable.writeHardwareValues = True return process def keepHardwareValuesOnly(process): + """Customisation to keep ONLY the hardware values of L1-Scouting objects in the NanoAOD output tables + """ # first, add hardware values process = addHardwareValues(process) @@ -114,24 +153,36 @@ def keepHardwareValuesOnly(process): process.l1scoutingEGammaTable.externalVariables = cms.PSet() process.l1scoutingTauTable.externalVariables = cms.PSet() process.l1scoutingJetTable.externalVariables = cms.PSet() + process.l1scoutingCaloTowerTable.externalVariables = cms.PSet() # EtSum uses dedicated EDProducer and can remove physical values by setting a boolean - process.l1scoutingEtSumTable.writePhysicalValues = cms.bool(False) + process.l1scoutingEtSumTable.writePhysicalValues = False return process def outputMultipleEtSums(process): - process.l1scoutingEtSumTable.singleton = cms.bool(False) + """Customisation to output multiple L1-Scouting EtSums in the relevant NanoAOD table + (l1scoutingEtSumTable.singleton = False) + """ + process.l1scoutingEtSumTable.singleton = False return process def dropEmptyBXs(process): - process.NANOAODoutput.skipEmptyBXs = cms.bool(True) + """Customisation to set "skipEmptyBXs = True" in all the instances of OrbitNanoAODOutputModule + """ + for outModLabel in _getOrbitNanoAODOutputModuleLabels(process): + getattr(process, outModLabel).skipEmptyBXs = True return process def keepEmptyBXs(process): - process.NANOAODoutput.skipEmptyBXs = cms.bool(False) + """Customisation to set "skipEmptyBXs = False" in all the instances of OrbitNanoAODOutputModule + """ + for outModLabel in _getOrbitNanoAODOutputModuleLabels(process): + getattr(process, outModLabel).skipEmptyBXs = False return process def dropBMTFStub(process): - process.nanoTableTaskCommon.remove(process.l1scoutingBMTFStubTable) + """Customisation to remove the NanoAOD output table for the L1-Scouting BMTF stubs + """ + process.l1scoutingNanoTask.remove(process.l1scoutingBMTFStubTable) return process diff --git a/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py b/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py index 22d87c6a6ad87..5c51f54382bc0 100644 --- a/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py +++ b/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py @@ -1,4 +1,5 @@ import FWCore.ParameterSet.Config as cms + from PhysicsTools.NanoAOD.common_cff import * ############################## @@ -13,7 +14,7 @@ hwPt = Var("hwPt()", "int", doc="hardware pt"), hwEta = Var("hwEta()", "int", doc="hardware eta"), hwPhi = Var("hwPhi()", "int", doc="hardware phi"), - hwPtUnconstrained = Var("hwPtUnconstrained", "int", doc="harware unconstrained pt"), + hwPtUnconstrained = Var("hwPtUnconstrained", "int", doc="hardware unconstrained pt"), hwEtaAtVtx = Var("hwEtaAtVtx()", "int", doc="hardware eta extrapolated at beam line"), hwPhiAtVtx = Var("hwPhiAtVtx()", "int", doc="hardware phi extrapolated at beam line"), ) @@ -25,6 +26,13 @@ hwPhi = Var("hwPhi()", "int", doc="hardware phi"), ) +# CaloTowers +l1scoutingCaloTowerUnconvertedVariables = cms.PSet( + hwEt = Var("hwEt()", "int16", doc="hardware Et"), + hwEta = Var("hwEta()", "int16", doc="hardware eta"), + hwPhi = Var("hwPhi()", "int16", doc="hardware phi"), +) + ################################################# # Physical Value Conversion from Hardware Value # ################################################# @@ -66,6 +74,11 @@ conversions = l1scoutingCaloObjectConversions ) +# Physical values (Et, Eta, Phi) for CaloTowers (Calo Layer-1) +l1scoutingCaloTowerPhysicalValueMap = cms.EDProducer("L1ScoutingCaloTowerPhysicalValueMapProducer", + src = cms.InputTag("l1ScCaloTowerUnpacker", "CaloTower") +) + #################### # Table Definition # #################### @@ -182,3 +195,21 @@ tag = Var("tag()", "int16", doc="tag (raw L1T units)"), ), ) + +# CaloTowers +l1scoutingCaloTowerTable = cms.EDProducer("SimpleL1ScoutingCaloTowerOrbitFlatTableProducer", + src = cms.InputTag("l1ScCaloTowerUnpacker", "CaloTower"), + name = cms.string("L1CaloTower"), + doc = cms.string("CaloTowers from Calo Layer-1"), + singleton = cms.bool(False), + skipNonExistingSrc = cms.bool(False), + variables = cms.PSet( + erBits = Var("erBits()", "int16", doc="hardware energy-ratio bits"), + miscBits = Var("miscBits()", "int16", doc="hardware misc-bits"), + ), + externalVariables = cms.PSet( + pt = ExtVar(cms.InputTag("l1scoutingCaloTowerPhysicalValueMap", "fEt"), "float", doc="pt", precision=10), + eta = ExtVar(cms.InputTag("l1scoutingCaloTowerPhysicalValueMap", "fEta"), "float", doc="eta", precision=10), + phi = ExtVar(cms.InputTag("l1scoutingCaloTowerPhysicalValueMap", "fPhi"), "float", doc="phi", precision=10), + ) +) From 97452698f58742f152ab5a03fb411c9c9e6f3158 Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Sat, 7 Mar 2026 17:57:49 +0100 Subject: [PATCH 3/8] L1S(Nano): add L1ScoutingNano wfs for 2025 and 2026 This change adds wfs to test on real data the NANOAOD flavours "L1Scout" and "L1ScoutSelect" for the Eras "Run3_2025" and "Run3_2026". --- .../PyReleaseValidation/python/relval_nano.py | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/Configuration/PyReleaseValidation/python/relval_nano.py b/Configuration/PyReleaseValidation/python/relval_nano.py index 042873a6f4f79..188de21a857fb 100644 --- a/Configuration/PyReleaseValidation/python/relval_nano.py +++ b/Configuration/PyReleaseValidation/python/relval_nano.py @@ -256,7 +256,7 @@ def next(self, index: int = None) -> None: ################################################################ -# current release cycle workflows : 14.0 +# 14.0 workflows steps['TTbarMINIAOD14.0'] = {'INPUT': InputInfo( location='STD', dataSet='/RelValTTbar_14TeV/CMSSW_14_0_0-PU_140X_mcRun3_2024_realistic_v3_STD_2024_PU-v2/MINIAODSIM')} @@ -432,6 +432,12 @@ def next(self, index: int = None) -> None: steps['ScoutingPFMonitor_Run2025C_MINIAOD_150X'] = {'INPUT': InputInfo( location='STD', ls=lumis_Run2025C, dataSet='/ScoutingPFMonitor/Run2025C-PromptReco-v1/MINIAOD')} +steps['L1Scouting2025RAW15.0'] = {'INPUT': InputInfo(location='STD', ls={398860: [[498, 498]]}, + dataSet='/L1Scouting/Run2025G-v1/L1SCOUT')} + +steps['L1ScoutingSelection2025RAW15.0'] = {'INPUT': InputInfo(location='STD', ls={398860: [[498, 498]]}, + dataSet='/L1ScoutingSelection/Run2025G-v1/L1SCOUT')} + steps['NANO_data15.0'] = merge([{'--era': 'Run3_2025', '--conditions': 'auto:run3_data_prompt'}, _NANO_data]) steps['NANO_data15.0_prompt'] = merge([{'-s': 'NANO:@Prompt,DQM:@nanoAODDQM', '-n': '1000'}, @@ -467,6 +473,29 @@ def next(self, index: int = None) -> None: steps['scoutingNANO_monitorWithPrompt_data15.0'] = merge([{'-s': 'NANO:@Prompt+@ScoutMonitor'}, steps['NANO_data15.0']]) +steps['l1ScoutingNANO_data15.0'] = merge([{'-s': 'NANO:@L1Scout', '-n': '1000'}, + steps['NANO_data15.0']]) + +steps['l1ScoutingSelectionNANO_data15.0'] = merge([{'-s': 'NANO:@L1ScoutSelect', '-n': '1000'}, + steps['NANO_data15.0']]) + +################################################################ +# Run-3, 16_0_X (2026 data-taking) + +steps['L1Scouting2026RAW16.0'] = {'INPUT': InputInfo(location='STD', ls={402144: [[100, 100]]}, + dataSet='/L1Scouting/Run2026B-v1/L1SCOUT')} + +steps['L1ScoutingSelection2026RAW16.0'] = {'INPUT': InputInfo(location='STD', ls={402144: [[100, 100]]}, + dataSet='/L1ScoutingSelection/Run2026B-v1/L1SCOUT')} + +steps['NANO_data16.0'] = merge([{'--era': 'Run3_2026', '--conditions': 'auto:run3_data_prompt'}, _NANO_data]) + +steps['l1ScoutingNANO_data16.0'] = merge([{'-s': 'NANO:@L1Scout', '-n': '1000'}, + steps['NANO_data16.0']]) + +steps['l1ScoutingSelectionNANO_data16.0'] = merge([{'-s': 'NANO:@L1ScoutSelect', '-n': '1000'}, + steps['NANO_data16.0']]) + ################################################################ # NANOGEN steps['NANOGENFromGen'] = merge([{'-s': 'NANO:@GEN,DQM:@nanogenDQM', @@ -634,6 +663,8 @@ def next(self, index: int = None) -> None: workflows[_wfn()] = ['ScoutingNANOmonitordata150Xrun3', ['ScoutingPFMonitor_Run2025C_MINIAOD_150X', 'scoutingNANO_monitor_data15.0']] # noqa workflows[_wfn()] = ['ScoutingNANOmonitorWithPromptdata150Xrun3', ['ScoutingPFMonitor_Run2025C_MINIAOD_150X', 'scoutingNANO_monitorWithPrompt_data15.0']] # noqa workflows[_wfn()] = ['BPHNANOdata150Xrun3', ['JetMET1_Run2025C_MINIAOD_150X', 'BPHNANO_data15.0']] +workflows[_wfn()] = ['L1ScoutingNANOdata150Xrun3', ['L1Scouting2025RAW15.0', 'l1ScoutingNANO_data15.0']] +workflows[_wfn()] = ['L1ScoutingSelectionNANOdata150Xrun3', ['L1ScoutingSelection2025RAW15.0', 'l1ScoutingSelectionNANO_data15.0']] # DPG custom NANOs, data _wfn.subnext() @@ -641,6 +672,27 @@ def next(self, index: int = None) -> None: # DPG custom NANOs, MC _wfn.subnext() +_wfn.next(4) +######## 2500.4xxx ######## +# Run3, 16_0_X input (2026 data-taking) +# Standard NANO, MC + +# Standard NANO, data +_wfn.subnext() + +# POG/PAG custom NANOs, MC +_wfn.subnext() + +# POG/PAG custom NANOs, data +_wfn.subnext() +workflows[_wfn()] = ['L1ScoutingNANOdata160Xrun3', ['L1Scouting2026RAW16.0', 'l1ScoutingNANO_data16.0']] +workflows[_wfn()] = ['L1ScoutingSelectionNANOdata160Xrun3', ['L1ScoutingSelection2026RAW16.0', 'l1ScoutingSelectionNANO_data16.0']] + +# DPG custom NANOs, data +_wfn.subnext() + +# DPG custom NANOs, MC +_wfn.subnext() _wfn.next(9) ######## 2500.9xxx ######## From 93a017df337c7a1e9795e5ef0afbfedd5fd75cf7 Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Sat, 21 Mar 2026 12:02:25 +0100 Subject: [PATCH 4/8] L1S(Nano): add NanoAOD-related EventContents of Run-3 L1-Scouting data to EventContent_cff The EventContents (incl. compression settings) of Run-3 L1-Scouting data for the NANO(EDM)AOD data tiers are now defined in a dedicated file [1], and the latter file is imported by [2], following what is done for other EventContents used in "central production". The implementation of the L1-Scouting NANOAOD flavours is updated accordingly (removing explicit "keep/drop statements" from the relevant customisation functions). [1] PhysicsTools/NanoAOD/python/L1SCOUTNanoAODEDMEventContent_cff.py [2] Configuration/EventContent/python/EventContent_cff.py --- .../EventContent/python/EventContent_cff.py | 3 +- .../L1SCOUTNanoAODEDMEventContent_cff.py | 20 +++++++++++ .../python/custom_l1scoutingrun3_cff.py | 34 +++++++++---------- 3 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 PhysicsTools/NanoAOD/python/L1SCOUTNanoAODEDMEventContent_cff.py diff --git a/Configuration/EventContent/python/EventContent_cff.py b/Configuration/EventContent/python/EventContent_cff.py index e0aba912ac9e7..35b301beffd71 100644 --- a/Configuration/EventContent/python/EventContent_cff.py +++ b/Configuration/EventContent/python/EventContent_cff.py @@ -118,10 +118,11 @@ from DQMOffline.Configuration.DQMOffline_EventContent_cff import * # # -# NANOAOD +# NANOAOD (incl. the NANO(EDM)AOD event contents for Run-3 L1-Scouting data) # # from PhysicsTools.NanoAOD.NanoAODEDMEventContent_cff import * +from PhysicsTools.NanoAOD.L1SCOUTNanoAODEDMEventContent_cff import * # # # FastSim diff --git a/PhysicsTools/NanoAOD/python/L1SCOUTNanoAODEDMEventContent_cff.py b/PhysicsTools/NanoAOD/python/L1SCOUTNanoAODEDMEventContent_cff.py new file mode 100644 index 0000000000000..5cc9a4d04f505 --- /dev/null +++ b/PhysicsTools/NanoAOD/python/L1SCOUTNanoAODEDMEventContent_cff.py @@ -0,0 +1,20 @@ +'''EventContent of Run-3 L1-Scouting data for the NANO(EDM)AOD data tiers''' +import FWCore.ParameterSet.Config as cms + +L1SCOUTNanoAODEDMEventContent = cms.PSet( + outputCommands = cms.untracked.vstring( + "drop *", + + # NanoAOD tables for products in L1-Scouting data + "keep l1ScoutingRun3OrbitFlatTable_*_*_*", + + # vector of selected BXs in an orbit + # (present only in some of the L1-Scouting data sets) + "keep uints_*_*_*", + ) +) + +L1SCOUTNANOAODEventContent = L1SCOUTNanoAODEDMEventContent.clone( + compressionLevel = cms.untracked.int32(9), + compressionAlgorithm = cms.untracked.string("LZMA"), +) diff --git a/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py b/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py index 5dea8b253634f..765a8354f29f0 100644 --- a/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py +++ b/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py @@ -1,6 +1,7 @@ import FWCore.ParameterSet.Config as cms from PhysicsTools.NanoAOD.l1scoutingrun3_cff import * +from PhysicsTools.NanoAOD.L1SCOUTNanoAODEDMEventContent_cff import L1SCOUTNanoAODEDMEventContent, L1SCOUTNANOAODEventContent from Configuration.Eras.Modifier_run3_l1scouting_2026_cff import run3_l1scouting_2026 @@ -51,25 +52,27 @@ def _getOrbitNanoAODOutputModuleLabels(process): def customiseL1ScoutingNanoAOD(process): """Customisation to run on the "L1Scouting" primary dataset """ + # NANOEDM: customise the event content of instances of PoolOutputModule + for outModLabel in _getNanoPoolOutputModuleLabels(process): + outMod = getattr(process, outModLabel) + outMod.update_(L1SCOUTNanoAODEDMEventContent) + # NANO: convert instances of NanoAODOutputModule to instances of OrbitNanoAODOutputModule + # (and customise the event content and compression settings) nanoAODOutputModuleLabels = _getNanoAODOutputModuleLabels(process) for outModLabel in nanoAODOutputModuleLabels: outMod = getattr(process, outModLabel) + # customise the event content and compression settings + outMod.update_(L1SCOUTNANOAODEventContent) + # convert to OrbitNanoAODOutputModule setattr(process, outModLabel, cms.OutputModule("OrbitNanoAODOutputModule", **outMod.parameters_(), - skipEmptyBXs = cms.bool(True), # drop empty BXs - selectedBx = cms.InputTag("") # not used if the InputTag's label is empty + # skip BXs in which all the L1-Scouting tables are empty + skipEmptyBXs = cms.bool(True), + # "selectedBx" is not used if the InputTag's label is empty + selectedBx = cms.InputTag("") )) - # NANO and NANOEDM: customise the event content - nanoPoolOutputModuleLabels = _getNanoPoolOutputModuleLabels(process) - for outModLabel in (nanoAODOutputModuleLabels + nanoPoolOutputModuleLabels): - outMod = getattr(process, outModLabel) - outMod.outputCommands = [ - "drop *", - "keep l1ScoutingRun3OrbitFlatTable_*_*_*", - ] - return process def customiseL1ScoutingNanoAODSelection(process): @@ -96,13 +99,8 @@ def customiseL1ScoutingNanoAODSelection(process): # NANO: customise instances of OrbitNanoAODOutputModule for outModLabel in _getOrbitNanoAODOutputModuleLabels(process): outMod = getattr(process, outModLabel) - outMod.outputCommands += ["keep uints_*_SelBx_*"] # keep SelBx - outMod.selectedBx = "FinalBxSelector:SelBx" # use to select products - - # NANOEDM: modify outputCommands of PoolOutputModule instances - for outModLabel in _getNanoPoolOutputModuleLabels(process): - outMod = getattr(process, outModLabel) - outMod.outputCommands += ["keep uints_*_SelBx_*"] # keep SelBx + # keep only the BXs in "selectedBx" + outMod.selectedBx = "FinalBxSelector:SelBx" return process From d0dcf041b6d6762b12dd2225a0778a92b1322d1c Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Sat, 7 Mar 2026 17:55:17 +0100 Subject: [PATCH 5/8] L1S(Nano): introduce L1-Scouting "scenario" for 2026 data-taking This adds a scenario named "l1ScoutingEra_Run3_2026", in order to produce "L1ScoutingNano" data sets at Tier-0 in 2026. This closely resembles what has been done for HLT-Scouting since 2024, see for example https://github.com/cms-sw/cmssw/pull/44970 --- .../DataProcessing/python/Impl/l1Scouting.py | 97 +++++++++++++++++++ .../python/Impl/l1ScoutingEra_Run3_2026.py | 23 +++++ .../DataProcessing/test/BuildFile.xml | 3 +- .../DataProcessing/test/run_CfgTest_14.sh | 20 ++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 Configuration/DataProcessing/python/Impl/l1Scouting.py create mode 100644 Configuration/DataProcessing/python/Impl/l1ScoutingEra_Run3_2026.py create mode 100755 Configuration/DataProcessing/test/run_CfgTest_14.sh diff --git a/Configuration/DataProcessing/python/Impl/l1Scouting.py b/Configuration/DataProcessing/python/Impl/l1Scouting.py new file mode 100644 index 0000000000000..586022ae9badc --- /dev/null +++ b/Configuration/DataProcessing/python/Impl/l1Scouting.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +_l1Scouting_ + +Scenario supporting proton collisions with input L1-Scouting data + +""" +from Configuration.DataProcessing.Scenario import * +from Configuration.DataProcessing.Utils import stepSKIMPRODUCER, addMonitoring, dictIO, nanoFlavours, gtNameAndConnect + +import FWCore.ParameterSet.Config as cms + +class l1Scouting(Scenario): + def __init__(self): + Scenario.__init__(self) + self.recoSeq = '' + self.cbSc = 'pp' + self.isRepacked = False + self.promptCustoms = ['Configuration/DataProcessing/RecoTLR.customisePrompt'] + self.promptModifiers = cms.ModifierChain() + """ + _l1Scouting_ + + Implement configuration building for data processing for proton + collision data taking with input L1-Scouting data + """ + + def promptReco(self, globalTag, **args): + """ + _promptReco_ + + Proton collision data taking prompt reco with input L1-Scouting data + + """ + + options = Options() + options.__dict__.update(defaultOptions.__dict__) + options.scenario = self.cbSc + + if 'nThreads' in args: + options.nThreads = args['nThreads'] + + PhysicsSkimStep = '' + if 'PhysicsSkims' in args: + PhysicsSkimStep = stepSKIMPRODUCER(args['PhysicsSkims']) + + miniAODStep = '' + nanoAODStep = '' + + if 'outputs' in args: + outputs = [] + for a in args['outputs']: + if a['dataTier'] in ['NANOAOD', 'NANOEDMAOD']: + if 'nanoFlavours' not in args: + raise SystemExit(f'l1Scouting: fatal error - requesting {a["dataTier"]} dataTier without specifying a NanoFlavour' + ' ("nanoFlavours" must be either ["@L1Scout"] or ["@L1ScoutSelect"])') + args_nanoFlavours = args['nanoFlavours'] + if args_nanoFlavours not in [['@L1Scout'], ['@L1ScoutSelect']]: + raise SystemExit(f'l1Scouting: fatal error - invalid "nanoFlavours": {args_nanoFlavours}' + ' (must be either ["@L1Scout"] or ["@L1ScoutSelect"])') + nanoAODStep = ',NANO' + nanoFlavours(args_nanoFlavours) + outputs.append(a) + else: + print(f'l1Scouting: warning - dataTier:{a["dataTier"]} is currently not supported and will be removed from outputs') + if {output['dataTier'] for output in outputs} != {a['dataTier'] for a in args['outputs']}: + print(f'l1Scouting: warning - the outputs will be changed from {args["outputs"]} to {outputs}') + args['outputs'] = outputs + + if not 'customs' in args: + args['customs'] = [] + + for c in self.promptCustoms: + args['customs'].append(c) + options.customisation_file = args['customs'] + + options.isRepacked = args.get('repacked', self.isRepacked) + + options.step = '' + options.step += self.recoSeq + PhysicsSkimStep + options.step += miniAODStep + nanoAODStep + + dictIO(options, args) + options.conditions = gtNameAndConnect(globalTag, args) + + process = cms.Process('L1SCOUT', cms.ModifierChain(self.eras, self.promptModifiers)) + cb = ConfigBuilder(options, process = process, with_output = True) + + # Input source + process.source = cms.Source("PoolSource", + fileNames = cms.untracked.vstring() + ) + + cb.prepare() + + addMonitoring(process) + + return process diff --git a/Configuration/DataProcessing/python/Impl/l1ScoutingEra_Run3_2026.py b/Configuration/DataProcessing/python/Impl/l1ScoutingEra_Run3_2026.py new file mode 100644 index 0000000000000..09d4e1cb32143 --- /dev/null +++ b/Configuration/DataProcessing/python/Impl/l1ScoutingEra_Run3_2026.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +""" +_l1ScoutingEra_Run3_2026_ + +Scenario supporting proton collisions with input L1-Scouting data for 2026 + +""" +from Configuration.DataProcessing.Impl.l1Scouting import l1Scouting + +from Configuration.Eras.Era_Run3_2026_cff import Run3_2026 + +class l1ScoutingEra_Run3_2026(l1Scouting): + def __init__(self): + l1Scouting.__init__(self) + self.recoSeq = '' + self.cbSc = 'pp' + self.eras = Run3_2026 + self.promptCustoms += ['Configuration/DataProcessing/RecoTLR.customisePostEra_Run3_2026'] + """ + _l1ScoutingEra_Run3_2026_ + Implement configuration building for data processing for proton + collision data taking with input L1-Scouting data for Era_Run3_2026 + """ diff --git a/Configuration/DataProcessing/test/BuildFile.xml b/Configuration/DataProcessing/test/BuildFile.xml index 2040ca4ba1350..95f0f9e413b67 100644 --- a/Configuration/DataProcessing/test/BuildFile.xml +++ b/Configuration/DataProcessing/test/BuildFile.xml @@ -10,4 +10,5 @@ - + + diff --git a/Configuration/DataProcessing/test/run_CfgTest_14.sh b/Configuration/DataProcessing/test/run_CfgTest_14.sh new file mode 100755 index 0000000000000..d292434a56633 --- /dev/null +++ b/Configuration/DataProcessing/test/run_CfgTest_14.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Test suite for various ConfigDP scenarios +# run using: scram build runtests +# feel free to contribute with your favourite configuration + + +# Pass in name and status +function die { echo $1: status $2 ; exit $2; } + +function runTest { echo $1 ; python3 $1 || die "Failure for configuration: $1" $?; } + +declare -a arr=("l1ScoutingEra_Run3_2026") +for scenario in "${arr[@]}" +do + # RECO, AOD and DQMIO are not supported for L1-Scouting scenarios, and RunPromptReco.py is expected to ignore them without failing + runTest "${SCRAM_TEST_PATH}/RunPromptReco.py --scenario $scenario --nanoaod --global-tag GLOBALTAG --lfn=/store/whatever --nanoFlavours=@L1Scout --reco --aod --dqmio" + runTest "${SCRAM_TEST_PATH}/RunPromptReco.py --scenario $scenario --nanoaod --global-tag GLOBALTAG --lfn=/store/whatever --nanoFlavours=@L1Scout" + runTest "${SCRAM_TEST_PATH}/RunPromptReco.py --scenario $scenario --nanoaod --global-tag GLOBALTAG --lfn=/store/whatever --nanoFlavours=@L1ScoutSelect" +done From 8b380f7e2a3857906a73c96c0a095e55b994a7f7 Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Sat, 21 Mar 2026 13:14:57 +0100 Subject: [PATCH 6/8] L1S(Nano): add "isL1Scouting" parameter to mergeProcess This updates the "mergeProcess" function in Merge.py in order to support the merging of NanoEDMAOD files for Run-3 L1-Scouting data. Creating flat NanoAOD files from NANOEDMAOD files of Run-3 L1-Scouting data requires using the plugin "OrbitNanoAODOutputModule" (instead of "NanoAODOutputModule"). "OrbitNanoAODOutputModule" converts orbit-based NanoAOD tables (EDM) to event/BX-based "flat" NanoAOD branches. See https://github.com/dmwm/WMCore/pull/12485 for related updates to the WMCore package. --- Configuration/DataProcessing/python/Merge.py | 14 +++++++++++++- Configuration/DataProcessing/test/RunMerge.py | 12 ++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Configuration/DataProcessing/python/Merge.py b/Configuration/DataProcessing/python/Merge.py index c031f5f8ac33c..bfce940a2e8f0 100644 --- a/Configuration/DataProcessing/python/Merge.py +++ b/Configuration/DataProcessing/python/Merge.py @@ -42,6 +42,7 @@ def mergeProcess(*inputFiles, **options): newDQMIO = options.get("newDQMIO", False) mergeNANO = options.get("mergeNANO", False) bypassVersionCheck = options.get("bypassVersionCheck", False) + isL1Scouting = options.get("isL1Scouting", False) # // # // build process #// @@ -72,7 +73,18 @@ def mergeProcess(*inputFiles, **options): outMod = OutputModule("DQMRootOutputModule") elif mergeNANO: import Configuration.EventContent.EventContent_cff - outMod = OutputModule("NanoAODOutputModule",Configuration.EventContent.EventContent_cff.NANOAODEventContent.clone()) + if isL1Scouting: + # For Run-3 L1-Scouting data, the plugin "OrbitNanoAODOutputModule" + # is used in the "merge" step, instead of "NanoAODOutputModule". + # "OrbitNanoAODOutputModule" converts orbit-based NanoAOD tables (EDM) + # to event/BX-based "flat" NanoAOD branches. + outMod = OutputModule("OrbitNanoAODOutputModule", + Configuration.EventContent.EventContent_cff.L1SCOUTNANOAODEventContent.clone(), + # skip BXs in which all the L1-Scouting tables are empty + skipEmptyBXs = CfgTypes.bool(True) + ) + else: + outMod = OutputModule("NanoAODOutputModule", Configuration.EventContent.EventContent_cff.NANOAODEventContent.clone()) else: outMod = OutputModule("PoolOutputModule") outMod.mergeJob = CfgTypes.untracked.bool(True) diff --git a/Configuration/DataProcessing/test/RunMerge.py b/Configuration/DataProcessing/test/RunMerge.py index a78e7e18471cb..53ea1e90de6c0 100644 --- a/Configuration/DataProcessing/test/RunMerge.py +++ b/Configuration/DataProcessing/test/RunMerge.py @@ -24,7 +24,8 @@ def __init__(self): self.newDQMIO = False self.mergeNANO = False self.bypassVersionCheck = False - + self.isL1Scouting = False + def __call__(self): if self.inputFiles == []: @@ -39,7 +40,8 @@ def __call__(self): output_lfn = self.outputLFN, newDQMIO = self.newDQMIO, mergeNANO = self.mergeNANO, - bypassVersionCheck = self.bypassVersionCheck) + bypassVersionCheck = self.bypassVersionCheck, + isL1Scouting = self.isL1Scouting) except Exception as ex: msg = "Error creating process for Merge:\n" msg += str(ex) @@ -55,8 +57,8 @@ def __call__(self): if __name__ == '__main__': - valid = ["input-files=", "output-file=", "output-lfn=", "dqmroot", "mergeNANO", "bypassVersionCheck" ] - + valid = ["input-files=", "output-file=", "output-lfn=", "dqmroot", "mergeNANO", "bypassVersionCheck", "isL1Scouting"] + usage = """RunMerge.py """ try: opts, args = getopt.getopt(sys.argv[1:], "", valid) @@ -83,5 +85,7 @@ def __call__(self): merger.mergeNANO = True if opt == "--bypassVersionCheck" : merger.bypassVersionCheck = True + if opt == "--isL1Scouting" : + merger.isL1Scouting = True merger() From 97ebc8952cd0c26a8906c953162c4f571b56ad69 Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Tue, 24 Mar 2026 10:15:36 +0100 Subject: [PATCH 7/8] L1S(Nano): sync branch names and types to L1T-DPG NanoAOD flavour This updates the content of the NanoAOD flavour for L1-Scouting data in order to use the same branch names and data types as those used in the NanoAOD flavour used for the L1T-DPG studies ("L1DPG"). --- .../L1ScoutingEtSumOrbitFlatTableProducer.cc | 3 +- .../NanoAOD/python/l1scoutingrun3_cff.py | 35 ++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc b/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc index 9630fd6e5326f..d90811ddf68a6 100644 --- a/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc +++ b/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc @@ -401,7 +401,8 @@ std::unique_ptr L1ScoutingEtSumOrbitFlatTablePro out->template addColumn("phi", phi, "phi", phiPrecision_); } if (writeHardwareValues_) { - out->template addColumn("hwEt", pt, "hardware Et"); + // use hwPt instead of hwEt in order to match the branch names used in the L1T-DPG NanoAOD flavour + out->template addColumn("hwPt", pt, "hardware Pt"); out->template addColumn("hwPhi", phi, "hardware phi"); } diff --git a/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py b/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py index 5c51f54382bc0..804bfcc3df863 100644 --- a/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py +++ b/PhysicsTools/NanoAOD/python/l1scoutingrun3_cff.py @@ -11,19 +11,21 @@ # Muon l1scoutingMuonUnconvertedVariables = cms.PSet( - hwPt = Var("hwPt()", "int", doc="hardware pt"), - hwEta = Var("hwEta()", "int", doc="hardware eta"), - hwPhi = Var("hwPhi()", "int", doc="hardware phi"), - hwPtUnconstrained = Var("hwPtUnconstrained", "int", doc="hardware unconstrained pt"), - hwEtaAtVtx = Var("hwEtaAtVtx()", "int", doc="hardware eta extrapolated at beam line"), - hwPhiAtVtx = Var("hwPhiAtVtx()", "int", doc="hardware phi extrapolated at beam line"), + # use hwPt instead of hwEt in order to match the branch names used in the L1T-DPG NanoAOD flavour + hwPt = Var("hwPt()", "int16", doc="hardware pt"), + hwEta = Var("hwEta()", "int16", doc="hardware eta"), + hwPhi = Var("hwPhi()", "int16", doc="hardware phi"), + hwPtUnconstrained = Var("hwPtUnconstrained", "int16", doc="hardware unconstrained pt"), + hwEtaAtVtx = Var("hwEtaAtVtx()", "int16", doc="hardware eta extrapolated at beam line"), + hwPhiAtVtx = Var("hwPhiAtVtx()", "int16", doc="hardware phi extrapolated at beam line"), ) # Calo objects l1scoutingCaloObjectUnconvertedVariables = cms.PSet( - hwEt = Var("hwEt()", "int", doc="hardware Et"), - hwEta = Var("hwEta()", "int", doc="hardware eta"), - hwPhi = Var("hwPhi()", "int", doc="hardware phi"), + # use hwPt instead of hwEt in order to match the branch names used in the L1T-DPG NanoAOD flavour + hwPt = Var("hwEt()", "int16", doc="hardware pt"), + hwEta = Var("hwEta()", "int16", doc="hardware eta"), + hwPhi = Var("hwPhi()", "int16", doc="hardware phi"), ) # CaloTowers @@ -89,15 +91,16 @@ # Muon l1scoutingMuonTable = cms.EDProducer("SimpleL1ScoutingMuonOrbitFlatTableProducer", src = cms.InputTag("l1ScGmtUnpacker", "Muon"), - name = cms.string("L1Muon"), + name = cms.string("L1Mu"), doc = cms.string("Muons from GMT"), singleton = cms.bool(False), extension = cms.bool(False), variables = cms.PSet( - hwCharge = Var("hwCharge()", "int", doc="charge (0 = invalid)"), - hwQuality = Var("hwQual()", "int", doc="quality"), - tfMuonIndex = Var("tfMuonIndex()", "int", + hwCharge = Var("hwCharge()", "int16", doc="charge (0 = invalid)"), + hwQual = Var("hwQual()", "int16", doc="hardware quality"), + tfMuonIndex = Var("tfMuonIndex()", "uint16", doc="index of muon at the uGMT input. 3 indices per link/sector/wedge. EMTF+ are 0-17, OMTF+ are 18-35, BMTF are 36-71, OMTF- are 72-89, EMTF- are 90-107"), + hwDXY = Var("hwDXY()", "uint16", doc="hardware dxy"), ), externalVariables = cms.PSet( pt = ExtVar(cms.InputTag("l1scoutingMuonPhysicalValueMap", "fPt"), "float", doc="pt"), @@ -117,7 +120,7 @@ singleton = cms.bool(False), extension = cms.bool(False), variables = cms.PSet( - hwIso = Var("hwIso()", "int", doc="hardware isolation (trigger units)") + hwIso = Var("hwIso()", "int16", doc="hardware isolation (trigger units)") ), externalVariables = cms.PSet( pt = ExtVar(cms.InputTag("l1scoutingEGammaPhysicalValueMap", "fEt"), "float", doc="pt"), @@ -134,7 +137,7 @@ singleton = cms.bool(False), extension = cms.bool(False), variables = cms.PSet( - hwIso = Var("hwIso()", "int", doc="hardware isolation (trigger units)") + hwIso = Var("hwIso()", "int16", doc="hardware isolation (trigger units)") ), externalVariables = cms.PSet( pt = ExtVar(cms.InputTag("l1scoutingTauPhysicalValueMap", "fEt"), "float", doc="pt"), @@ -152,7 +155,7 @@ extension = cms.bool(False), skipNonExistingSrc = cms.bool(False), variables = cms.PSet( - hwQual = Var("hwQual()", "int", doc="qualitiy"), + hwQual = Var("hwQual()", "int16", doc="hardware quality"), ), externalVariables = cms.PSet( pt = ExtVar(cms.InputTag("l1scoutingJetPhysicalValueMap", "fEt"), "float", doc="pt"), From 6d5ddc293c95b521814812804d0215bc10ed0ecd Mon Sep 17 00:00:00 2001 From: Marino Missiroli Date: Wed, 25 Mar 2026 23:31:43 +0100 Subject: [PATCH 8/8] L1S(Nano): fix content of hardware-values columns in L1ScoutingEtSumOrbitFlatTableProducer The columns of the NanoAOD table produced by the plugin L1ScoutingEtSumOrbitFlatTableProducer supposed to be holding the pt/phi hardware values of the EtSum objects (in the case singleton==False) were mistakenly being filled with the pt/phi physical (floating-point) values. --- .../plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc b/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc index d90811ddf68a6..133cc8bdd667d 100644 --- a/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc +++ b/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc @@ -401,9 +401,9 @@ std::unique_ptr L1ScoutingEtSumOrbitFlatTablePro out->template addColumn("phi", phi, "phi", phiPrecision_); } if (writeHardwareValues_) { - // use hwPt instead of hwEt in order to match the branch names used in the L1T-DPG NanoAOD flavour - out->template addColumn("hwPt", pt, "hardware Pt"); - out->template addColumn("hwPhi", phi, "hardware phi"); + // use "hwPt" (instead of "hwEt") in order to match the branch names used in the L1T-DPG NanoAOD flavour + out->template addColumn("hwPt", hwEt, "hardware pt"); + out->template addColumn("hwPhi", hwPhi, "hardware phi"); } out->template addColumn( @@ -411,6 +411,7 @@ std::unique_ptr L1ScoutingEtSumOrbitFlatTablePro sumType, "the type of the EtSum " "(https://github.com/cms-sw/cmssw/blob/master/DataFormats/L1Trigger/interface/EtSum.h#L27-L56)"); + return out; }