rivet is hosted by Hepforge, IPPP Durham

Rivet analyses reference

CMS_2017_I1594909

Search for SUSY in multijet events with missing transverse momentum in $pp$ collisions at 13 TeV
Experiment: CMS (LHC)
Inspire ID: 1594909
Status: VALIDATED
Authors:
  • Andy Buckley
References: Beams: p+ p+
Beam energies: (6500.0, 6500.0) GeV
Run details:
  • BSM signal events.

A search for supersymmetry based on multijet events with large missing transverse momentum produced in proton-proton collisions at a center-of-mass energy of $\sqrt{s} = 13\,\text{TeV}$. The data, corresponding to an integrated luminosity of 35.9/fb, were collected with the CMS detector at the CERN LHC in 2016. The analysis utilizes four-dimensional exclusive search regions defined in terms of the number of jets, the number of tagged bottom-quark jets, the scalar sum of jet transverse momenta, and the magnitude of the vector sum of jet transverse momenta. This coding presents the fully detailed 174 signal regions, as well as 12 aggregate signal regions.

Source code: CMS_2017_I1594909.cc
  1// -*- C++ -*-
  2#include "Rivet/Analysis.hh"
  3#include "Rivet/Projections/FinalState.hh"
  4#include "Rivet/Projections/FastJets.hh"
  5#include "Rivet/Projections/ChargedFinalState.hh"
  6#include "Rivet/Projections/VisibleFinalState.hh"
  7#include "Rivet/Projections/MissingMomentum.hh"
  8#include "Rivet/Projections/SmearedParticles.hh"
  9#include "Rivet/Projections/SmearedJets.hh"
 10#include "Rivet/Projections/SmearedMET.hh"
 11#include "Rivet/Tools/Cutflow.hh"
 12#include <tuple>
 13
 14namespace Rivet {
 15
 16
 17  /// CMS search for SUSY with multijet + MET signatures in 36/fb of 13 TeV pp data
 18  class CMS_2017_I1594909 : public Analysis {
 19  public:
 20
 21    /// Constructor
 22    RIVET_DEFAULT_ANALYSIS_CTOR(CMS_2017_I1594909);
 23
 24
 25    /// @name Analysis methods
 26    /// @{
 27
 28    /// Book histograms and initialise projections before the run
 29    void init() {
 30
 31      VisibleFinalState pfall;
 32      declare(pfall, "PFAll");
 33      ChargedFinalState pfchg(Cuts::abseta < 2.5);
 34      declare(pfchg, "PFChg");
 35
 36      FastJets jets(FinalState(Cuts::abseta < 4.9), JetAlg::ANTIKT, 0.4);
 37      SmearedJets recojets(jets, JET_SMEAR_CMS_RUN2, [](const Jet& j){ return j.bTagged() ? 0.55 : j.cTagged() ? 0.12 : 0.016; });
 38      declare(recojets, "Jets");
 39
 40      FinalState electrons(Cuts::abspid == PID::ELECTRON && Cuts::abseta < 2.5);
 41      SmearedParticles recoelectrons(electrons, ELECTRON_EFF_CMS_RUN2);
 42      declare(recoelectrons, "Electrons");
 43
 44      FinalState muons(Cuts::abspid == PID::MUON && Cuts::abseta < 2.4);
 45      SmearedParticles recomuons(muons, MUON_EFF_CMS_RUN2);
 46      declare(recomuons, "Muons");
 47
 48      VisibleFinalState calofs(Cuts::abseta < 4.9 && Cuts::abspid != PID::MUON);
 49      MissingMomentum met(calofs);
 50      SmearedMET recomet(met, MET_SMEAR_CMS_RUN2);
 51      declare(recomet, "MET");
 52
 53
 54      // Book counters, into a map of 3 indices since the global index is not obvious to calculate
 55      size_t i = 0;
 56      for (int j = 1; j <= 5; ++j) {
 57        for (int b = 1; b <= 4; ++b) {
 58          if (j == 1 && b == 4) continue;
 59          for (int k = 1; k <= 10; ++k) {
 60            if (j > 3 && (k == 1 || k == 4)) continue;
 61            stringstream s; s << "count_" << (i+1); // << "_" << j << b << k;
 62            book(_counts[std::make_tuple(j,b,k)], s.str());
 63            i += 1;
 64          }
 65        }
 66      }
 67      MSG_DEBUG("Booked " << i << " signal regions (should be 174)");
 68      // Aggregate SR counters
 69      for (size_t i = 0; i < 12; ++i)
 70        book(_counts_agg[i], "count_agg_" + toString(i+1));
 71
 72
 73      // Book cut-flow
 74      book(_flow, "Presel", {"Njet>=2", "HT>300", "HTmiss>300",
 75            "Nmuon=0", "Nmuisotrk=0", "Nelec=0", "Nelisotrk=0", "Nhadisotrk=0",
 76            "dPhi_miss,j1>0.5", "dPhi_miss,j2>0.5", "dPhi_miss,j3>0.3", "dPhi_miss,j4>0.3"
 77            });
 78    }
 79
 80
 81    /// Perform the per-event analysis
 82    void analyze(const Event& event) {
 83
 84      _flow->fillinit();
 85
 86      // Find leptons and isolation particles
 87      const Particles elecs = apply<ParticleFinder>(event, "Electrons").particlesByPt();
 88      const Particles mus = apply<ParticleFinder>(event, "Muons").particlesByPt();
 89      const Particles pfall = apply<ParticleFinder>(event, "PFAll").particlesByPt();
 90      const Particles pfiso = select(pfall, [](const Particle& p){ return p.isHadron() || p.pid() == PID::PHOTON; });
 91
 92      // Find isolated leptons
 93      const Particles isoleps = select(elecs+mus, [&](const Particle& l){
 94          const double dR = l.pT() < 50*GeV ? 0.2 : l.pT() < 200*GeV ? 10*GeV/l.pT() : 0.05;
 95          const double sumpt = sum(select(pfiso, deltaRLess(l, dR)), Kin::pT, 0.0);
 96          return sumpt/l.pT() < (l.abspid() == PID::ELECTRON ? 0.1 : 0.2); //< different I criteria for e and mu
 97        });
 98
 99      // Find other isolated tracks
100      const Particles pfchg = apply<ParticleFinder>(event, "PFChg").particlesByPt();
101      const Particles isochgs = select(pfchg, [&](const Particle& t){
102          if (t.abseta() > 2.4) return false;
103          if (any(isoleps, deltaRLess(t, 0.01))) return false; //< don't count isolated leptons here
104          const double sumpt = sum(select(pfchg, deltaRLess(t, 0.3)), Kin::pT, -t.pT());
105          return sumpt/t.pT() < ((t.abspid() == PID::ELECTRON || t.abspid() == PID::MUON) ? 0.2 : 0.1);
106        });
107
108      // Find and isolate jets
109      const Jets jets = apply<JetFinder>(event, "Jets").jetsByPt(Cuts::pT > 30*GeV);
110      const Jets cjets = select(jets, Cuts::abseta < 2.4);
111      const Jets isojets = cjets; //discardIfAnyDeltaRLess(cjets, elecs+mus, 0.4);
112      const int njets = isojets.size();
113      const Jets isobjets = select(isojets, hasBTag());
114      const int nbjets = isobjets.size();
115      MSG_DEBUG("Njets = " << jets.size() << ", Nisojets = " << njets << ", Nbjets = " << nbjets);
116
117      // Calculate HT, HTmiss, and pTmiss quantities
118      const double ht = sum(jets, Kin::pT, 0.0);
119      const Vector3 vhtmiss = -sum(jets, pTvec, Vector3());
120      const double htmiss = vhtmiss.perp();
121      const Vector3& vptmiss = -apply<SmearedMET>(event, "MET").vectorEt();
122      const double ptmiss = vptmiss.perp();
123      MSG_DEBUG("HT = " << ht/GeV << " GeV, HTmiss = " << htmiss/GeV << " GeV");
124
125
126      /////////////////////////////////////
127      // Event selection
128
129      // Njet cut
130      if (_flow->fillnext(njets < 2))  vetoEvent;
131      // HT cut
132      if (_flow->fillnext(ht < 300*GeV))  vetoEvent;
133      // HTmiss cut
134      if (_flow->fillnext(htmiss < 300*GeV))  vetoEvent;
135
136      // Isolated leptons cut
137      if (!select(isoleps, Cuts::pT > 10*GeV).empty()) vetoEvent;
138      // Isolated tracks cut
139      for (const Particle& t : isochgs) {
140        const double mT = sqrt(2*t.pT()*ptmiss * (1 - cos(deltaPhi(t, vptmiss))) );
141        if (mT < 100*GeV) continue;
142        const double pTmax = (t.abspid() == PID::ELECTRON || t.abspid() == PID::MUON) ? 5*GeV : 10*GeV;
143        if (t.pT() > pTmax) vetoEvent;
144      }
145      //
146      // // Inefficiently separated version of isolation cuts for detailed cutflow debugging
147      // // Muon cut
148      // if (!select(isoleps, Cuts::pT > 10*GeV && Cuts::abspid == PID::MUON).empty()) vetoEvent;
149      // _flow.fill(4);
150      // // Muon isotrk cut
151      // for (const Particle& t : select(isochgs, Cuts::abspid == PID::MUON)) {
152      //   const double mT = sqrt(2*t.pT()*ptmiss * (1 - cos(deltaPhi(t, vptmiss))) );
153      //   if (mT > 100*GeV && t.pT() > 5*GeV) vetoEvent;
154      // }
155      // _flow.fill(5);
156      // // Electron cut
157      // if (!select(isoleps, Cuts::pT > 10*GeV && Cuts::abspid == PID::ELECTRON).empty()) vetoEvent;
158      // _flow.fill(6);
159      // // Electron isotrk cut
160      // for (const Particle& t : select(isochgs, Cuts::abspid == PID::ELECTRON)) {
161      //   const double mT = sqrt(2*t.pT()*ptmiss * (1 - cos(deltaPhi(t, vptmiss))) );
162      //   if (mT > 100*GeV && t.pT() > 5*GeV) vetoEvent;
163      // }
164      // _flow.fill(7);
165      // // Hadron isotrk cut
166      // for (const Particle& t : select(isochgs, Cuts::abspid != PID::ELECTRON && Cuts::abspid != PID::MUON)) {
167      //   const double mT = sqrt(2*t.pT()*ptmiss * (1 - cos(deltaPhi(t, vptmiss))) );
168      //   if (mT > 100*GeV && t.pT() > 10*GeV) vetoEvent;
169      // }
170      _flow->fillnext();
171
172
173      // dPhi(jet,HTmiss) cuts
174      if (_flow->fillnext(deltaPhi(vhtmiss, isojets[0]) < 0.5))  vetoEvent;
175      if (_flow->fillnext(deltaPhi(vhtmiss, isojets[1]) < 0.5))  vetoEvent;
176      if (_flow->fillnext(njets >= 3 && deltaPhi(vhtmiss, isojets[2]) < 0.3))  vetoEvent;
177      if (_flow->fillnext(njets >= 4 && deltaPhi(vhtmiss, isojets[3]) < 0.3))  vetoEvent;
178
179
180      /////////////////////////////////////
181      // Find SR index and fill counter
182
183      const double w = 1.0;
184
185      const int idx_j = binIndex(njets, vector<int>{2,3,5,7,9}, true);
186      const int idx_b = binIndex(nbjets, vector<int>{0,1,2,3}, true);
187      int idx_k = -1;
188      if (inRange(htmiss/GeV, 300, 350)) {
189        idx_k = ht < 500*GeV ? 1 : ht < 1000*GeV ? 2 : 3;
190      } else if (inRange(htmiss/GeV, 350, 500) && ht > 350*GeV) {
191        idx_k = ht < 500*GeV ? 4 : ht < 1000*GeV ? 5 : 6;
192      } else if (inRange(htmiss/GeV, 500, 750) && ht > 500*GeV) {
193        idx_k = ht < 1000*GeV ? 7 : 8;
194      } else if (htmiss/GeV > 750 && ht > 750*GeV) {
195        idx_k = ht < 1500*GeV ? 9 : 10;
196      }
197
198      // Fill via 3-tuple index
199      if (idx_j >= 0 && idx_b >= 0 && idx_k >= 0) {
200        const auto idx = std::make_tuple(idx_j+1,idx_b+1,idx_k);
201        if (has_key(_counts, idx)) _counts[idx]->fill(w);
202      }
203
204
205      /////////////////////////////////////
206      // Aggregate SRs
207
208      // Region Njet Nb-jet HT [GeV] HTmiss [GeV] Parton multiplicity Heavy flavor ? ∆m
209      if (njets >= 2 && nbjets == 0 && ht >=  500*GeV && htmiss >= 500*GeV) _counts_agg[0]->fill(w);
210      if (njets >= 3 && nbjets == 0 && ht >= 1500*GeV && htmiss >= 750*GeV) _counts_agg[1]->fill(w);
211      if (njets >= 5 && nbjets == 0 && ht >=  500*GeV && htmiss >= 500*GeV) _counts_agg[2]->fill(w);
212      if (njets >= 5 && nbjets == 0 && ht >= 1500*GeV && htmiss >= 750*GeV) _counts_agg[3]->fill(w);
213      if (njets >= 9 && nbjets == 0 && ht >= 1500*GeV && htmiss >= 750*GeV) _counts_agg[4]->fill(w);
214      if (njets >= 2 && nbjets >= 2 && ht >=  500*GeV && htmiss >= 500*GeV) _counts_agg[5]->fill(w);
215      if (njets >= 3 && nbjets >= 1 && ht >=  750*GeV && htmiss >= 750*GeV) _counts_agg[6]->fill(w);
216      if (njets >= 5 && nbjets >= 3 && ht >=  500*GeV && htmiss >= 500*GeV) _counts_agg[7]->fill(w);
217      if (njets >= 5 && nbjets >= 2 && ht >= 1500*GeV && htmiss >= 750*GeV) _counts_agg[8]->fill(w);
218      if (njets >= 9 && nbjets >= 3 && ht >=  750*GeV && htmiss >= 750*GeV) _counts_agg[9]->fill(w);
219      if (njets >= 7 && nbjets >= 1 && ht >=  300*GeV && htmiss >= 300*GeV) _counts_agg[10]->fill(w);
220      if (njets >= 5 && nbjets >= 1 && ht >=  750*GeV && htmiss >= 750*GeV) _counts_agg[11]->fill(w);
221
222    }
223
224
225    /// Normalise histograms etc., after the run
226    void finalize() {
227
228      const double norm = 35.9*crossSection()/femtobarn;
229      const double sf = norm/sumOfWeights();
230
231      for (auto& idx_cptr : _counts)
232        scale(idx_cptr.second, sf);
233      for (CounterPtr& cptr : _counts_agg)
234        scale(cptr, sf);
235
236      scale(_flow, sf);
237      MSG_INFO("CUTFLOWS:\n\n" << _flow);
238
239    }
240
241    /// @}
242
243
244  private:
245
246    CutflowPtr _flow;
247
248    map<tuple<int,int,int>, CounterPtr> _counts;
249    CounterPtr _counts_agg[12];
250
251  };
252
253
254  RIVET_DECLARE_PLUGIN(CMS_2017_I1594909);
255
256
257}