Belle II Software  release-05-02-19
beamspot.py
1 # -*- coding: utf-8 -*-
2 
3 '''
4 Validation of the Beam Spot calibration
5 '''
6 
7 
8 import basf2
9 from prompt import ValidationSettings
10 import ROOT
11 import sys
12 import subprocess
13 import json
14 
15 import numpy as np
16 import scipy.linalg as la
17 import scipy.stats
18 import matplotlib.pyplot as plt
19 
20 import re
21 import os
22 from glob import glob
23 from math import sqrt, frexp, asin
24 from itertools import groupby
25 
26 from datetime import datetime, timedelta
27 from ROOT.Belle2 import Unit
28 
29 
30 settings = ValidationSettings(name='BeamSpot Calibrations',
31  description=__doc__,
32  download_files=['stdout'],
33  expert_config={})
34 
35 
36 # Get the eigen parameters of the matrix
37 def getEigenPars(covM):
38  sxx, syy, szz, sxy, sxz, syz = covM
39 
40  mat = np.array([[sxx, sxy, sxz],
41  [sxy, syy, syz],
42  [sxz, syz, szz]])
43 
44  result = la.eig(mat)
45  eigVals = result[0].real
46  eigVecs = result[1]
47 
48  # sort it by size
49  idx = eigVals.argsort()[::-1]
50  eigVals = eigVals[idx]
51  eigVecs = eigVecs[:, idx]
52 
53  assert(eigVals[0] > 1e-4)
54  assert(eigVals[1] > 1e-4)
55  assert(eigVals[2] > 1e-4)
56 
57  sxEig = sqrt((eigVals[1]))
58  syEig = sqrt((eigVals[2]))
59  szEig = sqrt((eigVals[0]))
60 
61  # get the signs right
62  if eigVecs[2][0] < 0:
63  eigVecs[:, 0] *= -1
64 
65  if eigVecs[0][1] < 0:
66  eigVecs[:, 1] *= -1
67 
68  if eigVecs[1][2] < 0:
69  eigVecs[:, 2] *= -1
70 
71  # calculate the angles in mrad
72  angleXZ = Unit.rad / Unit.mrad * asin(eigVecs[0][0] / sqrt(1 - eigVecs[1][0]**2))
73  angleYZ = Unit.rad / Unit.mrad * asin(eigVecs[1][0])
74  angleXY = Unit.rad / Unit.mrad * asin(eigVecs[1][1] / sqrt(1 - eigVecs[1][0]**2))
75 
76  return [sxEig, syEig, szEig, angleXZ, angleYZ, angleXY]
77 
78 
79 # Load the values of the Beam Spot parameters into the list
80 def getBSvalues(path):
81  runDict = {}
82  with open(path + '/database.txt') as fDB:
83  for ll in fDB:
84  ll = ll.strip()
85  ll = ll.split(' ')
86 
87  # deriving file name
88  fN = ll[0].replace('/', '_') + '_rev_' + ll[1] + '.root'
89 
90  r = ll[2].split(',')
91  runDict[fN] = (int(r[0]), int(r[1]))
92 
93  arr = []
94 
95  fList = glob(path + '/*.root')
96  for fName in fList:
97  bName = os.path.basename(fName)
98  runExp = runDict[bName]
99 
100  f = ROOT.TFile.Open(fName)
101  bsAll = f.Get("BeamSpot")
102  assert(bsAll.ClassName() == "Belle2::EventDependency")
103 
104  evNums = bsAll.getEventNumbers()
105 
106  for i in range(len(evNums) + 1):
107  bs = bsAll.getObjectByIndex(i)
108  ipR = bs.getIPPosition()
109  ip = [ipR(i) * Unit.cm / Unit.um for i in range(3)] # from cm to um
110  ipeR = bs.getIPPositionCovMatrix()
111  ipe = [sqrt(ipeR(i, i)) * Unit.cm / Unit.um for i in range(3)] # from cm to um
112  covR = bs.getSizeCovMatrix()
113  covM = (covR(0, 0), covR(1, 1), covR(2, 2), covR(0, 1), covR(0, 2), covR(1, 2))
114  covM = [x * (Unit.cm / Unit.um)**2 for x in covM] # from cm2 to um2
115 
116  tStart = ipeR(0, 1) * 1e20
117  tEnd = ipeR(0, 2) * 1e20
118  tStart = int(frexp(ipeR(0, 1))[0] * 2**53) % 2**32 / 3600.
119  tEnd = int(frexp(ipeR(0, 2))[0] * 2**53) % 2**32 / 3600.
120 
121  eigM = getEigenPars(covM)
122  arr.append((runExp, tStart, tEnd, ip, ipe, covM, eigM))
123  f.Close()
124 
125  arr = sorted(arr, key=lambda x: x[1])
126 
127  return arr
128 
129 # Print the Beam Spot parameters to the text file
130 
131 
132 def printToFile(arr):
133 
134  with open('bsData.csv', 'w') as outFile:
135  outFile.write('exp run tStart tEnd xIP yIP zIP xeIP yeIP zeIP sXX sYY sZZ sXY sXZ '
136  + 'sYZ xSizeEig ySizeEig zSizeEig xzAngle yzAngle xyAngle\n')
137 
138  for e in arr:
139  outFile.write(
140  # exp run
141  str(e[0][0]) + ' ' +
142  str(e[0][1]) + ' ' +
143  # tStart tEnd
144  str(e[1]) + ' ' +
145  str(e[2]) + ' ' +
146  # xIP yIP zIP
147  str(e[3][0]) + ' ' +
148  str(e[3][1]) + ' ' +
149  str(e[3][2]) + ' ' +
150  # xeIP yeIP zeIP
151  str(e[4][0]) + ' ' +
152  str(e[4][1]) + ' ' +
153  str(e[4][2]) + ' ' +
154  # sXX sYY sZZ sXY sXZ sYZ
155  str(e[5][0]) + ' ' +
156  str(e[5][1]) + ' ' +
157  str(e[5][2]) + ' ' +
158  str(e[5][3]) + ' ' +
159  str(e[5][4]) + ' ' +
160  str(e[5][5]) + ' ' +
161  # xSizeEig ySizeEig zSizeEig xzAngle yzAngle xyAngle
162  str(e[6][0]) + ' ' +
163  str(e[6][1]) + ' ' +
164  str(e[6][2]) + ' ' +
165  str(e[6][3]) + ' ' +
166  str(e[6][4]) + ' ' +
167  str(e[6][5]) + '\n')
168  return arr
169 
170 
171 # Create a plot with the specified variable
172 def plotVar(arr, limits, vName, getterV, getterE=None):
173 
174  tVals = []
175  Vals = []
176  Errs = []
177 
178  tGapVals = []
179  GapVals = []
180 
181  for i, el in enumerate(arr):
182  s, e = el[1], el[2]
183  s = datetime.utcfromtimestamp((s + 9) * 3600) # Convert to the JST (+9 hours)
184  e = datetime.utcfromtimestamp((e + 9) * 3600)
185  tVals.append(s)
186  tVals.append(e)
187  Vals.append(getterV(el))
188  Vals.append(getterV(el))
189 
190  if getterE is not None:
191  Errs.append(getterE(el))
192  Errs.append(getterE(el))
193 
194  # Add breaks for longer gap if not the last interval
195  if i >= len(arr) - 1:
196  continue
197 
198  dt = (arr[i + 1][1] - arr[i][2]) * 3600
199 
200  # only consider gaps longer than 10 mins
201  if dt < 10 * 60:
202  continue
203 
204  # start-time of gap, end-time of gap
205  gS = datetime.utcfromtimestamp((arr[i][2] + 9) * 3600) # Convert to the JST (+9 hours)
206  gE = datetime.utcfromtimestamp((arr[i + 1][1] + 9) * 3600)
207 
208  tVals.append(gS + (gE - gS) / 2)
209  Vals.append(np.nan)
210  if getterE is not None:
211  Errs.append(np.nan)
212 
213  # store curve connecting gaps
214  tGapVals.append(gS - timedelta(seconds=1))
215  tGapVals.append(gS)
216  tGapVals.append(gE)
217  tGapVals.append(gE + timedelta(seconds=1))
218 
219  GapVals.append(np.nan)
220  GapVals.append(getterV(arr[i]))
221  GapVals.append(getterV(arr[i + 1]))
222  GapVals.append(np.nan)
223 
224  plt.plot(tVals, Vals, linewidth=2, color='C0')
225  plt.plot(tGapVals, GapVals, linewidth=2, color='C0', alpha=0.35)
226 
227  Vals = np.array(Vals)
228  Errs = np.array(Errs)
229 
230  if getterE is not None:
231  plt.fill_between(tVals, Vals - Errs, Vals + Errs, alpha=0.2)
232 
233  plt.xlabel('time')
234  if 'Angle' in vName:
235  plt.ylabel(vName + ' [mrad]')
236  else:
237  plt.ylabel(vName + ' [um]')
238 
239  loc = 'plots/allData'
240  if limits is not None:
241  plt.xlim(datetime.strptime(limits[0], '%Y-%m-%d'), datetime.strptime(limits[1], '%Y-%m-%d'))
242  loc = 'plots/' + limits[0] + 'to' + limits[1]
243 
244  # if not os.path.isdir(loc):
245  os.makedirs(loc, exist_ok=True)
246 
247  plt.savefig(loc + '/' + vName + '.png')
248  plt.clf()
249 
250 
251 def plotPullSpectrum(arr, vName, getterV, getterE):
252 
253  valsOrg = np.array([getterV(v) for v in arr])
254  errsOrg = np.array([getterE(v) for v in arr])
255 
256  # remove repeating values
257  indx = np.array([list(g)[0][0] for k, g in groupby(zip(np.arange(len(valsOrg)), valsOrg), lambda x: x[1])])
258  vals = valsOrg[indx]
259  errs = errsOrg[indx]
260 
261  diffs = (vals[1:] - vals[:-1]) / np.hypot(errs[1:], errs[:-1])
262 
263  # Get 1sigma quantiles of the normal distribution
264  n1, n2 = scipy.stats.norm.cdf([-1, 1])
265 
266  q1 = np.quantile(diffs, n1)
267  q2 = np.quantile(diffs, n2)
268 
269  plt.hist(diffs, bins=np.linspace(-20, 20, 80), label=f'q1={round(q1,1)}, q2={round(q2,1)}')
270  plt.xlabel(vName)
271  plt.legend()
272 
273  loc = 'plots/allData'
274  plt.savefig(loc + '/' + vName + '.png')
275  plt.clf()
276 
277 
278 def run_validation(job_path, input_data_path, requested_iov, expert_config):
279  '''
280  Run the validation.
281  '''
282 
283  # Expert config can contain the time ranges of the plots
284  if expert_config != '':
285  expert_config = json.loads(expert_config)
286 
287  allLimits = [None]
288  if expert_config is not None and 'plotsRanges' in expert_config:
289  allLimits += expert_config['plotsRanges']
290 
291  # Path to the database.txt file and to the payloads.
292  dbFile = glob(f'{job_path}/**/database.txt', recursive=True)
293  dbFile = [db for db in dbFile if 'algorithm_output' not in db]
294  assert(len(dbFile) == 1)
295  dbFile = dbFile[0]
296  inputDir = dbFile[:dbFile.rfind('/')]
297 
298  arr = getBSvalues(inputDir)
299 
300  # print the results to the CSV file
301  printToFile(arr)
302 
303  plt.figure(figsize=(18, 9))
304 
305  # plot the results
306  for limits in allLimits:
307  plotVar(arr, limits, 'xIP', lambda e: e[3][0], lambda e: e[4][0])
308  plotVar(arr, limits, 'yIP', lambda e: e[3][1], lambda e: e[4][1])
309  plotVar(arr, limits, 'zIP', lambda e: e[3][2], lambda e: e[4][2])
310 
311  plotVar(arr, limits, 'xSpotSize', lambda e: sqrt(e[5][0]))
312  plotVar(arr, limits, 'ySpotSize', lambda e: sqrt(e[5][1]))
313  plotVar(arr, limits, 'zSpotSize', lambda e: sqrt(e[5][2]))
314 
315  plotVar(arr, limits, 'xSpotSizeEig', lambda e: e[6][0])
316  plotVar(arr, limits, 'ySpotSizeEig', lambda e: e[6][1])
317  plotVar(arr, limits, 'zSpotSizeEig', lambda e: e[6][2])
318 
319  plotVar(arr, limits, 'xzSpotAngle', lambda e: e[6][3])
320  plotVar(arr, limits, 'yzSpotAngle', lambda e: e[6][4])
321  plotVar(arr, limits, 'xySpotAngle', lambda e: e[6][5])
322 
323  plotPullSpectrum(arr, 'xIPpulls', lambda e: e[3][0], lambda e: e[4][0])
324  plotPullSpectrum(arr, 'yIPpulls', lambda e: e[3][1], lambda e: e[4][1])
325  plotPullSpectrum(arr, 'zIPpulls', lambda e: e[3][2], lambda e: e[4][2])
326 
327 
328 if __name__ == "__main__":
329  run_validation(*sys.argv[1:])