ATLAS Offline Software
EvoMonitoring.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2 #
3 # This script is used to make plots comparing the latest IOV in the DB with the new calibration.
4 # In order to run it standalone you need to setup Athena and call setupRunning(path_newCalib, path_oldCalib)
5 # oldCalib file has the structure of the Recobery.py (after running the calibration)
6 # newCalib file has the structure of the MakeReferenceFile (It is on IOV from the central DB)
7 #
8 
9 from PixelCalibAlgs.Recovery import ReadDbFile
10 from PathResolver import PathResolver
11 import array as ar
12 import numpy as np
13 from matplotlib.backends.backend_agg import FigureCanvasAgg
14 from matplotlib.figure import Figure
15 
16 
17 def arrayCharge(parameters, layer):
18 
19  if layer == "Blayer":
20  # Range [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42] (includes the ToT tuning point: 18)
21  m_array = [charge(parameters, 3*(1+i)) for i in range(14)]
22  else:
23  # Range [5, 10, 15, 20, 25, 30, 35, 40, 45, 50] (includes the ToT tuning point: 30)
24  m_array = [charge(parameters, 5*(1+i)) for i in range(8)]
25 
26  return ar.array('d',m_array)
27 
28 def charge(parameters, tot):
29  num = tot*parameters[2] - parameters[0]*parameters[1]
30  den = parameters[0] - tot
31  return (num/den) if den != 0 else 0
32 
33 def percent( a,b):
34  return a/(a+b)*100 if (a+b) != 0 else 999.99
35 
36 
37 def failsCheckTuning(parameters, layer):
38  tot = 30
39  error = 5 # percentage
40  expected_charge = 20000 # electrons
41 
42  if layer == "Blayer":
43  tot = 18
44 
45  if layer == "IBL":
46  tot = 10
47  expected_charge = 16000 # electrons
48 
49  if abs(parameters[9]-expected_charge)/expected_charge*100 > 5:
50  return True, [parameters[9], abs(parameters[9]-expected_charge)/expected_charge*100, expected_charge]
51  return False, [0,0,0]
52 
53  low = expected_charge*(1-error/100)
54  high = expected_charge*(1+error/100)
55  realQ = charge(parameters, tot)
56 
57  if realQ < low or realQ > high:
58  return True, [charge(parameters, tot), abs(charge(parameters, tot)-expected_charge)/expected_charge*100,expected_charge]
59  return False, [0,0,0]
60 
61 
62 def EvoMon(old_calib, new_calib, mapping, old_iov, new_iov):
63 
64  save = 1
65  log_info = {}
66  plot_range = np.array([0,75000])
67 
68  slopes = []
69  information = {"Total_mods": 0,
70  "Total_FE" : 0,
71  "IBL" :{"bad":0, "ok":0},
72  "Blayer":{"bad":0, "ok":0},
73  "L1" :{"bad":0, "ok":0},
74  "L2" :{"bad":0, "ok":0},
75  "Disk" :{"bad":0, "ok":0}
76  }
77 
78  # There are 2048 modules in the structure
79  for mod in range(2048):
80 
81  mod_str = mapping[str(mod)]
82  mod_layer = ""
83  print( "%-18s - %4i" % (mod_str, mod), end='\r')
84 
85  # Skipping modules that have not been calibrated
86  if str(mod) not in new_calib:
87  continue
88 
89  if mod_str.startswith("L0"):
90  mod_layer = "Blayer"
91  elif mod_str.startswith("L1"):
92  mod_layer = "L1"
93  elif mod_str.startswith("L2"):
94  mod_layer = "L2"
95  elif mod_str.startswith("D"):
96  mod_layer = "Disk"
97  else:
98  mod_layer = "IBL"
99  if mod_str.startswith("LI_S15"):
100  continue
101 
102  information["Total_mods"] += 1
103  fig = Figure(figsize=(13,10))
104  axs = fig.add_subplot(1,1,1)
105  status = "_OK"
106 
107 
108 
109  for fe in range(len(new_calib[str(mod)])):
110  information["Total_FE"] += 1
111  newQ = []
112  oldQ = []
113  boolTOT = False
114  realQ = []
115 
116  if mod_layer != "IBL":
117  newCal_normal_pix = new_calib[str(mod)][fe][12:15]
118  oldCal_normal_pix = old_calib[str(mod)][fe][12:15]
119  boolTOT, realQ = failsCheckTuning(newCal_normal_pix, mod_layer)
120 
121  # We just fet the first point since we loose linearity afterwards
122  newQ = arrayCharge(newCal_normal_pix,mod_layer)
123  oldQ = arrayCharge(oldCal_normal_pix,mod_layer)
124  else:
125  # For IBL we dont need to convert TOT into charge, DB already in charge
126  newQ = new_calib[str(mod)][fe][4:20]
127  oldQ = old_calib[str(mod)][fe][4:20]
128  plot_range = np.array([0,35000])
129  boolTOT, realQ = failsCheckTuning(newQ, mod_layer)
130 
131  m,b = np.polyfit(newQ ,oldQ,1)
132  slopes.append(m)
133  boolFit = (abs((1-m)/m)*100) > 5
134 
135  if boolFit or boolTOT:
136  key = "%-18s - %i" % (mod_str, mod)
137  if boolFit:
138  if key not in log_info:
139  log_info[key] = "\tFE%02i ---> slope: %5.2f - dev: %5.1f%%\n" % (fe,m, abs((1-m)/m)*100)
140  else:
141  log_info[key] += "\tFE%02i ---> slope: %5.2f - dev: %5.1f%%\n" % (fe,m, abs((1-m)/m)*100)
142  if boolTOT:
143  if key not in log_info:
144  log_info[key] = "\tFE%02i ---> Charge= %5ie (dev %6.2f%%) out of error bars. Expected: %5ie\n" % (fe, realQ[0], realQ[1], realQ[2])
145  else:
146  log_info[key] += "\tFE%02i ---> Charge= %5ie (dev %6.2f%%) out of error bars. Expected: %5ie\n" % (fe, realQ[0], realQ[1], realQ[2])
147 
148  information[mod_layer]["bad"] += 1
149  status = "_BAD"
150  if boolTOT:
151  # Fails charge tuning
152  status += "_Q"
153  if boolFit:
154  # Fails the slope at y = x (old vs. new calib)
155  status += "_Slope"
156  else:
157  information[mod_layer]["ok"] += 1
158 
159 
160  if save:
161  lstyle = "dotted" if fe < 8 else "solid"
162  axs.plot(newQ ,oldQ, marker='o', linestyle=lstyle, label = ("FE%02d" % (fe)))
163  axs.set_xlim(plot_range)
164  axs.set_ylim(plot_range)
165 
166  if save:
167  fig.suptitle("Hash ID %d - %s" % (mod, mod_str))
168  axs.legend(loc='upper left', ncol=4)
169  axs.set_xlabel(new_iov+" - Charge[e]")
170  axs.set_ylabel(old_iov+" - Charge[e]")
171  axs.plot(plot_range,plot_range,"k:",lw=1)
172 
173  if mod_layer == 'IBL':
174  axs.plot([16000,16000],plot_range,"k:",lw=1)
175  axs.text(17000,1000,"TOT@10 = 16 ke")
176  elif mod_layer == 'Blayer':
177  axs.plot([20000,20000],plot_range,"k:",lw=1)
178  axs.text(21000,1000,"TOT@18 = 20 ke")
179  else:
180  axs.plot([20000,20000],plot_range,"k:",lw=1)
181  axs.text(21000,1000,"TOT@30 = 20 ke")
182 
183  storage = "plots/" + mod_layer + "/"
184 
185  canvas = FigureCanvasAgg(fig)
186  canvas.print_figure(storage+"Id"+str(mod)+ "_" +mod_str+status+".png", dpi=150)
187 
188  if(save):
189  fig = Figure(figsize=(13,10))
190  fig.suptitle("All modules")
191  axs = fig.add_subplot(1,1,1)
192  axs.hist(np.clip(slopes, -1, 1.49), bins=100)
193  axs.set_xlabel("Fit slope")
194  axs.set_ylabel("Counts")
195  FigureCanvasAgg(fig).print_figure("plots/slopes.png", dpi=150)
196 
197 
198  print("-"*20+" SUMMARY "+"-"*20 )
199  print("%-20s: %5i" % ("Total mods in det.", information["Total_mods"]))
200  print("%-20s: %5i" % ("Total FE in det." , information["Total_FE"] ))
201  print("%-20s: %5i" % ("Total FE IBL" , information["IBL"]["ok"] +information["IBL"]["bad"] ))
202  print("%-20s: %5i" % ("Total FE Blayer" , information["Blayer"]["ok"]+information["Blayer"]["bad"]))
203  print("%-20s: %5i" % ("Total FE L1" , information["L1"]["ok"] +information["L1"]["bad"] ))
204  print("%-20s: %5i" % ("Total FE L2" , information["L2"]["ok"] +information["L2"]["bad"] ))
205  print("%-20s: %5i\n" % ("Total FE Disk" , information["Disk"]["ok"] +information["Disk"]["bad"] ))
206 
207 
208  print('FrontEnds deviating more than 5% of TOT vs charge gradient between new and previous calibration:')
209  print("%-11s: %-4i (%6.2f%%)" % ("IBL FEs" , information["IBL"]["bad"] , percent(information["IBL"]["bad"] ,information["IBL"]["ok"]) ))
210  print("%-11s: %-4i (%6.2f%%)" % ("Blayer FEs", information["Blayer"]["bad"], percent(information["Blayer"]["bad"],information["Blayer"]["ok"])))
211  print("%-11s: %-4i (%6.2f%%)" % ("L1 FEs" , information["L1"]["bad"] , percent(information["L1"]["bad"] ,information["L1"]["ok"]) ))
212  print("%-11s: %-4i (%6.2f%%)" % ("L2 FEs" , information["L2"]["bad"] , percent(information["L2"]["bad"] ,information["L2"]["ok"]) ))
213  print("%-11s: %-4i (%6.2f%%)\n" % ("Disk FEs" , information["Disk"]["bad"] , percent(information["Disk"]["bad"] ,information["Disk"]["ok"]) ))
214 
215 
216  print("+"*20+" List of bad FE "+"+"*20 )
217  print("Expected TOT vs. charge for the different layers:")
218  print("%-10s: TOT@%2i = %2ike" % ("IBL" , 10, 16))
219  print("%-10s: TOT@%2i = %2ike" % ("Blayer" , 18, 20))
220  print("%-10s: TOT@%2i = %2ike\n" % ("L1/L2/Disk", 30, 20))
221  for key, val in log_info.items():
222  print(key)
223  print(val)
224 
225 
226 def ReadCSV():
227  mydict = {}
228  with open(PathResolver.FindCalibFile("PixelCalibAlgs/mapping.csv")) as fp:
229  lines = fp.readlines()
230  for line in lines:
231  arrayline = line.rstrip("\n").split(', ')
232  mydict[arrayline[1]] = arrayline[0]
233  return mydict
234 
235 def ReadCalibOutput(file):
236  mydict = {}
237  with open(file) as fp:
238  lines = fp.readlines()
239  for line in lines:
240  arrayline = line.rstrip("\n").split(' ')
241 
242  if arrayline[0] not in mydict:
243  mydict[arrayline[0]] = []
244 
245  # Removing empty values. This is usually used for IBL calib file
246  while("" in arrayline):
247  arrayline.remove("")
248 
249  mydict[arrayline[0]].append([ int(arrayline[i]) if i<13 else float(arrayline[i]) for i in range(1,len(arrayline)) ])
250 
251  return mydict, "[latest,0]"
252 
253 def setupRunEvo(path_newCalib, path_oldCalib):
254  new_iov = "Latest IOV"
255  old_iov = "Older IOV"
256 
257  print("Files chosen for the comparison:")
258 
259  print("New calibration: '%s'" % path_newCalib)
260  new_calib, new_iov = ReadCalibOutput(path_newCalib)
261 
262  print("Old calibration: '%s'" % path_oldCalib)
263  old_calib, old_iov = ReadDbFile(path_oldCalib)
264 
265  mapping = ReadCSV()
266 
267  import os
268  os.makedirs("plots/Blayer", exist_ok=True)
269  os.makedirs("plots/L1" , exist_ok=True)
270  os.makedirs("plots/L2" , exist_ok=True)
271  os.makedirs("plots/Disk" , exist_ok=True)
272  os.makedirs("plots/IBL" , exist_ok=True)
273 
274  EvoMon(old_calib, new_calib, mapping, old_iov, new_iov )
275 
276 
277 
278 if __name__ == "__main__":
279 
280  import argparse
281  parser = argparse.ArgumentParser(prog='python -m PixelCalibAlgs.EvoMonitoring',
282  description="""Compares two IOV and plots the results.\n\n
283  Example: python -m PixelCalibAlgs.EvoMonitoring --new "path/to/file" --old "path/to/file" """)
284 
285  parser.add_argument('--new', default="FINAL_calibration_candidate.txt", help="New calibration file (output format from the Recovery.py)")
286  parser.add_argument('--old', default="PixelChargeCalibration-DATA-RUN2-UPD4-27.log", help="Old DB IOV calibration")
287 
288  args = parser.parse_args()
289  setupRunEvo(args.new, args.old)
290  exit(0)
EvoMonitoring.charge
def charge(parameters, tot)
Definition: EvoMonitoring.py:28
EvoMonitoring.arrayCharge
def arrayCharge(parameters, layer)
Definition: EvoMonitoring.py:17
PathResolver::FindCalibFile
static std::string FindCalibFile(const std::string &logical_file_name)
Definition: PathResolver.h:108
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
EvoMonitoring.percent
def percent(a, b)
Definition: EvoMonitoring.py:33
EvoMonitoring.EvoMon
def EvoMon(old_calib, new_calib, mapping, old_iov, new_iov)
Definition: EvoMonitoring.py:62
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
while
while((inf=(TStreamerInfo *) nextinfo()) !=0)
Definition: liststreamerinfos.cxx:13
EvoMonitoring.failsCheckTuning
def failsCheckTuning(parameters, layer)
Definition: EvoMonitoring.py:37
EvoMonitoring.setupRunEvo
def setupRunEvo(path_newCalib, path_oldCalib)
Definition: EvoMonitoring.py:253
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:195
Recovery.ReadDbFile
def ReadDbFile(name)
Definition: Recovery.py:7
calibdata.exit
exit
Definition: calibdata.py:236
EvoMonitoring.ReadCalibOutput
def ReadCalibOutput(file)
Definition: EvoMonitoring.py:235
Trk::open
@ open
Definition: BinningType.h:40
Muon::print
std::string print(const MuPatSegment &)
Definition: MuonTrackSteering.cxx:28
EvoMonitoring.ReadCSV
def ReadCSV()
Definition: EvoMonitoring.py:226
if
if(febId1==febId2)
Definition: LArRodBlockPhysicsV0.cxx:569
str
Definition: BTagTrackIpAccessor.cxx:11
readCCLHist.float
float
Definition: readCCLHist.py:83
Trk::split
@ split
Definition: LayerMaterialProperties.h:38