ATLAS Offline Software
yearwise_luminosity.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
3 
4 """
5 Plot comparisons of Zee/Zmumu and Z/ATLAS over entire data-periods.
6 This can be done as a function of time (validated, working perfectly),
7 and pileup (not yet fully validated).
8 """
9 
10 import numpy as np
11 import pandas as pd
12 import ROOT as R
13 import python_tools as pt
14 import math
15 from array import array
16 import time
17 import argparse
18 
19 parser = argparse.ArgumentParser()
20 parser.add_argument('--year', type=str, help='15-18 or 22-23, Run3 for full Run-3')
21 parser.add_argument('--channel', type=str, help='Zee or Zmumu')
22 parser.add_argument('--comp', action='store_true', help='Compare Zee and Zmumu?')
23 parser.add_argument('--absolute', action='store_true', help='Compare absolute luminosity')
24 parser.add_argument('--indir', type=str, help='Input directory for CSV files')
25 parser.add_argument('--outdir', type=str, help='Output directory for plots')
26 parser.add_argument('--dir_2022', type=str, help='Input directory for 2022 data')
27 parser.add_argument('--dir_2023', type=str, help='Input directory for 2023 data')
28 
29 args = parser.parse_args()
30 year = args.year
31 channel = args.channel
32 absolute = args.absolute
33 indir = args.indir
34 outdir = args.outdir
35 dir_2022 = args.dir_2022
36 dir_2023 = args.dir_2023
37 
38 print("------------------------------------------")
39 print("Begin Yearwise Lumi vs Time")
40 print("------------------------------------------")
41 
42 # Do all of the ugly plot stlying here
43 if year == "run3":
44  years = ["22", "23"]
45  out_tag = "_run3"
46  time_format = "%m/%y"
47  if args.absolute:
48  ymin, ymax = 1.01, 1.11
49  else:
50  ymin, ymax = 0.95, 1.05
51  xtitle = 'Month / Year'
52  date_tag = "Run 3, #sqrt{s} = 13.6 TeV"
53  norm_type = "Run3"
54  if channel is not None:
55  xval = 0.25
56  yval = 0.85
57  else:
58  xval = 0.25
59  yval = 0.85
60  set_size = 1
61 else:
62  years = [year]
63  out_tag = year
64  time_format = "%d/%m"
65  ymin, ymax = 0.94, 1.06
66  xtitle = 'Date in 20' + year
67  date_tag = "Data 20" + year + ", #sqrt{s} = 13.6 TeV"
68  norm_type = "year"
69  xval = 0.20
70  yval = 0.86
71  set_size = 0
72 
73 
74 if channel == "Zee":
75  zstring = "Z #rightarrow ee counting"
76  ytitle = 'L_{Z #rightarrow ee}/L_{ATLAS}'
77  if args.absolute:
78  leg_entry = "L_{Z #rightarrow ee}"
79  else:
80  leg_entry = "L_{Z #rightarrow ee}^{"+norm_type+"-normalised}/L_{ATLAS}"
81 elif channel == "Zmumu":
82  zstring = "Z #rightarrow #mu#mu counting"
83  ytitle = 'L_{Z #rightarrow #mu#mu}/L_{ATLAS}'
84  if args.absolute:
85  leg_entry = "L_{Z #rightarrow #mu#mu}"
86  else:
87  leg_entry = "L_{Z #rightarrow #mu#mu}^{"+norm_type+"-normalised}/L_{ATLAS}"
88 elif channel == "Zll":
89  zstring = "Z #rightarrow ll counting"
90  ytitle = 'L_{Z #rightarrow ll}/L_{ATLAS}'
91  if args.absolute:
92  leg_entry = "L_{Z #rightarrow ll}"
93  else:
94  leg_entry = "L_{Z #rightarrow ll}^{"+norm_type+"-normalised}/L_{ATLAS}"
95 
96 def main():
97  if args.comp:
98  channel_comparison(years)
99  else:
100  zcounting_vs_atlas(channel, years)
101 
103 
104  print("------------------------------------------")
105  print("Begin Yearwise Lumi Channel Comparison vs Time")
106  print("------------------------------------------")
107 
108  dict_zlumi = {}
109  for year in years:
110 
111  print("year = ", year)
112  grl = pt.get_grl(year)
113 
114  if year == "23":
115  maindir = args.indir + dir_2023
116  print("2023 grl = ", grl)
117 
118  elif year == "22":
119  maindir = args.indir + dir_2022
120  print("2022 grl = ", grl)
121 
122  else:
123  grl = pt.get_grl(year)
124  print("other grl = ", grl)
125 
126  for channel in ["Zee", "Zmumu"]:
127 
128  for run in grl:
129  dfz = pd.read_csv(maindir + "run_" + run + ".csv")
130  dfz_small = dfz
131  dfz_small['ZLumi'] = dfz_small[channel + 'Lumi']
132  dfz_small['ZLumiErr'] = dfz_small[channel + 'LumiErr']
133  dfz_small = dfz_small.drop(dfz_small[dfz_small.ZLumi == 0].index)
134  dfz_small = dfz_small.drop(dfz_small[(dfz_small['LBLive']<10) | (dfz_small['PassGRL']==0)].index)
135  # Cut out all runs shorter than 40 minutes
136  if dfz_small['LBLive'].sum()/60 < 40:
137  print("Skip Run", run, "because of live time", dfz_small['LBLive'].sum()/60, "min")
138  continue
139 
140  dfz_small['ZLumi'] *= dfz_small['LBLive']
141  dfz_small['ZLumiErr'] *= dfz_small['LBLive']
142  # If plotting vs. date simply fill the arrays here
143  zlumi = dfz_small['ZLumi'].sum()
144 
145  dfz_small['ZLumiErr'] *= dfz_small['ZLumiErr']
146  zerr = math.sqrt(dfz_small['ZLumiErr'].sum())
147 
148  # Grab start of the run for plotting later on
149  run_start = dfz_small['LBStart'].iloc[0]
150  timestamp = time.gmtime(run_start)
151  timestamp = R.TDatime(timestamp[0], timestamp[1], timestamp[2], timestamp[3], timestamp[4], timestamp[5])
152  timestamp = timestamp.Convert()
153  dict_zlumi[channel, run] = (zlumi, zerr, timestamp)
154 
155  vec_times = array('d')
156  vec_ratio = array('d')
157  vec_ratio_err = array('d')
158  keys = [key[1] for key in dict_zlumi if "Zee" in key]
159 
160  # If plotting vs. date simply calculate integrated lumi per run and fill array
161  for key in sorted(keys):
162  try:
163  ratio = dict_zlumi["Zee", key][0]/dict_zlumi["Zmumu", key][0]
164  error = ratio * math.sqrt( pow(dict_zlumi["Zee", key][1]/dict_zlumi["Zee", key][0], 2) + pow(dict_zlumi["Zmumu", key][1]/dict_zlumi["Zmumu", key][0], 2) )
165  date = dict_zlumi["Zee", key][2]
166 
167  if ratio < ymin or ratio > ymax:
168  print("Run", key, "has ratio", ratio)
169  print("Outside of y-axis range")
170  else:
171  vec_times.append(date)
172  vec_ratio.append(ratio)
173  vec_ratio_err.append(error)
174  except KeyError:
175  print("Cannot do ratio for", key)
176 
177  tg = R.TGraphErrors(len(vec_times), vec_times, vec_ratio, R.nullptr, vec_ratio_err)
178  leg = R.TLegend(0.645, 0.72, 0.805, 0.91)
179 
180  # Depending if we're plotting over whole Run-3, change canvas size
181  if out_tag == "_run3":
182  c1 = R.TCanvas("c1", "c1", 2000, 1000)
183  else:
184  c1 = R.TCanvas()
185 
186  tg.Draw('ap')
187  tg.GetYaxis().SetTitle('L_{Z #rightarrow ee} / L_{Z #rightarrow #mu#mu}')
188  tg.Fit('pol0', '0q')
189  tg.GetFunction('pol0').SetLineColor(R.kRed)
190 
191  mean = round(tg.GetFunction('pol0').GetParameter(0), 4)
192 
193  # Plot 68% percentile band
194  stdev = np.percentile(abs(vec_ratio - np.median(vec_ratio)), 68)
195  line1 = pt.make_bands(vec_times, stdev, mean)
196  line1.Draw("same 3")
197  tg.GetFunction('pol0').Draw("same l")
198  tg.Draw('same ep')
199 
200  print("#### STDEV =", round(stdev, 3))
201 
202  leg.SetBorderSize(0)
203  leg.SetTextSize(0.045)
204  leg.AddEntry(tg, "L_{Z #rightarrow ee}/L_{Z #rightarrow #mu#mu}", "ep")
205  leg.AddEntry(tg.GetFunction("pol0"), "Mean = " + str(round(mean, 3)), "l")
206  leg.AddEntry(line1, "68% band", "f")
207  leg.Draw()
208 
209  pt.drawAtlasLabel(xval, 0.86, "Internal")
210  pt.drawText(xval, 0.80, date_tag, set_size)
211 
212  new_trig_line = R.TLine(1683743066.0, 0.95, 1683743066.0, 1.05)
213 
214  new_trig_line.SetLineColor(R.kBlue)
215  new_trig_line.SetLineWidth(3)
216  new_trig_line.SetLineStyle(2)
217  new_trig_line.Draw("same")
218  R.gPad.Update()
219 
220  tg.GetYaxis().SetRangeUser(ymin, ymax)
221  tg.GetXaxis().SetTitle(xtitle)
222  tg.GetXaxis().SetTimeDisplay(2)
223  tg.GetXaxis().SetNdivisions(9,R.kFALSE)
224  tg.GetXaxis().SetTimeFormat(time_format)
225  tg.GetXaxis().SetTimeOffset(0,"gmt")
226 
227  if years == ["22", "23"]:
228  plot_title = "Ratio of Electron and Muon channel Z-counting Luminosities across Run 3"
229  else:
230  plot_title = "Ratio of Electron and Muon channel Z-counting Luminosities across 20" + years[0]
231 
232  tg.SetTitle(plot_title)
233  c1.Update()
234  c1.SaveAs(outdir + "channel_comp_data"+out_tag+".pdf")
235 
236 
237 def zcounting_vs_atlas(channel, years):
238  """
239  Plot normalised comparison of Z-counting luminosity to ATLAS luminosity.
240  This can be done as a function of time and pileup.
241  """
242 
243  print("------------------------------------------")
244  print("Begin Yearwise ", channel, " Lumi ATLAS comparison vs Time")
245  print("------------------------------------------")
246 
247  arr_date = []
248  arr_olumi = []
249  arr_zlumi = []
250  arr_zerr = []
251  run_num = []
252 
253  for year in years:
254  print("year = ", year)
255 
256  if year == "23":
257  maindir = args.indir + dir_2023
258  grl = pt.get_grl(year)
259  print("2023 grl = ", grl)
260 
261  elif year == "22":
262  maindir = args.indir + dir_2022
263  grl = pt.get_grl(year)
264  print("2022 grl = ", grl)
265 
266  else:
267  grl = pt.get_grl(year)
268  print("other grl = ", grl)
269 
270  for run in grl:
271 
272  dfz = pd.read_csv(maindir + "run_" + run + ".csv")
273  dfz_small = dfz
274  dfz_small['ZLumi'] = dfz_small[channel + 'Lumi']
275  dfz_small['ZLumiErr'] = dfz_small[channel + 'LumiErr']
276  dfz_small['LBLive'] = dfz_small['LBLive']
277  dfz_small = dfz_small.drop(dfz_small[dfz_small.ZLumi == 0].index)
278  dfz_small = dfz_small.drop(dfz_small[(dfz_small['LBLive']<10) | (dfz_small['PassGRL']==0)].index)
279 
280  # Cut out all runs shorter than 40 minutes
281  if dfz_small['LBLive'].sum()/60 < 40:
282  print("Skip Run", run, "because of live time", dfz_small['LBLive'].sum()/60, "min")
283  continue
284 
285  # Grab start of the run for plotting later on
286  run_start = dfz_small['LBStart'].iloc[0]
287  timestamp = time.gmtime(run_start)
288  timestamp = R.TDatime(timestamp[0], timestamp[1], timestamp[2], timestamp[3], timestamp[4], timestamp[5])
289  timestamp = timestamp.Convert()
290 
291  # Calculate integrated ATLAS luminosity
292  dfz_small['OffLumi'] *= dfz_small['LBLive']
293  olumi = dfz_small['OffLumi'].sum()
294 
295  # Calculate integrated Z-counting luminosity
296  dfz_small['ZLumi'] *= dfz_small['LBLive']
297  zlumi = dfz_small['ZLumi'].sum()
298 
299  # Calculate uncertainty on Z-counting
300  dfz_small['ZLumiErr'] *= dfz_small['LBLive']
301  dfz_small['ZLumiErr'] *= dfz_small['ZLumiErr']
302  zerr = math.sqrt(dfz_small['ZLumiErr'].sum())
303 
304  # If plotting vs. date simply fill the arrays here
305  arr_date.append(timestamp)
306  arr_olumi.append(olumi)
307  arr_zlumi.append(zlumi)
308  arr_zerr.append(zerr)
309  run_num.append(run)
310 
311  # for ROOT plotting we need Python arrays
312  arr_date = array('d', arr_date)
313  # convert lists to numpy arrays
314  arr_olumi = np.array(arr_olumi)
315  arr_zlumi = np.array(arr_zlumi)
316  arr_zerr = np.array(arr_zerr)
317  total_zlumi = arr_zlumi.sum()/1000000
318  total_zlumi_string = "Official Data Quality, " + str(round(total_zlumi, 2)) + " fb^-1"
319 
320 #-----------Normalisation------------
321 
322  # Calculate and apply overall normalisation
323  if args.absolute:
324  normalisation = 1.0
325  else:
326  normalisation = np.sum(arr_zlumi) / np.sum(arr_olumi)
327  # do normalisation to period integral
328  arr_zlumi /= normalisation
329  arr_zerr /= normalisation
330 
331  # calculate ratio to ATLAS preferred lumi
332  arr_zlumi_ratio = arr_zlumi/arr_olumi
333  arr_zerr_ratio = arr_zerr/arr_olumi
334 
335 #-----------Normalisation------------
336 
337  tg = R.TGraphErrors(len(arr_date), arr_date, array('d',arr_zlumi_ratio), R.nullptr, array('d',arr_zerr_ratio))
338 
339  if args.absolute:
340  plot_title = "Ratio of absolute "+ zstring +" Luminosity to ATLAS Luminosity across " + norm_type
341  else:
342  plot_title = "Ratio of normalised "+ zstring +" Luminosity to ATLAS Luminosity across " + norm_type
343  tg.SetTitle(plot_title+";"+xtitle+";"+ytitle)
344 
345  # Depending if we're plotting over whole Run-3, change canvas size
346  if out_tag == "_run3":
347  c1 = R.TCanvas("c1", "c1", 2000, 1200)
348  c1.SetTopMargin(0.1)
349  else:
350  c1 = R.TCanvas("c1", "c1", 1000, 750)
351  c1.SetTopMargin(0.1)
352 
353  tg.Draw('ap')
354  tg.GetYaxis().SetRangeUser(ymin, ymax)
355 
356  # Plot 68% percentile band
357  stdev = np.percentile(abs(arr_zlumi_ratio - np.median(arr_zlumi_ratio)), 68)
358  print("68% band =", stdev)
359  tg.Fit('pol0', '0q')
360  mean = tg.GetFunction('pol0').GetParameter(0)
361  print("const of pol0 fit", mean)
362  print("median", np.median(arr_zlumi_ratio))
363  print("mean", np.mean(arr_zlumi_ratio))
364 
365  line1 = pt.make_bands(arr_date, stdev, mean)
366  line1.Draw("same 3")
367  tg.Draw('same ep')
368 
369  leg = R.TLegend(0.55, 0.20, 0.69, 0.45)
370  leg.SetBorderSize(0)
371  leg.SetTextSize(0.045)
372  leg.AddEntry(tg, leg_entry, "ep")
373  leg.AddEntry(line1, "68% band", "f")
374  leg.Draw()
375 
376  if args.absolute:
377  pt.drawAtlasLabel(xval, yval-0.47, "Internal")
378  pt.drawText(xval, yval-0.53, date_tag, set_size)
379  pt.drawText(xval, yval-0.59, zstring, set_size)
380  pt.drawText(xval, yval-0.65, "OflLumi-Run3-003", set_size)
381  else:
382  pt.drawAtlasLabel(xval, yval-0.47, "Internal")
383  pt.drawText(xval, yval-0.53, date_tag, set_size)
384  pt.drawText(xval, yval-0.59, zstring, set_size)
385  pt.drawText(xval, yval-0.65, "OflLumi-Run3-003", set_size)
386  pt.drawText(xval, yval-0.02, total_zlumi_string, set_size)
387 
388  pt.drawText(xval-0.12, 0.95, plot_title, set_size)
389 
390  tg.GetYaxis().SetRangeUser(ymin, ymax)
391  tg.GetXaxis().SetTimeDisplay(2)
392  tg.GetXaxis().SetLabelSize(0.04)
393  tg.GetYaxis().SetLabelSize(0.04)
394  tg.GetXaxis().SetNdivisions(9,R.kFALSE)
395  tg.GetXaxis().SetTimeFormat(time_format)
396  tg.GetXaxis().SetTimeOffset(0,"gmt")
397 
398  c1.Update()
399  c1.Modified()
400 
401  if args.absolute:
402  c1.SaveAs(outdir + channel + "_counting_data"+out_tag+"_abs.pdf")
403  outfile = R.TFile(outdir + channel + "_counting_data"+out_tag+"_abs.root", "RECREATE")
404  else:
405  c1.SaveAs(outdir + channel + "_counting_data"+out_tag+".pdf")
406  outfile = R.TFile(outdir + channel + "_counting_data"+out_tag+".root", "RECREATE")
407 
408  tg.Write()
409  line1.SetName("Line")
410  line1.Write()
411  outfile.Close()
412 
413 def local_fit(tg, start, end, year):
414  """
415  Fit over a sub-range of the data and print the mean and chi^2/NDF.
416  Useful to test the remaining trends after the global Run-3 normalisation.
417  """
418 
419  tg.Fit('pol0', 'Rq0','0', start, end)
420  mean = round(tg.GetFunction('pol0').GetParameter(0), 3)
421  chi2 = tg.GetFunction('pol0').GetChisquare()
422  ndf = tg.GetFunction('pol0').GetNDF()
423  print("|", year, "|", mean, "|", round(chi2/ndf, 3), "|")
424 
425 
426 if __name__ == "__main__":
427  pt.setAtlasStyle()
428  R.gROOT.SetBatch(R.kTRUE)
429  main()
conifer::pow
constexpr int pow(int x)
Definition: conifer.h:20
MuonGM::round
float round(const float toRound, const unsigned int decimals)
Definition: Mdt.cxx:27
convertTimingResiduals.sum
sum
Definition: convertTimingResiduals.py:55
plotting.yearwise_luminosity.zcounting_vs_atlas
def zcounting_vs_atlas(channel, years)
Definition: yearwise_luminosity.py:237
plotting.yearwise_luminosity.channel_comparison
def channel_comparison(years)
Definition: yearwise_luminosity.py:102
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename T::value_type > sorted(T begin, T end)
Helper function to create a sorted vector from an unsorted one.
array
Muon::print
std::string print(const MuPatSegment &)
Definition: MuonTrackSteering.cxx:28
plotting.yearwise_luminosity.local_fit
def local_fit(tg, start, end, year)
Definition: yearwise_luminosity.py:413
str
Definition: BTagTrackIpAccessor.cxx:11
plotting.yearwise_luminosity.main
def main()
Definition: yearwise_luminosity.py:96