rivet is hosted by Hepforge, IPPP Durham

Rivet analyses reference

ATLAS_2010_I879407

Track-based underlying event at 900 GeV and 7 TeV in ATLAS
Experiment: ATLAS (LHC)
Inspire ID: 879407
Status: VALIDATED
Authors:
  • Andy Buckley
  • Holger Schulz
References: Beams: p+ p+
Beam energies: (450.0, 450.0); (3500.0, 3500.0) GeV
Run details:
  • pp QCD interactions at 900 GeV and 7 TeV. Diffractive events should be included, but only influence the lowest bins. Multiple kinematic cuts should not be required.'

The underlying event measurements with the ATLAS detector at the LHC at the center of mass energies of 900 GeV and 7 TeV. The observables sensitive to the underlying event, i.e the charged particle density and charged pT sum, as well as their standard deviations and the average pT, are measured as functions of the leading track. A track pT cut of 500 MeV is applied for most observables, but the main profile plots are also shown for a lower track cut of 100 MeV, which includes much more of the soft cross-section. The angular distribution of the charged tracks with respect to the leading track is also studied, as are the correlation between mean transverse momentum and charged particle multiplicity, and the `plateau' height as a function of the leading track $|\eta|$.'

Source code: ATLAS_2010_I879407.cc
  1// -*- C++ -*-
  2#include "Rivet/Analysis.hh"
  3#include "Rivet/Projections/ChargedFinalState.hh"
  4
  5namespace Rivet {
  6
  7
  8  /// @brief Track-based underlying event at 900 GeV and 7 TeV in ATLAS
  9  class ATLAS_2010_I879407 : public Analysis {
 10  public:
 11
 12    RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2010_I879407);
 13
 14
 15    void init() {
 16      const ChargedFinalState cfs100(Cuts::abseta < 2.5 && Cuts::pT >=  100*MeV);
 17      declare(cfs100, "CFS100");
 18      const ChargedFinalState cfs500(Cuts::abseta < 2.5 && Cuts::pT >=  500*MeV);
 19      declare(cfs500, "CFS500");
 20      const ChargedFinalState cfslead(Cuts::abseta < 2.5 && Cuts::pT >= 1.0*GeV);
 21      declare(cfslead, "CFSlead");
 22
 23      for (double eVal : allowedEnergies()) {
 24
 25        const string en = toString(int(eVal));
 26        if (isCompatibleWithSqrtS(eVal))  _sqs = en;
 27        bool ih(en == "7000"s);
 28
 29        // Nch profiles, 500 MeV track pT cut
 30        book(_p[en+"nch_transverse_500_0"], 1+ih, 1, 1);
 31        book(_p[en+"nch_toward_500"],       1+ih, 1, 2);
 32        book(_p[en+"nch_away_500"],         1+ih, 1, 3);
 33
 34        // pTsum profiles, 500 MeV track pT cut
 35        book(_p[en+"ptsum_transverse_500_0"], 3+ih, 1, 1);
 36        book(_p[en+"ptsum_toward_500"],       3+ih, 1, 2);
 37        book(_p[en+"ptsum_away_500"],         3+ih, 1, 3);
 38
 39        // Standard deviation profiles
 40        // First the higher moments of main profiles
 41        // to calculate variance and error on variance...
 42        for (size_t i = 1; i < 4; ++i) {
 43          const string mom = toString(i);
 44          book(_p[en+"nch_transverse_500_"+mom],   "TMP/nch"+mom+"_"+en,   refData(1+ih, 1, 1));
 45          book(_p[en+"ptsum_transverse_500_"+mom], "TMP/ptsum"+mom+"_"+en, refData(3+ih, 1, 1));
 46        }
 47        // Then the data point sets into which the results will be inserted
 48        book(_e[en+"nch_transverse_500"]  , 5+ih, 1, 1);
 49        book(_e[en+"ptsum_transverse_500"], 7+ih, 1, 1);
 50
 51        // <pT> profiles, 500 MeV track pT cut
 52        book(_p[en+"ptavg_transverse_500"], 9+ih, 1, 1);
 53        book(_p[en+"ptavg_toward_500"],     9+ih, 1, 2);
 54        book(_p[en+"ptavg_away_500"],       9+ih, 1, 3);
 55
 56        // <pT> vs. Nch profiles, 500 MeV track pT cut
 57        book(_p[en+"dn_dpt_transverse_500"], 11+ih, 1, 1);
 58        book(_p[en+"dn_dpt_toward_500"],     11+ih, 1, 2);
 59        book(_p[en+"dn_dpt_away_500"],       11+ih, 1, 3);
 60
 61        // Nch vs. Delta(phi) profiles, 500 MeV track pT cut
 62        book(_p[en+"N_vs_dPhi_1_500"], 13+ih, 1, 1);
 63        book(_p[en+"N_vs_dPhi_2_500"], 13+ih, 1, 2);
 64        book(_p[en+"N_vs_dPhi_3_500"], 13+ih, 1, 3);
 65        book(_p[en+"N_vs_dPhi_5_500"], 13+ih, 1, 4);
 66        // pT vs. Delta(phi) profiles, 500 MeV track pT cut
 67        book(_p[en+"pT_vs_dPhi_1_500"], 15+ih, 1, 1);
 68        book(_p[en+"pT_vs_dPhi_2_500"], 15+ih, 1, 2);
 69        book(_p[en+"pT_vs_dPhi_3_500"], 15+ih, 1, 3);
 70        book(_p[en+"pT_vs_dPhi_5_500"], 15+ih, 1, 4);
 71
 72        // Nch and pTsum profiles, 100 MeV track pT cut
 73        book(_p[en+"nch_transverse_100"],   17+ih, 1, 1);
 74        book(_p[en+"nch_toward_100"],       17+ih, 1, 2);
 75        book(_p[en+"nch_away_100"],         17+ih, 1, 3);
 76        book(_p[en+"ptsum_transverse_100"], 19+ih, 1, 1);
 77        book(_p[en+"ptsum_toward_100"],     19+ih, 1, 2);
 78        book(_p[en+"ptsum_away_100"],       19+ih, 1, 3);
 79      }
 80      if (_sqs == "" && !merging()) {
 81        throw BeamError("Invalid beam energy for " + name() + "\n");
 82      }
 83
 84      // Profiles vs. eta (7 TeV only)
 85      book(_p["nch_vs_eta_transverse_100"],  21, 1, 1);
 86      book(_p["ptsum_vs_eta_transverse_100"], 22, 1, 1);
 87
 88    }
 89
 90
 91    // Little helper function to identify Delta(phi) regions
 92    inline int region_index(double dphi) const {
 93      assert(inRange(dphi, 0.0, PI, CLOSED, CLOSED));
 94      if (dphi < PI/3.0) return 0;
 95      if (dphi < 2*PI/3.0) return 1;
 96      return 2;
 97    }
 98
 99
100    void analyze(const Event& event) {
101      // Require at least one track in the event with pT >= 1 GeV
102      const ChargedFinalState& cfslead = apply<ChargedFinalState>(event, "CFSlead");
103      if (cfslead.size() < 1)  vetoEvent;
104
105      // These are the charged particles (tracks) with pT > 500 MeV
106      const ChargedFinalState& charged500 = apply<ChargedFinalState>(event, "CFS500");
107
108      // Identify leading track and its phi and pT (this is the same for both the 100 MeV and 500 MeV track cuts)
109      Particles particles500 = charged500.particlesByPt();
110      Particle p_lead = particles500[0];
111      const double philead = p_lead.phi();
112      const double etalead = p_lead.eta();
113      const double pTlead  = p_lead.perp();
114      MSG_DEBUG("Leading track: pT = " << pTlead << ", eta = " << etalead << ", phi = " << philead);
115
116      // Iterate over all > 500 MeV particles and count particles and scalar pTsum in the three regions
117      vector<double> num500(3, 0), ptSum500(3, 0.0);
118      // Temporary histos that bin Nch and pT in dPhi.
119      // NB. Only one of each needed since binnings are the same for the energies and pT cuts
120      Histo1D htemp_num(_p[_sqs+"N_vs_dPhi_1_500"]->binning());
121      Histo1D htemp_pt(_p[_sqs+"pT_vs_dPhi_1_500"]->binning());
122      for (const Particle& p : particles500) {
123        const double pT = p.pT();
124        const double dPhi = deltaPhi(philead, p.phi());
125        const int ir = region_index(dPhi);
126        num500[ir] += 1;
127        ptSum500[ir] += pT;
128
129        // Fill temp histos to bin Nch and pT in dPhi
130        if (p.genParticle() != p_lead.genParticle()) { // We don't want to fill all those zeros from the leading track...
131          htemp_num.fill(dPhi, 1);
132          htemp_pt.fill(dPhi, pT);
133        }
134      }
135
136
137      // Iterate over charged particles again for profiles against Nch
138      // This is necessary since the Nch are region-specific and so are only known after the first loop
139      for (const Particle& p : particles500) {
140        const double pT = p.pT();
141        const double dPhi = deltaPhi(philead, p.phi());
142        const int ir = region_index(dPhi);
143        switch (ir) {
144        case 0:
145          _p[_sqs+"dn_dpt_toward_500"]->fill(num500[0], pT);
146          break;
147        case 1:
148          _p[_sqs+"dn_dpt_transverse_500"]->fill(num500[1], pT);
149          break;
150        case 2:
151          _p[_sqs+"dn_dpt_away_500"]->fill(num500[2], pT);
152          break;
153        default:
154          assert(false && "How did we get here?");
155        }
156      }
157
158
159      // Now fill underlying event histograms
160      // The densities are calculated by dividing the UE properties by dEta*dPhi
161      // -- each region has a dPhi of 2*PI/3 and dEta is two times 2.5
162      const double dEtadPhi = (2*2.5 * 2*PI/3.0);
163      // Transverse profiles need 4 orders of moments for stddev with errors
164      for (int i = 0; i < 4; ++i) {
165        const string mom = toString(i);
166        _p[_sqs+"nch_transverse_500_"+mom]->fill(pTlead/GeV, intpow(num500[1]/dEtadPhi, i+1));
167        _p[_sqs+"ptsum_transverse_500_"+mom]->fill(pTlead/GeV, intpow(ptSum500[1]/GeV/dEtadPhi, i+1));
168      }
169      // Toward and away profiles only need the first moment (the mean)
170      _p[_sqs+"nch_toward_500"]->fill(pTlead/GeV, num500[0]/dEtadPhi);
171      _p[_sqs+"nch_away_500"]->fill(pTlead/GeV, num500[2]/dEtadPhi);
172      _p[_sqs+"ptsum_toward_500"]->fill(pTlead/GeV, ptSum500[0]/GeV/dEtadPhi);
173      _p[_sqs+"ptsum_away_500"]->fill(pTlead/GeV, ptSum500[2]/GeV/dEtadPhi);
174      // <pT> profiles
175      //MSG_INFO("Trans pT1, pTsum, Nch, <pT>" << pTlead/GeV << ", " <<  ptSum500[1]/GeV << ", " << num500[1] << ", " << ptSum500[1]/GeV/num500[1]);
176      if (num500[1] > 0) _p[_sqs+"ptavg_transverse_500"]->fill(pTlead/GeV, ptSum500[1]/GeV/num500[1]);
177      if (num500[0] > 0) _p[_sqs+"ptavg_toward_500"]->fill(pTlead/GeV, ptSum500[0]/GeV/num500[0]);
178      if (num500[2] > 0) _p[_sqs+"ptavg_away_500"]->fill(pTlead/GeV, ptSum500[2]/GeV/num500[2]);
179
180
181      // Update the "proper" dphi profile histograms
182      // Note that we fill dN/dEtadPhi: dEta = 2*2.5, dPhi = 2*PI/nBins
183      // The values tabulated in the note are for an (undefined) signed Delta(phi) rather than
184      // |Delta(phi)| and so differ by a factor of 2: we have to actually norm for angular range = 2pi
185      std::vector<double> ptcut;
186      if (_sqs == "900"s) {
187        ptcut += 1.0; ptcut += 1.5; ptcut += 2.0; ptcut += 2.5;
188      }
189      else if (_sqs == "7000"s) {
190        ptcut += 1.0; ptcut += 2.0; ptcut += 3.0; ptcut += 5.0;
191      }
192      assert(ptcut.size() == 4);
193      for (size_t i = 1; i < htemp_num.numBins()+1; ++i) {
194        // First Nch
195        double mean = htemp_num.bin(i).xMid();
196        double value = 0.;
197        if (htemp_num.bin(i).numEntries() > 0) {
198          mean = htemp_num.bin(i).xMean();
199          value = htemp_num.bin(i).sumW()/htemp_num.bin(i).xWidth()/10.0;
200        }
201        if (pTlead/GeV >= ptcut[0]) _p[_sqs+"N_vs_dPhi_1_500"]->fill(mean, value);
202        if (pTlead/GeV >= ptcut[1]) _p[_sqs+"N_vs_dPhi_2_500"]->fill(mean, value);
203        if (pTlead/GeV >= ptcut[2]) _p[_sqs+"N_vs_dPhi_3_500"]->fill(mean, value);
204        if (pTlead/GeV >= ptcut[3]) _p[_sqs+"N_vs_dPhi_5_500"]->fill(mean, value);
205
206        // Then pT
207        mean = htemp_pt.bin(i).xMid();
208        value = 0.;
209        if (htemp_pt.bin(i).numEntries() > 0) {
210          mean = htemp_pt.bin(i).xMean();
211          value = htemp_pt.bin(i).sumW()/htemp_pt.bin(i).xWidth()/10.0;
212        }
213        if (pTlead/GeV >= ptcut[0]) _p[_sqs+"pT_vs_dPhi_1_500"]->fill(mean, value);
214        if (pTlead/GeV >= ptcut[1]) _p[_sqs+"pT_vs_dPhi_2_500"]->fill(mean, value);
215        if (pTlead/GeV >= ptcut[2]) _p[_sqs+"pT_vs_dPhi_3_500"]->fill(mean, value);
216        if (pTlead/GeV >= ptcut[3]) _p[_sqs+"pT_vs_dPhi_5_500"]->fill(mean, value);
217      }
218
219
220      //////////////////////
221
222
223      // These are the charged particles (tracks) with pT > 100 MeV
224      const ChargedFinalState& charged100 = apply<ChargedFinalState>(event, "CFS100");
225
226      // Iterate over all > 100 MeV particles and count particles and scalar pTsum in the three regions
227      vector<double> num100(3, 0), ptSum100(3, 0.0);
228      for (const Particle& p : charged100.particles()) {
229        const double pT = p.pT();
230        const double dPhi = deltaPhi(philead, p.phi());
231        const int ir = region_index(dPhi);
232        num100[ir] += 1;
233        ptSum100[ir] += pT;
234      }
235
236      // Now fill the two sets of 100 MeV underlying event histograms
237      _p[_sqs+"nch_transverse_100"]->fill(pTlead/GeV, num100[1]/dEtadPhi);
238      _p[_sqs+"nch_toward_100"]->fill(pTlead/GeV, num100[0]/dEtadPhi);
239      _p[_sqs+"nch_away_100"]->fill(pTlead/GeV, num100[2]/dEtadPhi);
240      _p[_sqs+"ptsum_transverse_100"]->fill(pTlead/GeV, ptSum100[1]/GeV/dEtadPhi);
241      _p[_sqs+"ptsum_toward_100"]->fill(pTlead/GeV, ptSum100[0]/GeV/dEtadPhi);
242      _p[_sqs+"ptsum_away_100"]->fill(pTlead/GeV, ptSum100[2]/GeV/dEtadPhi);
243
244      // And finally the Nch and pT vs eta_lead profiles (again from > 100 MeV tracks, and only at 7 TeV)
245      if (_sqs == "7000"s && pTlead > 5*GeV) {
246        _p["nch_vs_eta_transverse_100"]->fill(etalead, num100[1]/dEtadPhi);
247        _p["ptsum_vs_eta_transverse_100"]->fill(etalead, ptSum100[1]/GeV/dEtadPhi);
248      }
249
250    }
251
252
253    void finalize() {
254      // Convert the various moments of the 500 MeV trans pT and Nch distributions to std devs with correct error
255      for (double eVal : allowedEnergies()) {
256        const string en = toString(int(eVal));
257        moments_to_stddev(en+"nch_transverse_500");
258        moments_to_stddev(en+"ptsum_transverse_500");
259      }
260    }
261
262    void moments_to_stddev(const string& label) {
263      for (size_t b = 1; b < _p[label+"_0"]->numBins()+1; ++b) { // loop over points
264        /// @todo Assuming unit weights here! Should use N_effective = sumW**2/sumW2?
265        const double numentries = _p[label+"_0"]->bin(b).numEntries();
266        double var = 0., sd = 0.;
267        if (numentries > 0) {
268          var = _p[label+"_1"]->bin(b).yMean() - intpow(_p[label+"_0"]->bin(b).yMean(), 2);
269          sd = fuzzyLessEquals(var,0.) ? 0 : sqrt(var); ///< Numerical safety check
270        }
271        if (sd == 0 || numentries < 3) {
272          MSG_DEBUG("Need at least 3 bin entries and a non-zero central value to calculate "
273                      << "an error on standard deviation profiles (bin " << b << ")");
274          _e[label]->bin(b).set(sd, 0);
275          continue;
276        }
277        // c2(y) = m4(x) - 4 m3(x) m1(x) - m2(x)^2 + 8 m2(x) m1(x)^2 - 4 m1(x)^4
278        const double var_on_var = _p[label+"_3"]->bin(b).yMean()
279          - 4 * _p[label+"_2"]->bin(b).yMean() * _p[label+"_0"]->bin(b).yMean()
280          - intpow(_p[label+"_1"]->bin(b).yMean(), 2)
281          + 8 * _p[label+"_1"]->bin(b).yMean() * intpow(_p[label+"_0"]->bin(b).yMean(), 2)
282          - 4 * intpow(_p[label+"_0"]->bin(b).yMean(), 4);
283        const double stderr_on_var = sqrt(var_on_var/(numentries-2.0));
284        const double stderr_on_sd = stderr_on_var / (2.0*sd);
285        _e[label]->bin(b).set(sd, stderr_on_sd);
286      }
287    }
288
289
290  private:
291
292    map<string,Profile1DPtr> _p;
293    map<string,Estimate1DPtr> _e;
294
295    string _sqs = "";
296
297  };
298
299
300
301  RIVET_DECLARE_ALIASED_PLUGIN(ATLAS_2010_I879407, ATLAS_2010_S8894728);
302
303}