rivet is hosted by Hepforge, IPPP Durham
ATLAS_2012_I1094568.cc
Go to the documentation of this file.
00001 // -*- C++ -*-
00002 #include "Rivet/Analysis.hh"
00003 #include "Rivet/Projections/FinalState.hh"
00004 #include "Rivet/Projections/IdentifiedFinalState.hh"
00005 #include "Rivet/Projections/LeadingParticlesFinalState.hh"
00006 #include "Rivet/Projections/UnstableFinalState.hh"
00007 #include "Rivet/Projections/HadronicFinalState.hh"
00008 #include "Rivet/Projections/VetoedFinalState.hh"
00009 #include "Rivet/Projections/DressedLeptons.hh"
00010 #include "Rivet/Projections/FastJets.hh"
00011 
00012 namespace Rivet {
00013 
00014 
00015   struct ATLAS_2012_I1094568_Plots {
00016     // Track which veto region this is, to match the autobooked histograms
00017     int region_index;
00018 
00019     // Lower rapidity boundary or veto region
00020     double y_low;
00021     // Upper rapidity boundary or veto region
00022     double y_high;
00023 
00024     double vetoJetPt_Q0;
00025     double vetoJetPt_Qsum;
00026 
00027     // Histograms to store the veto jet pT and sum(veto jet pT) histograms.
00028     Histo1DPtr _h_vetoJetPt_Q0;
00029     Histo1DPtr _h_vetoJetPt_Qsum;
00030 
00031     // Scatter2Ds for the gap fractions
00032     Scatter2DPtr _d_gapFraction_Q0;
00033     Scatter2DPtr _d_gapFraction_Qsum;
00034   };
00035 
00036 
00037 
00038   /// Top pair production with central jet veto
00039   class ATLAS_2012_I1094568 : public Analysis {
00040   public:
00041 
00042     /// Constructor
00043     ATLAS_2012_I1094568()
00044       : Analysis("ATLAS_2012_I1094568")
00045     {   }
00046 
00047 
00048     /// Book histograms and initialise projections before the run
00049     void init() {
00050 
00051       const FinalState fs(-4.5, 4.5);
00052       addProjection(fs, "ALL_FS");
00053 
00054       /// Get electrons from truth record
00055       IdentifiedFinalState elec_fs(-2.47, 2.47, 25.0*GeV);
00056       elec_fs.acceptIdPair(PID::ELECTRON);
00057       addProjection(elec_fs, "ELEC_FS");
00058 
00059       /// Get muons which pass the initial kinematic cuts:
00060       IdentifiedFinalState muon_fs(-2.5, 2.5, 20.0*GeV);
00061       muon_fs.acceptIdPair(PID::MUON);
00062       addProjection(muon_fs, "MUON_FS");
00063 
00064       /// Get all neutrinos. These will not be used to form jets.
00065       /// We'll use the highest 2 pT neutrinos to calculate the MET
00066       IdentifiedFinalState neutrino_fs(-4.5, 4.5, 0.0*GeV);
00067       neutrino_fs.acceptNeutrinos();
00068       addProjection(neutrino_fs, "NEUTRINO_FS");
00069 
00070       // Final state used as input for jet-finding.
00071       // We include everything except the muons and neutrinos
00072       VetoedFinalState jet_input(fs);
00073       jet_input.vetoNeutrinos();
00074       jet_input.addVetoPairId(PID::MUON);
00075       addProjection(jet_input, "JET_INPUT");
00076 
00077       // Get the jets
00078       FastJets jets(jet_input, FastJets::ANTIKT, 0.4);
00079       addProjection(jets, "JETS");
00080 
00081       // Initialise weight counter
00082       m_total_weight = 0.0;
00083 
00084       // Init histogramming for the various regions
00085       m_plots[0].region_index = 1;
00086       m_plots[0].y_low = 0.0;
00087       m_plots[0].y_high = 0.8;
00088       initializePlots(m_plots[0]);
00089       //
00090       m_plots[1].region_index = 2;
00091       m_plots[1].y_low = 0.8;
00092       m_plots[1].y_high = 1.5;
00093       initializePlots(m_plots[1]);
00094       //
00095       m_plots[2].region_index = 3;
00096       m_plots[2].y_low = 1.5;
00097       m_plots[2].y_high = 2.1;
00098       initializePlots(m_plots[2]);
00099       //
00100       m_plots[3].region_index = 4;
00101       m_plots[3].y_low = 0.0;
00102       m_plots[3].y_high = 2.1;
00103       initializePlots(m_plots[3]);
00104     }
00105 
00106 
00107     void initializePlots(ATLAS_2012_I1094568_Plots& plots) {
00108       const string vetoPt_Q0_name = "vetoJetPt_Q0_" + to_str(plots.region_index);
00109       plots.vetoJetPt_Q0 = 0.0;
00110       plots._h_vetoJetPt_Q0   = bookHisto1D(vetoPt_Q0_name, 200, 0.0, 1000.0);
00111       plots._d_gapFraction_Q0 = bookScatter2D(plots.region_index, 1, 1);
00112       foreach (Point2D p, refData(plots.region_index, 1, 1).points()) {
00113         p.setY(0, 0);
00114         plots._d_gapFraction_Q0->addPoint(p);
00115       }
00116 
00117       const string vetoPt_Qsum_name = "vetoJetPt_Qsum_" + to_str(plots.region_index);
00118       plots._h_vetoJetPt_Qsum   = bookHisto1D(vetoPt_Qsum_name, 200, 0.0, 1000.0);
00119       plots._d_gapFraction_Qsum = bookScatter2D(plots.region_index, 2, 1);
00120       plots.vetoJetPt_Qsum = 0.0;
00121       foreach (Point2D p, refData(plots.region_index, 2, 1).points()) {
00122         p.setY(0, 0);
00123         plots._d_gapFraction_Qsum->addPoint(p);
00124       }
00125     }
00126 
00127 
00128     /// Perform the per-event analysis
00129     void analyze(const Event& event) {
00130 
00131       const double weight = event.weight();
00132 
00133       /// Get the various sets of final state particles
00134       const Particles& elecFS = applyProjection<IdentifiedFinalState>(event, "ELEC_FS").particlesByPt();
00135       const Particles& muonFS = applyProjection<IdentifiedFinalState>(event, "MUON_FS").particlesByPt();
00136       const Particles& neutrinoFS = applyProjection<IdentifiedFinalState>(event, "NEUTRINO_FS").particlesByPt();
00137 
00138       // Get all jets with pT > 25 GeV
00139       const Jets& jets = applyProjection<FastJets>(event, "JETS").jetsByPt(25.0*GeV);
00140 
00141       // Keep any jets that pass the initial rapidity cut
00142       vector<const Jet*> central_jets;
00143       foreach(const Jet& j, jets) {
00144         if (fabs(j.rapidity()) < 2.4) central_jets.push_back(&j);
00145       }
00146 
00147       // For each of the jets that pass the rapidity cut, only keep those that are not
00148       // too close to any leptons
00149       vector<const Jet*> good_jets;
00150       foreach(const Jet* j, central_jets) {
00151         bool goodJet = true;
00152 
00153         foreach (const Particle& e, elecFS) {
00154           double elec_jet_dR = deltaR(e.momentum(), j->momentum());
00155           if (elec_jet_dR < 0.4) { goodJet = false; break; }
00156         }
00157         if (!goodJet) continue;
00158         if (!goodJet) continue;
00159 
00160         foreach (const Particle& m, muonFS) {
00161           double muon_jet_dR = deltaR(m.momentum(), j->momentum());
00162           if (muon_jet_dR < 0.4) { goodJet = false; break; }
00163         }
00164         if (!goodJet) continue;
00165 
00166         good_jets.push_back(j);
00167       }
00168 
00169       // Get b hadrons with pT > 5 GeV
00170       /// @todo This is a hack -- replace with UnstableFinalState
00171       vector<HepMC::GenParticle*> B_hadrons;
00172       vector<HepMC::GenParticle*> allParticles = particles(event.genEvent());
00173       for (size_t i = 0; i < allParticles.size(); i++) {
00174         GenParticle* p = allParticles[i];
00175         if (!PID::isHadron(p->pdg_id()) || !PID::hasBottom(p->pdg_id())) continue;
00176         if (p->momentum().perp() < 5*GeV) continue;
00177         B_hadrons.push_back(p);
00178       }
00179 
00180       // For each of the good jets, check whether any are b-jets (via dR matching)
00181       vector<const Jet*> b_jets;
00182       foreach(const Jet* j, good_jets) {
00183         bool isbJet = false;
00184         foreach(HepMC::GenParticle* b, B_hadrons) {
00185           if (deltaR(j->momentum(), FourMomentum(b->momentum())) < 0.3) isbJet = true;
00186         }
00187         if (isbJet) b_jets.push_back(j);
00188       }
00189 
00190 
00191       // Check the good jets again and keep track of the "additional jets"
00192       // i.e. those which are not either of the 2 highest pT b-jets
00193       vector<const Jet*> veto_jets;
00194       int n_bjets_matched = 0;
00195       foreach(const Jet* j, good_jets) {
00196         bool isBJet = false;
00197         foreach(const Jet* b, b_jets) {
00198           if (n_bjets_matched == 2) break;
00199           if (b == j){isBJet = true; ++ n_bjets_matched;}
00200         }
00201         if (!isBJet) veto_jets.push_back(j);
00202       }
00203 
00204 
00205       // Get the MET by taking the vector sum of all neutrinos
00206       /// @todo Use MissingMomentum instead?
00207       double MET = 0;
00208       FourMomentum p_MET;
00209       foreach (const Particle& p, neutrinoFS) {
00210         p_MET = p_MET + p.momentum();
00211       }
00212       MET = p_MET.pT();
00213 
00214       // Now we have everything we need to start doing the event selections
00215       bool passed_ee = false;
00216       vector<const Jet*> vetoJets_ee;
00217 
00218       // We want exactly 2 electrons...
00219       if (elecFS.size() == 2) {
00220         // ... with opposite sign charges.
00221         if (PID::charge(elecFS[0]) != PID::charge(elecFS[1])) {
00222           // Check the MET
00223           if (MET >= 40*GeV) {
00224             // Do some dilepton mass cuts
00225             const double dilepton_mass = (elecFS[0].momentum() + elecFS[1].momentum()).mass();
00226             if (dilepton_mass >= 15*GeV) {
00227               if (fabs(dilepton_mass - 91.0*GeV) >= 10.0*GeV) {
00228                 // We need at least 2 b-jets
00229                 if (b_jets.size() > 1) {
00230                   // This event has passed all the cuts;
00231                   passed_ee = true;
00232                 }
00233               }
00234             }
00235           }
00236         }
00237       }
00238 
00239       bool passed_mumu = false;
00240       // Now do the same checks for the mumu channel
00241       vector<const Jet*> vetoJets_mumu;
00242       // So we now want 2 good muons...
00243       if (muonFS.size() == 2) {
00244         // ...with opposite sign charges.
00245         if (PID::charge(muonFS.at(0)) != PID::charge(muonFS.at(1))) {
00246           // Check the MET
00247           if (MET >= 40*GeV) {
00248             // and do some di-muon mass cuts
00249             const double dilepton_mass = (muonFS.at(0).momentum() + muonFS.at(1).momentum()).mass();
00250             if (dilepton_mass >= 15*GeV) {
00251               if (fabs(dilepton_mass - 91.0*GeV) >= 10.0*GeV) {
00252                 // Need at least 2 b-jets
00253                 if (b_jets.size() > 1) {
00254                   // This event has passed all mumu-channel cuts
00255                   passed_mumu = true;
00256                 }
00257               }
00258             }
00259           }
00260         }
00261       }
00262 
00263       bool passed_emu = false;
00264       // Finally, the same again with the emu channel
00265       vector<const Jet*> vetoJets_emu;
00266       // We want exactly 1 electron and 1 muon
00267       if (elecFS.size() == 1 && muonFS.size() == 1) {
00268         // With opposite sign charges
00269         if (PID::charge(elecFS.at(0)) != PID::charge(muonFS.at(0))) {
00270           // Calculate HT: scalar sum of the pTs of the leptons and all good jets
00271           double HT = 0;
00272           HT += elecFS[0].pT();
00273           HT += muonFS[0].pT();
00274           foreach (const Jet* j, good_jets)
00275             HT += fabs(j->pT());
00276           // Keep events with HT > 130 GeV
00277           if (HT > 130.0*GeV) {
00278             // And again we want 2 or more b-jets
00279             if (b_jets.size() > 1) {
00280               passed_emu = true;
00281             }
00282           }
00283         }
00284       }
00285 
00286       if (passed_ee == true || passed_mumu == true || passed_emu == true) {
00287         // If the event passes the selection, we use it for all gap fractions
00288         m_total_weight += weight;
00289 
00290         // Loop over each veto jet
00291         foreach (const Jet* j, veto_jets) {
00292           const double pt = j->pT();
00293           const double rapidity = fabs(j->rapidity());
00294           // Loop over each region
00295           for (size_t i = 0; i < 4; ++i) {
00296             // If the jet falls into this region, get its pT and increment sum(pT)
00297             if (inRange(rapidity, m_plots[i].y_low, m_plots[i].y_high)) {
00298               m_plots[i].vetoJetPt_Qsum += pt;
00299               // If we've already got a veto jet, don't replace it
00300               if (m_plots[i].vetoJetPt_Q0 == 0.0) m_plots[i].vetoJetPt_Q0 = pt;
00301             }
00302           }
00303         }
00304         for (size_t i = 0; i < 4; ++i) {
00305           m_plots[i]._h_vetoJetPt_Q0->fill(m_plots[i].vetoJetPt_Q0, weight);
00306           m_plots[i]._h_vetoJetPt_Qsum->fill(m_plots[i].vetoJetPt_Qsum, weight);
00307           m_plots[i].vetoJetPt_Q0 = 0.0;
00308           m_plots[i].vetoJetPt_Qsum = 0.0;
00309         }
00310       }
00311     }
00312 
00313 
00314     /// Normalise histograms etc., after the run
00315     void finalize() {
00316       for (size_t i = 0; i < 4; ++i) {
00317         finalizeGapFraction(m_total_weight, m_plots[i]._d_gapFraction_Q0, m_plots[i]._h_vetoJetPt_Q0);
00318         finalizeGapFraction(m_total_weight, m_plots[i]._d_gapFraction_Qsum, m_plots[i]._h_vetoJetPt_Qsum);
00319       }
00320     }
00321 
00322 
00323     /// Convert temporary histos to cumulative efficiency scatters
00324     /// @todo Should be possible to replace this with a couple of YODA one-lines for diff -> integral and "efficiency division"
00325     void finalizeGapFraction(double total_weight, Scatter2DPtr gapFraction, Histo1DPtr vetoPt) {
00326      // Stores the cumulative frequency of the veto jet pT histogram
00327      double vetoPtWeightSum = 0.0;
00328 
00329      // Keep track of which gap fraction point we're currently populating (#final_points != #tmp_bins)
00330      size_t fgap_point = 0;
00331      for (size_t i = 0; i < vetoPt->numBins(); ++i) {
00332        // If we've done the last "final" point, stop
00333        if (fgap_point == gapFraction->numPoints()) break;
00334 
00335        // Increment the cumulative vetoPt counter for this temp histo bin
00336        /// @todo Get rid of this and use vetoPt->integral(i+1) when points and bins line up?
00337        vetoPtWeightSum += vetoPt->bin(i).sumW();
00338 
00339        // If this temp histo bin's upper edge doesn't correspond to the reference point, don't finalise the scatter.
00340        // Note that points are ON the bin edges and have no width: they represent the integral up to exactly that point.
00341        if ( !fuzzyEquals(vetoPt->bin(i).xMax(), gapFraction->point(fgap_point).x()) ) continue;
00342 
00343        // Calculate the gap fraction and its uncertainty
00344        const double frac = (total_weight != 0.0) ? vetoPtWeightSum/total_weight : 0;
00345        const double fracErr = (total_weight != 0.0) ? sqrt(frac*(1-frac)/total_weight) : 0;
00346        gapFraction->point(fgap_point).setY(frac, fracErr);
00347 
00348        ++fgap_point;
00349      }
00350      /// @todo Delete vetoPt temp histos: use /TMP system
00351     }
00352 
00353 
00354   private:
00355 
00356     // Weight counter
00357     double m_total_weight;
00358 
00359     // Structs containing all the plots, for each event selection
00360     ATLAS_2012_I1094568_Plots m_plots[4];
00361 
00362   };
00363 
00364 
00365   // The hook for the plugin system
00366   DECLARE_RIVET_PLUGIN(ATLAS_2012_I1094568);
00367 
00368 }