Belle II Software  release-05-02-19
boostvector.py
1 # -*- coding: utf-8 -*-
2 
3 '''
4 Validation of the Boost Vector 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, hypot, atan2
24 
25 from datetime import datetime, timedelta
26 from ROOT.Belle2 import Unit
27 
28 
29 settings = ValidationSettings(name='BoostVector Calibrations',
30  description=__doc__,
31  download_files=['stdout'],
32  expert_config={})
33 
34 
35 # Get the eigen parameters of the matrix
36 def getBVvalsErrs(bv, bve):
37 
38  bv = np.array(bv)
39  bve = np.array(bve)
40 
41  mag = np.linalg.norm(bv)
42  xzAngle = atan2(bv[0], bv[2])
43  yzAngle = atan2(bv[1], bv[2])
44 
45  magUnc = np.linalg.norm(bv * bve) / mag
46  xzAngleUnc = 1. / (1 + (bv[0] / bv[2])**2) * hypot(bve[0] / bv[2], -bve[1] * bv[0] / bv[2]**2)
47  yzAngleUnc = 1. / (1 + (bv[1] / bv[2])**2) * hypot(bve[1] / bv[2], -bve[1] * bv[1] / bv[2]**2)
48 
49  tomrad = Unit.rad / Unit.mrad
50  return [1e3 * mag, tomrad * xzAngle, tomrad * yzAngle], [1e3 * magUnc, tomrad * xzAngleUnc, tomrad * yzAngleUnc]
51 
52 
53 # Load the values of the Boost Vector parameters into the list
54 def getBVvalues(path):
55  runDict = {}
56  with open(path + '/database.txt') as fDB:
57  for ll in fDB:
58  ll = ll.strip()
59  ll = ll.split(' ')
60 
61  # deriving file name
62  fN = ll[0].replace('/', '_') + '_rev_' + ll[1] + '.root'
63 
64  r = ll[2].split(',')
65  runDict[fN] = (int(r[0]), int(r[1]))
66 
67  arr = []
68 
69  fList = glob(path + '/*.root')
70  for fName in fList:
71  bName = os.path.basename(fName)
72  runExp = runDict[bName]
73 
74  f = ROOT.TFile.Open(fName)
75  bvAll = f.Get("CollisionBoostVector")
76  assert(bvAll.ClassName() == "Belle2::EventDependency")
77 
78  evNums = bvAll.getEventNumbers()
79 
80  for i in range(len(evNums) + 1):
81  bvObj = bvAll.getObjectByIndex(i)
82 
83  bvV = bvObj.getBoost()
84  bv = [bvV(i) for i in range(3)]
85  bveV = bvObj.getBoostCovariance()
86  bve = [sqrt(bveV(i, i)) for i in range(3)]
87 
88  tStart = int(frexp(bveV(0, 1))[0] * 2**53) % 2**32 / 3600.
89  tEnd = int(frexp(bveV(0, 2))[0] * 2**53) % 2**32 / 3600.
90 
91  bvV, bvE = getBVvalsErrs(bv, bve)
92 
93  arr.append((runExp, tStart, tEnd, bvV, bvE))
94  f.Close()
95 
96  arr = sorted(arr, key=lambda x: x[1])
97 
98  return arr
99 
100 # Print the Boost Vector parameters to the text file
101 
102 
103 def printToFile(arr):
104 
105  with open('bvData.csv', 'w') as outFile:
106  outFile.write('exp run tStart tEnd mag xzAngle yzAngle magUnc xzAngleUnc yzAngleUnc \n')
107 
108  for e in arr:
109  outFile.write(
110  # exp run
111  str(e[0][0]) + ' ' +
112  str(e[0][1]) + ' ' +
113  # tStart tEnd
114  str(e[1]) + ' ' +
115  str(e[2]) + ' ' +
116  # mag xzAngle yzAngle
117  str(e[3][0]) + ' ' +
118  str(e[3][1]) + ' ' +
119  str(e[3][2]) + ' ' +
120  # magUnc xzAngleUnc yzAngleUnc
121  str(e[4][0]) + ' ' +
122  str(e[4][1]) + ' ' +
123  str(e[4][2]) + '\n')
124  return arr
125 
126 
127 # Create a plot with the specified variable
128 def plotVar(arr, limits, vName, getterV, getterE=None):
129 
130  tVals = []
131  Vals = []
132  Errs = []
133 
134  tGapVals = []
135  GapVals = []
136 
137  for i, el in enumerate(arr):
138  s, e = el[1], el[2]
139  s = datetime.utcfromtimestamp((s + 9) * 3600) # Convert to the JST (+9 hours)
140  e = datetime.utcfromtimestamp((e + 9) * 3600)
141  tVals.append(s)
142  tVals.append(e)
143  Vals.append(getterV(el))
144  Vals.append(getterV(el))
145 
146  if getterE is not None:
147  Errs.append(getterE(el))
148  Errs.append(getterE(el))
149 
150  # Add breaks for longer gap if not the last interval
151  if i >= len(arr) - 1:
152  continue
153 
154  dt = (arr[i + 1][1] - arr[i][2]) * 3600
155 
156  # only consider gaps longer than 10 mins
157  if dt < 10 * 60:
158  continue
159 
160  # start-time of gap, end-time of gap
161  gS = datetime.utcfromtimestamp((arr[i][2] + 9) * 3600) # Convert to the JST (+9 hours)
162  gE = datetime.utcfromtimestamp((arr[i + 1][1] + 9) * 3600)
163 
164  tVals.append(gS + (gE - gS) / 2)
165  Vals.append(np.nan)
166  if getterE is not None:
167  Errs.append(np.nan)
168 
169  # store curve connecting gaps
170  tGapVals.append(gS - timedelta(seconds=1))
171  tGapVals.append(gS)
172  tGapVals.append(gE)
173  tGapVals.append(gE + timedelta(seconds=1))
174 
175  GapVals.append(np.nan)
176  GapVals.append(getterV(arr[i]))
177  GapVals.append(getterV(arr[i + 1]))
178  GapVals.append(np.nan)
179 
180  plt.plot(tVals, Vals, linewidth=2, color='C0')
181  plt.plot(tGapVals, GapVals, linewidth=2, color='C0', alpha=0.35)
182 
183  Vals = np.array(Vals)
184  Errs = np.array(Errs)
185 
186  if getterE is not None:
187  plt.fill_between(tVals, Vals - Errs, Vals + Errs, alpha=0.2)
188 
189  plt.xlabel('time')
190  if 'Angle' in vName:
191  plt.ylabel(vName + ' [mrad]')
192  else:
193  plt.ylabel(vName + r' $[10^{-3}]$')
194 
195  loc = 'plots/allData'
196  if limits is not None:
197  plt.xlim(datetime.strptime(limits[0], '%Y-%m-%d'), datetime.strptime(limits[1], '%Y-%m-%d'))
198  loc = 'plots/' + limits[0] + 'to' + limits[1]
199 
200  # if not os.path.isdir(loc):
201  os.makedirs(loc, exist_ok=True)
202 
203  plt.savefig(loc + '/' + vName + '.png')
204  plt.clf()
205 
206 
207 def plotPullSpectrum(arr, vName, getterV, getterE):
208 
209  from itertools import groupby
210  vals = np.array([k for k, g in groupby([getterV(v) for v in arr])])
211  errs = np.array([k for k, g in groupby([getterE(v) for v in arr])])
212  assert(len(vals) == len(errs))
213 
214  diffs = (vals[1:] - vals[:-1]) / np.hypot(errs[1:], errs[:-1])
215 
216  # Get 1sigma quantiles of the normal distribution
217  n1, n2 = scipy.stats.norm.cdf([-1, 1])
218 
219  q1 = np.quantile(diffs, n1)
220  q2 = np.quantile(diffs, n2)
221 
222  plt.hist(diffs, bins=np.linspace(-10, 10, 20), label=f'q1={round(q1,1)}, q2={round(q2,1)}')
223  plt.xlabel(vName)
224  plt.legend()
225 
226  loc = 'plots/allData'
227  plt.savefig(loc + '/' + vName + '.png')
228  plt.clf()
229 
230 
231 def run_validation(job_path, input_data_path, requested_iov, expert_config):
232  '''
233  Run the validation.
234  '''
235 
236  # Expert config can contain the time ranges of the plots
237  if expert_config != '':
238  expert_config = json.loads(expert_config)
239 
240  allLimits = [None]
241  if expert_config is not None and 'plotsRanges' in expert_config:
242  allLimits += expert_config['plotsRanges']
243 
244  # Path to the database.txt file and to the payloads.
245  dbFile = glob(f'{job_path}/**/database.txt', recursive=True)
246  dbFile = [db for db in dbFile if 'algorithm_output' not in db]
247  assert(len(dbFile) == 1)
248  dbFile = dbFile[0]
249  inputDir = dbFile[:dbFile.rfind('/')]
250 
251  arr = getBVvalues(inputDir)
252 
253  # print the results to the CSV file
254  printToFile(arr)
255 
256  plt.figure(figsize=(18, 9))
257 
258  # plot the results
259  for limits in allLimits:
260  plotVar(arr, limits, 'boostMag', lambda e: e[3][0], lambda e: e[4][0])
261  plotVar(arr, limits, 'xzBoostAngle', lambda e: e[3][1], lambda e: e[4][1])
262  plotVar(arr, limits, 'yzBoostAngle', lambda e: e[3][2], lambda e: e[4][2])
263 
264  plotPullSpectrum(arr, 'boostMagPulls', lambda e: e[3][0], lambda e: e[4][0])
265  plotPullSpectrum(arr, 'xzBoostAnglePulls', lambda e: e[3][1], lambda e: e[4][1])
266  plotPullSpectrum(arr, 'yzBoostAnglePulls', lambda e: e[3][2], lambda e: e[4][2])
267 
268 
269 if __name__ == "__main__":
270  run_validation(*sys.argv[1:])