Belle II Software  release-08-01-10
ecms.py
1 # disable doxygen check for this file
2 # @cond
3 
4 
11 
12 """
13 Validation of the Invariant Collision Energy calibration
14 """
15 
16 
17 from prompt import ValidationSettings
18 
19 import ROOT
20 import sys
21 import subprocess
22 import json
23 
24 import numpy as np
25 import pandas as pd
26 import matplotlib.pyplot as plt
27 from matplotlib import rcParams
28 
29 import re
30 import os
31 from glob import glob
32 
33 from datetime import datetime
34 
35 from collections import OrderedDict
36 from shutil import copyfile, rmtree
37 
38 
39 
40 settings = ValidationSettings(name='eCMS Calibrations',
41  description=__doc__,
42  download_files=['stdout'],
43  expert_config={})
44 
45 
46 def setPlotRange(df, tag):
47  """
48  Function which adjusts the y-axis range of the plot according to tag
49  """
50 
51  if tag == '4S':
52  df4S = df[df['Ecms'] > 10560]['Ecms']
53  if len(df4S) > 0:
54  yMin = df4S.min()
55  plt.ylim(bottom=yMin-3)
56 
57  dfHigh = df[df['Ecms'] < 10600]['Ecms']
58  if len(dfHigh) > 0:
59  yMax = dfHigh.max()
60  plt.ylim(top=yMax+3)
61 
62  elif tag == 'Off':
63  dfOff = df[df['Ecms'] < 10560]['Ecms']
64  if len(dfOff) > 0:
65  yMax = dfOff.max()
66  plt.ylim(top=yMax+3)
67 
68 
69 def toJST(times):
70  """
71  Converts time from UTC to the JST
72  """
73  return np.vectorize(lambda t: datetime.utcfromtimestamp((t + 9) * 3600))(times)
74 
75 
76 def plotSplitLines(dfC):
77  """
78  Plot vertical lines in places where run energy type changes
79  """
80  indx = np.where(np.diff(dfC['pull'] == 0))[0]
81  for i in indx:
82  tt1 = toJST(dfC['t2'].loc[i]).item()
83  tt2 = toJST(dfC['t1'].loc[i+1]).item()
84  tt = tt1 + (tt2 - tt1) / 2
85  plt.axvline(x=tt, color='g', linestyle='--')
86 
87 
88 def get_Ecms_values(path):
89  """
90  Load the values of the Ecms properties from the payloads into the list
91  """
92 
93  runDict = {}
94  with open(path + '/database.txt') as fDB:
95  for ll in fDB:
96  ll = ll.strip()
97  ll = ll.split()
98 
99  # deriving file name
100  fN = ll[0].replace('/', '_') + '_rev_' + ll[1] + '.root'
101 
102  r = ll[2].split(',')
103  runDict[fN] = ((int(r[0]), int(r[1])), (int(r[2]), int(r[3])))
104 
105  arr = []
106 
107  fList = glob(path + '/*.root')
108  for fName in fList:
109  bName = os.path.basename(fName)
110  runExp = runDict[bName]
111 
112  f = ROOT.TFile.Open(fName)
113  ecmsObj = f.Get("CollisionInvariantMass")
114  assert(ecmsObj.ClassName() == "Belle2::CollisionInvariantMass")
115 
116  eCMS = ecmsObj.getMass()
117  eCMSe = ecmsObj.getMassError()
118  eCMSs = ecmsObj.getMassSpread()
119 
120  arr.append((runExp, (eCMS, eCMSe, eCMSs)))
121 
122  f.Close()
123 
124  arr = sorted(arr, key=lambda x: x[0])
125 
126  return arr
127 
128 
129 class Plotter():
130  def __init__(self, location):
131  """
132  Data are loaded from text files to pandas
133  """
134 
135  # read B-only calibration
136  self.dfB = pd.read_csv(f'{location}/BonlyEcmsCalib.txt', delim_whitespace=True)
137 
138  # read combined calibration
139  self.dfC = pd.read_csv(f'{location}/finalEcmsCalib.txt', delim_whitespace=True)
140 
141  # read mumu calibration
142  self.dfM = pd.read_csv(f'{location}/mumuEcalib.txt', delim_whitespace=True)
143 
144  # add the state
145  dfM = self.dfM
146  dfC = self.dfC
147  state = -1
148  dfM['type'] = 1
149  dfC['type'] = 1
150  for i in range(len(dfM)):
151  if (dfM['id'][i]) == 0:
152  state *= -1
153 
154  dfM.at[i, 'type'] = state
155  dfC.at[i, 'type'] = state
156 
157  @staticmethod
158  def plotLine(df, var, color, label):
159  """
160  Plot single line with error band
161  """
162 
163  varUnc = var + 'Unc'
164  nan = np.full(len(df), np.nan)
165  avg = (df['t1']+df['t2']).to_numpy()/2
166  times = np.c_[df[['t1', 't2']].to_numpy(), avg].ravel()
167  eCMS = np.c_[df[[var, var]].to_numpy(), nan].ravel()
168  eCMSu = np.c_[df[[varUnc, varUnc]].to_numpy(), nan].ravel()
169 
170  timesg = np.c_[df['t1'].to_numpy(), avg, df['t2'].to_numpy()].ravel()
171  eCMSg = np.c_[df[var].to_numpy(), nan, df[var].to_numpy()].ravel()
172 
173  times = toJST(times)
174  timesg = toJST(timesg)
175 
176  plt.fill_between(times, eCMS-eCMSu, eCMS+eCMSu, alpha=0.2, color=color)
177  plt.plot(times, eCMS, linewidth=2, color=color, label=label)
178  plt.plot(timesg, eCMSg, linewidth=2, color=color, alpha=0.35)
179 
180  def plotEcmsComparison(self, limits=None, tag=''):
181  """
182  Plot Ecms obtained from combined, hadB and mumu method
183  """
184 
185  Plotter.plotLine(self.dfB, 'Ecms', 'green', label='B decay method')
186  Plotter.plotLine(self.dfC, 'Ecms', 'red', label='Combined method')
187 
188  d = np.nanmedian(self.dfC['Ecms']-self.dfM['Ecms'])
189 
190  dfMc = self.dfM.copy()
191  dfMc['Ecms'] += d
192  Plotter.plotLine(dfMc, 'Ecms', 'blue', label=f'mumu method (+{round(d,1)} MeV)')
193 
194  plotSplitLines(self.dfC)
195  setPlotRange(self.dfC, tag)
196 
197  plt.xlabel('time')
198  plt.ylabel('Ecms [MeV]')
199 
200  plt.legend()
201 
202  loc = 'plots/allData'
203  if limits is not None:
204  plt.xlim(datetime.strptime(limits[0], '%Y-%m-%d'), datetime.strptime(limits[1], '%Y-%m-%d'))
205  loc = 'plots/' + limits[0] + 'to' + limits[1]
206 
207  os.makedirs(loc, exist_ok=True)
208  plt.savefig(f'{loc}/EcmsComparison{tag}.png')
209  plt.clf()
210 
211  def plotSpreadComparison(self, limits=None):
212  """
213  Plot Ecms spread estimated from the combined method and B-decay method
214  """
215 
216  Plotter.plotLine(self.dfB, 'spread', 'green', label='B decay method')
217  Plotter.plotLine(self.dfC, 'spread', 'red', label='Combined method')
218 
219  plotSplitLines(self.dfC)
220 
221  plt.legend()
222 
223  plt.xlabel('time')
224  plt.ylabel('spread [MeV]')
225 
226  loc = 'plots/allData'
227  if limits is not None:
228  plt.xlim(datetime.strptime(limits[0], '%Y-%m-%d'), datetime.strptime(limits[1], '%Y-%m-%d'))
229  loc = 'plots/' + limits[0] + 'to' + limits[1]
230 
231  os.makedirs(loc, exist_ok=True)
232  plt.savefig(f'{loc}/SpreadComparison.png')
233  plt.clf()
234 
235  @staticmethod
236  def plotCurve(df, var, label, withBand=True, withCurve=True):
237  """
238  Plot curve with possible error band where large intervals are distinguished by color
239  """
240 
241  def plotBands(df, var, color, withBand=True, withCurve=True):
242  varUnc = var + 'Unc'
243  nan = np.full(len(df), np.nan)
244  avg = (df['t1']+df['t2']).to_numpy()/2
245  times = np.c_[df[['t1', 't2']].to_numpy(), avg].ravel()
246  eCMS = np.c_[df[[var, var]].to_numpy(), nan].ravel()
247  times = toJST(times)
248 
249  if withBand:
250  eCMSu = np.c_[df[[varUnc, varUnc]].to_numpy(), nan].ravel()
251  plt.fill_between(times, eCMS-eCMSu, eCMS+eCMSu, alpha=0.15, color=color)
252  if withCurve:
253  plt.plot(times, eCMS, linewidth=2, color=color)
254 
255  nan = np.full(len(df), np.nan)
256  avg = (df['t1']+df['t2']).to_numpy()/2
257  timesg = np.c_[df['t1'].to_numpy(), avg, df['t2'].to_numpy()].ravel()
258  eCMSg = np.c_[df[var].to_numpy(), nan, df[var].to_numpy()].ravel()
259  timesg = toJST(timesg)
260 
261  if withCurve:
262  plt.plot(timesg, eCMSg, linewidth=2, color='gray', alpha=0.35)
263 
264  plotBands(df[df['type'] == 1], var, 'red', withBand, withCurve)
265  plotBands(df[df['type'] == -1], var, 'blue', withBand, withCurve)
266 
267  def plotShift(self, limits=None):
268  """
269  Plot shift between Bhad and mumu method
270  """
271 
272  Plotter.plotCurve(self.dfC, 'shift', label='Combined method')
273 
274  plotSplitLines(self.dfC)
275 
276  plt.xlabel('time')
277  plt.ylabel('shift [MeV]')
278 
279  loc = 'plots/allData'
280  if limits is not None:
281  plt.xlim(datetime.strptime(limits[0], '%Y-%m-%d'), datetime.strptime(limits[1], '%Y-%m-%d'))
282  loc = 'plots/' + limits[0] + 'to' + limits[1]
283 
284  os.makedirs(loc, exist_ok=True)
285  plt.savefig(f'{loc}/Shift.png')
286 
287  plt.clf()
288 
289  def plotEcms(self, limits=None, tag=''):
290  """
291  Plot Ecms of the combined fit, 'tag' can be '4S' means that y-axis is zoomed to 4S mass region, 'Off' to off-resonance
292  """
293 
294  Plotter.plotCurve(self.dfC, 'Ecms', label='Combined method')
295 
296  plotSplitLines(self.dfC)
297 
298  plt.xlabel('time')
299  plt.ylabel('Ecms [MeV]')
300 
301  setPlotRange(self.dfC, tag)
302 
303  loc = 'plots/allData'
304  if limits is not None:
305  plt.xlim(datetime.strptime(limits[0], '%Y-%m-%d'), datetime.strptime(limits[1], '%Y-%m-%d'))
306  loc = 'plots/' + limits[0] + 'to' + limits[1]
307 
308  os.makedirs(loc, exist_ok=True)
309  plt.savefig(f'{loc}/Ecms{tag}.png')
310  plt.clf()
311 
312  def plotPulls(self, limits=None):
313  """
314  Plot pulls of the combined fit.
315  """
316 
317  dfC = self.dfC.copy()
318  dfC['ref'] = 0
319  dfC['refUnc'] = 1
320  Plotter.plotCurve(dfC, 'pull', label='Combined method', withBand=False)
321  Plotter.plotCurve(dfC, 'ref', label='Combined method', withCurve=False)
322 
323  plotSplitLines(self.dfC)
324 
325  plt.ylim(-1.5, 1.5)
326  plt.xlabel('time')
327  plt.ylabel('pull')
328 
329  loc = 'plots/allData'
330  if limits is not None:
331  plt.xlim(datetime.strptime(limits[0], '%Y-%m-%d'), datetime.strptime(limits[1], '%Y-%m-%d'))
332  loc = 'plots/' + limits[0] + 'to' + limits[1]
333 
334  os.makedirs(loc, exist_ok=True)
335  plt.savefig(f'{loc}/Pull.png')
336  plt.clf()
337 
338 
339 # Get the results from the combined calibration
340 def read_Combined_data(outputDir):
341  """
342  It reads the calibration table from the text file produced by the CAF calibration.
343  This text file includes the results from the final calibration.
344  (combined or mumu-only)
345  """
346 
347  arr = []
348  with open(outputDir + '/finalEcmsCalib.txt') as text_file:
349  for i, ll in enumerate(text_file):
350  if i == 0:
351  continue
352  ll = ll.strip().split()
353  arr.append(
354  ((float(
355  ll[0]), float(
356  ll[1])), (int(ll[2]), int(ll[3]), int(ll[4]), int(ll[5])), int(
357  ll[2+4]), (float(
358  ll[3+4]), float(
359  ll[4+4])), float(
360  ll[5+4]), (float(
361  ll[6+4]), float(
362  ll[7+4])), (float(
363  ll[8+4]), float(
364  ll[9+4]))))
365  return arr
366 
367 
368 # Get the results from the combined calibration
369 def read_Bonly_data(outputDir):
370  """
371  It reads the calibration table from the text file produced by the CAF calibration.
372  This text file includes the results from the B-only calibration.
373  """
374 
375  arr = []
376  with open(outputDir + '/BonlyEcmsCalib.txt') as text_file:
377  for i, ll in enumerate(text_file):
378  if i == 0:
379  continue
380  ll = ll.strip().split()
381  arr.append(
382  ((float(
383  ll[0]), float(
384  ll[1])), (int(ll[2]), int(ll[3]), int(ll[4]), int(ll[5])),
385  (float(ll[2+4]), float(ll[3+4])), (float(ll[4+4]), float(ll[5+4]))))
386  return arr
387 
388 
389 def read_mumu_data(outputDir):
390  """
391  It reads the calibration table from the text file produced by the CAF calibration.
392  This text file includes the results from the mumu-based calibration.
393  """
394 
395  arr = []
396  with open(outputDir + '/mumuEcalib.txt') as text_file:
397  for i, ll in enumerate(text_file):
398  if i == 0:
399  continue
400  ll = ll.strip().split()
401  arr.append(
402  ((float(
403  ll[2+0]), float(
404  ll[2+1])), (int(ll[2+2]), int(ll[2+3]), int(ll[2+4]), int(ll[2+5])),
405  (float(ll[2+6]), float(ll[2+7])), (int(ll[0]), int(ll[1]))))
406  return arr
407 
408 
409 # Create multi-page pdf file with the fit plots
410 def create_hadB_fit_plots(outputDir, pdflatex):
411  """
412  Create multi-page pdf file with the fit plots for hadronic B decays (with mumu constraint).
413  The file is created using pdflatex
414  """
415 
416  arr = read_Combined_data(outputDir)
417 
418  dName = 'plotsHadB'
419 
420  files = glob(outputDir+'/'+dName + '/*.pdf')
421  files = list(map(os.path.basename, files))
422 
423  items = OrderedDict()
424 
425  for f in files:
426  res = re.search('B[p0]_([0-9]*)_([0-9]*)\\.pdf', f)
427  t = int(res.group(1))
428  i = int(res.group(2))
429  if t not in items:
430  items[t] = 1
431  else:
432  items[t] = max(items[t], i + 1)
433 
434  times = sorted(items)
435 
436  header = """\\documentclass[aspectratio=169]{beamer}
437  \\usepackage{graphicx}
438 
439  \\begin{document}
440  """
441 
442  body = ""
443  for t in times:
444 
445  i0 = None
446  for i0Temp in range(len(arr)):
447  if int(round(arr[i0Temp][0][0], 0)) == t:
448  i0 = i0Temp
449  break
450  assert(i0 is not None)
451 
452  frac = 1. / (items[t] + 0.2)
453  if items[t] >= 6:
454  frac = 1. / (items[t] + 0.3)
455 
456  body += '\\begin{frame}[t]\n'
457  shift, shiftE = str(round(arr[i0][5][0], 1)), str(round(arr[i0][5][1], 1))
458  spread, spreadE = str(round(arr[i0][6][0], 1)), str(round(arr[i0][6][1], 1))
459 
460  body += '$E_\\mathrm{shift} = (' + shift + '\\pm' + shiftE + ')$~MeV \\hspace{2cm} \n'
461  body += '$E_\\mathrm{spread} = (' + spread + '\\pm' + spreadE + ')$~MeV \\\\ \\vspace{0.5cm}\n'
462  for iShort in range(items[t]):
463  i = i0 + iShort
464  tStart, tEnd = arr[i][0][0], arr[i][0][1]
465 
466  exp1, run1, exp2, run2 = map(str, arr[i][1])
467 
468  eCMS, eCMSe = str(round(arr[i][3][0], 1)), str(round(arr[i][3][1], 1))
469  pull = str(round(arr[i][4], 2))
470 
471  t1 = datetime.utcfromtimestamp((tStart + 9) * 3600).strftime('%y-%m-%d %H:%M')
472  t2 = datetime.utcfromtimestamp((tEnd + 9) * 3600).strftime('%y-%m-%d %H:%M')
473 
474  body += '\\begin{minipage}{' + str(frac) + '\\textwidth}\n'
475  body += '\\begin{center}\n'
476  body += '\\scriptsize ' + exp1 + ' '+run1 + ' \\\\\n'
477  body += '\\scriptsize ' + exp2 + ' '+run2 + ' \\\\\n'
478  body += '\\scriptsize ' + t1 + ' \\\\\n'
479  body += '\\scriptsize ' + t2 + ' \\\\\n'
480  body += '\\scriptsize ' + str(round(tEnd - tStart, 1)) + ' hours \\\\ \\vspace{0.3cm}\n'
481  body += '$(' + eCMS + '\\pm' + eCMSe + ')$~MeV \\\\\n'
482  body += '$\\mathrm{pull} = ' + pull + '$ \\\\\n'
483  body += '\\includegraphics[width=1.0\\textwidth]{' + outputDir + \
484  '/' + dName + '/B0_' + str(t) + '_' + str(iShort) + '.pdf}\\\\\n'
485  body += '\\includegraphics[width=1.0\\textwidth]{' + outputDir + \
486  '/' + dName + '/Bp_' + str(t) + '_' + str(iShort) + '.pdf}\n'
487  body += '\\end{center}\n'
488  body += '\\end{minipage}\n'
489 
490  body += '\\end{frame}\n\n'
491 
492  tail = '\n\\end{document}'
493 
494  whole = header + body + tail
495 
496  os.makedirs('tmp', exist_ok=True)
497 
498  with open("tmp/hadBfits.tex", "w") as text_file:
499  text_file.write(whole)
500 
501  subprocess.call(f'{pdflatex} tmp/hadBfits.tex', shell=True)
502 
503  os.makedirs('plots', exist_ok=True)
504  copyfile('hadBfits.pdf', 'plots/hadBfits.pdf')
505 
506  ext = ['aux', 'log', 'nav', 'out', 'pdf', 'snm', 'toc']
507  for e in ext:
508  if os.path.exists(f'hadBfits.{e}'):
509  os.remove(f'hadBfits.{e}')
510 
511  rmtree('tmp')
512 
513 
514 # Create multi-page pdf file with the fit plots
515 def create_hadBonly_fit_plots(outputDir, pdflatex):
516  """
517  Create multi-page pdf file with the fit plots for hadronic B decays (without using mumu constraint).
518  The file is created using pdflatex
519  """
520 
521  arr = read_Bonly_data(outputDir)
522 
523  dName = 'plotsHadBonly'
524 
525  files = glob(outputDir+'/'+dName + '/*.pdf')
526  files = list(map(os.path.basename, files))
527 
528  items = set()
529 
530  for f in files:
531  res = re.search('B[p0]Single_([0-9]*)\\.pdf', f)
532  t = int(res.group(1))
533  items.add(t) # [t] = 1
534 
535  items = sorted(items)
536 
537  header = """\\documentclass[aspectratio=169]{beamer}
538  \\usepackage{graphicx}
539 
540  \\begin{document}
541  """
542 
543  body = ""
544  for i, t in enumerate(items):
545 
546  body += '\\begin{frame}[t]\n'
547 
548  exp1, run1, exp2, run2 = map(str, arr[i][1])
549  eCMS, eCMSe = str(round(arr[i][2][0], 1)), str(round(arr[i][2][1], 1))
550  spread, spreadE = str(round(arr[i][3][0], 1)), str(round(arr[i][3][1], 1))
551 
552  body += '$E_\\mathrm{cms} = (' + eCMS + '\\pm' + eCMSe + ')$~MeV \\\\\n'
553  body += '$E_\\mathrm{spread} = (' + spread + '\\pm' + spreadE + ')$~MeV \\\\ \\vspace{0.5cm}\n'
554 
555  tStart, tEnd = arr[i][0][0], arr[i][0][1]
556 
557  t1 = datetime.utcfromtimestamp((tStart + 9) * 3600).strftime('%y-%m-%d %H:%M')
558  t2 = datetime.utcfromtimestamp((tEnd + 9) * 3600).strftime('%y-%m-%d %H:%M')
559 
560  body += '\\begin{minipage}{' + str(0.99) + '\\textwidth}\n'
561  body += '\\begin{center}\n'
562  body += '\\scriptsize ' + exp1 + ' ' + run1 + '\\hspace{1cm} ' + t1 + ' \\\\\n'
563  body += '\\scriptsize ' + exp2 + ' ' + run2 + '\\hspace{1cm} ' + t2 + ' \\\\\n'
564  body += '\\scriptsize ' + str(round(tEnd - tStart, 1)) + ' hours \\\\ \\vspace{0.3cm}\n'
565  body += '\\includegraphics[width=0.48\\textwidth]{'+outputDir + '/' + dName + '/B0Single_' + str(t) + '.pdf}\n'
566  body += '\\includegraphics[width=0.48\\textwidth]{'+outputDir + '/' + dName + '/BpSingle_' + str(t) + '.pdf}\n'
567  body += '\\end{center}\n'
568  body += '\\end{minipage}\n'
569 
570  body += '\\end{frame}\n\n'
571 
572  tail = '\n\\end{document}'
573 
574  whole = header + body + tail
575 
576  os.makedirs('tmp', exist_ok=True)
577 
578  with open("tmp/hadBonlyFits.tex", "w") as text_file:
579  text_file.write(whole)
580 
581  subprocess.call(f'{pdflatex} tmp/hadBonlyFits.tex', shell=True)
582 
583  os.makedirs('plots', exist_ok=True)
584  copyfile('hadBonlyFits.pdf', 'plots/hadBonlyFits.pdf')
585 
586  ext = ['aux', 'log', 'nav', 'out', 'pdf', 'snm', 'toc']
587  for e in ext:
588  if os.path.exists(f'hadBonlyFits.{e}'):
589  os.remove(f'hadBonlyFits.{e}')
590 
591  if os.path.exists("tmp/hadBonlyFits.tex"):
592  os.remove("tmp/hadBonlyFits.tex")
593  rmtree('tmp')
594 
595 
596 def create_mumu_fit_plots(outputDir, pdflatex):
597  """
598  Create multi-page pdf file with the fit plots for mumu method.
599  The file is created using pdflatex
600  """
601 
602  arr = read_mumu_data(outputDir)
603 
604  limits = []
605  for i, a in enumerate(arr):
606  if a[3][1] == a[3][0] - 1:
607  limits.append((i - a[3][1], a[3][0]))
608 
609  dName = 'plotsMuMu'
610 
611  files = glob(outputDir+'/'+dName + '/*.pdf')
612  files = list(map(os.path.basename, files))
613 
614  items = set()
615 
616  for f in files:
617  res = re.search('mumu_([0-9]*)\\.pdf', f)
618  t = int(res.group(1))
619  items.add(t)
620 
621  items = sorted(items)
622 
623  header = """\\documentclass[aspectratio=169]{beamer}
624  \\usepackage{graphicx}
625 
626  \\begin{document}
627  """
628 
629  body = ""
630  for k, n in limits:
631 
632  frac = None
633  if n >= 11:
634  frac = 0.159
635  elif n >= 9:
636  frac = 0.193
637  elif n >= 7:
638  frac = 0.24
639  elif n >= 5:
640  frac = 0.3
641  elif n >= 3:
642  frac = 0.33
643  elif n >= 2:
644  frac = 0.48
645  elif n >= 1:
646  frac = 0.75
647 
648  body += '\\begin{frame}[t]\n'
649 
650  for i in range(k, k+n):
651 
652  body += '\\begin{minipage}{' + str(frac) + '\\textwidth}\n'
653 
654  exp1, run1, exp2, run2 = map(str, arr[i][1])
655  eCMS, eCMSe = str(round(arr[i][2][0], 1)), str(round(arr[i][2][1], 1))
656 
657  tStart, tEnd = arr[i][0][0], arr[i][0][1]
658 
659  t1 = datetime.utcfromtimestamp((tStart + 9) * 3600).strftime('%y-%m-%d %H:%M')
660  t2 = datetime.utcfromtimestamp((tEnd + 9) * 3600).strftime('%y-%m-%d %H:%M')
661 
662  body += '\\begin{center}\n'
663  body += '\\tiny $E_\\mathrm{cms} = (' + eCMS + '\\pm' + eCMSe + ')$~MeV \\\\\n'
664  body += '\\tiny ' + exp1 + ' ' + run1 + '\\hspace{0.3cm} ' + t1 + ' \\\\\n'
665  body += '\\tiny ' + exp2 + ' ' + run2 + '\\hspace{0.3cm} ' + t2 + ' \\\\\n'
666  body += '\\tiny ' + str(round(tEnd - tStart, 1)) + ' hours \\vspace{0.3cm}\n'
667  body += '\\includegraphics[trim=0.3cm 0.0cm 1.3cm 0.7cm,clip=true,width=0.99\\textwidth]{' + \
668  outputDir + '/' + dName + '/mumu_' + str(items[i]) + '.pdf}\n'
669  body += '\\end{center}\n'
670  body += '\\end{minipage}\n'
671 
672  body += '\\end{frame}\n\n'
673 
674  tail = '\n\\end{document}'
675 
676  whole = header + body + tail
677 
678  os.makedirs('tmp', exist_ok=True)
679 
680  with open("tmp/mumuFits.tex", "w") as text_file:
681  text_file.write(whole)
682 
683  subprocess.call(f'{pdflatex} tmp/mumuFits.tex', shell=True)
684 
685  os.makedirs('plots', exist_ok=True)
686  copyfile('mumuFits.pdf', 'plots/mumuFits.pdf')
687 
688  ext = ['aux', 'log', 'nav', 'out', 'pdf', 'snm', 'toc']
689  for e in ext:
690  if os.path.exists(f'mumuFits.{e}'):
691  os.remove(f'mumuFits.{e}')
692 
693  if os.path.exists("tmp/mumuFits.tex"):
694  os.remove("tmp/mumuFits.tex")
695  rmtree('tmp')
696 
697 
698 def run_validation(job_path, input_data_path, requested_iov, expert_config):
699  """
700  Create validation plots related to the Ecms calibration
701  """
702 
703  # get location of pdflatex
704  result = subprocess.run('locate pdflatex | grep "/pdflatex$"', stdout=subprocess.PIPE, shell=True)
705  pdflatex = result.stdout.strip().decode('utf-8')
706 
707  # Expert config can contain the time ranges of the plots
708  if expert_config != '':
709  expert_config = json.loads(expert_config)
710 
711  allLimits = [None]
712  if expert_config is not None and 'plotsRanges' in expert_config:
713  allLimits += expert_config['plotsRanges']
714 
715  plt.figure(figsize=(18, 9))
716  rcParams['axes.formatter.useoffset'] = False
717 
718  location = f'{job_path}/eCMS/0/algorithm_output'
719 
720  plotter = Plotter(location)
721  # plot the results
722  for limits in allLimits:
723  plotter.plotEcmsComparison(limits, tag='4S')
724  plotter.plotEcmsComparison(limits, tag='Off')
725  plotter.plotEcmsComparison(limits)
726  plotter.plotSpreadComparison(limits)
727 
728  plotter.plotEcms(limits, tag='4S')
729  plotter.plotEcms(limits, tag='Off')
730  plotter.plotEcms(limits, tag='')
731 
732  plotter.plotShift(limits)
733  plotter.plotPulls(limits)
734 
735  # create pdf with plots of fits
736  create_hadB_fit_plots(location, pdflatex)
737  create_hadBonly_fit_plots(location, pdflatex)
738  create_mumu_fit_plots(location, pdflatex)
739 
740  # copy csv files to validation directory
741  copyfile(f'{location}/BonlyEcmsCalib.txt', 'plots/BonlyEcmsCalib.txt')
742  copyfile(f'{location}/finalEcmsCalib.txt', 'plots/finalEcmsCalib.txt')
743  copyfile(f'{location}/mumuEcalib.txt', 'plots/mumuEcalib.txt')
744 
745 
746 if __name__ == "__main__":
747  run_validation(*sys.argv[1:])
748 
749 # @endcond