diff --git a/.gitignore b/.gitignore index f47363bb..b9e9614d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/__pycache__/* +*.pyc **/*.png **/.DS_Store **/*.parquet diff --git a/rates/table/README.md b/rates/table/README.md index afdd252c..f6e8bb2e 100644 --- a/rates/table/README.md +++ b/rates/table/README.md @@ -1,16 +1,35 @@ -# Rate table for the Phase-2 L1 Trigger Menu -To run the rate table, for example for the L1 TDR results, do -``` -python run.py cfg/v10_TRIDAS_newThresholds_LHCCReview -``` +# L1 Phase2 Menu Tools: Rate Table -For the firmware-based emulators under 123x, utilise `FBE_noMu_L1TDRMET_mhtSeed_123x` (`FBE_noMu_L1TDRMET_mhtSeed_123x_singleJetEta5` only includes forward region for the singleJet seed). - -To display the rates in an easy-to-read format, run -``` -python3 printRateTable.py -c cfg/v10_TRIDAS_newThresholds_LHCCReview -r out/2020-05-26-MENU-LHCCReview-BugFix_v10_TRIDAS_newThresholds_LHCCReview/thresholds/menu.csv -``` -You can also edit the `CFG_RATE_COMBOS` dictionary at the top of -the file and run the script without any arguments `python3 printRateTable.py`. -This way multiple rate tables can be compared quickly. +The rates table can be produced using the following command: + ./rate_table.py cfg/v29/v29_cfg.yml + +where the `cfg` argument specifies the structure of the config file to be used. + +An example of config can be found in `./cfg/v29_cfg.yml` and it is a `yaml` file +with the following structure: + + MenuV29: + version: "V29" + sample: "/eos/cms/store/group/dpg_trigger/comm_trigger/L1Trigger/alobanov/phase2/menu/ntuples/13X/v29_RelVal/RelValTTbar_14TeV/RelVal_13X_TT_200PU_crab_v29_13X_RelVal_FixGenTree/230710_081407/L1NtuplePhaseII_Step1_hadd.root" + menu_config: "cfg/v29/v29_16Seeds_Final_clean_cfg.yml" + scalings: + scalings_path: "/eos/user/m/mbonanom/www/Postdoc/L1PhaseII/V29/scalings/" + collect_scalings: False + scalings_outdir: "scalings_input/V29/" + scalings_file: "scalings.yml" + table: + table_fname: "rates_16Seeds_Final" + table_outdir: "rates_tables/V29" + +The block above defines entirely a menu table (`MenuV29` in the example above). +Several blocks (with a different title) can be specified in the config if one wants to produce +rate tables for different menu configurations. + +The other fields that can be specified are: +* `version`: specifies the version of the ntuples used; +* `sample`: specifies the sample to be used; +* `menu_config`: user-defined config of the menu seeds. See `cfg/v29/v29_16Seeds_Final_clean_cfg.yml` for an example. The current example replicates the menu config implemented in `cfg/v29/v29_16Seeds_Final`; +* `scalings`: this block defines the properties of the scalings file to be used. If `collect_scalings` is `False`, +the scalings file in `scalings_outdir` will be used (`scalings.yml` in the example above corresponds to the `v29` scalings used for AR). If `collect_scalings` is `True`, then the `Scaler` (cf `scaler.py`) class is used to create a new scalings file, with the name specified in `scalings_file` (which will be located in `scalings_outdir`), starting from the per-object `.txt` scalings saved under `scalings_path` (i.e. the output of the `objectPerformance` code); +* `table`: this block defines the properties of the rates table that will be dumped to a `.csv` file. The table will be saved under `table_outdir` with `table_fname` as name. diff --git a/rates/table/cfg/v29/v29_16Seeds_Final_clean_cfg.yml b/rates/table/cfg/v29/v29_16Seeds_Final_clean_cfg.yml new file mode 100644 index 00000000..79bdd1b7 --- /dev/null +++ b/rates/table/cfg/v29/v29_16Seeds_Final_clean_cfg.yml @@ -0,0 +1,208 @@ +L1_DoubleEGEle: + cross_masks: + - ak.where(abs(leg2.phi-leg1.phi) 0.1 + leg1: + leg_mask: + - leg1.offline_pt >= 37 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.5) + leg1.passessaid * (abs(leg1.eta) >= 1.5) + # - ak.where(abs(leg1.eta)<1.5, leg1.passeseleid, leg1.passessaid) + obj: EG + leg2: + leg_mask: + - leg2.offline_pt >= 24 + - abs(leg2.eta)<2.4 + - leg2.passeseleid * (abs(leg2.eta)<1.5) + leg2.passessaid * (abs(leg2.eta) >= 1.5) + # - ak.where(abs(leg2.eta)<1.5, leg2.passeseleid, leg2.passessaid) + obj: EG +L1_SingleEGEle: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 51.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.5) + leg1.passessaid * (abs(leg1.eta) >= 1.5) + obj: EG +L1_SinglePfJet: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt > 230.0 + - leg1.et>25 + - abs(leg1.eta)<2.4 + obj: seededConePuppiJet +L1_SingleTkEle: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 36.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid + # - leg1.passeseleid > 0 + obj: tkElectron +L1_SingleTkEleIso: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 28.0 + - leg1.passeseleid>=0 + - ak.where(abs(leg1.eta)<1.479, leg1.trkiso<0.13, leg1.trkiso<0.28) + obj: tkIsoElectron +L1_SingleTkMu: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt > 22.0 + - abs(leg1.eta)<2.4 + obj: gmtTkMuon +L1_SingleTkPhoIso: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 36.0 + - abs(leg1.eta)<2.4 + - ak.where(abs(leg1.eta)<1.5, leg1.passeseleid, leg1.passesphoid) + - ak.where(abs(leg1.eta)<1.479, leg1.trkiso<0.25, leg1.trkiso<0.205) + obj: tkPhoton +L1_TkEleIso_EG: + cross_masks: + - leg2.deltaR(leg1) > 0.1 + leg1: + leg_mask: + - leg1.offline_pt > 22.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid >= 0 + - ak.where(abs(leg1.eta)<1.479, leg1.trkiso<0.13, leg1.trkiso<0.28) + obj: tkIsoElectron + leg2: + leg_mask: + - leg2.offline_pt > 12.0 + - abs(leg2.eta)<2.4 + - ak.where(abs(leg2.eta)<1.5, leg2.passeseleid, leg2.passessaid) + obj: EG +L1_TripleTkMu: + cross_masks: + - abs(leg2.z0-leg1.z0)<1 + - abs(leg3.z0-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>5 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>3 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.pt>3 + - abs(leg3.eta)<2.4 + - leg3.qual>0 + obj: gmtTkMuon +L1_DoubleTkEle: + cross_masks: + - abs(leg2.zvtx-leg1.zvtx)<1 + leg1: + leg_mask: + - leg1.offline_pt >= 25.0 + - abs(leg1.eta)<2.4 + - (((leg1.passeseleid) * (abs(leg1.eta)<1.479)) + ((abs(leg1.eta)>1.479))) + obj: tkElectron + leg2: + leg_mask: + - leg2.offline_pt >= 12.0 + - abs(leg2.eta)<2.4 + - (((leg2.passeseleid) * (abs(leg2.eta)<1.479)) + ((abs(leg2.eta)>1.479))) + obj: tkElectron +L1_DoubleTkMu: + cross_masks: + - abs(leg1.z0-leg2.z0)<1 + leg1: + leg_mask: + - leg1.offline_pt > 15.0 + - abs(leg1.eta)<2.4 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt > 7.0 + - abs(leg2.eta)<2.4 + - leg2.qual > 0 + obj: gmtTkMuon +L1_DoubleTkPhoIso: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt > 22.0 + - abs(leg1.eta)<2.4 + - ak.where(abs(leg1.eta)<1.5, leg1.passeseleid, leg1.passesphoid) + - ak.where(abs(leg1.eta)<1.479, leg1.trkiso<0.25, leg1.trkiso<0.205) + obj: tkPhoton + leg2: + leg_mask: + - leg2.offline_pt > 12.0 + - abs(leg2.eta)<2.4 + - ak.where(abs(leg2.eta)<1.5, leg2.passeseleid, leg2.passesphoid) + - ak.where(abs(leg2.eta)<1.479, leg2.trkiso<0.25, leg2.trkiso<0.205) + obj: tkPhoton +L1_PFHTT: + cross_masks: [] + leg1: + leg_mask: + # - leg1.pt > 372.9 + - leg1.offline_pt > 450 + obj: seededConePuppiHT +L1_PFHTT_QuadJet: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt > 400.0 + obj: seededConePuppiHT + leg2: + leg_mask: + - leg2.offline_pt > 70.0 + - leg2.et>25.0 + - abs(leg2.eta)<2.4 + obj: seededConePuppiJet + leg3: + leg_mask: + - leg3.offline_pt > 55.0 + - leg3.et>25.0 + - abs(leg3.eta)<2.4 + obj: seededConePuppiJet + leg4: + leg_mask: + - leg4.offline_pt > 40.0 + - leg4.et>25.0 + - abs(leg4.eta)<2.4 + obj: seededConePuppiJet + leg5: + leg_mask: + - leg5.offline_pt > 40.0 + - leg5.et>25.0 + - abs(leg5.eta)<2.4 + obj: seededConePuppiJet +L1_PFIsoTau_PFIsoTau: + cross_masks: + # - ak.where(abs(leg2.phi-leg1.phi) 0.5 + - leg1.deltaR(leg2) > 0.5 + leg1: + leg_mask: + - leg1.offline_pt > 52.0 + - abs(leg1.eta)<2.172 + - leg1.passloosenn>0 + obj: nnTau + leg2: + leg_mask: + - leg2.offline_pt > 52.0 + - abs(leg2.eta)<2.172 + - leg2.passloosenn>0 + obj: nnTau +L1_PFMet: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt > 200.0 + obj: puppiMET diff --git a/rates/table/cfg/v29/v29_16seed_cfg.yml b/rates/table/cfg/v29/v29_16seed_cfg.yml new file mode 100644 index 00000000..2d70e325 --- /dev/null +++ b/rates/table/cfg/v29/v29_16seed_cfg.yml @@ -0,0 +1,12 @@ +MenuV29: + version: "V29" + sample: "/eos/cms/store/group/dpg_trigger/comm_trigger/L1Trigger/alobanov/phase2/menu/ntuples/13X/v29_RelVal/RelValTTbar_14TeV/RelVal_13X_TT_200PU_crab_v29_13X_RelVal_FixGenTree/230710_081407/L1NtuplePhaseII_Step1_hadd.root" + menu_config: "cfg/v29/v29_16Seeds_Final_clean_cfg.yml" + scalings: + scalings_path: "/eos/user/m/mbonanom/www/Postdoc/L1PhaseII/V29/scalings/" + collect_scalings: False + scalings_outdir: "scalings_input/V29/" + scalings_file: "scalings.yml" + table: + table_fname: "rates_16Seeds_Final" + table_outdir: "rates_tables/V29" \ No newline at end of file diff --git a/rates/table/cfg/v29/v29_WITHMUONS_Final_clean_cfg.yml b/rates/table/cfg/v29/v29_WITHMUONS_Final_clean_cfg.yml new file mode 100644 index 00000000..ee00e269 --- /dev/null +++ b/rates/table/cfg/v29/v29_WITHMUONS_Final_clean_cfg.yml @@ -0,0 +1,683 @@ +L1_DoubleEGEle: + cross_masks: + - leg1.deltaR(leg2) > 0.1 + leg1: + leg_mask: + - leg1.offline_pt >= 37.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.5) + leg1.passessaid * (abs(leg1.eta) >= 1.5) + obj: EG + leg2: + leg_mask: + - leg2.offline_pt >= 24.0 + - abs(leg2.eta)<2.4 + - leg2.passeseleid * (abs(leg2.eta)<1.5) + leg2.passessaid * (abs(leg2.eta) >= 1.5) + obj: EG +L1_DoublePFJet_MassMin: + cross_masks: + - pairinvmass(leg2.et,leg1.et,leg2.eta,leg1.eta,leg2.phi,leg1.phi)>620.0 + leg1: + leg_mask: + - leg1.offline_pt >= 160.0 + obj: seededConePuppiJet + leg2: + leg_mask: + - leg2.offline_pt >= 35.0 + - leg2.et>25 + obj: seededConePuppiJet +L1_DoublePFJet_dEtaMax: + cross_masks: + - abs(leg2.eta-leg1.eta)<1.6 + leg1: + leg_mask: + - leg1.offline_pt >= 112.0 + - leg1.et>25 + - abs(leg1.eta)<2.4 + obj: seededConePuppiJet + leg2: + leg_mask: + - leg2.offline_pt >= 112.0 + - leg2.et>25 + - abs(leg2.eta)<2.4 + obj: seededConePuppiJet +L1_DoubleTkEle: + cross_masks: + - abs(leg2.zvtx-leg1.zvtx)<1 + leg1: + leg_mask: + - leg1.offline_pt >= 25.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.479) + (abs(leg1.eta)>1.479) + obj: tkElectron + leg2: + leg_mask: + - leg2.offline_pt >= 12.0 + - abs(leg2.eta)<2.4 + - leg2.passeseleid * (abs(leg2.eta)<1.479) + (abs(leg2.eta)>1.479) + obj: tkElectron +L1_DoubleTkEle_PFHTT: + cross_masks: + - abs(leg2.zvtx-leg1.et)<1 + - abs(leg3.zvtx-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.offline_pt >= 8.0 + - abs(leg2.eta)<2.5 + - leg2.passeseleid * (abs(leg2.eta)<1.479) + (abs(leg2.eta)>1.479) + obj: tkElectron + leg3: + leg_mask: + - leg3.offline_pt >= 8.0 + - abs(leg3.eta)<2.5 + - leg3.passeseleid * (abs(leg3.eta)<1.479) + (abs(leg3.eta)>1.479) + obj: tkElectron + leg4: + leg_mask: + - leg4.offline_pt >= 390.0 + obj: seededConePuppiHT +L1_DoubleTkMu: + cross_masks: + - abs(leg1.z0-leg2.z0)<1 + leg1: + leg_mask: + - leg1.offline_pt >= 15.0 + - abs(leg1.eta)<2.4 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>7 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon +L1_DoubleTkMu0er1p5_SQ_OS_dR_Max1p4: + cross_masks: + - leg1.deltaR(leg2)<1.4 + - leg1.chg*leg2.chg<0.0 + - abs(leg2.z0-leg1.z0)<1 + leg1: + leg_mask: + - abs(leg1.eta)<1.5 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - abs(leg2.eta)<1.5 + - leg2.qual>0 + obj: gmtTkMuon +L1_DoubleTkMu4_SQ_OS_dR_Max1p2: + cross_masks: + - leg1.deltaR(leg2)<1.2 + - leg1.chg*leg2.chg<0.0 + - abs(leg2.z0-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>4 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>4 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon +L1_DoubleTkMu4p5er2p0_SQ_OS_Mass7to18: + cross_masks: + - pairinvmass(leg2.pt,leg1.pt,leg2.eta,leg1.eta,leg2.phi,leg1.phi)>7.0 + - pairinvmass(leg2.pt,leg1.pt,leg2.eta,leg1.eta,leg2.phi,leg1.phi)<18.0 + - leg1.chg*leg2.chg<0.0 + - abs(leg2.z0-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>4.5 + - abs(leg1.eta)<2.0 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>4.5 + - abs(leg2.eta)<2.0 + - leg2.qual>0 + obj: gmtTkMuon +L1_DoubleTkMu_PfHTT: + cross_masks: + - abs(leg2.z0-leg1.et)<1 + - abs(leg3.z0-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.pt>3 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.pt>3 + - abs(leg3.eta)<2.4 + - leg3.qual>0 + obj: gmtTkMuon + leg4: + leg_mask: + - leg4.offline_pt >= 300.0 + obj: seededConePuppiHT +L1_DoubleTkMu_PfJet_PfMet: + cross_masks: + - abs(leg2.z0-leg1.et)<1 + - abs(leg3.z0-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.pt>3 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.pt>3 + - abs(leg3.eta)<2.4 + - leg3.qual>0 + obj: gmtTkMuon + leg4: + leg_mask: + - leg4.offline_pt >= 60.0 + - leg4.et>25 + - abs(leg4.eta)<2.4 + obj: seededConePuppiJet + leg5: + leg_mask: + - leg5.offline_pt >= 130.0 + obj: puppiMET +L1_DoubleTkMu_TkEle: + cross_masks: + - abs(leg2.z0-leg1.z0)<1 + - abs(leg3.zvtx-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>5 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>5 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.offline_pt >= 9.0 + - abs(leg3.eta)<2.4 + - leg3.passeseleid * (abs(leg3.eta)<1.479) + (abs(leg3.eta)>1.479) + obj: tkElectron +L1_DoubleTkPhoIso: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 22.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.5) + leg1.passesphoid * (abs(leg1.eta) >= 1.5) + - (leg1.trkiso<0.25) * (abs(leg1.eta)<1.479) + (leg1.trkiso<0.205) * (abs(leg1.eta)>=1.479) + obj: tkPhoton + leg2: + leg_mask: + - leg2.offline_pt >= 12.0 + - abs(leg2.eta)<2.4 + - leg2.passeseleid * (abs(leg2.eta)<1.5) + leg2.passesphoid * (abs(leg2.eta) >= 1.5) + - (leg2.trkiso<0.25) * (abs(leg2.eta)<1.479) + (leg2.trkiso<0.205) * (abs(leg2.eta)>=1.479) + obj: tkPhoton +L1_PFHTT: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 450.0 + obj: seededConePuppiHT +L1_PFHTT_QuadJet: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 400.0 + obj: seededConePuppiHT + leg2: + leg_mask: + - leg2.offline_pt >= 70.0 + - leg2.et>25.0 + - abs(leg2.eta)<2.4 + obj: seededConePuppiJet + leg3: + leg_mask: + - leg3.offline_pt >= 55.0 + - leg3.et>25.0 + - abs(leg3.eta)<2.4 + obj: seededConePuppiJet + leg4: + leg_mask: + - leg4.offline_pt >= 40.0 + - leg4.et>25.0 + - abs(leg4.eta)<2.4 + obj: seededConePuppiJet + leg5: + leg_mask: + - leg5.offline_pt >= 40.0 + - leg5.et>25.0 + - abs(leg5.eta)<2.4 + obj: seededConePuppiJet +L1_PFIsoTau_PFIsoTau: + cross_masks: + - leg1.deltaR(leg2)>0.5 + leg1: + leg_mask: + - leg1.offline_pt >= 52.0 + - abs(leg1.eta)<2.172 + - leg1.passloosenn>0 + obj: nnTau + leg2: + leg_mask: + - leg2.offline_pt >= 52.0 + - abs(leg2.eta)<2.172 + - leg2.passloosenn>0 + obj: nnTau +L1_PFIsoTau_PFMet: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 55.0 + - abs(leg1.eta)<2.172 + - leg1.passloosenn>0 + obj: nnTau + leg2: + leg_mask: + - leg2.offline_pt >= 190.0 + obj: puppiMET +L1_PFIsoTau_TkMu: + cross_masks: + - abs(leg3.z0-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.offline_pt >= 42.0 + - abs(leg2.eta)<2.172 + - leg2.passloosenn>0 + obj: nnTau + leg3: + leg_mask: + - leg3.offline_pt >= 18.0 + - abs(leg3.eta)<2.1 + obj: gmtTkMuon +L1_PFMHTT: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 135.5 + obj: seededConePuppiMHT +L1_PFMet: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 200.0 + obj: puppiMET +L1_PFTau_PFTau: + cross_masks: + - leg1.deltaR(leg2)>0.5 + leg1: + leg_mask: + - leg1.offline_pt > 90.0 + - abs(leg1.eta)<2.172 + obj: caloTau + leg2: + leg_mask: + - leg2.offline_pt > 90.0 + - abs(leg2.eta)<2.172 + obj: caloTau +L1_SingleEGEle: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 51.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.5) + leg1.passessaid * (abs(leg1.eta) >= 1.5) + obj: EG +L1_SinglePFTau: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt > 150.0 + - abs(leg1.eta)<2.172 + obj: caloTau +L1_SinglePfJet: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 230.0 + - leg1.et>25 + - abs(leg1.eta)<2.4 + obj: seededConePuppiJet +L1_SingleTkEle: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 36.0 + - abs(leg1.eta)<2.4 + - tkelequalhigh(leg1.et,leg1.eta,leg1.passeseleid) + obj: tkElectron +L1_SingleTkEleIso: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt > 28.0 + - leg1.passeseleid >= 0 + - (leg1.trkiso<0.13) * (abs(leg1.eta)<1.479) + (leg1.trkiso<0.28) * (abs(leg1.eta)>=1.479) + obj: tkIsoElectron +L1_SingleTkMu: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 22.0 + - abs(leg1.eta)<2.4 + obj: gmtTkMuon +L1_SingleTkPhoIso: + cross_masks: [] + leg1: + leg_mask: + - leg1.offline_pt >= 36.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.5) + leg1.passesphoid * (abs(leg1.eta) >= 1.5) + - (leg1.trkiso<0.25) * (abs(leg1.eta)<1.479) + (leg1.trkiso<0.205) * (abs(leg1.eta)>=1.479) + obj: tkPhoton +L1_TkEleIso_EG: + cross_masks: + - leg1.deltaR(leg2) > 0.1 + leg1: + leg_mask: + - leg1.offline_pt >= 22.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid >= 0 + - (leg1.trkiso<0.13) * (abs(leg1.eta)<1.479) + (leg1.trkiso<0.28) * (abs(leg1.eta)>=1.479) + obj: tkIsoElectron + leg2: + leg_mask: + - leg2.offline_pt >= 12.0 + - abs(leg2.eta)<2.4 + - leg2.passeseleid * (abs(leg2.eta)<1.5) + leg2.passessaid * (abs(leg2.eta) >= 1.5) + obj: EG +L1_TkEleIso_PFHTT: + cross_masks: + - abs(leg2.zvtx-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.offline_pt >= 26.0 + - abs(leg2.eta)<2.1 + - leg2.passeseleid >= 0 + - (leg2.trkiso<0.13) * (abs(leg2.eta)<1.479) + (leg2.trkiso<0.28) * (abs(leg2.eta)>=1.479) + obj: tkIsoElectron + leg3: + leg_mask: + - leg3.offline_pt >= 190.0 + obj: seededConePuppiHT +L1_TkEleIso_PFIsoTau: + cross_masks: + - abs(leg2.zvtx-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.offline_pt >= 22.0 + - abs(leg2.eta)<2.1 + - leg2.passeseleid >= 0 + - (leg2.trkiso<0.13) * (abs(leg2.eta)<1.479) + (leg2.trkiso<0.28) * (abs(leg2.eta)>=1.479) + obj: tkIsoElectron + leg3: + leg_mask: + - leg3.offline_pt >= 45.0 + - abs(leg3.eta)<2.172 + - leg3.passloosenn>0 + obj: nnTau +L1_TkEle_PFJet_dRMin: + cross_masks: + - abs(leg2.zvtx-leg1.et)<1 + - leg2.deltaR(leg3)>0.3 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.offline_pt >= 28.0 + - abs(leg2.eta)<2.1 + - leg2.passeseleid * (abs(leg2.eta)<1.479) + (abs(leg2.eta)>1.479) + obj: tkElectron + leg3: + leg_mask: + - leg3.offline_pt >= 40.0 + - leg3.et>25 + - abs(leg3.eta)<2.4 + obj: seededConePuppiJet +L1_TkEle_TkMu: + cross_masks: + - abs(leg2.z0-leg1.zvtx)<1 + leg1: + leg_mask: + - leg1.offline_pt >= 10.0 + - abs(leg1.eta)<2.4 + - leg1.passeseleid * (abs(leg1.eta)<1.479) + (abs(leg1.eta)>1.479) + obj: tkElectron + leg2: + leg_mask: + - leg2.offline_pt >= 20.0 + - abs(leg2.eta)<2.4 + obj: gmtTkMuon +L1_TkMu_DoubleTkEle: + cross_masks: + - abs(leg2.zvtx-leg1.z0)<1 + - abs(leg3.zvtx-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>6 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.offline_pt >= 17.0 + - abs(leg2.eta)<2.4 + - leg2.passeseleid * (abs(leg2.eta)<1.479) + (abs(leg2.eta)>1.479) + obj: tkElectron + leg3: + leg_mask: + - leg3.offline_pt >= 17.0 + - abs(leg3.eta)<2.4 + - leg3.passeseleid * (abs(leg3.eta)<1.479) + (abs(leg3.eta)>1.479) + obj: tkElectron +L1_TkMu_PfHTT: + cross_masks: + - abs(leg2.z0-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.pt>6 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.offline_pt >= 320.0 + obj: seededConePuppiHT +L1_TkMu_PfJet_PfMet: + cross_masks: + - abs(leg2.z0-leg1.et)<1 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.pt>3 + - abs(leg2.eta)<2.1 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.offline_pt >= 110.0 + - leg3.et>25 + - abs(leg3.eta)<2.5 + obj: seededConePuppiJet + leg4: + leg_mask: + - leg4.offline_pt >= 120.0 + obj: puppiMET +L1_TkMu_PfJet_dRMax_DoubleJet_dEtaMax: + cross_masks: + - abs(leg2.z0-leg1.et)<1 + - leg2.deltaR(leg3)<0.4 + - abs(leg5.eta-leg4.eta)<1.6 + leg1: + leg_mask: + - leg1.et>-99999.0 + obj: z0L1TkPV + leg2: + leg_mask: + - leg2.offline_pt >= 12.0 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.offline_pt >= 40.0 + - leg3.et>25 + - abs(leg3.eta)<2.4 + obj: seededConePuppiJet + leg4: + leg_mask: + - leg4.offline_pt >= 40.0 + - leg4.et>25 + - abs(leg4.eta)<2.4 + obj: seededConePuppiJet + leg5: + leg_mask: + - leg5.offline_pt >= 40.0 + - leg5.et>25 + - abs(leg5.eta)<2.4 + obj: seededConePuppiJet +L1_TkMu_TkEle: + cross_masks: + - abs(leg2.zvtx-leg1.z0)<1 + leg1: + leg_mask: + #- leg1.offline_pt >= 7.0 + - leg1.pt>7 + - abs(leg1.eta)<2.4 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.offline_pt >= 23.0 + - abs(leg2.eta)<2.4 + - leg2.passeseleid * (abs(leg2.eta)<1.479) + (abs(leg2.eta)>1.479) + obj: tkElectron +L1_TkMu_TkEleIso: + cross_masks: + - abs(leg2.zvtx-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>7 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.offline_pt >= 20.0 + - abs(leg2.eta)<2.4 + - leg2.passeseleid >= 0 + - (leg2.trkiso<0.13) * (abs(leg2.eta)<1.479) + (leg2.trkiso<0.28) * (abs(leg2.eta)>=1.479) + obj: tkIsoElectron +L1_TripleTkMu: + cross_masks: + - abs(leg2.z0-leg1.z0)<1 + - abs(leg3.z0-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>5 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>3 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.pt>3 + - abs(leg3.eta)<2.4 + - leg3.qual>0 + obj: gmtTkMuon +L1_TripleTkMu_5SQ_3SQ_0OQ_DoubleMu_5_3_SQ_OS_Mass_Max9: + cross_masks: + - pairinvmass(leg2.pt,leg1.pt,leg2.eta,leg1.eta,leg2.phi,leg1.phi)<9.0 + - leg1.chg*leg2.chg<0.0 + - abs(leg2.z0-leg1.z0)<1 + - abs(leg3.z0-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>5 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>3 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.pt>0 + - abs(leg3.eta)<2.4 + - leg3.qual>0 + obj: gmtTkMuon +L1_TripleTkMu_5_3p5_2p5_OS_Mass_5to17: + cross_masks: + - abs(leg2.z0-leg1.z0)<1 + - leg1.chg*leg3.chg<0.0 + - pairinvmass(leg3.pt,leg1.pt,leg3.eta,leg1.eta,leg3.phi,leg1.phi)>5.0 + - pairinvmass(leg3.pt,leg1.pt,leg3.eta,leg1.eta,leg3.phi,leg1.phi)<17.0 + - abs(leg3.z0-leg1.z0)<1 + leg1: + leg_mask: + - leg1.pt>5 + - abs(leg1.eta)<2.4 + - leg1.qual>0 + obj: gmtTkMuon + leg2: + leg_mask: + - leg2.pt>3.5 + - abs(leg2.eta)<2.4 + - leg2.qual>0 + obj: gmtTkMuon + leg3: + leg_mask: + - leg3.pt>2.5 + - abs(leg3.eta)<2.4 + - leg3.qual>0 + obj: gmtTkMuon diff --git a/rates/table/cfg/v29/v29_cfg.yml b/rates/table/cfg/v29/v29_cfg.yml new file mode 100644 index 00000000..e464ec13 --- /dev/null +++ b/rates/table/cfg/v29/v29_cfg.yml @@ -0,0 +1,12 @@ +MenuV29: + version: "V29" + sample: "/eos/cms/store/group/dpg_trigger/comm_trigger/L1Trigger/alobanov/phase2/menu/ntuples/13X/v29_RelVal/RelValTTbar_14TeV/RelVal_13X_TT_200PU_crab_v29_13X_RelVal_FixGenTree/230710_081407/L1NtuplePhaseII_Step1_hadd.root" + menu_config: "cfg/v29/v29_WITHMUONS_Final_clean_cfg.yml" + scalings: + scalings_path: "/eos/user/m/mbonanom/www/Postdoc/L1PhaseII/V29/scalings/" + collect_scalings: False + scalings_outdir: "scalings_input/V29/" + scalings_file: "scalings.yml" + table: + table_fname: "rates_full_Final" + table_outdir: "rates_tables/V29" \ No newline at end of file diff --git a/rates/table/menu_config.py b/rates/table/menu_config.py new file mode 100644 index 00000000..2a60c3dd --- /dev/null +++ b/rates/table/menu_config.py @@ -0,0 +1,44 @@ +class MenuConfig: + + def __init__(self, cfg: dict): + self._cfg = cfg + + @property + def sample(self): + return self._cfg["sample"] + + @property + def scalings_path(self): + return self._cfg["scalings"]["scalings_path"] + + @property + def do_scalings(self): + return self._cfg["scalings"]["collect_scalings"] + + @property + def scalings_file(self): + return self._cfg["scalings"]["scalings_file"] + + @property + def scalings_outdir(self): + return self._cfg["scalings"]["scalings_outdir"] + + @property + def menu_cfg(self): + return self._cfg["menu_config"] + + @property + def menu_objects(self): + return self._cfg["menu_objects"] + + @property + def version(self): + return self._cfg["version"] + + @property + def table_outdir(self): + return self._cfg["table"]["table_outdir"] + + @property + def table_fname(self): + return self._cfg["table"]["table_fname"] \ No newline at end of file diff --git a/rates/table/menu_table.py b/rates/table/menu_table.py new file mode 100644 index 00000000..dd2de591 --- /dev/null +++ b/rates/table/menu_table.py @@ -0,0 +1,404 @@ +#!/afs/cern.ch/user/d/dhundhau/public/miniconda3/envs/py310/bin/python +import numpy as np + +from glob import glob + +import yaml, re, os, math + +from utils import * +from menu_config import MenuConfig + +import uproot +import awkward as ak +import vector + +vector.register_awkward() + +class MenuTable: + ''' + Base class that defines the rates table. + This class contains method to read the minbias sample, + convert online to offline pT, and compute the trigger rates. + All the relevant information is dumped to a csv table. + ''' + def __init__(self, cfg): + self.cfg = MenuConfig(cfg) + self.version = self.cfg.version + self.fname = self.cfg.sample + self.table_outdir = self.cfg.table_outdir + self.table_fname = self.cfg.table_fname + self.cfg_fname = self.cfg.menu_cfg + self.scalings = self.get_scalings(os.path.join(self.cfg.scalings_outdir, + self.cfg.scalings_file)) + self.trig_seeds = self.get_trig_seeds() + + def load_minbias(self, obj): + ''' + Function to load the minbias sample to be used for the rates computation. + The name of the file is specified in the config used for the MenuTable init. + ''' + with uproot.open(self.fname) as f: + arr = f["l1PhaseIITree/L1PhaseIITree"].arrays( + filter_name = f"{obj}*", + how = "zip" + ) + return arr + + def get_scalings(self, scalings): + ''' + Get the list of scalings for all the L1 objects. + Scalings are collected by the Scaler() class and + saved to a yaml file. + The inputs used are the files created in `objectPerformance` + and saved in `objectPerformance/output/VX/scalings/*.txt` + ''' + with open(f'{scalings}', 'r') as infile: + scalings_eta = yaml.safe_load(infile.read()) + return scalings_eta + + def get_trig_seeds(self): + ''' + Get the menu definition. + Load a yaml file containing the definition of the objects + and the cuts of each leg for the different trigger paths. + ''' + with open(self.cfg_fname, 'r') as infile: + test_trig_seeds = yaml.safe_load(infile.read()) + + return test_trig_seeds + + def add_offline_pt(self, arr, obj_scalings, pt_var = None): + ''' + Use the scalings to convert online pT to offline pT. + The `pt_var` argument can be used to specify which observables + should be used as "pT" for a given object. + If `pt_var` is not specified, `pt` or `et` are used. + For each object, a dedicated scaling in the barrel/endcap regions + is applied to the online pT. + ''' + # initialise array of zeros identical to the original pt + if pt_var is not None: pt_orig = arr[pt_var] + elif "et" in arr.fields: pt_orig = arr.et + elif "pt" in arr.fields: pt_orig = arr.pt + elif "" in arr.fields: pt_orig = arr[""][:,0] + else: + print("Error! Unknown pt branch") + return 0 + + if None in obj_scalings: + values = obj_scalings[None] + new_pt = pt_orig * values["slope"] + values["offset"] * (pt_orig > 0) + else: + new_pt = ak.zeros_like(pt_orig) + + # loop through eta regions with it's scaling parameters + for region, values in obj_scalings.items(): + # create eta mask for this eta region + eta_mask = (abs(arr.eta) >= values["eta_min"]) & (abs(arr.eta) < values["eta_max"]) + # scale pt for non-masked elements of this eta region + new_pt = new_pt + eta_mask * (pt_orig * values["slope"] + values["offset"]) + + return ak.with_field(arr, new_pt, "offline_pt") + + def scale_pt(self, obj, arr): + ''' + Wrapper function that calls `add_offline_pt` if the scaling is defined. + If the scaling for a given object is not found, `offline_pt` is set to + be equal to the online pt. + ''' + + if obj in self.scalings: + # print(self.scalings[obj]) + arr = self.add_offline_pt(arr, self.scalings[obj]) + else: + print("No scalings found for " + obj) + if "" in arr.fields: + arr["et"] = arr[""] + arr["pt"] = arr[""] + arr["offline_pt"] = arr.pt + + if "eta" in arr.fields: arr = ak.with_name(arr, "Momentum3D") + + arr["idx"] = ak.local_index(arr) + return arr + + def format_values(self, arr): + ''' + Function to format values in the array. + The `et` branch is converted to `pt`, if no `pt` is found in the array. + If neither `pt` nor `et` are found in the array, the corresponding + entries will be left empty or filled with the unique field of the array. + The ID branches (`["passeseleid","passessaid","passesphoid"]`) are + converted into boolean variables for easier usage in the triggers definition. + ''' + if "et" not in arr.fields: + if "pt" in arr.fields: + arr["et"] = arr.pt + elif "" in arr.fields: + arr["pt"] = arr[""] + arr["et"] = arr[""] + elif "pt" not in arr.fields: + if "et" in arr.fields: + arr["pt"] = arr.et + + for x in ["passeseleid","passessaid","passesphoid"]: + if x in arr.fields: + arr[x] = ak.values_astype(arr[x], bool) + + return arr + + def get_obj_arr(self, obj): + ''' + Function that loads the minbias sample and gets the relevant object from the TTree. + The TBranches are loaded in an awkward array, `format_values` is used to parse the + `pt`, `et`, and ID branches. + The `scale_pt` function is used to convert the online pT into offline using the scalings. + ''' + # TODO: Implement reading from parquet + # vers = self.version + # fname = f"/eos/cms/store/group/dpg_trigger/comm_trigger/L1Trigger/alobanov/phase2/menu/ntuples/cache/{vers}/{vers}_MinBias_{obj}.parquet" + # arr = ak.from_parquet(fname) + + load_obj = obj + + if obj == "tkIsoElectron": load_obj = "tkElectron" + + arr = self.load_minbias(load_obj) + if "jagged0" in arr.fields: + arr = arr["jagged0"] + + arr = ak.zip({f.replace(load_obj,"").lower():arr[f] for f in arr.fields}) + arr = self.format_values(arr) + + arr = self.scale_pt(obj, arr) + + return arr + + def get_legs(self, seed_legs): + ''' + Function that parses the config file (menu definition) + to get the cuts to be used for the definition of each trigger leg + and the L1 object used. + The function returns the awkard array after the application of the cuts. + ''' + all_arrs = {} + leg_arrs = {} + + for leg, items in seed_legs.items(): + obj = items["obj"] + + if obj not in all_arrs: all_arrs[obj] = self.get_obj_arr(obj) + + leg_mask_str = items["leg_mask"] + + leg_mask_str = "&".join([f"({s})" for s in leg_mask_str]).replace(leg,"leg_arr") + leg_arr = all_arrs[obj] + + # get masked array + leg_mask = eval(leg_mask_str) + + ## apply mask if regular (non-jagged) array, e.g. MET/HT etc + if "var" in str(leg_arr.type): + leg_arrs[leg] = leg_arr[leg_mask] + else: + leg_arrs[leg] = ak.mask(leg_arr, leg_mask) + + return leg_arrs + + def get_combos(self, leg_arrs, seed_legs): + ''' + For multi-leg triggers, this function creates the combination of the legs. + After the trigger legs are combined, the resulting array corresponding to the + AND of all the conditions on each leg is returned. + ''' + if len(leg_arrs) > 1: + combos = ak.cartesian(leg_arrs) + else: + combos = leg_arrs + + ## duplicate handling (exclude combinations) + ## first check whether objects are repeating + objs = [o["obj"] for o in seed_legs.values()] + obj_cnts = {i: objs.count(i) for i in objs} + + if np.max(list(obj_cnts.values())) > 1: + nodup_masks = [] + + for i,l1 in enumerate(leg_arrs.keys()): + for j,l2 in enumerate(leg_arrs.keys()): + if i>=j: continue + ## check that the legs are the same type object, skip otherwise + if seed_legs[l1]["obj"] != seed_legs[l2]["obj"]: continue + nodup_masks.append(combos[l1].idx != combos[l2].idx) + + if len(nodup_masks) > 0: + eval_str = " & ".join([f"nodup_masks[{i}]" for i in range(len(nodup_masks))]) + nodup_mask = eval(eval_str) + combos = combos[nodup_mask] + + return combos + + def get_legs_and_masks(self, seed_legs): + ''' + Wrapper function that calls `get_legs` and `get_combos`. + This function returns the awkward arrays with the legs definition + and the definition of the combinations in case of multi-leg triggers. + ''' + ### load all legs + leg_arrs = self.get_legs(seed_legs) + + ### leg duplicate removal + combos = self.get_combos(leg_arrs, seed_legs) + + return leg_arrs, combos + + def get_eval_string(self, leg_arrs): + ''' + Function that selects only relevant entries in the arrays and returns the + awkward array corresponding to events which satisfy the cuts on the trigger legs. + ''' + eval_str = [] + for leg, leg_arr in leg_arrs.items(): + if "var" in str(leg_arr.type): + eval_str.append(f"(ak.num({leg}) > 0)") + else: + eval_str.append(f"(ak.is_none({leg}) == False)") + eval_str = " & ".join(eval_str) + + return eval_str + + def seeds_from_cfg(self, seed): + ''' + Function that loads the information from the menu config. + Returns the legs, cross_masks, and cross-triggers (if present). + ''' + seed_legs = {l: self.trig_seeds[seed][l] for l in self.trig_seeds[seed] if "leg" in l} + cross_masks_str = self.trig_seeds[seed]["cross_masks"] + if len(cross_masks_str)>0: cross_masks_str = [cross_masks_str] + cross_seeds = [] + for leg, items in self.trig_seeds[seed].items(): + if leg == "x-seeds": + if isinstance(items, list): cross_seeds+=items + else: cross_seeds.append(items) + return seed_legs, cross_masks_str, cross_seeds + + def get_npass(self, seed, trig_seed): + ''' + Main function that computes the nr of events passing each trigger. + After loading the minbias sample and the menu definition, + each leg is selected and the masks are applied (together with cross-masks/seeds). + The function returns the total mask that defines the trigger. + ''' + seed_legs, cross_masks_str, cross_seeds = self.seeds_from_cfg(seed) + leg_arrs, combos = self.get_legs_and_masks(seed_legs) + + ## define leg arrays + for leg in leg_arrs: exec(f"{leg} = combos['{leg}']") + + ## require presence of legs + eval_str = self.get_eval_string(leg_arrs) + nleg_mask = eval(eval_str) + + ## create event mask + total_mask = nleg_mask + + ## add cross_conditions + if len(cross_masks_str) > 0: + cross_mask = [] + + for cross_mask_str in [item for sublist in cross_masks_str for item in sublist]: + cross_mask.append(eval(cross_mask_str)) + + ## combine cross_masks + eval_str = " & ".join([f"cross_mask[{i}]" for i in range(len(cross_mask))]) + cross_mask_all = eval(f"ak.any({eval_str}, axis = 1)") + + total_mask = total_mask & cross_mask_all + + ## Add cross-seeds: + for xseed in cross_seeds: + xseed_mask = self.get_npass(self.trig_seeds[xseed]) + total_mask = total_mask & xseed_mask + + total_mask = ak.fill_none(total_mask, False) + return total_mask + + def prepare_masks(self): + ''' + Wrapper function that calls `get_npass` + for each object defined in the menu. + The function returns the masks for each object. + ''' + trig_masks = {} + + seeds = self.trig_seeds + + for seed in sorted(seeds): + + print(seed) + + mask = self.get_npass(seed, self.trig_seeds[seed]) + npass = np.sum(mask) + print("##### Npasses:", npass,"\n") + + trig_masks[seed] = mask.to_numpy() + + return trig_masks + + def make_table(self): + ''' + Function that prints to screen the rates table. + Returns a list containing the csv-compatible table. + ''' + table = [] + table.append("Seed,NPass,Eff,Rate\n") + total_mask = 0 + trig_masks = self.prepare_masks() + self.trig_masks = trig_masks + + for seed, mask in trig_masks.items(): + + total_mask = total_mask | mask + npass = np.sum(mask) + eff = npass/len(mask) + rate = eff * 2760*11246 / 1e3 + table.append(f"{seed},{npass},{eff},{rate}\n") + print(seed.ljust(50), ":\t%8i\t%.5f\t%.1f" %(npass, eff, rate)) + + ## total + npass = np.sum(total_mask) + eff = npass/len(total_mask) + rate = eff * 2760*11246 / 1e3 + + tot_str = "Total:".ljust(50)+ "\t%8i\t%.5f\t%.1f" %(npass, eff, rate) + table.append(f"Total,{npass},{eff},{rate}\n") + table.append(f"Total nev,{len(total_mask)},,\n") + print((len(tot_str)+5)*"-") + print(tot_str) + + print("Total nev: %i" % len(total_mask)) + + return table + + def dump_masks(self): + ''' + Function that dumps to file the masks produced by `prepare_masks`. + ''' + if hasattr(self, "trig_masks"): + os.makedirs(f"{self.table_outdir}", exist_ok=True) + fname = f"{self.table_outdir}/{self.table_fname}_{self.version}_masks.parquet" + print(f"Dumping masks to parquet in: {fname}") + + ak.to_parquet(ak.zip(self.trig_masks), fname) + else: + print("No masks created! Run `prepare_masks` first.") + + def dump_table(self, table): + ''' + Function that dumps to file the table produced by `make_table`. + ''' + os.makedirs(f"{self.table_outdir}", exist_ok=True) + f = open(f"{self.table_outdir}/{self.table_fname}_{self.version}.csv", "w") + for line in table: + f.write(line) + f.close() diff --git a/rates/table/old_tool/README.md b/rates/table/old_tool/README.md new file mode 100644 index 00000000..bd5e82e4 --- /dev/null +++ b/rates/table/old_tool/README.md @@ -0,0 +1,18 @@ +# L1 Phase2 Menu Tools: Rate Table + +## Old fwk: Rate table for the Phase-2 L1 Trigger Menu +To run the rate table, for example for the L1 TDR results, do +``` +python run.py cfg/v10_TRIDAS_newThresholds_LHCCReview +``` + +For the firmware-based emulators under 123x, utilise `FBE_noMu_L1TDRMET_mhtSeed_123x` (`FBE_noMu_L1TDRMET_mhtSeed_123x_singleJetEta5` only includes forward region for the singleJet seed). + +To display the rates in an easy-to-read format, run +``` +python3 printRateTable.py -c cfg/v10_TRIDAS_newThresholds_LHCCReview -r out/2020-05-26-MENU-LHCCReview-BugFix_v10_TRIDAS_newThresholds_LHCCReview/thresholds/menu.csv +``` +You can also edit the `CFG_RATE_COMBOS` dictionary at the top of +the file and run the script without any arguments `python3 printRateTable.py`. +This way multiple rate tables can be compared quickly. + diff --git a/rates/table/cfg/v10/v10_TRIDAS_newThresholds_LHCCReview b/rates/table/old_tool/cfg/v10/v10_TRIDAS_newThresholds_LHCCReview similarity index 100% rename from rates/table/cfg/v10/v10_TRIDAS_newThresholds_LHCCReview rename to rates/table/old_tool/cfg/v10/v10_TRIDAS_newThresholds_LHCCReview diff --git a/rates/table/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x b/rates/table/old_tool/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x similarity index 100% rename from rates/table/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x rename to rates/table/old_tool/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x diff --git a/rates/table/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x_singleJetEta5 b/rates/table/old_tool/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x_singleJetEta5 similarity index 100% rename from rates/table/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x_singleJetEta5 rename to rates/table/old_tool/cfg/v22/FBE_noMu_L1TDRMET_mhtSeed_123x_singleJetEta5 diff --git a/rates/table/cfg/v27/v27_1252_noSoftMu b/rates/table/old_tool/cfg/v27/v27_1252_noSoftMu similarity index 100% rename from rates/table/cfg/v27/v27_1252_noSoftMu rename to rates/table/old_tool/cfg/v27/v27_1252_noSoftMu diff --git a/rates/table/cfg/v29/v29_16Seeds_Final b/rates/table/old_tool/cfg/v29/v29_16Seeds_Final similarity index 100% rename from rates/table/cfg/v29/v29_16Seeds_Final rename to rates/table/old_tool/cfg/v29/v29_16Seeds_Final diff --git a/rates/table/cfg/v29/v29_NOMUONS_Final b/rates/table/old_tool/cfg/v29/v29_NOMUONS_Final similarity index 100% rename from rates/table/cfg/v29/v29_NOMUONS_Final rename to rates/table/old_tool/cfg/v29/v29_NOMUONS_Final diff --git a/rates/table/cfg/v29/v29_WITHMUONS_Final b/rates/table/old_tool/cfg/v29/v29_WITHMUONS_Final similarity index 100% rename from rates/table/cfg/v29/v29_WITHMUONS_Final rename to rates/table/old_tool/cfg/v29/v29_WITHMUONS_Final diff --git a/rates/table/lib/__init__.py b/rates/table/old_tool/lib/__init__.py similarity index 100% rename from rates/table/lib/__init__.py rename to rates/table/old_tool/lib/__init__.py diff --git a/rates/table/lib/cfg.py b/rates/table/old_tool/lib/cfg.py similarity index 100% rename from rates/table/lib/cfg.py rename to rates/table/old_tool/lib/cfg.py diff --git a/rates/table/lib/functions.py b/rates/table/old_tool/lib/functions.py similarity index 100% rename from rates/table/lib/functions.py rename to rates/table/old_tool/lib/functions.py diff --git a/rates/table/lib/functionsTreeReader.py b/rates/table/old_tool/lib/functionsTreeReader.py similarity index 100% rename from rates/table/lib/functionsTreeReader.py rename to rates/table/old_tool/lib/functionsTreeReader.py diff --git a/rates/table/lib/master.py b/rates/table/old_tool/lib/master.py similarity index 100% rename from rates/table/lib/master.py rename to rates/table/old_tool/lib/master.py diff --git a/rates/table/lib/menu.py b/rates/table/old_tool/lib/menu.py similarity index 99% rename from rates/table/lib/menu.py rename to rates/table/old_tool/lib/menu.py index e68e70d9..5094e1ae 100644 --- a/rates/table/lib/menu.py +++ b/rates/table/old_tool/lib/menu.py @@ -69,8 +69,8 @@ def dump(self, outdir): print refs names = refs.keys()[:] names.sort() - fs = "%-"+str(maxlength)+"s: %9.2f" - fss = fs + " %1.5f %3.3f" + fs = "%-"+str(maxlength)+"s\t%9.2f" + fss = fs + " \t%1.5f \t%3.3f" print print "MENU RESULT:" for tname in names: diff --git a/rates/table/lib/object.py b/rates/table/old_tool/lib/object.py similarity index 100% rename from rates/table/lib/object.py rename to rates/table/old_tool/lib/object.py diff --git a/rates/table/lib/sample.py b/rates/table/old_tool/lib/sample.py similarity index 100% rename from rates/table/lib/sample.py rename to rates/table/old_tool/lib/sample.py diff --git a/rates/table/lib/samplemanager.py b/rates/table/old_tool/lib/samplemanager.py similarity index 100% rename from rates/table/lib/samplemanager.py rename to rates/table/old_tool/lib/samplemanager.py diff --git a/rates/table/lib/trigger.py b/rates/table/old_tool/lib/trigger.py similarity index 100% rename from rates/table/lib/trigger.py rename to rates/table/old_tool/lib/trigger.py diff --git a/rates/table/lib/vb.py b/rates/table/old_tool/lib/vb.py similarity index 100% rename from rates/table/lib/vb.py rename to rates/table/old_tool/lib/vb.py diff --git a/rates/table/printRateTable.py b/rates/table/old_tool/printRateTable.py similarity index 100% rename from rates/table/printRateTable.py rename to rates/table/old_tool/printRateTable.py diff --git a/rates/table/run.py b/rates/table/old_tool/run.py similarity index 100% rename from rates/table/run.py rename to rates/table/old_tool/run.py diff --git a/rates/table/rate_table.py b/rates/table/rate_table.py new file mode 100755 index 00000000..96ae2fd6 --- /dev/null +++ b/rates/table/rate_table.py @@ -0,0 +1,31 @@ +#!/afs/cern.ch/user/d/dhundhau/public/miniconda3/envs/py310/bin/python +import argparse +import yaml + +from scaler import Scaler +from menu_table import MenuTable +from menu_config import MenuConfig + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument( + "cfg", + default="cfg/v29/v29_cfg.yml", + help="" + ) + args = parser.parse_args() + + with open(args.cfg, 'r') as f: + cfg = yaml.safe_load(f) + + for menu_title, menu_cfg in cfg.items(): + scaler = Scaler(menu_cfg) + scaler.collect_scalings + scaler.dump_scalings + + menu_config = MenuTable(menu_cfg) + table = menu_config.make_table() + menu_config.dump_table(table) + + menu_config.dump_masks() \ No newline at end of file diff --git a/rates/table/scaler.py b/rates/table/scaler.py new file mode 100644 index 00000000..ea67902a --- /dev/null +++ b/rates/table/scaler.py @@ -0,0 +1,152 @@ +#!/afs/cern.ch/user/d/dhundhau/public/miniconda3/envs/py310/bin/python + +import os, yaml +from glob import glob +from menu_config import MenuConfig + +class Scaler: + ''' + Base class that takes as input the scalings computed + in `objectPerformance` and aggregates all of them together + to be used for the rates computation. + ''' + def __init__(self, cfg): + self.cfg = MenuConfig(cfg) + self.scalings_path = self.cfg.scalings_path + self.scalings_file = self.cfg.scalings_file + self.scalings_outdir = self.cfg.scalings_outdir + self.do_scalings = self.cfg.do_scalings + self.fnames = glob(f"{self.scalings_path}/*.txt") + self.scaling_dict = {} + self.init_log + + @property + def init_log(self): + print(f"::: The scalings file used is: {self.scalings_outdir}/{self.scalings_file} :::") + if (not os.path.isfile(f"{self.scalings_outdir}/{self.scalings_file}")) and (not self.do_scalings): + print(f"::: WARNING!! You are trying to use {self.scalings_outdir}/{self.scalings_file}, but the file does not exist! :::") + print("::: WARNING!! Set do_scalings to True in config or specify a different location for the scalings file! :::") + if self.do_scalings: + print(f"::: Will collect scalings from scratch and recreate {self.scalings_file} :::") + print(f"::: Will load scalings from {self.scalings_path} :::") + print(f"::: Will dump scalings into {self.scalings_outdir} :::") + + def get_lines(self, fname): + with open(fname) as f: + lines = f.readlines() + + return lines + + def get_basename(self, fname): + # TODO: Harmonize the naming of the scaligns in `objectPerformance` + # so that we can drop this function. + basename = os.path.basename(fname).replace(".txt","") + basename = basename.replace( + "Turnon","").replace( + "Trigger","").replace( + "_","") + + return basename + + def eta_ranges(self, obj, suffix): + ''' + Wrapper function that defines the Barrel/Overlap/Endcap + range definitions for different objects. + ''' + eta_range = None + if obj == "Muons": + if suffix == "Barrel": + eta_range = (0,0.83) + elif suffix == "Overlap": + eta_range = (0.83,1.24) + elif suffix == "Endcap": + eta_range = (1.24,2.5) + else: + if suffix == "Barrel": + eta_range = (0,1.5) + elif suffix == "Endcap": + eta_range = (1.5,2.5) + elif suffix == "Forward": + eta_range = (2.5,5) + + return eta_range + + def get_eta_range(self, fname): + ''' + Wrapper function that calls `eta_ranges` + and returns the object and the relevant eta ranges + for the various detector regions. + ''' + basename = self.get_basename(fname) + + for suffix in ["Barrel","Endcap","Overlap"]: + if suffix in basename: + obj = basename.split(suffix)[0] + eta_range = self.eta_ranges(obj, suffix) + + if eta_range is None: + print("Not found! ", basename, obj) + else: + return obj, suffix, eta_range + + return None + + def decode_scaling(self, line): + ''' + Function that parses the syntax used in the scaling.txt files + and returns the slope and offset of the scaling law for each object. + ''' + line = line.replace(" ","") + items = line.split("::") + + obj = items[1][:-len("Scaling")] + slope = float(items[2][len("args:=(offline);lambda:="):items[2].find("*off")-10]) + offset = float(items[2][items[2].find("*off")+len("*offline"):-10]) + + return obj,slope,offset + + @property + def collect_scalings(self): + ''' + Property that collects the scalings for all the objects available + and saves them to `self.scaling_dict`. + This function works only if `do_scalings` is set to True in the config. + ''' + if not self.do_scalings: return + for fname in self.fnames: + r = self.get_eta_range(os.path.basename(fname)) + + if r is None: + objcat = None + region = None + eta_range = (None,None) + else: + objcat,region,eta_range = r + + lines = self.get_lines(fname) + + for line in lines: + obj,slope,offset = self.decode_scaling(line) + d = { region : { + "eta_min" : eta_range[0], + "eta_max" : eta_range[1], + "offset" : offset, + "slope" : slope + } + } + + if obj in self.scaling_dict: self.scaling_dict[obj].update(d) + else: self.scaling_dict[obj] = d + + @property + def dump_scalings(self): + ''' + Property that dumps to file the content of `self.scaling_dict`. + This function works only if `do_scalings` is set to True in the config. + ''' + if not self.do_scalings: return + os.makedirs(f"{self.scalings_outdir}", exist_ok=True) + with open(f'{self.scalings_outdir}/{self.scalings_file}', 'w') as outfile: + yaml.dump(self.scaling_dict, + outfile, + default_flow_style=False) diff --git a/rates/table/scalings_input/V29/scalings.yml b/rates/table/scalings_input/V29/scalings.yml new file mode 100644 index 00000000..ba55c642 --- /dev/null +++ b/rates/table/scalings_input/V29/scalings.yml @@ -0,0 +1,198 @@ +EG: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 2.707 + slope: 1.188 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 1.572 + slope: 1.249 +caloTau: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: -2.553 + slope: 1.525 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: -1.273 + slope: 1.968 +gmtMuon: + Barrel: + eta_max: 0.83 + eta_min: 0 + offset: -0.2379695 + slope: 1.13674 + Endcap: + eta_max: 2.5 + eta_min: 1.24 + offset: 11.219282 + slope: 1.5027 + Overlap: + eta_max: 1.24 + eta_min: 0.83 + offset: -2.5687838 + slope: 1.34598 +gmtTkMuon: + Barrel: + eta_max: 0.83 + eta_min: 0 + offset: 0.986 + slope: 1.049 + Endcap: + eta_max: 2.5 + eta_min: 1.24 + offset: 0.792 #1.075 + slope: 1.054 # 1.052 + Overlap: + eta_max: 1.24 + eta_min: 0.83 + offset: 1.075 # 0.792 + slope: 1.052 # 1.054 +nnTau: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: -2.065 + slope: 1.899 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 19.596 + slope: 1.584 +phase1PuppiHT: + null: + eta_max: null + eta_min: null + offset: 54.550 + slope: 1.087 +phase1PuppiJet: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 15.497 + slope: 1.383 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 9.362 + slope: 1.959 + null: + eta_max: null + eta_min: null + offset: 75.5 + slope: 1.41 +phase1PuppiMHT: + null: + eta_max: null + eta_min: null + offset: 49.175 + slope: 1.321 +puppiMET: + null: + eta_max: null + eta_min: null + offset: 63.781 + slope: 1.465 +seededConePuppiHT: + null: + eta_max: null + eta_min: null + offset: 47.986 + slope: 1.084 +seededConePuppiJet: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 20.10841 + slope: 1.30781 + Endcap: + eta_max: 2.4 + eta_min: 1.5 + offset: 7.971 + slope: 2.05 + Forward: + eta_max: 6 + eta_min: 2.4 + offset: 72.567 + slope: 1.418 +seededConePuppiMHT: + null: + offset: -20.499 + slope: 1.170 +tkElectron: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 1.441 + slope: 1.159 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 1.256 + slope: 1.217 +tkIsoElectron: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 1.638 + slope: 1.144 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 1.219 + slope: 1.214 +tkIsoPhoton: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 2.697 + slope: 1.096 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 5.038 + slope: 1.067 +tkPhoton: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 2.697 + slope: 1.096 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 5.038 + slope: 1.067 +trackerHT: + null: + eta_max: null + eta_min: null + offset: -25.35696 + slope: 3.622799 +trackerJet: + Barrel: + eta_max: 1.5 + eta_min: 0 + offset: 446.22001 + slope: 0.341314 + Endcap: + eta_max: 2.5 + eta_min: 1.5 + offset: 477.198 + slope: 0.04346206 +trackerMET: + null: + eta_max: null + eta_min: null + offset: 417.67308 + slope: 0.2483366 +trackerMHT: + null: + eta_max: null + eta_min: null + offset: 410.9299 + slope: 0.295772 diff --git a/rates/table/utils.py b/rates/table/utils.py new file mode 100644 index 00000000..6bf8511d --- /dev/null +++ b/rates/table/utils.py @@ -0,0 +1,33 @@ +import numpy as np + +def dr(leg1,leg2): + return leg1.deltaR(leg2) + +def deltar(eta1,eta2,phi1,phi2): + return np.sqrt(np.power(abs(eta1-eta2),2) + np.power(abs(phi1-phi2) if abs(phi1-phi2)<=np.pi else 2*np.pi-abs(phi1-phi2),2)) + #return np.sqrt(np.power(abs(eta1-eta2),2) + np.power(abs(phi1-phi2) * abs(phi1-phi2)<=np.pi else 2*np.pi-abs(phi1-phi2),2)) + +def notmatched(eta1,eta2,phi1,phi2): + return deltar(eta1,eta2,phi1,phi2) > 0.1 + +def pairinvmass(pt1,pt2,eta1,eta2,phi1,phi2): + return np.sqrt(2.0*pt1*pt2*(np.cosh(eta1-eta2)-np.cos(phi1-phi2))) + +def phoid(EleID, PhoID, Eta): + return (EleID * (abs(Eta)<1.5)) + (PhoID * (abs(Eta)>=1.5)) + +def egid(EleID, SaID, Eta): + return (EleID * (abs(Eta)<1.5)) + (SaID * (abs(Eta)>=1.5)) + +def TkEleQualHIGH(Et,Eta,PassesEleID): return PassesEleID +def TkEleQualLOW(Et,Eta,PassesEleID): return PassesEleID * (abs(Eta)<1.479) + (abs(Eta)>1.479) +def TkEleIsoQualHIGH(Et,Eta,PassesEleID): return PassesEleID * (abs(Eta)>1.479) + (abs(Eta)<1.479) +def TkEleIsoQualLOW(Et,Eta,PassesEleID): return (PassesEleID>=0) # this should be always true: we can remove this condition from the menu + +def tkelequalhigh(et,eta,passeseleid): return passeseleid +def tkelequallow(et,eta,passeseleid): return passeseleid * (abs(eta)<1.479) + (abs(eta)>1.479) +def tkeleisoqualhigh(et,eta,passeseleid): return passeseleid * (abs(eta)>1.479) + (abs(eta)<1.479) +def tkeleisoquallow(et,eta,passeseleid): return (passeseleid>=0) # this should be always true: we can remove this condition from the menu + +def rangecutless(x,eta,etaRange,cutInRange,cutOutRange): + return (x=etaRange)