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, int plane) 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 // Use direction-specific RPC resolution if available (version 2+)
204 // Fall back to combined RPC resolution for backward compatibility
205 if (plane == BKLMElementNumbers::c_ZPlane) {
206 float sigmaZ = m_eventT0HitResolution->getSigmaRPCZ();
207 return (sigmaZ > 0.0) ? sigmaZ : m_eventT0HitResolution->getSigmaRPC();
208 } else {
209 float sigmaPhi = m_eventT0HitResolution->getSigmaRPCPhi();
210 return (sigmaPhi > 0.0) ? sigmaPhi : m_eventT0HitResolution->getSigmaRPC();
211 }
212 } else {
213 return m_eventT0HitResolution->getSigmaBKLMScint();
214 }
215 } else { // EKLM
216 return m_eventT0HitResolution->getSigmaEKLMScint();
217 }
218}
219
221KLMEventT0EstimatorModule::matchExt(unsigned int key, ExtMap& v_ExtHits)
222{
223 ExtHit* entryHit = nullptr;
224 ExtHit* exitHit = nullptr;
225 auto itlow = v_ExtHits.lower_bound(key);
226 auto itup = v_ExtHits.upper_bound(key);
227 for (auto it = itlow; it != itup; ++it) {
228 if (!entryHit || it->second.getTOF() < entryHit->getTOF()) entryHit = &(it->second);
229 if (!exitHit || it->second.getTOF() > exitHit->getTOF()) exitHit = &(it->second);
230 }
231 return std::make_pair(entryHit, exitHit);
232}
233
235 ExtMap& scintMap,
236 ExtMap& rpcMap)
237{
238 scintMap.clear();
239 rpcMap.clear();
240
241 RelationVector<ExtHit> extHits = track->getRelationsTo<ExtHit>();
242 KLMMuidLikelihood* muidLikelihood = track->getRelatedTo<KLMMuidLikelihood>();
243
244 for (const ExtHit& eHit : extHits) {
245 if (eHit.getStatus() != EXT_EXIT) continue;
246 if (m_ignoreBackward && eHit.isBackwardPropagated()) continue;
247
248 const bool isB = (eHit.getDetectorID() == Const::EDetector::BKLM);
249 const bool isE = (eHit.getDetectorID() == Const::EDetector::EKLM);
250 if (!isB && !isE) continue;
251
252 int copyId = eHit.getCopyID();
253 int tFor, tSec, tLay, tPla, tStr;
254 int tSub = -1;
255
256 if (isE) {
259 &tFor, &tLay, &tSec, &tPla, &tStr);
260 }
261 if (isB) {
264 &tFor, &tSec, &tLay, &tPla, &tStr);
265 }
266 if (tSub < 0) continue;
267
268 if (isB) {
269 const bool crossed = muidLikelihood
270 ? muidLikelihood->isExtrapolatedBarrelLayerCrossed(tLay - 1)
271 : true;
272 if (!crossed) continue;
273
274 const bool isRPC = (tLay >= BKLMElementNumbers::c_FirstRPCLayer);
275
276 if (isRPC) {
277 // RPC: match by module
278 unsigned int moduleKey =
279 m_elementNum->moduleNumber(tSub, tFor, tSec, tLay);
280 rpcMap.insert(std::make_pair(moduleKey, eHit));
281 } else {
282 // BKLM scintillator: match by channel
283 unsigned int channelKey =
284 m_elementNum->channelNumber(tSub, tFor, tSec, tLay, tPla, tStr);
285 if (m_channelStatus.isValid() &&
286 m_channelStatus->getChannelStatus(channelKey) != KLMChannelStatus::c_Normal)
287 continue;
288 scintMap.insert(std::make_pair(channelKey, eHit));
289 }
290 }
291
292 if (isE) {
293 const bool crossed = muidLikelihood
294 ? muidLikelihood->isExtrapolatedEndcapLayerCrossed(tLay - 1)
295 : true;
296 if (!crossed) continue;
297
298 unsigned int channelKey =
299 m_elementNum->channelNumber(tSub, tFor, tSec, tLay, tPla, tStr);
300 if (m_channelStatus.isValid() &&
301 m_channelStatus->getChannelStatus(channelKey) != KLMChannelStatus::c_Normal)
302 continue;
303 scintMap.insert(std::make_pair(channelKey, eHit));
304 }
305 }
306}
307
308/* Per-subdetector accumulators. */
309
310namespace {
311 // Weighted accumulation using per-hit sigma
312 inline void acc_stat_weighted(double t, double sigma, double& sumW, double& sumWT)
313 {
314 if (sigma <= 0.0 || !std::isfinite(sigma)) return;
315 const double w = 1.0 / (sigma * sigma);
316 sumW += w;
317 sumWT += w * t;
318 }
319}
320
321/* EKLM scintillator */
323 const ExtMap& scintMap,
324 double& sumW, double& sumWT)
325{
326 DBObjPtr<KLMTimeConstants> timeConstants;
327 DBObjPtr<KLMTimeCableDelay> timeCableDelay;
328
329 const double delayScint = timeConstants.isValid()
330 ? timeConstants->getDelay(KLMTimeConstants::c_EKLM)
331 : 0.0;
332
333 HepGeom::Point3D<double> hitGlobal_ext, hitLocal_ext;
334
335 for (const KLMHit2d& hit2d : klmHit2ds) {
336 if (hit2d.getSubdetector() != KLMElementNumbers::c_EKLM) continue;
337
338 RelationVector<KLMDigit> digits = hit2d.getRelationsTo<KLMDigit>();
339 if (digits.size() == 0) continue;
340
341 for (const KLMDigit& d : digits) {
342 if (!d.isGood()) continue;
343
344 unsigned int cid = d.getUniqueChannelID();
345 if (m_channelStatus.isValid() &&
346 m_channelStatus->getChannelStatus(cid) != KLMChannelStatus::c_Normal) continue;
347
348 // Apply ADC cut
349 if (!passesADCCut(d.getCharge(), KLMElementNumbers::c_EKLM, d.getLayer(), false)) {
350 continue;
351 }
352
353 // Match using channel ID
354 ExtPair ex = const_cast<KLMEventT0EstimatorModule*>(this)->matchExt(cid, const_cast<ExtMap&>(scintMap));
355 if (!ex.first || !ex.second) continue;
356 const double flyTime = 0.5 * (ex.first->getTOF() + ex.second->getTOF());
357
358 // Distance along strip to readout
359 const ROOT::Math::XYZVector posGlobExt = 0.5 * (ex.first->getPosition() + ex.second->getPosition());
360 hitGlobal_ext.setX(posGlobExt.X() / Unit::mm * CLHEP::mm);
361 hitGlobal_ext.setY(posGlobExt.Y() / Unit::mm * CLHEP::mm);
362 hitGlobal_ext.setZ(posGlobExt.Z() / Unit::mm * CLHEP::mm);
363
364 const double Lmm = m_geoParE->getStripLength(d.getStrip()) / CLHEP::mm * Unit::mm;
365 const HepGeom::Transform3D* tr = m_transformE->getStripGlobalToLocal(const_cast<KLMDigit*>(&d));
366 hitLocal_ext = (*tr) * hitGlobal_ext;
367 const double dist_mm = 0.5 * Lmm - hitLocal_ext.x() / CLHEP::mm * Unit::mm;
368
369 // Components
370 const double Trec = d.getTime();
371 const double Tcable = timeCableDelay.isValid() ? timeCableDelay->getTimeDelay(cid) : 0.0;
372 const double Tprop = dist_mm * delayScint;
373 const double Tfly = flyTime;
374
375 // Correct digit time
376 double t = Trec;
377 if (timeCableDelay.isValid()) t -= Tcable;
378 t -= Tprop;
379
380 const double t0_est = t - Tfly;
381 if (!std::isfinite(t0_est)) continue;
382
383 // Weighted accumulation using calibrated sigma
384 const double sigma = getHitSigma(KLMElementNumbers::c_EKLM, d.getLayer(), false);
385 acc_stat_weighted(t0_est, sigma, sumW, sumWT);
386 }
387 }
388}
389
390/* BKLM scintillator */
392 const ExtMap& scintMap,
393 double& sumW, double& sumWT)
394{
395 DBObjPtr<KLMTimeConstants> timeConstants;
396 DBObjPtr<KLMTimeCableDelay> timeCableDelay;
397
398 const double delayScint = timeConstants.isValid()
399 ? timeConstants->getDelay(KLMTimeConstants::c_BKLM)
400 : 0.0;
401
402 for (KLMHit2d& hit2d : klmHit2ds) {
403 if (hit2d.getSubdetector() != KLMElementNumbers::c_BKLM) continue;
404 if (hit2d.inRPC()) continue;
405 if (hit2d.isOutOfTime()) continue;
406
407 RelationVector<BKLMHit1d> b1ds = hit2d.getRelationsTo<BKLMHit1d>();
408 if (b1ds.size() == 0) continue;
409
410 const bklm::Module* mod = m_geoParB->findModule(hit2d.getSection(), hit2d.getSector(), hit2d.getLayer());
411 const ROOT::Math::XYZVector posG2d = hit2d.getPosition();
412
413 for (const BKLMHit1d& h1d : b1ds) {
414 RelationVector<KLMDigit> digits = h1d.getRelationsTo<KLMDigit>();
415
416 for (const KLMDigit& d : digits) {
417 if (d.inRPC() || !d.isGood()) continue;
418
419 unsigned int cid = d.getUniqueChannelID();
420 if (m_channelStatus.isValid() &&
421 m_channelStatus->getChannelStatus(cid) != KLMChannelStatus::c_Normal) continue;
422
423 // Apply ADC cut
424 if (!passesADCCut(d.getCharge(), KLMElementNumbers::c_BKLM, d.getLayer(), false)) {
425 continue;
426 }
427
428 // Match using channel ID
429 ExtPair p = const_cast<KLMEventT0EstimatorModule*>(this)->matchExt(cid, const_cast<ExtMap&>(scintMap));
430 if (!p.first || !p.second) continue;
431
432 const double flyTime = 0.5 * (p.first->getTOF() + p.second->getTOF());
433 const ROOT::Math::XYZVector posGext = 0.5 * (p.first->getPosition() + p.second->getPosition());
434
435 // Gate in local
436 const CLHEP::Hep3Vector locExt = mod->globalToLocal(CLHEP::Hep3Vector(posGext.X(), posGext.Y(), posGext.Z()), true);
437 const CLHEP::Hep3Vector locHit2 = mod->globalToLocal(CLHEP::Hep3Vector(posG2d.X(), posG2d.Y(), posG2d.Z()), true);
438 const CLHEP::Hep3Vector diff = locExt - locHit2;
439 if (std::fabs(diff.z()) > mod->getZStripWidth() || std::fabs(diff.y()) > mod->getPhiStripWidth()) continue;
440
441 // Prop distance
442 const bool isPhiReadout = h1d.isPhiReadout();
443 double propaLen = mod->getPropagationDistance(locExt, d.getStrip(), isPhiReadout);
444
445 // Components
446 const double Trec = d.getTime();
447 const double Tcable = timeCableDelay.isValid() ? timeCableDelay->getTimeDelay(cid) : 0.0;
448 const double Tprop = propaLen * delayScint;
449 const double Tfly = flyTime;
450
451 double t = Trec;
452 if (timeCableDelay.isValid()) t -= Tcable;
453 t -= Tprop;
454
455 const double t0_est = t - Tfly;
456 if (!std::isfinite(t0_est)) continue;
457
458 // Weighted accumulation using calibrated sigma
459 const double sigma = getHitSigma(KLMElementNumbers::c_BKLM, d.getLayer(), false);
460 acc_stat_weighted(t0_est, sigma, sumW, sumWT);
461 }
462 }
463 }
464}
465
466/* BKLM RPC */
468 const ExtMap& rpcMap,
469 double& sumW, double& sumWT)
470{
471 DBObjPtr<KLMTimeConstants> timeConstants;
472 DBObjPtr<KLMTimeCableDelay> timeCableDelay;
473
474 const double delayPhi = timeConstants.isValid()
475 ? timeConstants->getDelay(KLMTimeConstants::c_RPCPhi)
476 : 0.0;
477 const double delayZ = timeConstants.isValid()
478 ? timeConstants->getDelay(KLMTimeConstants::c_RPCZ)
479 : 0.0;
480
481 for (KLMHit2d& hit2d : klmHit2ds) {
482 if (hit2d.getSubdetector() != KLMElementNumbers::c_BKLM) continue;
483 if (!hit2d.inRPC()) continue;
484 if (hit2d.isOutOfTime()) continue;
485
486 RelationVector<BKLMHit1d> b1ds = hit2d.getRelationsTo<BKLMHit1d>();
487 if (b1ds.size() == 0) continue;
488
489 const bklm::Module* mod = m_geoParB->findModule(hit2d.getSection(), hit2d.getSector(), hit2d.getLayer());
490 const ROOT::Math::XYZVector posG2d = hit2d.getPosition();
491
492 for (const BKLMHit1d& h1d : b1ds) {
493 const bool isPhi = h1d.isPhiReadout();
494 RelationVector<KLMDigit> digits = h1d.getRelationsTo<KLMDigit>();
495
496 for (const KLMDigit& d : digits) {
497 if (!d.inRPC()) continue;
498
499 unsigned int cid = d.getUniqueChannelID();
500 if (m_channelStatus.isValid() &&
501 m_channelStatus->getChannelStatus(cid) != KLMChannelStatus::c_Normal) continue;
502
503 if (!d.isGood()) continue;
504
505 // RPC matched by module key
506 unsigned int moduleKey = m_elementNum->moduleNumber(d.getSubdetector(), d.getSection(), d.getSector(), d.getLayer());
507 ExtPair p = const_cast<KLMEventT0EstimatorModule*>(this)->matchExt(moduleKey, const_cast<ExtMap&>(rpcMap));
508 if (!p.first || !p.second) continue;
509
510 const double flyTime = 0.5 * (p.first->getTOF() + p.second->getTOF());
511 const ROOT::Math::XYZVector posGext = 0.5 * (p.first->getPosition() + p.second->getPosition());
512
513 const CLHEP::Hep3Vector locExt = mod->globalToLocal(CLHEP::Hep3Vector(posGext.X(), posGext.Y(), posGext.Z()), true);
514 const CLHEP::Hep3Vector locHit2 = mod->globalToLocal(CLHEP::Hep3Vector(posG2d.X(), posG2d.Y(), posG2d.Z()), true);
515
516 const CLHEP::Hep3Vector diff = locExt - locHit2;
517 if (std::fabs(diff.z()) > mod->getZStripWidth() || std::fabs(diff.y()) > mod->getPhiStripWidth()) continue;
518
519 const CLHEP::Hep3Vector propaV = mod->getPropagationDistance(locExt);
520 const double propaDist = isPhi ? propaV.y() : propaV.z();
521
522 // Components
523 const double Trec = d.getTime();
524 const double Tcable = timeCableDelay.isValid() ? timeCableDelay->getTimeDelay(cid) : 0.0;
525 const double Tprop = propaDist * (isPhi ? delayPhi : delayZ);
526 const double Tfly = flyTime;
527
528 double t = Trec;
529 if (timeCableDelay.isValid()) t -= Tcable;
530 t -= Tprop;
531
532 const double t0_est = t - Tfly;
533 if (!std::isfinite(t0_est)) continue;
534
535 // Weighted accumulation using calibrated sigma with direction-specific resolution
537 const double sigma = getHitSigma(KLMElementNumbers::c_BKLM, d.getLayer(), true, plane);
538 acc_stat_weighted(t0_est, sigma, sumW, sumWT);
539 }
540 }
541 }
542}
543
544/* Event. */
545
547{
548 // CDC seed for logging (not used to compute the means)
549 m_seedT0 = 0.0;
552 if (evtT0.isValid() && evtT0->hasTemporaryEventT0(Const::EDetector::CDC)) {
553 const auto best = evtT0->getBestCDCTemporaryEventT0();
554 if (best) m_seedT0 = best->eventT0;
555 }
556 }
557
558 if (!m_MuonList.isValid()) { B2WARNING("KLMEventT0Estimator: ParticleList '" << m_MuonListName << "' not found."); return; }
559 const unsigned nTracks = m_MuonList->getListSize();
560 if (nTracks == 0u) return;
561
562 // Weighted mean and uncertainty from running sums
563 auto weighted_result = [](double wsum, double wtsum) -> std::pair<double, double> {
564 if (wsum <= 0.0) return {NAN, NAN};
565 return {wtsum / wsum, std::sqrt(1.0 / wsum)};
566 };
567
568 // Weighted track averaging using inverse-variance (1/SEM²) weighting
569 auto mean_sem_tracks = [](const std::vector<std::pair<double, double>>& v) -> std::pair<double, double> {
570 if (v.empty()) return {NAN, NAN};
571 if (v.size() == 1)
572 {
573 return {v[0].first, std::isfinite(v[0].second) ? v[0].second : 0.0};
574 }
575
576 bool allValid = true;
577 for (const auto& [t0, sem] : v)
578 {
579 if (!std::isfinite(sem) || sem <= 0.0) { allValid = false; break; }
580 }
581
582 if (allValid)
583 {
584 double wsum = 0.0, wtsum = 0.0;
585 for (const auto& [t0, sem] : v) {
586 const double w = 1.0 / (sem * sem);
587 wsum += w;
588 wtsum += w * t0;
589 }
590 if (wsum > 0.0) {
591 return {wtsum / wsum, std::sqrt(1.0 / wsum)};
592 }
593 }
594
595 // Fallback to simple average if weights not valid
596 double s = 0.0;
597 for (const auto& [t0, sem] : v) s += t0;
598 const double mu = s / v.size();
599 double ss = 0.0;
600 for (const auto& [t0, sem] : v) { const double d = t0 - mu; ss += d * d; }
601 const double var = (v.size() > 1) ? ss / (v.size() - 1) : 0.0;
602 return {mu, std::sqrt(var / v.size())};
603 };
604
605 // For per-event track-averages: pairs of (T0, SEM) for weighted averaging
606 std::vector<std::pair<double, double>> vTrk_B, vTrk_R, vTrk_E, vTrk_All;
607
608 for (unsigned i = 0; i < nTracks; ++i) {
609 const Particle* particle = m_MuonList->getParticle(i);
610 if (!particle) continue;
611 const Track* track = particle->getTrack();
612 if (!track) continue;
613
614 RelationVector<KLMHit2d> hit2ds = track->getRelationsTo<KLMHit2d>();
615 if (hit2ds.size() == 0) continue;
616
617 // Build ExtHit maps for this track
618 m_extScint.clear();
619 m_extRPC.clear();
621
622 // Per-track digit sums per category (weighted)
623 double wE = 0, wTE = 0;
624 accumulateEKLM(hit2ds, m_extScint, wE, wTE);
625
626 double wB = 0, wTB = 0;
627 accumulateBKLMScint(hit2ds, m_extScint, wB, wTB);
628
629 double wR = 0, wTR = 0;
630 accumulateBKLMRPC(hit2ds, m_extRPC, wR, wTR);
631
632 // Per-track means and SEMs by category
633 if (wB > 0.0) {
634 auto [muB, seB] = weighted_result(wB, wTB);
635 if (m_hT0Trk_BKLM_Scint && std::isfinite(muB)) m_hT0Trk_BKLM_Scint->Fill(muB);
636 if (std::isfinite(muB)) vTrk_B.push_back({muB, seB});
637 }
638
639 if (wR > 0.0) {
640 auto [muR, seR] = weighted_result(wR, wTR);
641 if (m_hT0Trk_BKLM_RPC && std::isfinite(muR)) m_hT0Trk_BKLM_RPC->Fill(muR);
642 if (std::isfinite(muR)) vTrk_R.push_back({muR, seR});
643 }
644
645 if (wE > 0.0) {
646 auto [muE, seE] = weighted_result(wE, wTE);
647 if (m_hT0Trk_EKLM_Scint && std::isfinite(muE)) m_hT0Trk_EKLM_Scint->Fill(muE);
648 if (std::isfinite(muE)) vTrk_E.push_back({muE, seE});
649 }
650
651 // Per-track overall (if any category present)
652 {
653 const double wAll = wB + wE + wR;
654 const double wtAll = wTB + wTE + wTR;
655 if (wAll > 0.0) {
656 const double t0 = wtAll / wAll;
657 const double se = std::sqrt(1.0 / wAll);
658 vTrk_All.push_back({t0, se});
659 }
660 }
661 }
662
663 if (vTrk_All.empty()) {
664 B2DEBUG(20, "KLMEventT0Estimator: no usable KLM timing residuals for this event.");
665 return;
666 }
667
668 // Per-event track-averages using inverse-variance (1/SEM²) weighting
669 const auto [muB_trk, seB_trk] = mean_sem_tracks(vTrk_B);
670 const auto [muR_trk, seR_trk] = mean_sem_tracks(vTrk_R);
671 const auto [muE_trk, seE_trk] = mean_sem_tracks(vTrk_E);
672 const auto [muAll_trk, seAll_trk] = mean_sem_tracks(vTrk_All);
673
674 if (m_hT0Evt_TrkAvg_BKLM_Scint && std::isfinite(muB_trk)) m_hT0Evt_TrkAvg_BKLM_Scint->Fill(muB_trk);
675 if (m_hT0Evt_TrkAvg_BKLM_RPC && std::isfinite(muR_trk)) m_hT0Evt_TrkAvg_BKLM_RPC->Fill(muR_trk);
676 if (m_hT0Evt_TrkAvg_EKLM_Scint && std::isfinite(muE_trk)) m_hT0Evt_TrkAvg_EKLM_Scint->Fill(muE_trk);
677 if (m_hT0Evt_TrkAvg_All && std::isfinite(muAll_trk)) m_hT0Evt_TrkAvg_All->Fill(muAll_trk);
678
679 if (m_hT0Evt_TrkAvg_BKLM_Scint_SEM && std::isfinite(seB_trk)) m_hT0Evt_TrkAvg_BKLM_Scint_SEM->Fill(seB_trk);
680 if (m_hT0Evt_TrkAvg_BKLM_RPC_SEM && std::isfinite(seR_trk)) m_hT0Evt_TrkAvg_BKLM_RPC_SEM->Fill(seR_trk);
681 if (m_hT0Evt_TrkAvg_EKLM_Scint_SEM && std::isfinite(seE_trk)) m_hT0Evt_TrkAvg_EKLM_Scint_SEM->Fill(seE_trk);
682 if (m_hT0Evt_TrkAvg_All_SEM && std::isfinite(seAll_trk)) m_hT0Evt_TrkAvg_All_SEM->Fill(seAll_trk);
683
684 // ---------------- Final KLM combination (single saved component) ----------------
685 const bool useB = std::isfinite(muB_trk);
686 const bool useE = std::isfinite(muE_trk);
687 const bool useR = std::isfinite(muR_trk);
688
689 double finalT0 = NAN, finalSE = NAN;
690 int sourceBin = -1;
691
692 {
693 std::vector<std::pair<double, double>> parts;
694 if (useB) parts.emplace_back(muB_trk, seB_trk);
695 if (useE) parts.emplace_back(muE_trk, seE_trk);
696 if (useR) parts.emplace_back(muR_trk, seR_trk);
697
698 auto [t0, se] = mean_sem_tracks(parts);
699 finalT0 = t0;
700 finalSE = se;
701
702 if (useB && useE && useR) sourceBin = 7;
703 else if (useB && useE) sourceBin = 4;
704 else if (useB && useR) sourceBin = 5;
705 else if (useE && useR) sourceBin = 6;
706 else if (useB) sourceBin = 1;
707 else if (useE) sourceBin = 2;
708 else if (useR) sourceBin = 3;
709 }
710
711 B2DEBUG(20, "KLMEventT0Estimator: "
712 << "T0_trkavg_all=" << muAll_trk << " ns (seed CDC=" << m_seedT0 << " ns)"
713 << " | E=" << muE_trk << " | Bsc=" << muB_trk << " | Brpc=" << muR_trk
714 << (std::isfinite(finalT0) ? (std::string(" | FINAL KLM=") + std::to_string(finalT0) + " ns") : std::string("")));
715
717 if (!outT0.isValid()) outT0.construct();
718
719 if (std::isfinite(finalT0)) {
720 if (m_hFinalSource && sourceBin > 0) m_hFinalSource->Fill(sourceBin);
721 const double quality = static_cast<double>((useB ? 1 : 0) + (useE ? 1 : 0) + (useR ? 1 : 0));
722 EventT0::EventT0Component klmT0Component(finalT0, std::isfinite(finalSE) ? finalSE : 0.0,
723 Const::KLM, "KLM", quality);
724 outT0->addTemporaryEventT0(klmT0Component);
725 outT0->setEventT0(klmT0Component);
726 }
727}
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.
void accumulateEKLM(const RelationVector< KLMHit2d > &, const ExtMap &, double &sumW, double &sumWT)
Accumulate EKLM scintillator per-digit T0 estimates (weighted).
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.
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.
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).
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.
double getHitSigma(int subdetector, int layer, bool inRPC, int plane=0) const
Get per-hit sigma for a digit based on detector category.
TH1D * m_hT0Evt_TrkAvg_All
Per-event T0 track-average combined (mean) [ns].
const KLMElementNumbers * m_elementNum
Element numbering helpers.
void accumulateBKLMRPC(RelationVector< KLMHit2d > &klmHit2ds, const ExtMap &rpcMap, double &sumW, double &sumWT)
Accumulate BKLM RPC per-digit T0 estimates (weighted, both readout directions).
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).
TH1D * m_hT0Evt_TrkAvg_EKLM_Scint
Per-event T0 track-average for EKLM scintillator (mean) [ns].
void accumulateBKLMScint(RelationVector< KLMHit2d > &, const ExtMap &, double &sumW, double &sumWT)
Accumulate BKLM scintillator per-digit T0 estimates (weighted).
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