Belle II Software development
KLMEventT0ValidationModule Class Reference
Inheritance diagram for KLMEventT0ValidationModule:

Public Member Functions

 __init__ (self, muon_list_name='mu+:forT0', output_file='KLMEventT0Validation.root', opposite_charges_only=True)
 
 initialize (self)
 
 terminate (self)
 
 event (self)
 

Protected Member Functions

 _cd (self, path)
 
 _book_histograms (self)
 
 _fill_diff (self, mu_a, err_a, mu_b, err_b, suffix, prefix='')
 
 _fill_pairs (self, lab, trk)
 
 _fill_charges (self, particle)
 
 _fill_summaries (self)
 

Protected Attributes

 _muon_list_name = muon_list_name
 ParticleList name.
 
 _output_file_path = output_file
 Output file name.
 
 _opp_charges_only = opposite_charges_only
 Opposite-charge pairs only.
 
dict _h = {}
 Histograms by key.
 
 _outfile = None
 Output file.
 
 _channel_status = None
 KLM channel status, for the charge histograms.
 

Detailed Description

Validation histograms for KLMEventT0Estimator, filled from the estimator's stored results.

Definition at line 110 of file KLMEventT0Validation.py.

Constructor & Destructor Documentation

◆ __init__()

__init__ ( self,
muon_list_name = 'mu+:forT0',
output_file = 'KLMEventT0Validation.root',
opposite_charges_only = True )
Parameters
----------
muon_list_name : str
    ParticleList given to KLMEventT0Estimator as MuonListName.
output_file : str
    ROOT file for the histograms.
opposite_charges_only : bool
    Use only opposite-charge track pairs for the same-category pulls, residuals and sector maps.

Definition at line 113 of file KLMEventT0Validation.py.

114 opposite_charges_only=True):
115 """
116 Parameters
117 ----------
118 muon_list_name : str
119 ParticleList given to KLMEventT0Estimator as MuonListName.
120 output_file : str
121 ROOT file for the histograms.
122 opposite_charges_only : bool
123 Use only opposite-charge track pairs for the same-category pulls, residuals and sector maps.
124 """
125 super().__init__()
126
127 self._muon_list_name = muon_list_name
128
129 self._output_file_path = output_file
130
131 self._opp_charges_only = opposite_charges_only
132
133 self._h = {}
134
135 self._outfile = None
136
137 self._channel_status = None
138

Member Function Documentation

◆ _book_histograms()

_book_histograms ( self)
protected
Book all histograms; the binning follows the histograms used for the KLM event T0 note.

Definition at line 162 of file KLMEventT0Validation.py.

162 def _book_histograms(self):
163 """Book all histograms; the binning follows the histograms used for the KLM event T0 note."""
164 h = self._h
165
166 self._cd('diagnostics')
167 for name, sfx, lab in CATEGORIES:
168 h[f'nhits_{lab}'] = TH1D(f'h_nhits_pertrk_{sfx}', f'Hits per track ({sfx});hits', 100, -0.5, 99.5)
169 h[f'err_{lab}'] = TH1D(f'h_err_pertrk_{sfx}', f'Per-track T0 uncertainty ({sfx});uncertainty [ns]',
170 800, 0.0, 20.0)
171 h[f'dimuon_{lab}'] = TH1D(f'h_dimuon_{sfx}', f'Di-muon T0(#mu^{{+}}) - T0(#mu^{{-}}) ({sfx});#DeltaT_{{0}} [ns]',
172 800, -100, 100)
173 h['dimuon_all'] = TH1D('h_dimuon_all', 'Di-muon T0(#mu^{+}) - T0(#mu^{-}) (all categories);#DeltaT_{0} [ns]',
174 800, -100, 100)
175 h['digitQ_B'] = TH1D('h_digitQ_bklm_scint', 'Digit charge (BKLM scintillator);charge [ADC counts]', 100, 0.0, 800.0)
176 h['digitQ_E'] = TH1D('h_digitQ_eklm_scint', 'Digit charge (EKLM scintillator);charge [ADC counts]', 100, 0.0, 800.0)
177 h['resolution'] = TH1D('h_per_track_resolution', 'Per-track resolution from di-muon #DeltaT_{0};;#sigma/#sqrt{2} [ns]',
178 4, 0.5, 4.5)
179 for i, lab in enumerate(['all', 'bklm_scint', 'bklm_rpc', 'eklm_scint'], 1):
180 h['resolution'].GetXaxis().SetBinLabel(i, lab)
181
182 self._cd('final')
183 h['final'] = TH1D('h_t0evt_klm_final', 'Final KLM event T0;T0 [ns]', 800, -100, 100)
184 h['final_err'] = TH1D('h_t0evt_klm_final_err', 'Final KLM event T0 uncertainty;uncertainty [ns]', 800, 0.0, 20.0)
185
186 for kind, title, nb, lo, hi in (('pull', 'Pull', 200, -10, 10), ('res', 'Residual', 500, -25, 25)):
187 top = 'pulls' if kind == 'pull' else 'residuals'
188 unit = '' if kind == 'pull' else ' [ns]'
189 self._cd(top)
190 for name, sfx, lab in CATEGORIES:
191 h[f'{kind}_{lab}'] = TH1D(f'h_{kind}_{sfx}', f'{title} ({sfx});T0(#mu^{{+}}) - T0(#mu^{{-}}){unit}',
192 nb, lo, hi)
193 for a, b in CROSS_PAIRS:
194 h[f'{kind}_{a}_vs_{b}'] = TH1D(f'h_{kind}_{a}_vs_{b}', f'{title}, same track;T0({a}) - T0({b}){unit}',
195 nb, lo, hi)
196 h[f'evt_{kind}_{a}_vs_{b}'] = TH1D(f'h_evt_{kind}_{a}_vs_{b}', f'{title}, per event;T0({a}) - T0({b}){unit}',
197 nb, lo, hi)
198 # Sector pairs, same binning as the global histograms of the old sector analysis
199 snb, slo, shi = (200, -10, 10) if kind == 'pull' else (200, -50, 50)
200 for name, sfx, lab in CATEGORIES:
201 self._cd(f'{top}/sector/{sfx}')
202 n = N_SECTOR_BINS[lab]
203 grid = {}
204 for i in range(n):
205 for j in range(n):
206 si, sj = _sector_name(lab, i), _sector_name(lab, j)
207 grid[(i, j)] = TH1D(f'h_{kind}_{sfx}_{si}_vs_{sj}',
208 f'{title} ({sfx}), #mu^{{+}} in {si}, #mu^{{-}} in {sj};'
209 f'T0(#mu^{{+}}) - T0(#mu^{{-}}){unit}', snb, slo, shi)
210 h[f'{kind}_sector_{lab}'] = grid
211 self._cd(f'{top}/sector')
212 for stat in ('mean', 'sigma'):
213 h2 = TH2D(f'h2_{kind}_{sfx}_{stat}', f'{title} {stat} ({sfx});#mu^{{+}} sector;#mu^{{-}} sector',
214 n, 0.5, n + 0.5, n, 0.5, n + 0.5)
215 for k in range(n):
216 h2.GetXaxis().SetBinLabel(k + 1, _sector_name(lab, k))
217 h2.GetYaxis().SetBinLabel(k + 1, _sector_name(lab, k))
218 h[f'{kind}_sector_{lab}_{stat}'] = h2
219

◆ _cd()

_cd ( self,
path )
protected
Create (if needed) and change to a directory of the output file.

Definition at line 154 of file KLMEventT0Validation.py.

154 def _cd(self, path):
155 """Create (if needed) and change to a directory of the output file."""
156 d = self._outfile
157 for part in path.split('/'):
158 sub = d.GetDirectory(part)
159 d = sub if sub else d.mkdir(part)
160 d.cd()
161

◆ _fill_charges()

_fill_charges ( self,
particle )
protected
Charge of the track's scintillator digits before the charge selection (good digits, normal channels).

Definition at line 327 of file KLMEventT0Validation.py.

327 def _fill_charges(self, particle):
328 """Charge of the track's scintillator digits before the charge selection (good digits, normal channels)."""
329 track = particle.getTrack()
330 if not track:
331 return
332 status = self._channel_status
333
334 def normal(digit):
335 return (not status.isValid() or
336 status.obj().getChannelStatus(digit.getUniqueChannelID()) == Belle2.KLMChannelStatus.c_Normal)
337
338 for hit2d in track.getRelationsTo['Belle2::KLMHit2d']('KLMHit2ds'):
339 if hit2d.getSubdetector() == Belle2.KLMElementNumbers.c_EKLM:
340 for digit in hit2d.getRelationsTo['Belle2::KLMDigit']('KLMDigits'):
341 if digit.isGood() and normal(digit):
342 self._h['digitQ_E'].Fill(digit.getCharge())
343 elif not hit2d.inRPC() and not hit2d.isOutOfTime():
344 for hit1d in hit2d.getRelationsTo['Belle2::BKLMHit1d']('BKLMHit1ds'):
345 for digit in hit1d.getRelationsTo['Belle2::KLMDigit']('KLMDigits'):
346 if not digit.inRPC() and digit.isGood() and normal(digit):
347 self._h['digitQ_B'].Fill(digit.getCharge())
348

◆ _fill_diff()

_fill_diff ( self,
mu_a,
err_a,
mu_b,
err_b,
suffix,
prefix = '' )
protected
Fill residual and pull of a - b.

Definition at line 294 of file KLMEventT0Validation.py.

294 def _fill_diff(self, mu_a, err_a, mu_b, err_b, suffix, prefix=''):
295 """Fill residual and pull of a - b."""
296 h = self._h
297 if not (math.isfinite(mu_a) and math.isfinite(mu_b)):
298 return
299 h[f'{prefix}res{suffix}'].Fill(mu_a - mu_b)
300 den2 = err_a * err_a + err_b * err_b
301 if den2 > 0.0:
302 h[f'{prefix}pull{suffix}'].Fill((mu_a - mu_b) / math.sqrt(den2))
303

◆ _fill_pairs()

_fill_pairs ( self,
lab,
trk )
protected
Same-category pairs of tracks, the mu+ first.

Definition at line 304 of file KLMEventT0Validation.py.

304 def _fill_pairs(self, lab, trk):
305 """Same-category pairs of tracks, the mu+ first."""
306 h = self._h
307 for i in range(len(trk)):
308 for j in range(i + 1, len(trk)):
309 a, b = trk[i], trk[j]
310 if self._opp_charges_only and a['charge'] * b['charge'] >= 0:
311 continue
312 if a['charge'] < 0 < b['charge']:
313 a, b = b, a
314 den2 = a['err'] ** 2 + b['err'] ** 2
315 if not den2 > 0.0:
316 continue
317 d = a['mu'] - b['mu']
318 pull = d / math.sqrt(den2)
319 h[f'res_{lab}'].Fill(d)
320 h[f'pull_{lab}'].Fill(pull)
321 ki, kj = _sector_index(lab, a), _sector_index(lab, b)
322 n = N_SECTOR_BINS[lab]
323 if 0 <= ki < n and 0 <= kj < n:
324 h[f'res_sector_{lab}'][(ki, kj)].Fill(d)
325 h[f'pull_sector_{lab}'][(ki, kj)].Fill(pull)
326

◆ _fill_summaries()

_fill_summaries ( self)
protected
Core-fit mean and width of every sector pair, and the per-track resolution.

Definition at line 349 of file KLMEventT0Validation.py.

349 def _fill_summaries(self):
350 """Core-fit mean and width of every sector pair, and the per-track resolution."""
351 h = self._h
352 for kind in ('pull', 'res'):
353 for name, sfx, lab in CATEGORIES:
354 for (i, j), hh in h[f'{kind}_sector_{lab}'].items():
355 fit = core_fit(hh)
356 if fit is None:
357 continue
358 h[f'{kind}_sector_{lab}_mean'].SetBinContent(i + 1, j + 1, fit[0])
359 h[f'{kind}_sector_{lab}_mean'].SetBinError(i + 1, j + 1, fit[1])
360 h[f'{kind}_sector_{lab}_sigma'].SetBinContent(i + 1, j + 1, fit[2])
361 h[f'{kind}_sector_{lab}_sigma'].SetBinError(i + 1, j + 1, fit[3])
362 for b, key in enumerate(['dimuon_all', 'dimuon_B', 'dimuon_R', 'dimuon_E'], 1):
363 fit = core_fit(h[key])
364 if fit is not None:
365 h['resolution'].SetBinContent(b, fit[2] / math.sqrt(2.0))
366 h['resolution'].SetBinError(b, fit[3] / math.sqrt(2.0))

◆ event()

event ( self)
Read the estimator's results for this event and fill the histograms.

Definition at line 221 of file KLMEventT0Validation.py.

221 def event(self):
222 """Read the estimator's results for this event and fill the histograms."""
223 h = self._h
224 plist = Belle2.PyStoreObj(self._muon_list_name)
225 if not plist.isValid():
226 return
227
228 tracks = {lab: [] for _, _, lab in CATEGORIES} # per category: one dict per track
229 all_tracks = [] # (charge, T0 over all categories)
230 for i in range(plist.obj().getListSize()):
231 particle = plist.obj().getParticle(i)
232 charge = particle.getCharge()
233 for name, sfx, lab in CATEGORIES:
234 if not particle.hasExtraInfo('klmTrackT0' + name):
235 continue
236 t = {'mu': particle.getExtraInfo('klmTrackT0' + name),
237 'err': particle.getExtraInfo('klmTrackT0Err' + name),
238 'n': int(particle.getExtraInfo('klmTrackNHits' + name)),
239 'sector': int(particle.getExtraInfo('klmTrackSector' + name)),
240 'section': int(particle.getExtraInfo('klmTrackSection' + name)),
241 'charge': charge, 'index': i}
242 tracks[lab].append(t)
243 h[f'nhits_{lab}'].Fill(t['n'])
244 h[f'err_{lab}'].Fill(t['err'])
245 if particle.hasExtraInfo('klmTrackT0All'):
246 all_tracks.append((charge, particle.getExtraInfo('klmTrackT0All')))
247 self._fill_charges(particle)
248
249 # Final KLM event T0
250 evt_t0 = Belle2.PyStoreObj('EventT0')
251 if evt_t0.isValid():
252 klm = evt_t0.obj().getTemporaryEventT0s(Belle2.Const.KLM)
253 if klm.size() > 0:
254 h['final'].Fill(klm.back().eventT0)
255 h['final_err'].Fill(klm.back().eventT0Uncertainty)
256
257 # Same-category pairs: pulls, residuals and sector maps
258 for name, sfx, lab in CATEGORIES:
259 self._fill_pairs(lab, tracks[lab])
260
261 # Two categories of the same track
262 for a, b in CROSS_PAIRS:
263 for ta in tracks[a]:
264 for tb in tracks[b]:
265 if tb['index'] == ta['index']:
266 self._fill_diff(ta['mu'], ta['err'], tb['mu'], tb['err'], f'_{a}_vs_{b}')
267
268 # Per-event results of two categories
269 extra = Belle2.PyStoreObj('EventExtraInfo')
270 if extra.isValid():
271 evt = {}
272 for name, sfx, lab in CATEGORIES:
273 if extra.obj().hasExtraInfo('klmEventT0' + name):
274 evt[lab] = (extra.obj().getExtraInfo('klmEventT0' + name),
275 extra.obj().getExtraInfo('klmEventT0Err' + name))
276 for a, b in CROSS_PAIRS:
277 if a in evt and b in evt:
278 self._fill_diff(evt[a][0], evt[a][1], evt[b][0], evt[b][1], f'_{a}_vs_{b}', prefix='evt_')
279
280 # Di-muon T0(mu+) - T0(mu-): the last track of each charge, as in the estimator's earlier version
281 def _last(pairs, sign):
282 vals = [mu for q, mu in pairs if q * sign > 0]
283 return vals[-1] if vals else None
284 pos, neg = _last(all_tracks, 1), _last(all_tracks, -1)
285 if pos is not None and neg is not None:
286 h['dimuon_all'].Fill(pos - neg)
287 for name, sfx, lab in CATEGORIES:
288 pairs = [(t['charge'], t['mu']) for t in tracks[lab]]
289 pos, neg = _last(pairs, 1), _last(pairs, -1)
290 if pos is not None and neg is not None:
291 h[f'dimuon_{lab}'].Fill(pos - neg)
292
a (simplified) python wrapper for StoreObjPtr.
Definition PyStoreObj.h:67

◆ initialize()

initialize ( self)
Open the output file and book the histograms.

Definition at line 140 of file KLMEventT0Validation.py.

140 def initialize(self):
141 """Open the output file and book the histograms."""
142 self._channel_status = Belle2.PyDBObj('KLMChannelStatus')
143 self._outfile = TFile(self._output_file_path, 'RECREATE')
144 self._book_histograms()
145
Class to access a DBObjPtr from Python.
Definition PyDBObj.h:50

◆ terminate()

terminate ( self)
Fill the summary histograms, write and close the file.

Definition at line 146 of file KLMEventT0Validation.py.

146 def terminate(self):
147 """Fill the summary histograms, write and close the file."""
148 self._fill_summaries()
149 self._outfile.Write('', ROOT.TObject.kOverwrite)
150 self._outfile.Close()
151 basf2.B2INFO(f'KLMEventT0ValidationModule: wrote {self._output_file_path}')
152

Member Data Documentation

◆ _channel_status

_channel_status = None
protected

KLM channel status, for the charge histograms.

Definition at line 137 of file KLMEventT0Validation.py.

◆ _h

dict _h = {}
protected

Histograms by key.

Definition at line 133 of file KLMEventT0Validation.py.

◆ _muon_list_name

_muon_list_name = muon_list_name
protected

ParticleList name.

Definition at line 127 of file KLMEventT0Validation.py.

◆ _opp_charges_only

_opp_charges_only = opposite_charges_only
protected

Opposite-charge pairs only.

Definition at line 131 of file KLMEventT0Validation.py.

◆ _outfile

_outfile = None
protected

Output file.

Definition at line 135 of file KLMEventT0Validation.py.

◆ _output_file_path

_output_file_path = output_file
protected

Output file name.

Definition at line 129 of file KLMEventT0Validation.py.


The documentation for this class was generated from the following file: