297 """Create all validation histograms inside the output ROOT file."""
301 def H1(name, title, nb, lo, hi):
302 return TH1D(name, title, nb, lo, hi)
304 def H1I(name, title, nb, lo, hi):
305 return TH1I(name, title, nb, lo, hi)
308 f.mkdir(
'per_track').cd()
309 h[
't0trk_B'] = H1(
'h_t0trk_bklm_scint',
310 'Per-track T_{0} (BKLM Scint);T_{0} [ns]', 800, -100, 100)
311 h[
't0trk_R'] = H1(
'h_t0trk_bklm_rpc',
312 'Per-track T_{0} (BKLM RPC);T_{0} [ns]', 800, -100, 100)
313 h[
't0trk_E'] = H1(
'h_t0trk_eklm_scint',
314 'Per-track T_{0} (EKLM Scint);T_{0} [ns]', 800, -100, 100)
317 f.mkdir(
'per_event').cd()
318 h[
't0evt_B'] = H1(
'h_t0evt_bklm_scint',
319 'Per-event T_{0} (BKLM Scint);T_{0} [ns]', 800, -100, 100)
320 h[
't0evt_R'] = H1(
'h_t0evt_bklm_rpc',
321 'Per-event T_{0} (BKLM RPC);T_{0} [ns]', 800, -100, 100)
322 h[
't0evt_E'] = H1(
'h_t0evt_eklm_scint',
323 'Per-event T_{0} (EKLM Scint);T_{0} [ns]', 800, -100, 100)
324 h[
't0evt_all'] = H1(
'h_t0evt_all',
325 'Per-event T_{0} (all KLM);T_{0} [ns]', 800, -100, 100)
326 h[
'final_source'] = H1I(
'h_final_source',
'Final KLM source;;events', 7, 0.5, 7.5)
327 for ib, lab
in enumerate([
'B only',
'E only',
'R only',
328 'B+E',
'B+R',
'E+R',
'B+E+R'], 1):
329 h[
'final_source'].GetXaxis().SetBinLabel(ib, lab)
332 f.mkdir(
'diagnostics').cd()
333 h[
'nhits_B'] = H1I(
'h_nhits_pertrk_bklm_scint',
334 'Hits per track (BKLM Scint);N_{hits}', 50, 0, 50)
335 h[
'nhits_E'] = H1I(
'h_nhits_pertrk_eklm_scint',
336 'Hits per track (EKLM Scint);N_{hits}', 50, 0, 50)
337 h[
'sem_B'] = H1(
'h_sem_pertrk_bklm_scint',
338 'SEM per track (BKLM Scint);SEM [ns]', 200, 0.0, 10.0)
339 h[
'sem_E'] = H1(
'h_sem_pertrk_eklm_scint',
340 'SEM per track (EKLM Scint);SEM [ns]', 200, 0.0, 10.0)
341 h[
'digitQ_B'] = H1(
'h_digitQ_bklm_scint',
342 'KLMDigit charge (BKLM Scint);ADC (a.u.)', 100, 0, 800)
343 h[
'digitQ_E'] = H1(
'h_digitQ_eklm_scint',
344 'KLMDigit charge (EKLM Scint);ADC (a.u.)', 100, 0, 800)
345 h[
'sample_type'] = H1I(
'h_sample_type',
'Sample type;;events', 2, 0.5, 2.5)
346 h[
'sample_type'].GetXaxis().SetBinLabel(1,
'Data')
347 h[
'sample_type'].GetXaxis().SetBinLabel(2,
'MC')
351 'B': (
'BKLM Scint', -5000, -4000, -5000, -4000),
352 'R': (
'BKLM RPC', -800, -500, -800, -500),
353 'E': (
'EKLM Scint', -5000, -4000, -5000, -4000),
355 for key, (label, r0, r1, rc0, rc1)
in _timing_cfg.items():
356 h[f
'Trec_{key}'] = H1(f
'h_Trec_{key.lower()}',
357 f
'T_{{rec}} ({label});time [ns]', 800, r0, r1)
358 h[f
'Tcable_{key}'] = H1(f
'h_Tcable_{key.lower()}',
359 f
'T_{{cable}} ({label});time [ns]', 800, rc0, rc1)
360 h[f
'Tprop_{key}'] = H1(f
'h_Tprop_{key.lower()}',
361 f
'T_{{prop}} ({label});time [ns]', 800, -50, 50)
362 h[f
'Tfly_{key}'] = H1(f
'h_Tfly_{key.lower()}',
363 f
'T_{{fly}} ({label});time [ns]', 800, -100, 100)
367 h[
'dimuon_all'] = H1(
'h_dimuon_all',
368 'Dimuon #DeltaT_{0} (all KLM);T_{0}(#mu^{+})-T_{0}(#mu^{-}) [ns]',
370 h[
'dimuon_B'] = H1(
'h_dimuon_bklm_scint',
371 'Dimuon #DeltaT_{0} (BKLM Scint);#DeltaT_{0} [ns]', 400, -50, 50)
372 h[
'dimuon_R'] = H1(
'h_dimuon_bklm_rpc',
373 'Dimuon #DeltaT_{0} (BKLM RPC);#DeltaT_{0} [ns]', 400, -50, 50)
374 h[
'dimuon_E'] = H1(
'h_dimuon_eklm_scint',
375 'Dimuon #DeltaT_{0} (EKLM Scint);#DeltaT_{0} [ns]', 400, -50, 50)
376 h[
'dimuon_scint'] = H1(
'h_dimuon_scint_only',
377 'Dimuon #DeltaT_{0} (Scint only);#DeltaT_{0} [ns]', 400, -50, 50)
378 h[
'dimuon_rpc'] = H1(
'h_dimuon_with_rpc',
379 'Dimuon #DeltaT_{0} (With RPC);#DeltaT_{0} [ns]', 400, -50, 50)
380 h[
'dimuon_rpcdir'] = H1(
'h_dimuon_with_rpc_dir',
381 'Dimuon #DeltaT_{0} (With RPC dir);#DeltaT_{0} [ns]', 400, -50, 50)
384 h[
'resolution'] = H1(
'h_per_track_resolution',
385 'Per-Track T_{0} Resolution (#sigma_{#DeltaT0}/#sqrt{2});'
386 'Category;Resolution [ns]', 6, 0.5, 6.5)
387 for ib, lab
in enumerate(
388 [
'BKLM Scint',
'BKLM RPC',
'EKLM Scint',
'Scint Only',
'With RPC',
'With RPC Dir'], 1):
389 h[
'resolution'].GetXaxis().SetBinLabel(ib, lab)
392 f.mkdir(
'pulls').cd()
393 h[
'pull_B'] = H1(
'h_pull_bklm_scint',
394 'Pull (BKLM Scint);(T_{0,i}-T_{0,j})/#sigma', 200, -10, 10)
395 h[
'pull_E'] = H1(
'h_pull_eklm_scint',
396 'Pull (EKLM Scint);(T_{0,i}-T_{0,j})/#sigma', 200, -10, 10)
397 h[
'pull_Rphi'] = H1(
'h_pull_rpc_phi',
398 'Pull (BKLM RPC Phi);(T_{0,i}-T_{0,j})/#sigma', 200, -10, 10)
399 h[
'pull_Rz'] = H1(
'h_pull_rpc_z',
400 'Pull (BKLM RPC Z);(T_{0,i}-T_{0,j})/#sigma', 200, -10, 10)
401 h[
'pull_R'] = H1(
'h_pull_bklm_rpc',
402 'Pull (BKLM RPC combined);(T_{0,i}-T_{0,j})/#sigma', 200, -10, 10)
403 h[
'pull_BvE'] = H1(
'h_pull_B_vs_E',
404 'Pull (BKLM Scint vs EKLM Scint);pull', 200, -10, 10)
405 h[
'pull_BvR'] = H1(
'h_pull_B_vs_R',
406 'Pull (BKLM Scint vs BKLM RPC);pull', 200, -10, 10)
407 h[
'pull_EvR'] = H1(
'h_pull_E_vs_R',
408 'Pull (EKLM Scint vs BKLM RPC);pull', 200, -10, 10)
409 h[
'pull_smean'] = H1(
'h_pull_summary_mean',
410 'Pull Mean;Category;#mu', 4, 0.5, 4.5)
411 h[
'pull_swidth'] = H1(
'h_pull_summary_width',
412 'Pull Sigma;Category;#sigma', 4, 0.5, 4.5)
413 for ib, lab
in enumerate([
'BKLM Scint',
'EKLM Scint',
'RPC Phi',
'RPC Z'], 1):
414 h[
'pull_smean'].GetXaxis().SetBinLabel(ib, lab)
415 h[
'pull_swidth'].GetXaxis().SetBinLabel(ib, lab)
419 f,
'pulls/sector/bklm_scint',
'h_pull_B_s{i}_vs_s{j}',
420 'BKLM Scint Pull: Sec {i} vs Sec {j};pull',
421 _N_BKLM_SECTORS, 200, -10, 10)
423 f,
'pulls/sector/eklm_scint_fwd_vs_bwd',
424 'h_pull_E_fwd{i}_vs_bwd{j}',
425 'EKLM Pull: Fwd Sec {i} vs Bwd Sec {j};pull',
426 _N_EKLM_SECTORS, 200, -10, 10, offset=1)
428 f,
'pulls/sector/rpc_phi',
'h_pull_Rphi_s{i}_vs_s{j}',
429 'RPC Phi Pull: Sec {i} vs Sec {j};pull',
430 _N_BKLM_SECTORS, 200, -10, 10)
433 f,
'pulls/sector/rpc_z',
'h_pull_Rz_s{i}_vs_s{j}',
434 'RPC Z Pull: Sec {i} vs Sec {j};pull',
435 _N_BKLM_SECTORS, 200, -10, 10)
440 'h2_pull_bklm_scint_mean',
'Pull Mean (BKLM Scint);Sec 1;Sec 2',
443 'h2_pull_bklm_scint_sigma',
'Pull Sigma (BKLM Scint);Sec 1;Sec 2',
446 'h2_pull_eklm_fwd_bwd_mean',
'Pull Mean (EKLM Fwd vs Bwd);Fwd Sec;Bwd Sec',
447 _N_EKLM_SECTORS, lo=0.5)
449 'h2_pull_eklm_fwd_bwd_sigma',
'Pull Sigma (EKLM Fwd vs Bwd);Fwd Sec;Bwd Sec',
450 _N_EKLM_SECTORS, lo=0.5)
452 'h2_pull_rpc_phi_mean',
'Pull Mean (RPC Phi);Sec 1;Sec 2', _N_BKLM_SECTORS)
454 'h2_pull_rpc_phi_sigma',
'Pull Sigma (RPC Phi);Sec 1;Sec 2', _N_BKLM_SECTORS)
456 'h2_pull_rpc_z_mean',
'Pull Mean (RPC Z);Sec 1;Sec 2', _N_BKLM_SECTORS)
458 'h2_pull_rpc_z_sigma',
'Pull Sigma (RPC Z);Sec 1;Sec 2', _N_BKLM_SECTORS)
461 f.mkdir(
'residuals').cd()
462 h[
'res_B'] = H1(
'h_residual_bklm_scint',
463 'Residual (BKLM Scint);#DeltaT_{0} [ns]', 200, -50, 50)
464 h[
'res_E'] = H1(
'h_residual_eklm_scint',
465 'Residual (EKLM Scint);#DeltaT_{0} [ns]', 200, -50, 50)
466 h[
'res_Rphi'] = H1(
'h_residual_rpc_phi',
467 'Residual (BKLM RPC Phi);#DeltaT_{0} [ns]', 200, -50, 50)
468 h[
'res_Rz'] = H1(
'h_residual_rpc_z',
469 'Residual (BKLM RPC Z);#DeltaT_{0} [ns]', 200, -50, 50)
470 h[
'res_R'] = H1(
'h_residual_bklm_rpc',
471 'Residual (BKLM RPC combined);#DeltaT_{0} [ns]', 200, -50, 50)
472 h[
'res_BvE'] = H1(
'h_residual_B_vs_E',
473 'Residual BKLM Scint - EKLM Scint;#DeltaT_{0} [ns]', 200, -50, 50)
474 h[
'res_BvR'] = H1(
'h_residual_B_vs_R',
475 'Residual BKLM Scint - BKLM RPC;#DeltaT_{0} [ns]', 200, -50, 50)
476 h[
'res_EvR'] = H1(
'h_residual_E_vs_R',
477 'Residual EKLM Scint - BKLM RPC;#DeltaT_{0} [ns]', 200, -50, 50)
478 h[
'res_smean'] = H1(
'h_residual_summary_mean',
479 'Residual Mean;Category;#mu [ns]', 4, 0.5, 4.5)
480 h[
'res_swidth'] = H1(
'h_residual_summary_width',
481 'Residual Sigma;Category;#sigma [ns]', 4, 0.5, 4.5)
482 for ib, lab
in enumerate([
'BKLM Scint',
'EKLM Scint',
'RPC Phi',
'RPC Z'], 1):
483 h[
'res_smean'].GetXaxis().SetBinLabel(ib, lab)
484 h[
'res_swidth'].GetXaxis().SetBinLabel(ib, lab)
488 f,
'residuals/sector/bklm_scint',
'h_residual_B_s{i}_vs_s{j}',
489 'BKLM Scint Residual: Sec {i} vs Sec {j};#DeltaT_{{0}} [ns]',
490 _N_BKLM_SECTORS, 200, -50, 50)
492 f,
'residuals/sector/eklm_scint_fwd_vs_bwd',
493 'h_residual_E_fwd{i}_vs_bwd{j}',
494 'EKLM Residual: Fwd Sec {i} vs Bwd Sec {j};#DeltaT_{{0}} [ns]',
495 _N_EKLM_SECTORS, 200, -50, 50, offset=1)
497 f,
'residuals/sector/rpc_phi',
'h_residual_Rphi_s{i}_vs_s{j}',
498 'RPC Phi Residual: Sec {i} vs Sec {j};#DeltaT_{{0}} [ns]',
499 _N_BKLM_SECTORS, 200, -50, 50)
501 f,
'residuals/sector/rpc_z',
'h_residual_Rz_s{i}_vs_s{j}',
502 'RPC Z Residual: Sec {i} vs Sec {j};#DeltaT_{{0}} [ns]',
503 _N_BKLM_SECTORS, 200, -50, 50)
506 self.
_safe_cd(f,
'residuals/sector')
508 'h2_residual_bklm_scint_mean',
'Residual Mean (BKLM Scint);Sec 1;Sec 2',
511 'h2_residual_bklm_scint_sigma',
'Residual Sigma (BKLM Scint);Sec 1;Sec 2',
514 'h2_residual_eklm_fwd_bwd_mean',
515 'Residual Mean (EKLM Fwd vs Bwd);Fwd Sec;Bwd Sec',
516 _N_EKLM_SECTORS, lo=0.5)
518 'h2_residual_eklm_fwd_bwd_sigma',
519 'Residual Sigma (EKLM Fwd vs Bwd);Fwd Sec;Bwd Sec',
520 _N_EKLM_SECTORS, lo=0.5)
522 'h2_residual_rpc_phi_mean',
'Residual Mean (RPC Phi);Sec 1;Sec 2',
525 'h2_residual_rpc_phi_sigma',
'Residual Sigma (RPC Phi);Sec 1;Sec 2',
528 'h2_residual_rpc_z_mean',
'Residual Mean (RPC Z);Sec 1;Sec 2',
531 'h2_residual_rpc_z_sigma',
'Residual Sigma (RPC Z);Sec 1;Sec 2',
535 f.mkdir(
'cross_detector').cd()
536 _reg_names = [
'EKLM_Bwd',
'EKLM_Fwd',
'BKLM_RPC',
'BKLM_Scint']
537 _reg_labels = [
'EKLM Backward',
'EKLM Forward',
'BKLM RPC',
'BKLM Scint']
539 for ri
in range(_N_REGIONS):
541 for rj
in range(_N_REGIONS):
542 h[
'dt0'][ri][rj] = H1(
543 f
'h_deltaT0_{_reg_names[ri]}_vs_{_reg_names[rj]}',
544 f
'#DeltaT_{{0}}: {_reg_labels[ri]} vs {_reg_labels[rj]};'
545 f
'#DeltaT_{{0}} [ns]', 200, -50, 50)
546 h[
'dt0_2d_mean'] = TH2D(
'h2_deltaT0_mean',
547 '#DeltaT_{0} Mean by Region;Region 1;Region 2',
548 _N_REGIONS, -0.5, _N_REGIONS - 0.5,
549 _N_REGIONS, -0.5, _N_REGIONS - 0.5)
550 h[
'dt0_2d_sigma'] = TH2D(
'h2_deltaT0_sigma',
551 '#DeltaT_{0} Sigma by Region;Region 1;Region 2',
552 _N_REGIONS, -0.5, _N_REGIONS - 0.5,
553 _N_REGIONS, -0.5, _N_REGIONS - 0.5)
554 h[
'dt0_2d_entries'] = TH2D(
'h2_deltaT0_entries',
555 '#DeltaT_{0} Entries by Region;Region 1;Region 2',
556 _N_REGIONS, -0.5, _N_REGIONS - 0.5,
557 _N_REGIONS, -0.5, _N_REGIONS - 0.5)
558 for ri, lab
in enumerate(_reg_labels, 1):
559 for hh
in (h[
'dt0_2d_mean'], h[
'dt0_2d_sigma'], h[
'dt0_2d_entries']):
560 hh.GetXaxis().SetBinLabel(ri, lab)
561 hh.GetYaxis().SetBinLabel(ri, lab)
564 f.mkdir(
'final').cd()
565 h[
'final_scint'] = H1(
'h_t0evt_final_scint_only',
566 'Final KLM T_{0} (Scint only);T_{0} [ns]', 800, -100, 100)
567 h[
'final_rpc'] = H1(
'h_t0evt_final_with_rpc',
568 'Final KLM T_{0} (Scint+RPC);T_{0} [ns]', 800, -100, 100)
569 h[
'final_rpcdir'] = H1(
'h_t0evt_final_with_rpc_dir',
570 'Final KLM T_{0} (Scint+RPC dir);T_{0} [ns]', 800, -100, 100)
647 Build channel→ExtHit maps for *track*.
651 scint_map : dict channel_key → (entry_ExtHit, exit_ExtHit)
652 rpc_map : dict module_key → (entry_ExtHit, exit_ExtHit)
654 where entry = minimum TOF, exit = maximum TOF among all ExtHits
655 with the same key. This mirrors the C++ matchExt() logic.
660 _muids = track.getRelationsTo[
'Belle2::KLMMuidLikelihood'](
'KLMMuidLikelihoods')
661 muid = _muids[0]
if _muids.size() > 0
else None
664 for ext
in track.getRelationsTo[
'Belle2::ExtHit'](
'ExtHits'):
665 if ext.getStatus() != Belle2.EXT_EXIT:
668 det = ext.getDetectorID()
669 is_bklm = (det == Belle2.Const.EDetector.BKLM)
670 is_eklm = (det == Belle2.Const.EDetector.EKLM)
671 if not is_bklm
and not is_eklm:
674 copy_id = ext.getCopyID()
680 fwd = ctypes.c_int(0)
681 sec = ctypes.c_int(0)
682 lay = ctypes.c_int(0)
683 pla = ctypes.c_int(0)
684 strip = ctypes.c_int(0)
686 copy_id & 0xFFFF, fwd, sec, lay, pla, strip)
687 fwd, sec, lay, pla, strip = (fwd.value, sec.value,
688 lay.value, pla.value, strip.value)
692 if not muid.isExtrapolatedBarrelLayerCrossed(lay - 1):
695 is_rpc = (lay >= Belle2.BKLMElementNumbers.c_FirstRPCLayer)
697 key = elem.moduleNumber(
698 Belle2.KLMElementNumbers.c_BKLM, fwd, sec, lay)
699 rpc_raw.setdefault(key, []).append(ext)
701 key = elem.channelNumber(
702 Belle2.KLMElementNumbers.c_BKLM, fwd, sec, lay, pla, strip)
703 scint_raw.setdefault(key, []).append(ext)
707 fwd = ctypes.c_int(0)
708 lay = ctypes.c_int(0)
709 sec = ctypes.c_int(0)
710 pla = ctypes.c_int(0)
711 strip = ctypes.c_int(0)
713 copy_id, fwd, lay, sec, pla, strip)
714 fwd, lay, sec, pla, strip = (fwd.value, lay.value,
715 sec.value, pla.value, strip.value)
718 if not muid.isExtrapolatedEndcapLayerCrossed(lay - 1):
721 key = elem.channelNumber(
722 Belle2.KLMElementNumbers.c_EKLM, fwd, sec, lay, pla, strip)
723 scint_raw.setdefault(key, []).append(ext)
726 def _entry_exit(exts):
727 en = min(exts, key=
lambda e: e.getTOF())
728 ex = max(exts, key=
lambda e: e.getTOF())
731 scint_map = {k: _entry_exit(v)
for k, v
in scint_raw.items()}
732 rpc_map = {k: _entry_exit(v)
for k, v
in rpc_raw.items()}
733 return scint_map, rpc_map
741 Accumulate EKLM scintillator hits for *track*.
745 results : list of dict, each with:
746 t0_est, Trec, Tcable, Tprop, Tfly, section, sector
753 for hit2d
in track.getRelationsTo[
'Belle2::KLMHit2d'](
'KLMHit2ds'):
754 if hit2d.getSubdetector() != Belle2.KLMElementNumbers.c_EKLM:
757 for digit
in hit2d.getRelationsTo[
'Belle2::KLMDigit'](
'KLMDigits'):
758 if not digit.isGood():
761 cid = digit.getUniqueChannelID()
762 if (status_db.isValid()
and
763 status_db.obj().getChannelStatus(cid) !=
764 Belle2.KLMChannelStatus.c_Normal):
768 charge = digit.getCharge()
769 if self.
_h[
'digitQ_E']:
770 self.
_h[
'digitQ_E'].Fill(charge)
777 pair = scint_map.get(cid)
780 entry_ext, exit_ext = pair
782 Tfly = 0.5 * (entry_ext.getTOF() + exit_ext.getTOF())
786 strip_len_cm = (self.
_geo_eklm.getStripLength(digit.getStrip())
790 pos = self.
_midpoint(entry_ext.getPosition(), exit_ext.getPosition())
791 hit_global = ROOT.HepGeom.Point3D(
'double')(
792 pos.X() * _BASF2_TO_MM,
793 pos.Y() * _BASF2_TO_MM,
794 pos.Z() * _BASF2_TO_MM)
798 hit_local = tr * hit_global
800 local_x_cm = hit_local.x() * _MM_TO_BASF2
802 dist_cm = 0.5 * strip_len_cm - local_x_cm
805 Trec = digit.getTime()
806 Tcable = (cable_db.obj().getTimeDelay(cid)
807 if cable_db.isValid()
else 0.0)
808 Tprop = dist_cm * delay
809 t0_est = Trec - Tcable - Tprop - Tfly
811 if not math.isfinite(t0_est):
817 'Trec': Trec,
'Tcable': Tcable,
818 'Tprop': Tprop,
'Tfly': Tfly,
819 'section': digit.getSection(),
820 'sector': digit.getSector(),
827 Accumulate BKLM scintillator hits for *track*.
831 results : list of dict (same fields as _accumulate_eklm, plus
832 'sector' as BKLM sector 1-8)
839 for hit2d
in track.getRelationsTo[
'Belle2::KLMHit2d'](
'KLMHit2ds'):
840 if hit2d.getSubdetector() != Belle2.KLMElementNumbers.c_BKLM:
844 if hit2d.isOutOfTime():
848 hit2d.getSection(), hit2d.getSector(), hit2d.getLayer())
849 pos2d = hit2d.getPosition()
851 for b1d
in hit2d.getRelationsTo[
'Belle2::BKLMHit1d'](
'BKLMHit1ds'):
852 is_phi = b1d.isPhiReadout()
854 for digit
in b1d.getRelationsTo[
'Belle2::KLMDigit'](
'KLMDigits'):
855 if digit.inRPC()
or not digit.isGood():
858 cid = digit.getUniqueChannelID()
859 if (status_db.isValid()
and
860 status_db.obj().getChannelStatus(cid) !=
861 Belle2.KLMChannelStatus.c_Normal):
864 charge = digit.getCharge()
865 if self.
_h[
'digitQ_B']:
866 self.
_h[
'digitQ_B'].Fill(charge)
871 pair = scint_map.get(cid)
874 entry_ext, exit_ext = pair
876 Tfly = 0.5 * (entry_ext.getTOF() + exit_ext.getTOF())
877 pos_ext = self.
_midpoint(entry_ext.getPosition(), exit_ext.getPosition())
880 loc_ext = mod.globalToLocal(
881 ROOT.CLHEP.Hep3Vector(pos_ext.X(), pos_ext.Y(), pos_ext.Z()),
883 loc_hit = mod.globalToLocal(
884 ROOT.CLHEP.Hep3Vector(pos2d.X(), pos2d.Y(), pos2d.Z()),
886 diff = loc_ext - loc_hit
887 if (abs(diff.z()) > mod.getZStripWidth()
or
888 abs(diff.y()) > mod.getPhiStripWidth()):
892 dist_cm = mod.getPropagationDistance(loc_ext, digit.getStrip(), is_phi)
894 Trec = digit.getTime()
895 Tcable = (cable_db.obj().getTimeDelay(cid)
896 if cable_db.isValid()
else 0.0)
897 Tprop = dist_cm * delay
898 t0_est = Trec - Tcable - Tprop - Tfly
900 if not math.isfinite(t0_est):
905 'Trec': Trec,
'Tcable': Tcable,
906 'Tprop': Tprop,
'Tfly': Tfly,
907 'section': hit2d.getSection(),
908 'sector': hit2d.getSector(),
915 Accumulate BKLM RPC hits for *track*, filtered by readout direction.
920 True → accumulate phi-plane digits only.
921 False → accumulate z-plane digits only.
925 results : list of dict (same fields as _accumulate_eklm)
932 for hit2d
in track.getRelationsTo[
'Belle2::KLMHit2d'](
'KLMHit2ds'):
933 if hit2d.getSubdetector() != Belle2.KLMElementNumbers.c_BKLM:
935 if not hit2d.inRPC():
937 if hit2d.isOutOfTime():
941 hit2d.getSection(), hit2d.getSector(), hit2d.getLayer())
942 pos2d = hit2d.getPosition()
944 for b1d
in hit2d.getRelationsTo[
'Belle2::BKLMHit1d'](
'BKLMHit1ds'):
945 is_phi = b1d.isPhiReadout()
946 if accept_phi
and not is_phi:
948 if not accept_phi
and is_phi:
951 for digit
in b1d.getRelationsTo[
'Belle2::KLMDigit'](
'KLMDigits'):
952 if not digit.inRPC():
954 if not digit.isGood():
957 cid = digit.getUniqueChannelID()
958 if (status_db.isValid()
and
959 status_db.obj().getChannelStatus(cid) !=
960 Belle2.KLMChannelStatus.c_Normal):
964 module_key = self.
_elem_num.moduleNumber(
965 digit.getSubdetector(),
966 digit.getSection(), digit.getSector(), digit.getLayer())
967 pair = rpc_map.get(module_key)
970 entry_ext, exit_ext = pair
972 Tfly = 0.5 * (entry_ext.getTOF() + exit_ext.getTOF())
973 pos_ext = self.
_midpoint(entry_ext.getPosition(), exit_ext.getPosition())
975 loc_ext = mod.globalToLocal(
976 ROOT.CLHEP.Hep3Vector(pos_ext.X(), pos_ext.Y(), pos_ext.Z()),
978 loc_hit = mod.globalToLocal(
979 ROOT.CLHEP.Hep3Vector(pos2d.X(), pos2d.Y(), pos2d.Z()),
981 diff = loc_ext - loc_hit
982 if (abs(diff.z()) > mod.getZStripWidth()
or
983 abs(diff.y()) > mod.getPhiStripWidth()):
987 propa_v = mod.getPropagationDistance(loc_ext)
988 dist_cm = propa_v.y()
if is_phi
else propa_v.z()
990 Trec = digit.getTime()
991 Tcable = (cable_db.obj().getTimeDelay(cid)
992 if cable_db.isValid()
else 0.0)
993 Tprop = dist_cm * delay
994 t0_est = Trec - Tcable - Tprop - Tfly
996 if not math.isfinite(t0_est):
1001 'Trec': Trec,
'Tcable': Tcable,
1002 'Tprop': Tprop,
'Tfly': Tfly,
1003 'section': hit2d.getSection(),
1004 'sector': hit2d.getSector(),
1014 """Read muon tracks, compute per-track T0, fill all histograms."""
1019 is_mc = mc_ip.isValid()
1020 if h.get(
'sample_type'):
1021 h[
'sample_type'].Fill(2
if is_mc
else 1)
1024 if not muon_list.isValid():
1026 n_particles = muon_list.obj().getListSize()
1027 if n_particles == 0:
1033 trk_B, trk_E, trk_R, trk_Rphi, trk_Rz = [], [], [], [], []
1035 for i
in range(n_particles):
1036 particle = muon_list.obj().getParticle(i)
1039 track = particle.getTrack()
1042 charge = int(particle.getCharge())
1054 for det_key, hits
in ((
'E', hits_E), (
'B', hits_B),
1055 (
'R', hits_Rphi + hits_Rz)):
1057 for comp
in (
'Trec',
'Tcable',
'Tprop',
'Tfly'):
1058 hh = h.get(f
'{comp}_{det_key}')
1063 def _trk_result(hits, h_nhits, h_sem):
1064 """Compute (mu, sem, dom_sector, dom_section) for a hit list."""
1067 vals = [hd[
't0_est']
for hd
in hits]
1068 mu, sem = _mu_sem(vals)
1069 if mu
is None or not math.isfinite(mu):
1072 dom_sec = int(round(sum(hd[
'sector']
for hd
in hits) / n))
1073 dom_section = int(round(sum(hd[
'section']
for hd
in hits) / n))
1076 if h_sem
and sem
is not None:
1078 return mu, (sem
if sem
is not None else 0.0), dom_sec, dom_section
1080 res_B = _trk_result(hits_B, h.get(
'nhits_B'), h.get(
'sem_B'))
1081 res_E = _trk_result(hits_E, h.get(
'nhits_E'), h.get(
'sem_E'))
1082 res_Rphi = _trk_result(hits_Rphi,
None,
None)
1083 res_Rz = _trk_result(hits_Rz,
None,
None)
1086 hits_R = hits_Rphi + hits_Rz
1087 res_R = _trk_result(hits_R,
None,
None)
1090 mu, se, sec, section = res_B
1091 if h.get(
't0trk_B'):
1092 h[
't0trk_B'].Fill(mu)
1093 trk_B.append({
'mu': mu,
'sem': se,
'charge': charge,
1094 'sector': sec,
'section': section})
1097 mu, se, sec, section = res_E
1098 if h.get(
't0trk_E'):
1099 h[
't0trk_E'].Fill(mu)
1100 trk_E.append({
'mu': mu,
'sem': se,
'charge': charge,
1101 'sector': sec,
'section': section})
1104 mu, se, sec, section = res_R
1105 if h.get(
't0trk_R'):
1106 h[
't0trk_R'].Fill(mu)
1107 trk_R.append({
'mu': mu,
'sem': se,
'charge': charge,
1108 'sector': sec,
'section': section})
1111 mu, se, sec, section = res_Rphi
1112 trk_Rphi.append({
'mu': mu,
'sem': se,
'charge': charge,
1113 'sector': sec,
'section': section})
1116 mu, se, sec, section = res_Rz
1117 trk_Rz.append({
'mu': mu,
'sem': se,
'charge': charge,
1118 'sector': sec,
'section': section})
1120 if not any([trk_B, trk_E, trk_R]):
1124 def _evt_t0(trk_list):
1125 pairs = [(t[
'mu'], t[
'sem'])
for t
in trk_list]
1126 return _weighted_mean(pairs)
1128 muB, seB = _evt_t0(trk_B)
1129 muE, seE = _evt_t0(trk_E)
1130 muR, seR = _evt_t0(trk_R)
1131 muRphi, seRphi = _evt_t0(trk_Rphi)
1132 muRz, seRz = _evt_t0(trk_Rz)
1134 def _safe_fill(key, val):
1136 if hh
and val
is not None and math.isfinite(val):
1139 _safe_fill(
't0evt_B', muB)
1140 _safe_fill(
't0evt_E', muE)
1141 _safe_fill(
't0evt_R', muR)
1144 all_parts = [(mu, se)
for mu, se
in ((muB, seB), (muE, seE), (muR, seR))
1145 if mu
is not None and math.isfinite(mu)]
1146 muAll, _ = _weighted_mean(all_parts)
1147 _safe_fill(
't0evt_all', muAll)
1150 useB = muB
is not None and math.isfinite(muB)
1151 useE = muE
is not None and math.isfinite(muE)
1152 useR = muR
is not None and math.isfinite(muR)
1153 if any((useB, useE, useR)):
1154 src = (useB, useE, useR)
1156 (
True,
False,
False): 1,
1157 (
False,
True,
False): 2,
1158 (
False,
False,
True): 3,
1159 (
True,
True,
False): 4,
1160 (
True,
False,
True): 5,
1161 (
False,
True,
True): 6,
1162 (
True,
True,
True): 7,
1164 sb = src_map.get(src)
1165 if sb
and h.get(
'final_source'):
1166 h[
'final_source'].Fill(sb)
1169 def _combine(parts):
1170 """Inverse-variance combine non-None (mu, se) pairs."""
1171 valid = [(mu, se)
for mu, se
in parts
1172 if mu
is not None and math.isfinite(mu)]
1173 return _weighted_mean(valid)
1175 t0_scint, _ = _combine([(muB, seB), (muE, seE)])
1176 t0_rpc, _ = _combine([(muB, seB), (muE, seE), (muR, seR)])
1177 t0_rpcdir, _ = _combine([(muB, seB), (muE, seE),
1178 (muRphi, seRphi), (muRz, seRz)])
1179 _safe_fill(
'final_scint', t0_scint)
1180 _safe_fill(
'final_rpc', t0_rpc)
1181 _safe_fill(
'final_rpcdir', t0_rpcdir)
1184 self.
_fill_pairs(trk_B, h.get(
'pull_B'), h.get(
'res_B'),
1185 h.get(
'ppw_B'), h.get(
'rpw_B'), bklm=
True)
1186 self.
_fill_pairs(trk_E, h.get(
'pull_E'), h.get(
'res_E'),
1187 h.get(
'ppw_E'), h.get(
'rpw_E'), bklm=
False)
1188 self.
_fill_pairs(trk_Rphi, h.get(
'pull_Rphi'), h.get(
'res_Rphi'),
1189 h.get(
'ppw_Rphi'), h.get(
'rpw_Rphi'), bklm=
True)
1190 self.
_fill_pairs(trk_Rz, h.get(
'pull_Rz'), h.get(
'res_Rz'),
1191 h.get(
'ppw_Rz'), h.get(
'rpw_Rz'), bklm=
True)
1192 self.
_fill_pairs(trk_R, h.get(
'pull_R'), h.get(
'res_R'),
1193 None,
None, bklm=
True)
1196 def _cross_pull(muX, seX, muY, seY, h_pull, h_res):
1197 if muX
is None or muY
is None:
1199 if not (math.isfinite(muX)
and math.isfinite(muY)):
1202 if h_res
and math.isfinite(dt):
1204 if (seX
is not None and seY
is not None and
1205 seX > 0
and seY > 0):
1206 denom2 = seX * seX + seY * seY
1207 pull = dt / math.sqrt(denom2)
1208 if h_pull
and math.isfinite(pull):
1211 _cross_pull(muB, seB, muE, seE, h.get(
'pull_BvE'), h.get(
'res_BvE'))
1212 _cross_pull(muB, seB, muR, seR, h.get(
'pull_BvR'), h.get(
'res_BvR'))
1213 _cross_pull(muE, seE, muR, seR, h.get(
'pull_EvR'), h.get(
'res_EvR'))
1218 def _eklm_region_t0(trk_list, target_section):
1219 """Per-event T0 from tracks in a specific EKLM section."""
1220 filtered = [t
for t
in trk_list
if t[
'section'] == target_section]
1223 return _weighted_mean([(t[
'mu'], t[
'sem'])
for t
in filtered])
1226 _REG_EKLM_BWD: _eklm_region_t0(trk_E, _EKLM_SECTION_BWD),
1227 _REG_EKLM_FWD: _eklm_region_t0(trk_E, _EKLM_SECTION_FWD),
1228 _REG_BKLM_RPC: (muR, seR),
1229 _REG_BKLM_SCINT: (muB, seB),
1231 dt0_h = h.get(
'dt0', {})
1232 for ri
in range(_N_REGIONS):
1233 mu_i, se_i = reg_t0[ri]
1234 if mu_i
is None or not math.isfinite(mu_i):
1236 for rj
in range(_N_REGIONS):
1237 mu_j, se_j = reg_t0[rj]
1238 if mu_j
is None or not math.isfinite(mu_j):
1241 hh = dt0_h.get(ri, {}).get(rj)
1242 if hh
and math.isfinite(dt):
1246 def _dimuon_fill(trk_list, h_hist):
1253 elif t[
'charge'] < 0:
1255 if (t0p
is not None and t0m
is not None and
1256 math.isfinite(t0p)
and math.isfinite(t0m)):
1257 h_hist.Fill(t0p - t0m)
1259 _dimuon_fill(trk_B, h.get(
'dimuon_B'))
1260 _dimuon_fill(trk_E, h.get(
'dimuon_E'))
1261 _dimuon_fill(trk_R, h.get(
'dimuon_R'))
1264 trk_all_by_charge = {}
1265 for lst
in (trk_B, trk_E, trk_R):
1268 if q
not in trk_all_by_charge:
1269 trk_all_by_charge[q] = t
1270 _dimuon_fill(list(trk_all_by_charge.values()), h.get(
'dimuon_all'))
1274 _dimuon_fill(trk_scint, h.get(
'dimuon_scint'))
1278 _dimuon_fill(trk_all_cats, h.get(
'dimuon_rpc'))
1282 _dimuon_fill(trk_rpcdir_cats, h.get(
'dimuon_rpcdir'))
1374 Fit Gaussian to pull/residual/dimuon histograms and fill
1375 summary histograms (mean, sigma per category; 2D pairwise summaries;
1376 per-track T0 resolution from dimuon ΔT0).
1381 for ib, (key, label)
in enumerate([(
'pull_B',
'BKLM Scint'),
1382 (
'pull_E',
'EKLM Scint'),
1383 (
'pull_Rphi',
'RPC Phi'),
1384 (
'pull_Rz',
'RPC Z')], 1):
1385 mu, muE, sig, sigE = _gauss_fit(h.get(key), -5.0, 5.0)
1387 h[
'pull_smean'].SetBinContent(ib, mu)
1388 h[
'pull_smean'].SetBinError(ib, muE)
1389 h[
'pull_swidth'].SetBinContent(ib, sig)
1390 h[
'pull_swidth'].SetBinError(ib, sigE)
1391 basf2.B2INFO(f
'KLMEventT0Validation: {label} pull fit:'
1392 f
' mean={mu:.3f}±{muE:.3f} sigma={sig:.3f}±{sigE:.3f}')
1395 for ib, (key, label)
in enumerate([(
'res_B',
'BKLM Scint'),
1396 (
'res_E',
'EKLM Scint'),
1397 (
'res_Rphi',
'RPC Phi'),
1398 (
'res_Rz',
'RPC Z')], 1):
1399 mu, muE, sig, sigE = _gauss_fit(h.get(key), -25.0, 25.0)
1401 h[
'res_smean'].SetBinContent(ib, mu)
1402 h[
'res_smean'].SetBinError(ib, muE)
1403 h[
'res_swidth'].SetBinContent(ib, sig)
1404 h[
'res_swidth'].SetBinError(ib, sigE)
1408 (
'ppw_B',
'ppw_2d_B_mean',
'ppw_2d_B_sigma', _N_BKLM_SECTORS),
1409 (
'ppw_E',
'ppw_2d_E_mean',
'ppw_2d_E_sigma', _N_EKLM_SECTORS),
1410 (
'ppw_Rphi',
'ppw_2d_Rphi_mean',
'ppw_2d_Rphi_sigma', _N_BKLM_SECTORS),
1411 (
'ppw_Rz',
'ppw_2d_Rz_mean',
'ppw_2d_Rz_sigma', _N_BKLM_SECTORS),
1413 for pw_key, mean_key, sig_key, n
in _pairwise_cfgs:
1414 pw = h.get(pw_key, {})
1415 h2m = h.get(mean_key)
1416 h2s = h.get(sig_key)
1419 mu, _, sig, _ = _gauss_fit(pw.get(i, {}).get(j), -5.0, 5.0)
1422 h2m.SetBinContent(i + 1, j + 1, mu)
1424 h2s.SetBinContent(i + 1, j + 1, sig)
1428 (
'rpw_B',
'rpw_2d_B_mean',
'rpw_2d_B_sigma', _N_BKLM_SECTORS),
1429 (
'rpw_E',
'rpw_2d_E_mean',
'rpw_2d_E_sigma', _N_EKLM_SECTORS),
1430 (
'rpw_Rphi',
'rpw_2d_Rphi_mean',
'rpw_2d_Rphi_sigma', _N_BKLM_SECTORS),
1431 (
'rpw_Rz',
'rpw_2d_Rz_mean',
'rpw_2d_Rz_sigma', _N_BKLM_SECTORS),
1433 for pw_key, mean_key, sig_key, n
in _rpairwise_cfgs:
1434 pw = h.get(pw_key, {})
1435 h2m = h.get(mean_key)
1436 h2s = h.get(sig_key)
1439 mu, _, sig, _ = _gauss_fit(pw.get(i, {}).get(j), -25.0, 25.0)
1442 h2m.SetBinContent(i + 1, j + 1, mu)
1444 h2s.SetBinContent(i + 1, j + 1, sig)
1447 dt0_h = h.get(
'dt0', {})
1448 h2m = h.get(
'dt0_2d_mean')
1449 h2s = h.get(
'dt0_2d_sigma')
1450 h2e = h.get(
'dt0_2d_entries')
1451 for ri
in range(_N_REGIONS):
1452 for rj
in range(_N_REGIONS):
1453 hh = dt0_h.get(ri, {}).get(rj)
1457 h2e.SetBinContent(ri + 1, rj + 1, hh.GetEntries())
1458 mu, _, sig, _ = _gauss_fit(hh, -25.0, 25.0)
1461 h2m.SetBinContent(ri + 1, rj + 1, mu)
1463 h2s.SetBinContent(ri + 1, rj + 1, sig)
1467 h_res = h.get(
'resolution')
1468 for ib, dm_key
in enumerate(
1469 [
'dimuon_B',
'dimuon_R',
'dimuon_E',
1470 'dimuon_scint',
'dimuon_rpc',
'dimuon_rpcdir'], 1):
1471 _, _, sig, sigE = _gauss_fit(h.get(dm_key), -40.0, 40.0)
1472 if sig
is not None and h_res:
1473 per_track = sig / math.sqrt(2.0)
1474 per_track_err = sigE / math.sqrt(2.0)
1475 h_res.SetBinContent(ib, per_track)
1476 h_res.SetBinError(ib, per_track_err)