Belle II Software development
KLMEventT0EstimatorModule.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9/* Own header. */
10#include <klm/modules/KLMEventT0Estimator/KLMEventT0EstimatorModule.h>
11
12/* KLM headers — only those not already pulled in via the own header. */
13#include <klm/bklm/geometry/Module.h>
14#include <klm/dataobjects/bklm/BKLMElementNumbers.h>
15#include <klm/dataobjects/eklm/EKLMElementNumbers.h>
16
17/* Basf2 headers — only those not already pulled in via the own header. */
18#include <framework/gearbox/Const.h>
19#include <framework/logging/Logger.h>
20#include <framework/datastore/DataStore.h>
21
22using namespace Belle2;
23using namespace Belle2::bklm;
24using namespace Belle2::EKLM;
25
26REG_MODULE(KLMEventT0Estimator);
27
28/* Constructor and destructor. */
29
30KLMEventT0EstimatorModule::KLMEventT0EstimatorModule() :
32 m_geoParB(nullptr),
33 m_geoParE(nullptr),
34 m_transformE(nullptr)
35{
36 setDescription("Estimate per-event T0 using KLM digits matched to extrapolated tracks (BKLM/EKLM scintillators and RPC) with per-event track averages, uncertainties, and final combined KLM value.");
37 setPropertyFlags(c_ParallelProcessingCertified);
38
39 addParam("MuonListName", m_MuonListName,
40 "Muon (or generic) ParticleList name used to access tracks and KLM relations (e.g. 'mu+:forT0').",
41 std::string("mu+:forT0"));
42 addParam("useCDCTemporaryT0", m_useCDCTemporaryT0,
43 "Read CDC temporary EventT0 as a seed/diagnostic (not applied to the mean).",
44 true);
45 addParam("IgnoreBackwardPropagation", m_ignoreBackward,
46 "Ignore backward-propagated ExtHits when forming entry/exit pairs.",
47 false);
48 addParam("histogramDirectoryName", m_histDirName,
49 "Top directory for KLMEventT0Estimator histograms inside the ROOT file.",
50 std::string("KLMEventT0Estimator"));
51 addParam("histogramSubdirUncorrected", m_histSubdirUncorr,
52 "Subdirectory name for uncorrected timing histograms.",
53 std::string("uncorrected"));
54
55 // ADC cut parameters
56 addParam("ADCCut_BKLM_Scint_Min", m_ADCCut_BKLM_Scint_Min,
57 "Minimum ADC charge cut for BKLM scintillator. Set to 0 to disable lower cut.",
58 30.0);
59 addParam("ADCCut_BKLM_Scint_Max", m_ADCCut_BKLM_Scint_Max,
60 "Maximum ADC charge cut for BKLM scintillator. Set to large value to disable upper cut.",
61 320.0);
62 addParam("ADCCut_EKLM_Scint_Min", m_ADCCut_EKLM_Scint_Min,
63 "Minimum ADC charge cut for EKLM scintillator. Set to 0 to disable lower cut.",
64 40.0);
65 addParam("ADCCut_EKLM_Scint_Max", m_ADCCut_EKLM_Scint_Max,
66 "Maximum ADC charge cut for EKLM scintillator. Set to large value to disable upper cut.",
67 350.0);
68
69}
70
71KLMEventT0EstimatorModule::~KLMEventT0EstimatorModule()
72{
73 delete m_transformE;
74}
75
76/* Histogram definition. */
77
79{
80 // Parent directory for this module
81 TDirectory* topdir = gDirectory->mkdir(m_histDirName.c_str());
82 TDirectory::TContext ctxTop{gDirectory, topdir};
83
84 // Subdirectory for uncorrected timing histograms (always created)
85 TDirectory* d_unc = topdir->mkdir(m_histSubdirUncorr.c_str());
86
87 auto H1 = [](const char* n, const char* t, int nb, double lo, double hi) {
88 return new TH1D(n, t, nb, lo, hi);
89 };
90
91 /* Uncorrected timing histograms. */
92 {
93 TDirectory::TContext ctxUnc{gDirectory, d_unc};
94
95 // --- per_track/ subdirectory ---
96 TDirectory* d_per_track = d_unc->mkdir("per_track");
97 {
98 TDirectory::TContext ctxTrk{gDirectory, d_per_track};
99 m_hT0Trk_BKLM_Scint = H1("h_t0trk_bklm_scint", "Per-track T0 (BKLM Scint);T0 [ns]", 800, -100, 100);
100 m_hT0Trk_BKLM_RPC = H1("h_t0trk_bklm_rpc", "Per-track T0 (BKLM RPC);T0 [ns]", 800, -100, 100);
101 m_hT0Trk_EKLM_Scint = H1("h_t0trk_eklm_scint", "Per-track T0 (EKLM Scint);T0 [ns]", 800, -100, 100);
102 }
103
104 // --- per_event/ subdirectory ---
105 TDirectory* d_per_event = d_unc->mkdir("per_event");
106 {
107 TDirectory::TContext ctxEvt{gDirectory, d_per_event};
108
109 // Track-average
110 m_hT0Evt_TrkAvg_BKLM_Scint = H1("h_t0evt_trkavg_bklm_scint", "Per-event T0 (track-avg, BKLM Scint);T0 [ns]", 800, -100,
111 100);
112 m_hT0Evt_TrkAvg_BKLM_RPC = H1("h_t0evt_trkavg_bklm_rpc", "Per-event T0 (track-avg, BKLM RPC);T0 [ns]", 800, -100,
113 100);
114 m_hT0Evt_TrkAvg_EKLM_Scint = H1("h_t0evt_trkavg_eklm_scint", "Per-event T0 (track-avg, EKLM Scint);T0 [ns]", 800, -100,
115 100);
116 m_hT0Evt_TrkAvg_All = H1("h_t0evt_trkavg_all", "Per-event T0 (track-avg, all categories);T0 [ns]", 800, -100,
117 100);
118 m_hT0Evt_TrkAvg_BKLM_Scint_SEM = H1("h_t0evt_trkavg_bklm_scint_sem", "SEM (track-avg, BKLM Scint);SEM [ns]", 800, 0.0, 20.0);
119 m_hT0Evt_TrkAvg_BKLM_RPC_SEM = H1("h_t0evt_trkavg_bklm_rpc_sem", "SEM (track-avg, BKLM RPC);SEM [ns]", 800, 0.0, 20.0);
120 m_hT0Evt_TrkAvg_EKLM_Scint_SEM = H1("h_t0evt_trkavg_eklm_scint_sem", "SEM (track-avg, EKLM Scint);SEM [ns]", 800, 0.0, 20.0);
121 m_hT0Evt_TrkAvg_All_SEM = H1("h_t0evt_trkavg_all_sem", "SEM (track-avg, all categories);SEM [ns]", 800, 0.0, 20.0);
122
123 // Final-source audit: all possible combinations of B(KLM Scint), E(KLM Scint), R(PC)
124 m_hFinalSource = new TH1I("h_final_source", "Final KLM source;;events", 7, 0.5, 7.5);
125 m_hFinalSource->GetXaxis()->SetBinLabel(1, "B only");
126 m_hFinalSource->GetXaxis()->SetBinLabel(2, "E only");
127 m_hFinalSource->GetXaxis()->SetBinLabel(3, "R only");
128 m_hFinalSource->GetXaxis()->SetBinLabel(4, "B+E");
129 m_hFinalSource->GetXaxis()->SetBinLabel(5, "B+R");
130 m_hFinalSource->GetXaxis()->SetBinLabel(6, "E+R");
131 m_hFinalSource->GetXaxis()->SetBinLabel(7, "B+E+R");
132 }
133 } // end uncorrected/ directory
134
135}
136
137/* Lifecycle. */
138
140{
141 // Register that we define histograms
142 REG_HISTOGRAM;
143
144 // Inputs
145 m_MuonList.isRequired(m_MuonListName);
146 m_tracks.isRequired();
147
148 // Geometry
152
153 // Log ADC cut settings
154 B2DEBUG(20, "KLMEventT0Estimator: ADC cuts configured:"
155 << LogVar("BKLM Scint min", m_ADCCut_BKLM_Scint_Min)
156 << LogVar("BKLM Scint max", m_ADCCut_BKLM_Scint_Max)
157 << LogVar("EKLM Scint min", m_ADCCut_EKLM_Scint_Min)
158 << LogVar("EKLM Scint max", m_ADCCut_EKLM_Scint_Max));
159}
160
162{
163 if (!m_eventT0HitResolution.isValid())
164 B2FATAL("KLMEventT0Estimator: KLM EventT0 hit resolution data are not available.");
165
166 B2DEBUG(20, "KLMEventT0Estimator: Using calibrated per-hit resolution."
167 << LogVar("sigma_RPC (ns)", m_eventT0HitResolution->getSigmaRPC())
168 << LogVar("sigma_BKLM_Scint (ns)", m_eventT0HitResolution->getSigmaBKLMScint())
169 << LogVar("sigma_EKLM_Scint (ns)", m_eventT0HitResolution->getSigmaEKLMScint()));
170}
171
172/* Helper methods. */
173
174bool KLMEventT0EstimatorModule::passesADCCut(double charge, int subdetector, int layer, bool inRPC) const
175{
176 // RPC: No ADC cut applied
177 if (subdetector == KLMElementNumbers::c_BKLM && (inRPC || layer >= BKLMElementNumbers::c_FirstRPCLayer)) {
178 return true;
179 }
180
181 // BKLM Scintillator
182 if (subdetector == KLMElementNumbers::c_BKLM) {
183 return (charge >= m_ADCCut_BKLM_Scint_Min && charge <= m_ADCCut_BKLM_Scint_Max);
184 }
185
186 // EKLM Scintillator
187 if (subdetector == KLMElementNumbers::c_EKLM) {
188 return (charge >= m_ADCCut_EKLM_Scint_Min && charge <= m_ADCCut_EKLM_Scint_Max);
189 }
190
191 return true;
192}
193
194double KLMEventT0EstimatorModule::getHitSigma(int subdetector, int layer, bool inRPC) const
195{
196 if (!m_eventT0HitResolution.isValid()) {
197 B2ERROR("KLMEventT0Estimator: Calibrated hit resolution payload not available!");
198 return 1.0; // Fallback
199 }
200
201 if (subdetector == KLMElementNumbers::c_BKLM) {
202 if (inRPC || layer >= BKLMElementNumbers::c_FirstRPCLayer) {
203 // Both readout directions use the RPC resolution measured with the phi and z hits together.
204 return m_eventT0HitResolution->getSigmaRPC();
205 } else {
206 return m_eventT0HitResolution->getSigmaBKLMScint();
207 }
208 } else { // EKLM
209 return m_eventT0HitResolution->getSigmaEKLMScint();
210 }
211}
212
214KLMEventT0EstimatorModule::matchExt(unsigned int key, ExtMap& v_ExtHits)
215{
216 ExtHit* entryHit = nullptr;
217 ExtHit* exitHit = nullptr;
218 auto itlow = v_ExtHits.lower_bound(key);
219 auto itup = v_ExtHits.upper_bound(key);
220 for (auto it = itlow; it != itup; ++it) {
221 if (!entryHit || it->second.getTOF() < entryHit->getTOF()) entryHit = &(it->second);
222 if (!exitHit || it->second.getTOF() > exitHit->getTOF()) exitHit = &(it->second);
223 }
224 return std::make_pair(entryHit, exitHit);
225}
226
228 ExtMap& scintMap,
229 ExtMap& rpcMap)
230{
231 scintMap.clear();
232 rpcMap.clear();
233
234 RelationVector<ExtHit> extHits = track->getRelationsTo<ExtHit>();
235 KLMMuidLikelihood* muidLikelihood = track->getRelatedTo<KLMMuidLikelihood>();
236
237 for (const ExtHit& eHit : extHits) {
238 if (eHit.getStatus() != EXT_EXIT) continue;
239 if (m_ignoreBackward && eHit.isBackwardPropagated()) continue;
240
241 const bool isB = (eHit.getDetectorID() == Const::EDetector::BKLM);
242 const bool isE = (eHit.getDetectorID() == Const::EDetector::EKLM);
243 if (!isB && !isE) continue;
244
245 int copyId = eHit.getCopyID();
246 int tFor, tSec, tLay, tPla, tStr;
247 int tSub = -1;
248
249 if (isE) {
252 &tFor, &tLay, &tSec, &tPla, &tStr);
253 }
254 if (isB) {
257 &tFor, &tSec, &tLay, &tPla, &tStr);
258 }
259 if (tSub < 0) continue;
260
261 if (isB) {
262 const bool crossed = muidLikelihood
263 ? muidLikelihood->isExtrapolatedBarrelLayerCrossed(tLay - 1)
264 : true;
265 if (!crossed) continue;
266
267 const bool isRPC = (tLay >= BKLMElementNumbers::c_FirstRPCLayer);
268
269 if (isRPC) {
270 // RPC: match by module
271 unsigned int moduleKey =
272 m_elementNum->moduleNumber(tSub, tFor, tSec, tLay);
273 rpcMap.insert(std::make_pair(moduleKey, eHit));
274 } else {
275 // BKLM scintillator: match by channel
276 unsigned int channelKey =
277 m_elementNum->channelNumber(tSub, tFor, tSec, tLay, tPla, tStr);
278 if (m_channelStatus.isValid() &&
279 m_channelStatus->getChannelStatus(channelKey) != KLMChannelStatus::c_Normal)
280 continue;
281 scintMap.insert(std::make_pair(channelKey, eHit));
282 }
283 }
284
285 if (isE) {
286 const bool crossed = muidLikelihood
287 ? muidLikelihood->isExtrapolatedEndcapLayerCrossed(tLay - 1)
288 : true;
289 if (!crossed) continue;
290
291 unsigned int channelKey =
292 m_elementNum->channelNumber(tSub, tFor, tSec, tLay, tPla, tStr);
293 if (m_channelStatus.isValid() &&
294 m_channelStatus->getChannelStatus(channelKey) != KLMChannelStatus::c_Normal)
295 continue;
296 scintMap.insert(std::make_pair(channelKey, eHit));
297 }
298 }
299}
300
301/* Per-subdetector accumulators. */
302
303namespace {
304 // Weighted accumulation using per-hit sigma; also counts the hit and its section and sector
305 inline void acc_stat_weighted(double t, double sigma, int section, int sector,
307 {
308 if (sigma <= 0.0 || !std::isfinite(sigma)) return;
309 const double w = 1.0 / (sigma * sigma);
310 sums.sumW += w;
311 sums.sumWT += w * t;
312 sums.nHits += 1;
313 sums.sumSector += sector;
314 sums.sumSection += section;
315 }
316}
317
318/* EKLM scintillator */
320 const ExtMap& scintMap,
321 CategorySums& sums)
322{
323 DBObjPtr<KLMTimeConstants> timeConstants;
324 DBObjPtr<KLMTimeCableDelay> timeCableDelay;
325
326 const double delayScint = timeConstants.isValid()
327 ? timeConstants->getDelay(KLMTimeConstants::c_EKLM)
328 : 0.0;
329
330 HepGeom::Point3D<double> hitGlobal_ext, hitLocal_ext;
331
332 for (const KLMHit2d& hit2d : klmHit2ds) {
333 if (hit2d.getSubdetector() != KLMElementNumbers::c_EKLM) continue;
334
335 RelationVector<KLMDigit> digits = hit2d.getRelationsTo<KLMDigit>();
336 if (digits.size() == 0) continue;
337
338 for (const KLMDigit& d : digits) {
339 if (!d.isGood()) continue;
340
341 unsigned int cid = d.getUniqueChannelID();
342 if (m_channelStatus.isValid() &&
343 m_channelStatus->getChannelStatus(cid) != KLMChannelStatus::c_Normal) continue;
344
345 // Apply ADC cut
346 if (!passesADCCut(d.getCharge(), KLMElementNumbers::c_EKLM, d.getLayer(), false)) {
347 continue;
348 }
349
350 // Match using channel ID
351 ExtPair ex = const_cast<KLMEventT0EstimatorModule*>(this)->matchExt(cid, const_cast<ExtMap&>(scintMap));
352 if (!ex.first || !ex.second) continue;
353 const double flyTime = 0.5 * (ex.first->getTOF() + ex.second->getTOF());
354
355 // Distance along strip to readout
356 const ROOT::Math::XYZVector posGlobExt = 0.5 * (ex.first->getPosition() + ex.second->getPosition());
357 hitGlobal_ext.setX(posGlobExt.X() / Unit::mm * CLHEP::mm);
358 hitGlobal_ext.setY(posGlobExt.Y() / Unit::mm * CLHEP::mm);
359 hitGlobal_ext.setZ(posGlobExt.Z() / Unit::mm * CLHEP::mm);
360
361 const double Lmm = m_geoParE->getStripLength(d.getStrip()) / CLHEP::mm * Unit::mm;
362 const HepGeom::Transform3D* tr = m_transformE->getStripGlobalToLocal(const_cast<KLMDigit*>(&d));
363 hitLocal_ext = (*tr) * hitGlobal_ext;
364 const double dist_mm = 0.5 * Lmm - hitLocal_ext.x() / CLHEP::mm * Unit::mm;
365
366 // Components
367 const double Trec = d.getTime();
368 const double Tcable = timeCableDelay.isValid() ? timeCableDelay->getTimeDelay(cid) : 0.0;
369 const double Tprop = dist_mm * delayScint;
370 const double Tfly = flyTime;
371
372 // Correct digit time
373 double t = Trec;
374 if (timeCableDelay.isValid()) t -= Tcable;
375 t -= Tprop;
376
377 const double t0_est = t - Tfly;
378 if (!std::isfinite(t0_est)) continue;
379
380 // Weighted accumulation using calibrated sigma
381 const double sigma = getHitSigma(KLMElementNumbers::c_EKLM, d.getLayer(), false);
382 acc_stat_weighted(t0_est, sigma, d.getSection(), d.getSector(), sums);
383 }
384 }
385}
386
387/* BKLM scintillator */
389 const ExtMap& scintMap,
390 CategorySums& sums)
391{
392 DBObjPtr<KLMTimeConstants> timeConstants;
393 DBObjPtr<KLMTimeCableDelay> timeCableDelay;
394
395 const double delayScint = timeConstants.isValid()
396 ? timeConstants->getDelay(KLMTimeConstants::c_BKLM)
397 : 0.0;
398
399 for (KLMHit2d& hit2d : klmHit2ds) {
400 if (hit2d.getSubdetector() != KLMElementNumbers::c_BKLM) continue;
401 if (hit2d.inRPC()) continue;
402 if (hit2d.isOutOfTime()) continue;
403
404 RelationVector<BKLMHit1d> b1ds = hit2d.getRelationsTo<BKLMHit1d>();
405 if (b1ds.size() == 0) continue;
406
407 const bklm::Module* mod = m_geoParB->findModule(hit2d.getSection(), hit2d.getSector(), hit2d.getLayer());
408 const ROOT::Math::XYZVector posG2d = hit2d.getPosition();
409
410 for (const BKLMHit1d& h1d : b1ds) {
411 RelationVector<KLMDigit> digits = h1d.getRelationsTo<KLMDigit>();
412
413 for (const KLMDigit& d : digits) {
414 if (d.inRPC() || !d.isGood()) continue;
415
416 unsigned int cid = d.getUniqueChannelID();
417 if (m_channelStatus.isValid() &&
418 m_channelStatus->getChannelStatus(cid) != KLMChannelStatus::c_Normal) continue;
419
420 // Apply ADC cut
421 if (!passesADCCut(d.getCharge(), KLMElementNumbers::c_BKLM, d.getLayer(), false)) {
422 continue;
423 }
424
425 // Match using channel ID
426 ExtPair p = const_cast<KLMEventT0EstimatorModule*>(this)->matchExt(cid, const_cast<ExtMap&>(scintMap));
427 if (!p.first || !p.second) continue;
428
429 const double flyTime = 0.5 * (p.first->getTOF() + p.second->getTOF());
430 const ROOT::Math::XYZVector posGext = 0.5 * (p.first->getPosition() + p.second->getPosition());
431
432 // Gate in local
433 const CLHEP::Hep3Vector locExt = mod->globalToLocal(CLHEP::Hep3Vector(posGext.X(), posGext.Y(), posGext.Z()), true);
434 const CLHEP::Hep3Vector locHit2 = mod->globalToLocal(CLHEP::Hep3Vector(posG2d.X(), posG2d.Y(), posG2d.Z()), true);
435 const CLHEP::Hep3Vector diff = locExt - locHit2;
436 if (std::fabs(diff.z()) > mod->getZStripWidth() || std::fabs(diff.y()) > mod->getPhiStripWidth()) continue;
437
438 // Prop distance
439 const bool isPhiReadout = h1d.isPhiReadout();
440 double propaLen = mod->getPropagationDistance(locExt, d.getStrip(), isPhiReadout);
441
442 // Components
443 const double Trec = d.getTime();
444 const double Tcable = timeCableDelay.isValid() ? timeCableDelay->getTimeDelay(cid) : 0.0;
445 const double Tprop = propaLen * delayScint;
446 const double Tfly = flyTime;
447
448 double t = Trec;
449 if (timeCableDelay.isValid()) t -= Tcable;
450 t -= Tprop;
451
452 const double t0_est = t - Tfly;
453 if (!std::isfinite(t0_est)) continue;
454
455 // Weighted accumulation using calibrated sigma
456 const double sigma = getHitSigma(KLMElementNumbers::c_BKLM, d.getLayer(), false);
457 acc_stat_weighted(t0_est, sigma, hit2d.getSection(), hit2d.getSector(), sums);
458 }
459 }
460 }
461}
462
463/* BKLM RPC */
465 const ExtMap& rpcMap,
466 CategorySums& sums)
467{
468 DBObjPtr<KLMTimeConstants> timeConstants;
469 DBObjPtr<KLMTimeCableDelay> timeCableDelay;
470
471 const double delayPhi = timeConstants.isValid()
472 ? timeConstants->getDelay(KLMTimeConstants::c_RPCPhi)
473 : 0.0;
474 const double delayZ = timeConstants.isValid()
475 ? timeConstants->getDelay(KLMTimeConstants::c_RPCZ)
476 : 0.0;
477
478 for (KLMHit2d& hit2d : klmHit2ds) {
479 if (hit2d.getSubdetector() != KLMElementNumbers::c_BKLM) continue;
480 if (!hit2d.inRPC()) continue;
481 if (hit2d.isOutOfTime()) continue;
482
483 RelationVector<BKLMHit1d> b1ds = hit2d.getRelationsTo<BKLMHit1d>();
484 if (b1ds.size() == 0) continue;
485
486 const bklm::Module* mod = m_geoParB->findModule(hit2d.getSection(), hit2d.getSector(), hit2d.getLayer());
487 const ROOT::Math::XYZVector posG2d = hit2d.getPosition();
488
489 for (const BKLMHit1d& h1d : b1ds) {
490 const bool isPhi = h1d.isPhiReadout();
491 RelationVector<KLMDigit> digits = h1d.getRelationsTo<KLMDigit>();
492
493 for (const KLMDigit& d : digits) {
494 if (!d.inRPC()) continue;
495
496 unsigned int cid = d.getUniqueChannelID();
497 if (m_channelStatus.isValid() &&
498 m_channelStatus->getChannelStatus(cid) != KLMChannelStatus::c_Normal) continue;
499
500 if (!d.isGood()) continue;
501
502 // RPC matched by module key
503 unsigned int moduleKey = m_elementNum->moduleNumber(d.getSubdetector(), d.getSection(), d.getSector(), d.getLayer());
504 ExtPair p = const_cast<KLMEventT0EstimatorModule*>(this)->matchExt(moduleKey, const_cast<ExtMap&>(rpcMap));
505 if (!p.first || !p.second) continue;
506
507 const double flyTime = 0.5 * (p.first->getTOF() + p.second->getTOF());
508 const ROOT::Math::XYZVector posGext = 0.5 * (p.first->getPosition() + p.second->getPosition());
509
510 const CLHEP::Hep3Vector locExt = mod->globalToLocal(CLHEP::Hep3Vector(posGext.X(), posGext.Y(), posGext.Z()), true);
511 const CLHEP::Hep3Vector locHit2 = mod->globalToLocal(CLHEP::Hep3Vector(posG2d.X(), posG2d.Y(), posG2d.Z()), true);
512
513 const CLHEP::Hep3Vector diff = locExt - locHit2;
514 if (std::fabs(diff.z()) > mod->getZStripWidth() || std::fabs(diff.y()) > mod->getPhiStripWidth()) continue;
515
516 const CLHEP::Hep3Vector propaV = mod->getPropagationDistance(locExt);
517 const double propaDist = isPhi ? propaV.y() : propaV.z();
518
519 // Components
520 const double Trec = d.getTime();
521 const double Tcable = timeCableDelay.isValid() ? timeCableDelay->getTimeDelay(cid) : 0.0;
522 const double Tprop = propaDist * (isPhi ? delayPhi : delayZ);
523 const double Tfly = flyTime;
524
525 double t = Trec;
526 if (timeCableDelay.isValid()) t -= Tcable;
527 t -= Tprop;
528
529 const double t0_est = t - Tfly;
530 if (!std::isfinite(t0_est)) continue;
531
532 // Weighted accumulation using calibrated sigma
533 const double sigma = getHitSigma(KLMElementNumbers::c_BKLM, d.getLayer(), true);
534 acc_stat_weighted(t0_est, sigma, hit2d.getSection(), hit2d.getSector(), sums);
535 }
536 }
537 }
538}
539
540/* Event. */
541
543{
544 // CDC seed for logging (not used to compute the means)
545 m_seedT0 = 0.0;
548 if (evtT0.isValid() && evtT0->hasTemporaryEventT0(Const::EDetector::CDC)) {
549 const auto best = evtT0->getBestCDCTemporaryEventT0();
550 if (best) m_seedT0 = best->eventT0;
551 }
552 }
553
554 if (!m_MuonList.isValid()) { B2WARNING("KLMEventT0Estimator: ParticleList '" << m_MuonListName << "' not found."); return; }
555 const unsigned nTracks = m_MuonList->getListSize();
556 if (nTracks == 0u) return;
557
558 // Weighted mean and uncertainty from running sums
559 auto weighted_result = [](double wsum, double wtsum) -> std::pair<double, double> {
560 if (wsum <= 0.0) return {NAN, NAN};
561 return {wtsum / wsum, std::sqrt(1.0 / wsum)};
562 };
563
564 // Weighted track averaging using inverse-variance (1/SEM²) weighting
565 auto mean_sem_tracks = [](const std::vector<std::pair<double, double>>& v) -> std::pair<double, double> {
566 if (v.empty()) return {NAN, NAN};
567 if (v.size() == 1)
568 {
569 return {v[0].first, std::isfinite(v[0].second) ? v[0].second : 0.0};
570 }
571
572 bool allValid = true;
573 for (const auto& [t0, sem] : v)
574 {
575 if (!std::isfinite(sem) || sem <= 0.0) { allValid = false; break; }
576 }
577
578 if (allValid)
579 {
580 double wsum = 0.0, wtsum = 0.0;
581 for (const auto& [t0, sem] : v) {
582 const double w = 1.0 / (sem * sem);
583 wsum += w;
584 wtsum += w * t0;
585 }
586 if (wsum > 0.0) {
587 return {wtsum / wsum, std::sqrt(1.0 / wsum)};
588 }
589 }
590
591 // Fallback to simple average if weights not valid
592 double s = 0.0;
593 for (const auto& [t0, sem] : v) s += t0;
594 const double mu = s / v.size();
595 double ss = 0.0;
596 for (const auto& [t0, sem] : v) { const double d = t0 - mu; ss += d * d; }
597 const double var = (v.size() > 1) ? ss / (v.size() - 1) : 0.0;
598 return {mu, std::sqrt(var / v.size())};
599 };
600
601 // Store the per-track result of one category as extra info of the muon
602 auto writeTrackInfo = [](Particle * particle, const std::string & category, double t0, double err,
603 const CategorySums & sums) {
604 particle->writeExtraInfo("klmTrackT0" + category, t0);
605 particle->writeExtraInfo("klmTrackT0Err" + category, err);
606 particle->writeExtraInfo("klmTrackNHits" + category, sums.nHits);
607 particle->writeExtraInfo("klmTrackSector" + category, std::round(double(sums.sumSector) / sums.nHits));
608 particle->writeExtraInfo("klmTrackSection" + category, std::round(double(sums.sumSection) / sums.nHits));
609 };
610
611 // For per-event track-averages: pairs of (T0, SEM) for weighted averaging
612 std::vector<std::pair<double, double>> vTrk_B, vTrk_R, vTrk_E, vTrk_All;
613
614 for (unsigned i = 0; i < nTracks; ++i) {
615 Particle* particle = m_MuonList->getParticle(i);
616 if (!particle) continue;
617 const Track* track = particle->getTrack();
618 if (!track) continue;
619
620 RelationVector<KLMHit2d> hit2ds = track->getRelationsTo<KLMHit2d>();
621 if (hit2ds.size() == 0) continue;
622
623 // Build ExtHit maps for this track
624 m_extScint.clear();
625 m_extRPC.clear();
627
628 // Per-track digit sums per category (weighted)
629 CategorySums sumsE, sumsB, sumsR;
630 accumulateEKLM(hit2ds, m_extScint, sumsE);
631 accumulateBKLMScint(hit2ds, m_extScint, sumsB);
632 accumulateBKLMRPC(hit2ds, m_extRPC, sumsR);
633 const double wE = sumsE.sumW, wTE = sumsE.sumWT;
634 const double wB = sumsB.sumW, wTB = sumsB.sumWT;
635 const double wR = sumsR.sumW, wTR = sumsR.sumWT;
636
637 // Per-track means and SEMs by category
638 if (wB > 0.0) {
639 auto [muB, seB] = weighted_result(wB, wTB);
640 if (m_hT0Trk_BKLM_Scint && std::isfinite(muB)) m_hT0Trk_BKLM_Scint->Fill(muB);
641 if (std::isfinite(muB)) {
642 vTrk_B.push_back({muB, seB});
643 writeTrackInfo(particle, "BKLMScint", muB, seB, sumsB);
644 }
645 }
646
647 if (wR > 0.0) {
648 auto [muR, seR] = weighted_result(wR, wTR);
649 if (m_hT0Trk_BKLM_RPC && std::isfinite(muR)) m_hT0Trk_BKLM_RPC->Fill(muR);
650 if (std::isfinite(muR)) {
651 vTrk_R.push_back({muR, seR});
652 writeTrackInfo(particle, "BKLMRPC", muR, seR, sumsR);
653 }
654 }
655
656 if (wE > 0.0) {
657 auto [muE, seE] = weighted_result(wE, wTE);
658 if (m_hT0Trk_EKLM_Scint && std::isfinite(muE)) m_hT0Trk_EKLM_Scint->Fill(muE);
659 if (std::isfinite(muE)) {
660 vTrk_E.push_back({muE, seE});
661 writeTrackInfo(particle, "EKLMScint", muE, seE, sumsE);
662 }
663 }
664
665 // Per-track overall (if any category present)
666 {
667 const double wAll = wB + wE + wR;
668 const double wtAll = wTB + wTE + wTR;
669 if (wAll > 0.0) {
670 const double t0 = wtAll / wAll;
671 const double se = std::sqrt(1.0 / wAll);
672 vTrk_All.push_back({t0, se});
673 particle->writeExtraInfo("klmTrackT0All", t0);
674 particle->writeExtraInfo("klmTrackT0ErrAll", se);
675 }
676 }
677 }
678
679 if (vTrk_All.empty()) {
680 B2DEBUG(20, "KLMEventT0Estimator: no usable KLM timing residuals for this event.");
681 return;
682 }
683
684 // Per-event track-averages using inverse-variance (1/SEM²) weighting
685 const auto [muB_trk, seB_trk] = mean_sem_tracks(vTrk_B);
686 const auto [muR_trk, seR_trk] = mean_sem_tracks(vTrk_R);
687 const auto [muE_trk, seE_trk] = mean_sem_tracks(vTrk_E);
688 const auto [muAll_trk, seAll_trk] = mean_sem_tracks(vTrk_All);
689
690 if (m_hT0Evt_TrkAvg_BKLM_Scint && std::isfinite(muB_trk)) m_hT0Evt_TrkAvg_BKLM_Scint->Fill(muB_trk);
691 if (m_hT0Evt_TrkAvg_BKLM_RPC && std::isfinite(muR_trk)) m_hT0Evt_TrkAvg_BKLM_RPC->Fill(muR_trk);
692 if (m_hT0Evt_TrkAvg_EKLM_Scint && std::isfinite(muE_trk)) m_hT0Evt_TrkAvg_EKLM_Scint->Fill(muE_trk);
693 if (m_hT0Evt_TrkAvg_All && std::isfinite(muAll_trk)) m_hT0Evt_TrkAvg_All->Fill(muAll_trk);
694
695 if (m_hT0Evt_TrkAvg_BKLM_Scint_SEM && std::isfinite(seB_trk)) m_hT0Evt_TrkAvg_BKLM_Scint_SEM->Fill(seB_trk);
696 if (m_hT0Evt_TrkAvg_BKLM_RPC_SEM && std::isfinite(seR_trk)) m_hT0Evt_TrkAvg_BKLM_RPC_SEM->Fill(seR_trk);
697 if (m_hT0Evt_TrkAvg_EKLM_Scint_SEM && std::isfinite(seE_trk)) m_hT0Evt_TrkAvg_EKLM_Scint_SEM->Fill(seE_trk);
698 if (m_hT0Evt_TrkAvg_All_SEM && std::isfinite(seAll_trk)) m_hT0Evt_TrkAvg_All_SEM->Fill(seAll_trk);
699
700 // Store the per-event result of each category in EventExtraInfo
701 if (!m_eventExtraInfo.isValid()) m_eventExtraInfo.create();
702 auto writeEventInfo = [this](const std::string & category, double t0, double err) {
703 if (!std::isfinite(t0)) return;
704 m_eventExtraInfo->setExtraInfo("klmEventT0" + category, t0);
705 m_eventExtraInfo->setExtraInfo("klmEventT0Err" + category, err);
706 };
707 writeEventInfo("BKLMScint", muB_trk, seB_trk);
708 writeEventInfo("BKLMRPC", muR_trk, seR_trk);
709 writeEventInfo("EKLMScint", muE_trk, seE_trk);
710
711 // ---------------- Final KLM combination (single saved component) ----------------
712 const bool useB = std::isfinite(muB_trk);
713 const bool useE = std::isfinite(muE_trk);
714 const bool useR = std::isfinite(muR_trk);
715
716 double finalT0 = NAN, finalSE = NAN;
717 int sourceBin = -1;
718
719 {
720 std::vector<std::pair<double, double>> parts;
721 if (useB) parts.emplace_back(muB_trk, seB_trk);
722 if (useE) parts.emplace_back(muE_trk, seE_trk);
723 if (useR) parts.emplace_back(muR_trk, seR_trk);
724
725 auto [t0, se] = mean_sem_tracks(parts);
726 finalT0 = t0;
727 finalSE = se;
728
729 if (useB && useE && useR) sourceBin = 7;
730 else if (useB && useE) sourceBin = 4;
731 else if (useB && useR) sourceBin = 5;
732 else if (useE && useR) sourceBin = 6;
733 else if (useB) sourceBin = 1;
734 else if (useE) sourceBin = 2;
735 else if (useR) sourceBin = 3;
736 }
737
738 B2DEBUG(20, "KLMEventT0Estimator: "
739 << "T0_trkavg_all=" << muAll_trk << " ns (seed CDC=" << m_seedT0 << " ns)"
740 << " | E=" << muE_trk << " | Bsc=" << muB_trk << " | Brpc=" << muR_trk
741 << (std::isfinite(finalT0) ? (std::string(" | FINAL KLM=") + std::to_string(finalT0) + " ns") : std::string("")));
742
744 if (!outT0.isValid()) outT0.construct();
745
746 if (std::isfinite(finalT0)) {
747 if (m_hFinalSource && sourceBin > 0) m_hFinalSource->Fill(sourceBin);
748 const double quality = static_cast<double>((useB ? 1 : 0) + (useE ? 1 : 0) + (useR ? 1 : 0));
749 EventT0::EventT0Component klmT0Component(finalT0, std::isfinite(finalSE) ? finalSE : 0.0,
750 Const::KLM, "KLM", quality);
751 // Stored alongside the other detectors' values; the event T0 itself is never changed here.
752 outT0->addTemporaryEventT0(klmT0Component);
753 }
754}
static void channelNumberToElementNumbers(KLMChannelNumber channel, int *section, int *sector, int *layer, int *plane, int *strip)
Get element numbers by channel number.
@ c_FirstRPCLayer
First RPC layer.
Store one reconstructed BKLM 1D hit as a ROOT object.
Definition BKLMHit1d.h:30
Class for accessing objects in the database.
Definition DBObjPtr.h:21
@ c_Event
Different object in each event, all objects/arrays are invalidated after event() function has been ca...
Definition DataStore.h:59
static const EKLMElementNumbers & Instance()
Instantiation.
void stripNumberToElementNumbers(int stripGlobal, int *section, int *layer, int *sector, int *plane, int *strip) const
Get element numbers by strip global number.
static const GeometryData & Instance(enum DataSource dataSource=c_Database, const GearDir *gearDir=nullptr)
Instantiation.
Transformation data.
@ c_None
Displacement is not used.
Store one Ext hit as a ROOT object.
Definition ExtHit.h:31
double getTOF() const
Get time of flight from the point of closest approach near the origin to this hit.
Definition ExtHit.h:136
HistoModule.h is supposed to be used instead of Module.h for the modules with histogram definitions t...
Definition HistoModule.h:29
@ c_Normal
Normally operating channel.
KLM digit (class representing a digitized hit in RPCs or scintillators).
Definition KLMDigit.h:29
Belle2::EKLM::TransformData * m_transformE
EKLM strip transformation data.
TH1D * m_hT0Evt_TrkAvg_BKLM_RPC_SEM
Per-event T0 track-average for BKLM RPC (SEM) [ns].
TH1D * m_hT0Evt_TrkAvg_EKLM_Scint_SEM
Per-event T0 track-average for EKLM scintillator (SEM) [ns].
TH1D * m_hT0Trk_BKLM_RPC
Per-track T0 for BKLM RPC [ns].
double m_ADCCut_BKLM_Scint_Min
Minimum ADC cut for BKLM scintillator.
TH1D * m_hT0Trk_EKLM_Scint
Per-track T0 for EKLM scintillator [ns].
TH1I * m_hFinalSource
Final EventT0 source selection (7 bins).
std::string m_histDirName
Parent directory inside the ROOT file (HistoManager) for this module.
ExtPair matchExt(unsigned int key, ExtMap &v_ExtHits)
Find earliest (entry) and latest (exit) ExtHits matching a key (channel or module).
const Belle2::EKLM::GeometryData * m_geoParE
EKLM geometry data.
double getHitSigma(int subdetector, int layer, bool inRPC) const
Get per-hit sigma for a digit based on detector category.
TH1D * m_hT0Trk_BKLM_Scint
Per-track T0 for BKLM scintillator [ns].
bool m_useCDCTemporaryT0
Use CDC temporary EventT0 as a diagnostic seed (not applied to averaging).
void initialize() override
Register inputs/params; get geometry; call REG_HISTOGRAM.
void event() override
Per-event algorithm: collect hits, compute residuals, fill outputs.
void accumulateEKLM(const RelationVector< KLMHit2d > &, const ExtMap &, CategorySums &sums)
Accumulate EKLM scintillator per-digit T0 estimates (weighted).
std::string m_histSubdirUncorr
Subdirectory name for uncorrected timing histograms.
DBObjPtr< KLMEventT0HitResolution > m_eventT0HitResolution
Per-hit time resolution for EventT0 estimation.
std::multimap< unsigned int, Belle2::ExtHit > ExtMap
Multimap of ExtHit objects keyed by channel or module number.
ExtMap m_extRPC
Extrapolated hits keyed by module number (RPC).
void accumulateBKLMScint(RelationVector< KLMHit2d > &, const ExtMap &, CategorySums &sums)
Accumulate BKLM scintillator per-digit T0 estimates (weighted).
TH1D * m_hT0Evt_TrkAvg_BKLM_Scint_SEM
Per-event T0 track-average for BKLM scintillator (SEM) [ns].
void collectExtrapolatedHits(const Track *track, ExtMap &scintMap, ExtMap &rpcMap)
Build maps of extrapolated hits for a track (scint: channel key; RPC: module key).
double m_ADCCut_BKLM_Scint_Max
Maximum ADC cut for BKLM scintillator.
DBObjPtr< KLMChannelStatus > m_channelStatus
Channel status (Normal/Dead/etc.).
StoreObjPtr< ParticleList > m_MuonList
Selected muon particle list.
void beginRun() override
Per-run resets if desired (histos remain booked).
StoreArray< Track > m_tracks
Reconstructed tracks.
double m_ADCCut_EKLM_Scint_Max
Maximum ADC cut for EKLM scintillator.
bool passesADCCut(double charge, int subdetector, int layer, bool inRPC) const
Check if a digit passes the ADC charge cut.
double m_ADCCut_EKLM_Scint_Min
Minimum ADC cut for EKLM scintillator.
Belle2::bklm::GeometryPar * m_geoParB
BKLM geometry.
bool m_ignoreBackward
Ignore backward-propagated ExtHits when forming entry/exit pairs.
TH1D * m_hT0Evt_TrkAvg_BKLM_Scint
Per-event T0 track-average for BKLM scintillator (mean) [ns].
std::pair< Belle2::ExtHit *, Belle2::ExtHit * > ExtPair
Pair of entry and exit ExtHit pointers.
double m_seedT0
Optional seed from CDC (for logging only).
std::string m_MuonListName
Input ParticleList (e.g.
StoreObjPtr< EventExtraInfo > m_eventExtraInfo
Event extra info, receives the per-event result of each category.
TH1D * m_hT0Evt_TrkAvg_All
Per-event T0 track-average combined (mean) [ns].
const KLMElementNumbers * m_elementNum
Element numbering helpers.
TH1D * m_hT0Evt_TrkAvg_All_SEM
Per-event T0 track-average combined (SEM) [ns].
TH1D * m_hT0Evt_TrkAvg_BKLM_RPC
Per-event T0 track-average for BKLM RPC (mean) [ns].
ExtMap m_extScint
Extrapolated hits keyed by channel number (scintillator).
void accumulateBKLMRPC(RelationVector< KLMHit2d > &klmHit2ds, const ExtMap &rpcMap, CategorySums &sums)
Accumulate BKLM RPC per-digit T0 estimates (weighted, both readout directions).
TH1D * m_hT0Evt_TrkAvg_EKLM_Scint
Per-event T0 track-average for EKLM scintillator (mean) [ns].
void defineHisto() override
Definition of histograms (called once by HistoManager).
KLM 2d hit.
Definition KLMHit2d.h:33
Class to store the likelihoods from KLM with additional information related to the extrapolation.
@ c_BKLM
BKLM scintillator.
@ c_EKLM
EKLM scintillator.
Class to store reconstructed particles.
Definition Particle.h:76
Class for type safe access to objects that are referred to in relations.
size_t size() const
Get number of relations.
Type-safe access to single objects in the data store.
Definition StoreObjPtr.h:96
bool isValid() const
Check whether the object was created.
bool construct(Args &&... params)
Construct an object of type T in this StoreObjPtr, using the provided constructor arguments.
Class that bundles various TrackFitResults.
Definition Track.h:25
static const double mm
[millimeters]
Definition Unit.h:70
static GeometryPar * instance(void)
Static method to get a reference to the singleton GeometryPar instance.
Define the geometry of a BKLM module Each sector [octant] contains Modules.
Definition Module.h:76
Class to store variables with their name which were sent to the logging service.
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition Module.h:649
Abstract base class for different kinds of events.
Structure for storing the extracted event t0s together with its detector and its uncertainty.
Definition EventT0.h:33
Running sums of the hits of one category on one track.
int sumSector
Sum of the sector numbers of the hits used.
double sumW
Sum of the hit weights 1/sigma^2 [1/ns^2].
double sumWT
Sum of weight times hit T0 [1/ns].
int sumSection
Sum of the section numbers of the hits used.