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/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/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/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() 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 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/Configuration/EventContent/python/EventContent_cff.py b/Configuration/EventContent/python/EventContent_cff.py index 0ab2435492bcb..8865c4cd6f7fc 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/Configuration/PyReleaseValidation/python/relval_nano.py b/Configuration/PyReleaseValidation/python/relval_nano.py index 4a77aa9f11379..4ac38b1c182c8 100644 --- a/Configuration/PyReleaseValidation/python/relval_nano.py +++ b/Configuration/PyReleaseValidation/python/relval_nano.py @@ -255,7 +255,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')} @@ -431,6 +431,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'}, @@ -466,6 +472,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', @@ -633,6 +662,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() @@ -640,6 +671,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 ######## 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/L1ScoutingEtSumOrbitFlatTableProducer.cc b/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc index 9630fd6e5326f..133cc8bdd667d 100644 --- a/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc +++ b/L1TriggerScouting/Utilities/plugins/L1ScoutingEtSumOrbitFlatTableProducer.cc @@ -401,8 +401,9 @@ std::unique_ptr L1ScoutingEtSumOrbitFlatTablePro out->template addColumn("phi", phi, "phi", phiPrecision_); } if (writeHardwareValues_) { - out->template addColumn("hwEt", pt, "hardware Et"); - 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( @@ -410,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; } diff --git a/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc b/L1TriggerScouting/Utilities/plugins/SimpleOrbitFlatTableProducer.cc index d7096bdb6e6cd..eada7d15527f6 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_) { @@ -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/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/autoNANO.py b/PhysicsTools/NanoAOD/python/autoNANO.py index 04d3ec7ad2c69..59525ca0791c2 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..765a8354f29f0 100644 --- a/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py +++ b/PhysicsTools/NanoAOD/python/custom_l1scoutingrun3_cff.py @@ -1,13 +1,11 @@ import FWCore.ParameterSet.Config as cms + from PhysicsTools.NanoAOD.l1scoutingrun3_cff import * +from PhysicsTools.NanoAOD.L1SCOUTNanoAODEDMEventContent_cff import L1SCOUTNanoAODEDMEventContent, L1SCOUTNANOAODEventContent -######################### -# 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 +18,130 @@ 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 + """ + # 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_(), + # 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("") + )) 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) - # change parameters in OrbitNanoAODOutputModule - process.NANOAODoutput.outputCommands += ["keep uints_*_SelBx_*"] # keep SelBx - process.NANOAODoutput.selectedBx = cms.InputTag("FinalBxSelector", "SelBx") # use to select products + # NANO: customise instances of OrbitNanoAODOutputModule + for outModLabel in _getOrbitNanoAODOutputModuleLabels(process): + outMod = getattr(process, outModLabel) + # keep only the BXs in "selectedBx" + outMod.selectedBx = "FinalBxSelector: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 +151,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..804bfcc3df863 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 * ############################## @@ -10,19 +11,28 @@ # 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="harware 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 +l1scoutingCaloTowerUnconvertedVariables = cms.PSet( + hwEt = Var("hwEt()", "int16", doc="hardware Et"), + hwEta = Var("hwEta()", "int16", doc="hardware eta"), + hwPhi = Var("hwPhi()", "int16", doc="hardware phi"), ) ################################################# @@ -66,6 +76,11 @@ conversions = l1scoutingCaloObjectConversions ) +# Physical values (Et, Eta, Phi) for CaloTowers (Calo Layer-1) +l1scoutingCaloTowerPhysicalValueMap = cms.EDProducer("L1ScoutingCaloTowerPhysicalValueMapProducer", + src = cms.InputTag("l1ScCaloTowerUnpacker", "CaloTower") +) + #################### # Table Definition # #################### @@ -76,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"), @@ -104,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"), @@ -121,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"), @@ -139,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"), @@ -182,3 +198,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), + ) +)