ATLAS Offline Software
Loading...
Searching...
No Matches
LArOFFCRawChannelBuilder.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7#include "GaudiKernel/SystemOfUnits.h"
19
20#include <algorithm>
21#include <array>
22#include <cmath>
23#include <memory>
24#include <span>
25#include <limits>
26#include <vector>
27
28namespace {
29// Lags entering the Q3 test, relative to the candidate peak. Lag 0 is left
30// out: its residual is identically zero, A being the corrected value at the
31// peak and the template 1 there. Nothing above lag +1 either, that is the
32// newest value available when the peak is tested.
33constexpr std::array<int, 3> q3Lags{-2, -1, 1};
34static_assert(std::ranges::is_sorted(q3Lags) && q3Lags.back() <= 1);
35
36// Entry of a pulse response that a pulse is found at. Pulses are identified as
37// local maxima of the filter output, so that is where the OFC window sits on
38// the shape. Undefined for an empty response, callers check.
39int anchorIndex(const std::vector<double>& response) {
40 return std::distance(response.begin(), std::ranges::max_element(response));
41}
42} // namespace
43
44namespace {
45// Region codes: |barrel_ec|-1 gives 0 = EMB, 1 = EMEC outer wheel,
46// 2 = EMEC inner wheel; HEC is 3 and FCAL 4. Written once because getting
47// it wrong attaches a channel to a plausible but wrong configuration,
48// which reads as a resolution problem rather than a lookup bug.
49constexpr std::array<const char*, 5> regionNames{"EMB", "EMEC-OW", "EMEC-IW",
50 "HEC", "FCAL"};
51constexpr int maxLayer = 4; // samplings 0..3; FCAL modules 1..3
52} // namespace
53
54size_t LArOFFCRawChannelBuilder::slotOf(int region, int layer) {
55 if (region < 0 || region >= static_cast<int>(regionNames.size()))
56 return s_nSlots;
57 if (layer < 0 || layer >= maxLayer) return s_nSlots;
58 return static_cast<size_t>(region) * maxLayer + static_cast<size_t>(layer);
59}
60
61size_t LArOFFCRawChannelBuilder::slotOfKey(const std::string& key) {
62 const auto slash = key.rfind('/');
63 if (slash == std::string::npos || slash + 1 >= key.size()) return s_nSlots;
64 const std::string region = key.substr(0, slash);
65 int layer = -1;
66 try {
67 layer = std::stoi(key.substr(slash + 1));
68 } catch (...) {
69 return s_nSlots;
70 }
71 for (size_t r = 0; r < regionNames.size(); ++r)
72 if (region == regionNames[r]) return slotOf(static_cast<int>(r), layer);
73 return s_nSlots;
74}
75
76std::string LArOFFCRawChannelBuilder::keyOfSlot(size_t slot) {
77 if (slot >= s_nSlots) return "?";
78 return std::string(regionNames[slot / maxLayer]) + "/" +
79 std::to_string(slot % maxLayer);
80}
81
83 const EventContext& ctx) const {
84 // Layer is an IDENTIFIER quantity, not a geometric one, so this needs no
85 // CaloDetDescrManager and has no matching tolerance to get wrong: a
86 // channel resolves to exactly one layer or to none.
87 const LArOnOffIdMapping* cabling{};
88 ATH_CHECK(SG::get(cabling, m_cablingKey, ctx));
89
90 const LArEM_ID* emId = m_caloId->em_idHelper();
91 const LArHEC_ID* hecId = m_caloId->hec_idHelper();
92 const LArFCAL_ID* fcalId = m_caloId->fcal_idHelper();
93
94 m_slotByHash.assign(m_onlineId->channelHashMax(),
95 static_cast<uint8_t>(s_nSlots));
96 size_t nMapped = 0, nUnmapped = 0;
97 for (auto it = m_onlineId->channel_begin(); it != m_onlineId->channel_end();
98 ++it) {
99 const HWIdentifier hw = *it;
100 if (!cabling->isOnlineConnected(hw)) continue;
101 const Identifier cid = cabling->cnvToIdentifier(hw);
102 int region = -1, layer = -1;
103 if (m_caloId->is_em(cid)) {
104 region = std::abs(emId->barrel_ec(cid)) - 1;
105 layer = emId->sampling(cid);
106 } else if (m_caloId->is_hec(cid)) {
107 region = 3;
108 layer = hecId->sampling(cid);
109 } else if (m_caloId->is_fcal(cid)) {
110 region = 4;
111 layer = fcalId->module(cid);
112 } else {
113 continue;
114 }
115 const size_t slot = slotOf(region, layer);
116 if (slot >= s_nSlots) { ++nUnmapped; continue; }
117 m_slotByHash[m_onlineId->channel_Hash(hw)] = static_cast<uint8_t>(slot);
118 ++nMapped;
119 }
120 ATH_MSG_INFO("layer map: " << nMapped << " channels resolved, " << nUnmapped
121 << " outside the known regions (these fall back "
122 "to the global settings)");
123 return StatusCode::SUCCESS;
124}
125
127 const ILArShape::ShapeRef_t& shape, const ILArOFC::OFCRef_t& ofc) const {
128 // Filter output for a unit-amplitude pulse: same correlation as the
129 // filtering loop in computeOFFC, samples replaced by the shape. Writing it
130 // as a convolution.
131
132 const int shapeSize = shape.size();
133 const int ofcSize = ofc.size();
134
135 auto shapeVal = [&](int k) {
136 return (k >= 0 && k < shapeSize) ? shape[k] : 0.0f;
137 };
138
139 // response[k] is the filter output for a pulse offset by k-(ofcSize-1)
140 // samples from the OFC window, tabulated over the whole range where the two
141 // overlap. Where the in-time position falls inside it is up to the OFCs.
142 std::vector<double> response(std::max(0, shapeSize + ofcSize - 1), 0.0);
143
144 for (int k = 0; k < static_cast<int>(response.size()); ++k) {
145 const int offset = k - (ofcSize - 1);
146 for (int j = 0; j < ofcSize; ++j)
147 response[k] += shapeVal(offset + j) * ofc[j];
148 }
149
150 return response;
151}
152
153double LArOFFCRawChannelBuilder::computeOFFC(const std::vector<short>& samples,
154 int firstSample,
155 const ILArOFC::OFCRef_t& ofc,
156 const ILArShape::ShapeRef_t& shape,
157 double pedestal,
158 const LayerParams& par) const {
159 // OFFC parameters (configured via job options)
160 // belowThreshold : ADC threshold to detect quiet regions
161 // belowTillReset : consecutive quiet samples before cache reset
162 // npulse : max number of overlapping pulses tracked
163 // Q3cut : shape-consistency cut for pulse acceptance
164 // filterThreshold : minimum filtered amplitude to seed a pulse
165
166 const int nSamples = samples.size();
167 const int ofcLen = ofc.size();
168
169 // Checked by the caller. The filter at position i needs samples
170 // i ... i+ofcLen-1, and every position it is evaluated at gets a corrected
171 // value, so the last one is nSamples-ofcLen.
172 if (ofcLen <= 0 || firstSample < 0 || firstSample + ofcLen > nSamples) {
173 ATH_MSG_WARNING("Cannot run the OFFC on " << nSamples << " samples with "
174 << ofcLen << " OFCs from sample "
175 << firstSample);
176 return 0.0;
177 }
178
179 // Pedestal subtraction
180 std::vector<double> samp_no_ped(nSamples);
181 for (int i = 0; i < nSamples; ++i)
182 samp_no_ped[i] = samples[i] - pedestal;
183
184 std::vector<double> reco(nSamples, 0.0);
185
186 // Precompute the filter response to a unit-amplitude pulse. cache[lagZero]
187 // is the correction of the sample being filtered, cache[lagZero+n] that of
188 // the one n later.
189 std::vector<double> response = pulseResponse(shape, ofc);
190 const int responseSize = response.size();
191 if (responseSize == 0) {
192 ATH_MSG_WARNING("Empty pulse response, shape size " << shape.size()
193 << " OFC size " << ofcLen);
194 return 0.0;
195 }
196
197 // Entry of response[] a pulse is found at. Pulses are identified as local
198 // maxima of 'filtered', so the template has to be anchored where the
199 // response peaks: that follows whatever alignment the OFCs carry, which is
200 // neither fixed nor the same for every channel (see LArOFCCondAlg).
201 const int lagZero = anchorIndex(response);
202 if (response[lagZero] <= 0.0) {
203 ATH_MSG_WARNING("Pulse response is nowhere positive, cannot subtract");
204 return 0.0;
205 }
206
207 // A is the filter output at that peak, so the template must be 1 there. It
208 // only already is when the response happens to peak where the OFCs were
209 // matched to the shape.
210 const double peakResponse = response[lagZero];
211 for (double& r : response)
212 r /= peakResponse;
213 std::vector<double> cache(responseSize, 0.0);
214
215 // A pulse writes cache entries lagZero ... responseSize-2, and entry k is
216 // consumed k-lagZero iterations later, so that is how long a slot stays busy.
217 const int correctionLength = responseSize - 2 - lagZero;
218
219 // Sample at which each pulse slot becomes available again
220 std::vector<int> slotFreeAt(par.nPulse, 0);
221 int belowCounter = 0;
222
223 const int loopEnd = std::max(0, nSamples - ofcLen + 1);
224
225 for (int i = 0; i < loopEnd; ++i) {
226
227 // Reset the correction cache after an extended quiet region
228 if (std::abs(samp_no_ped[i]) < par.belowThreshold)
229 ++belowCounter;
230 else
231 belowCounter = 0;
232
233 if (par.belowTillReset > 0 && belowCounter >= par.belowTillReset) {
234 belowCounter = 0;
235 std::fill(cache.begin(), cache.end(), 0.0);
236 // The corrections these slots were tracking have just been dropped, so
237 // the slots have to be released with them
238 std::fill(slotFreeAt.begin(), slotFreeAt.end(), 0);
239 }
240
241 // Standard OF filtering
242 double filtered = 0.0;
243 for (int j = 0; j < ofcLen; ++j)
244 filtered += samp_no_ped[i + j] * ofc[j];
245
246 // Corrected value of the sample just filtered, before anything found this
247 // iteration is subtracted from it
248 const double recoCurrent = filtered + cache[lagZero];
249
250 // Candidate peak: where a pulse would be in time with the OFCs, not where
251 // the pulse itself is largest. The search trails the filtering by one, a
252 // maximum can only be recognised once the following sample is in.
253 const int peak = i - 1;
254 if (peak + q3Lags.front() >= 0) {
255 const double A = reco.at(peak);
256
257 // Local maximum + amplitude cut on the corrected waveform: a pulse
258 // riding on the tail of one already subtracted need not be a local
259 // maximum of the raw filter output at all.
260 if (A > par.filterThreshold && A > reco.at(peak - 1) && A > recoCurrent) {
261
262 auto responseVal = [&](int lag) {
263 const int k = lagZero + lag;
264 return (k >= 0 && k < responseSize) ? response[k] : 0.0;
265 };
266 // reco[] is final for the peak and everything before it; the sample
267 // being filtered is not stored yet, and no later lag can occur here.
268 auto recoVal = [&](int lag) {
269 return lag <= 0 ? reco.at(peak + lag) : recoCurrent;
270 };
271
272 double Q3 = 0.0;
273 for (int lag : q3Lags)
274 Q3 += std::abs(recoVal(lag) - A * responseVal(lag));
275
276 // Accept pulse and subtract its forward correction, if NPulse leaves
277 // room for it. Shape mismatch scales with the amplitude while the
278 // noise floor does not, so the cut carries one term of each: written
279 // as a product rather than a ratio to avoid dividing, and A>0 here for
280 // any sensible FilterThreshold.
281 if (Q3 < par.q3Offset + par.q3Cut * A) {
282 const auto slot = std::ranges::find_if(
283 slotFreeAt, [i](int freeAt) { return freeAt <= i; });
284 if (slot == slotFreeAt.end()) {
285 ++m_nDropped;
286 } else {
287 // The pulse peaks one sample back, so its lag n lands on the entry
288 // for sample i+n-1, i.e. cache[lagZero+n-1]
289 for (int k = lagZero; k + 1 < responseSize; ++k)
290 cache[k] -= response[k + 1] * A;
291 *slot = i + correctionLength;
293 }
294 }
295 }
296 }
297
298 // Corrected filter output of this sample, now including any pulse just
299 // accepted one sample back
300 reco[i] = filtered + cache[lagZero];
301
302 // Advance correction cache in time
303 std::rotate(cache.begin(), cache.begin() + 1, cache.end());
304 cache.back() = 0.0;
305 }
306
307 // Corrected equivalent of the plain OF amplitude, which is the filter output
308 // of the window starting at firstSample
309 return reco[firstSample];
310}
311
313 ATH_CHECK(m_digitKey.initialize());
314 ATH_CHECK(m_rawChannelKey.initialize());
315 ATH_CHECK(m_pedestalKey.initialize());
316 ATH_CHECK(m_adc2MeVKey.initialize());
317 ATH_CHECK(m_ofcKey.initialize());
318 ATH_CHECK(m_shapeKey.initialize());
319 ATH_CHECK(m_cablingKey.initialize());
322
323 if (m_useDBFortQ) {
324 if (m_run1DSPThresholdsKey.empty() && m_run2DSPThresholdsKey.empty()) {
326 "useDB requested but neither Run1... nor Run2... initialized.");
327 return StatusCode::FAILURE;
328 }
329 }
330
331 ATH_CHECK(detStore()->retrieve(m_onlineId, "LArOnlineID"));
332 ATH_CHECK(detStore()->retrieve(m_caloId, "CaloCell_ID"));
333
334 // Resolve the per-layer table once. Every slot starts at the global values,
335 // so a job that sets no per-layer property behaves exactly as before.
336 m_layerParams.assign(s_nSlots + 1,
340 auto applyD = [&](const std::map<std::string, double>& m, const char* what,
341 double LayerParams::*field) -> StatusCode {
342 for (const auto& [key, val] : m) {
343 const size_t slot = slotOfKey(key);
344 if (slot >= s_nSlots) {
345 ATH_MSG_ERROR(what << " has key '" << key
346 << "' which is not <REGION>/<LAYER> with REGION in "
347 "EMB, EMEC-OW, EMEC-IW, HEC, FCAL and LAYER 0-3");
348 return StatusCode::FAILURE;
349 }
350 m_layerParams[slot].*field = val;
351 }
352 return StatusCode::SUCCESS;
353 };
354 ATH_CHECK(applyD(m_filterThresholdByLayer, "FilterThresholdByLayer",
356 ATH_CHECK(applyD(m_q3CutByLayer, "Q3CutByLayer", &LayerParams::q3Cut));
357 ATH_CHECK(applyD(m_q3OffsetByLayer, "Q3OffsetByLayer",
359 for (const auto& [key, val] : m_nPulseByLayer) {
360 const size_t slot = slotOfKey(key);
361 if (slot >= s_nSlots) {
362 ATH_MSG_ERROR("NPulseByLayer has unparseable key '" << key << "'");
363 return StatusCode::FAILURE;
364 }
365 if (val < 1) {
366 ATH_MSG_ERROR("NPulseByLayer['" << key << "'] is " << val
367 << ", must be >= 1");
368 return StatusCode::FAILURE;
369 }
370 m_layerParams[slot].nPulse = val;
371 }
372
373 // Disabling is an unreachable threshold, not a separate branch: no
374 // amplitude satisfies A > filterThreshold, so nothing is subtracted and
375 // the output is bit-identical to the plain OF (verified to 0 ADC).
376 if (!m_enabledLayers.empty()) {
377 std::vector<bool> on(s_nSlots + 1, false);
378 for (const std::string& key : m_enabledLayers.value()) {
379 const size_t slot = slotOfKey(key);
380 if (slot >= s_nSlots) {
381 ATH_MSG_ERROR("EnabledLayers contains unparseable key '" << key << "'");
382 return StatusCode::FAILURE;
383 }
384 on[slot] = true;
385 }
386 size_t nOff = 0;
387 for (size_t slot = 0; slot <= s_nSlots; ++slot) {
388 if (slot < s_nSlots && on[slot]) continue;
389 m_layerParams[slot].filterThreshold =
390 std::numeric_limits<double>::max();
391 ++nOff;
392 }
393 ATH_MSG_INFO("forward correction enabled in "
394 << m_enabledLayers.size() << " layers; " << nOff
395 << " slots left at the Optimal Filter");
396 }
397 for (size_t slot = 0; slot < s_nSlots; ++slot) {
398 const LayerParams& p = m_layerParams[slot];
399 if (p.filterThreshold == std::numeric_limits<double>::max()) continue;
400 ATH_MSG_DEBUG(keyOfSlot(slot) << ": Q3Cut=" << p.q3Cut << " Q3Offset="
401 << p.q3Offset << " FilterThreshold="
402 << p.filterThreshold << " NPulse="
403 << p.nPulse);
404 }
405
406 // The earliest testable candidate peak sits -q3Lags.front() samples into the
407 // digit, so anything less leaves no room to find a pulse before the in-time
408 // window and the forward correction can never contribute.
409 const int minFirstSample = -q3Lags.front() + 1;
410 if (m_firstSample < minFirstSample) {
411 ATH_MSG_ERROR("firstSample is "
412 << m_firstSample.value() << ", must be >= " << minFirstSample
413 << " for the OFFC to find any pulse before the in-time "
414 "window (set LAr.ROD.nPreceedingSamples accordingly)");
415 return StatusCode::FAILURE;
416 }
417
418 if (m_nPulse < 0) {
419 ATH_MSG_ERROR("NPulse is " << m_nPulse.value() << ", must be >= 0");
420 return StatusCode::FAILURE;
421 }
422 if (m_nPulse == 0) {
424 "NPulse is 0, no pulse will be subtracted and the OFFC reduces to "
425 "plain optimal filtering");
426 }
427
428 const std::string cutmsg = m_absECutFortQ.value() ? "fabs(E)" : "E";
429 if (m_useDBFortQ) {
430 ATH_MSG_INFO("Time and quality computed for "
431 << cutmsg << " above the threshold from COOL folder "
432 << m_run1DSPThresholdsKey.key() << " (run1) "
433 << m_run2DSPThresholdsKey.key() << " (run2)");
434 } else {
435 ATH_MSG_INFO("Time and quality computed for " << cutmsg << " above "
436 << m_eCutFortQ.value());
437 }
438
439 return StatusCode::SUCCESS;
440}
441
443 const unsigned long dropped = m_nDropped;
444 ATH_MSG_INFO("Subtracted " << m_nSubtracted.load() << " pulses, dropped "
445 << dropped << " for want of a free slot (NPulse = "
446 << m_nPulse.value() << ")");
447 if (dropped > 0)
448 ATH_MSG_WARNING(dropped
449 << " accepted pulses were not subtracted: their correction "
450 "is missing from the output. Raise NPulse to keep them");
451 return StatusCode::SUCCESS;
452}
453
454StatusCode LArOFFCRawChannelBuilder::execute(const EventContext& ctx) const {
455
456 ATH_MSG_VERBOSE("Executing LArOFFCRawChannelBuilder::execute");
457
458 // Get event inputs from read handles:
459 const LArDigitContainer* inputContainer{};
460 ATH_CHECK(SG::get(inputContainer, m_digitKey, ctx));
461
462 // Write output via write handle
463 auto outputContainer = std::make_unique<LArRawChannelContainer>();
464
465 // Get Conditions input
466 const ILArPedestal* peds{};
467 ATH_CHECK(SG::get(peds, m_pedestalKey, ctx));
468
469 const LArADC2MeV* adc2MeVs{};
470 ATH_CHECK(SG::get(adc2MeVs, m_adc2MeVKey, ctx));
471
472 const ILArOFC* ofcs{nullptr};
473 ATH_CHECK(SG::get(ofcs, m_ofcKey, ctx));
474
475 const ILArShape* shapes{};
476 ATH_CHECK(SG::get(shapes, m_shapeKey, ctx));
477
478 const LArOnOffIdMapping* cabling{};
479 ATH_CHECK(SG::get(cabling, m_cablingKey, ctx));
480
481 std::unique_ptr<LArDSPThresholdsFlat> run2DSPThresh;
482 const LArDSPThresholdsComplete* run1DSPThresh = nullptr;
483 ATH_CHECK(SG::get(run1DSPThresh, m_run1DSPThresholdsKey, ctx));
484 if (m_useDBFortQ) {
485 if (!m_run2DSPThresholdsKey.empty()) {
488 run2DSPThresh = std::make_unique<LArDSPThresholdsFlat>(*dspThrshAttr);
489 if (!run2DSPThresh->good()) [[unlikely]] {
491 "Failed to initialize LArDSPThresholdFlat from attribute list "
492 "loaded from "
493 << m_run2DSPThresholdsKey.key() << ". Aborting.");
494 return StatusCode::FAILURE;
495 }
496 } else if (!m_run1DSPThresholdsKey.empty()) {
499 run1DSPThresh = dspThresh.cptr();
500 } else {
501 ATH_MSG_ERROR("No DSP threshold configured.");
502 return StatusCode::FAILURE;
503 }
504 }
505
506 // Loop over digits:
507 // Built once on the first event, not in initialize(): it needs the
508 // cabling, which is conditions data with an IOV.
509 std::call_once(m_slotOnce, [&]() {
510 m_slotStatus = this->buildLayerMap(ctx);
511 });
512 ATH_CHECK(m_slotStatus);
513
514 for (const LArDigit* digit : *inputContainer) {
515
516 const size_t firstSample = m_firstSample;
517
518 const HWIdentifier id = digit->hardwareID();
519
520 const bool connected = cabling->isOnlineConnected(id);
521
522 // Per-layer parameters. The last entry is the global fallback, used for
523 // any channel outside the five known regions.
524 const IdentifierHash hash = m_onlineId->channel_Hash(id);
525 const size_t slot = (hash < m_slotByHash.size())
526 ? static_cast<size_t>(m_slotByHash[hash])
527 : s_nSlots;
528 const LayerParams& par = m_layerParams[slot];
529
530 const std::vector<short>& samples = digit->samples();
531 const int gain = digit->gain();
532 const float p = peds->pedestal(id, gain);
533
534 // The following autos will resolve either into vectors or vector-proxies
535 const auto& ofca = ofcs->OFC_a(id, gain);
536 const auto& adc2mev = adc2MeVs->ADC2MEV(id, gain);
537 const size_t nOFC = ofca.size();
538
539 if (ATH_UNLIKELY(nOFC == 0)) {
540 if (!connected)
541 continue; // No conditions for disconencted channel, who cares?
542 ATH_MSG_ERROR("No valid OFCs for connected channel "
543 << m_onlineId->channel_name(id) << " gain " << gain);
544 return StatusCode::FAILURE;
545 }
546
547 // Sanity check on input conditions data: ensure the samples vector is
548 // compatible with the ofc_a size when preceeding samples are saved.
549 // Compared this way round because samples.size()-firstSample would wrap
550 // for a short digit.
551 if (samples.size() < firstSample + nOFC) {
552 ATH_MSG_ERROR("digit has " << samples.size() << " samples, need at least "
553 << firstSample + nOFC << " for firstSample "
554 << firstSample << " and OFC_a size " << nOFC);
555 return StatusCode::FAILURE;
556 }
557
558 if (p == ILArPedestal::ERRORCODE) [[unlikely]] {
559 if (!connected)
560 continue; // No conditions for disconencted channel, who cares?
561 ATH_MSG_ERROR("No valid pedestal for connected channel "
562 << m_onlineId->channel_name(id) << " gain " << gain);
563 return StatusCode::FAILURE;
564 }
565
566 if (adc2mev.size() < 2) [[unlikely]] {
567 if (!connected)
568 continue; // No conditions for disconencted channel, who cares?
569 ATH_MSG_ERROR("No valid ADC2MeV for connected channel "
570 << m_onlineId->channel_name(id) << " gain " << gain);
571 return StatusCode::FAILURE;
572 }
573
574 // Apply OFFC to get amplitude
575 // Evaluate sums in double-precision to get consistent results
576 // across platforms.
577
578 bool saturated = false;
579 // Check saturation AND discount pedestal
580 std::vector<double> samp_no_ped(nOFC, 0.0);
581 for (size_t i = 0; i < nOFC; ++i) {
582 if (samples[i + firstSample] == 4096 || samples[i + firstSample] == 0)
583 saturated = true;
584 samp_no_ped[i] = samples[i + firstSample] - p;
585 }
586
587 uint16_t iquaShort = 0;
588 float tau = 0;
589
590 uint16_t prov = LArProv::DEFAULTRECO; // Means all constants from DB
591 if (saturated)
592 prov |= LArProv::SATURATED;
593
594 float ecut(0.);
595 if (m_useDBFortQ) {
596 if (run2DSPThresh) {
597 ecut = run2DSPThresh->tQThr(id);
598 } else if (run1DSPThresh) {
599 ecut = run1DSPThresh->tQThr(id);
600 } else {
601 ATH_MSG_ERROR("DSP threshold problem");
602 return StatusCode::FAILURE;
603 }
604 } else {
605 ecut = m_eCutFortQ;
606 }
607
608 const auto& fullShape = shapes->Shape(id, gain);
609
610 double A = computeOFFC(samples, firstSample, ofca, fullShape, p, par);
611
612 const float E = adc2mev[0] + A * adc2mev[1];
613
614 const float E1 = m_absECutFortQ.value() ? std::fabs(E) : E;
615
616 if (E1 > ecut) {
617 ATH_MSG_VERBOSE("Channel " << m_onlineId->channel_name(id) << " gain "
618 << gain
619 << " above threshold for tQ computation");
620 prov |= LArProv::QTPRESENT; // time+quality information are available
621
622 // Get time by applying OFC-b coefficients:
623 const auto& ofcb = ofcs->OFC_b(id, gain);
624 double At = 0;
625 for (size_t i = 0; i < nOFC; ++i) {
626 At += static_cast<double>(samp_no_ped[i]) * ofcb[i];
627 }
628
629 // Divide A*t/A to get time
630 tau = (std::fabs(A) > 0.1) ? At / A : 0.0;
631
632 // Get Q-factor. The shape has to be offset by the index the OFC window
633 // is matched to, which is not the digit offset: the digitisation writes
634 // shape index k-nPreceedingSamples into digit sample k. Reading it back
635 // from the conditions also covers the HEC shift and the fallback that
636 // LArOFCCondAlg applies per channel.
637 const std::vector<double> resp = pulseResponse(fullShape, ofca);
638 const int shapeShift =
639 resp.empty() ? -1 : anchorIndex(resp) - static_cast<int>(nOFC) + 1;
640
641 if (shapeShift < 0 || fullShape.size() < nOFC + shapeShift) [[unlikely]] {
642 if (!connected)
643 continue; // No conditions for disconnected channel, who cares?
644 ATH_MSG_ERROR("No valid shape for channel "
645 << m_onlineId->channel_name(id) << " gain " << gain);
646 ATH_MSG_ERROR("Got size " << fullShape.size() << " and offset "
647 << shapeShift << ", expected at least "
648 << nOFC << " samples from there");
649 return StatusCode::FAILURE;
650 }
651
652 std::span<const float> shape(fullShape.data() + shapeShift,
653 fullShape.size() - shapeShift);
654
655 double q = 0;
656 if (m_useShapeDer) {
657 const auto& fullshapeDer = shapes->ShapeDer(id, gain);
658 if (fullshapeDer.size() < nOFC + shapeShift) [[unlikely]] {
659 ATH_MSG_ERROR("No valid shape derivative for channel "
660 << m_onlineId->channel_name(id) << " gain " << gain);
661 ATH_MSG_ERROR("Got size " << fullshapeDer.size()
662 << ", expected at least "
663 << nOFC + shapeShift);
664 return StatusCode::FAILURE;
665 }
666
667 std::span<const float> shapeDer(fullshapeDer.data() + shapeShift,
668 fullshapeDer.size() - shapeShift);
669
670
671 for (size_t i = 0; i < nOFC; ++i) {
672 q += std::pow((A * (shape[i] - tau * shapeDer[i]) - (samp_no_ped[i])),
673 2);
674 }
675 } // end if useShapeDer
676 else {
677 // Q-factor w/o shape derivative
678 for (size_t i = 0; i < nOFC; ++i) {
679 q += std::pow((A * shape[i] - (samp_no_ped[i])), 2);
680 }
681 }
682
683 // Clamp before the cast, q can exceed the range of int
684 iquaShort = static_cast<uint16_t>(std::min(q, 65535.0));
685
686 tau -= ofcs->timeOffset(id, gain);
687 tau *= (Gaudi::Units::nanosecond /
688 Gaudi::Units::picosecond); // Convert time to ps
689 } // end if above cut
690
691 outputContainer->emplace_back(id, static_cast<int>(std::floor(E + 0.5)),
692 static_cast<int>(std::floor(tau + 0.5)),
693 iquaShort, prov, (CaloGain::CaloGain)gain);
694 }
695
697 ATH_CHECK(outputHandle.record(std::move(outputContainer)));
698
699 return StatusCode::SUCCESS;
700}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_WARNING(x,...)
#define ATH_MSG_VERBOSE(x,...)
#define ATH_MSG_INFO(x,...)
#define ATH_UNLIKELY(x)
MDT_Response response
Handle class for reading from StoreGate.
const ServiceHandle< StoreGateSvc > & detStore() const
virtual OFCRef_t OFC_b(const HWIdentifier &id, int gain, int tbin=0) const =0
virtual OFCRef_t OFC_a(const HWIdentifier &id, int gain, int tbin=0) const =0
access to OFCs by online ID, gain, and tbin (!=0 for testbeam)
LArVectorProxy OFCRef_t
This class defines the interface for accessing Optimal Filtering coefficients for each channel provid...
Definition ILArOFC.h:26
virtual float timeOffset(const HWIdentifier &CellID, int gain) const =0
virtual float pedestal(const HWIdentifier &id, int gain) const =0
LArVectorProxy ShapeRef_t
This class defines the interface for accessing Shape (Nsample variable, Dt = 25 ns fixed) @stereotype...
Definition ILArShape.h:26
virtual ShapeRef_t Shape(const HWIdentifier &id, int gain, int tbin=0, int mode=0) const =0
virtual ShapeRef_t ShapeDer(const HWIdentifier &id, int gain, int tbin=0, int mode=0) const =0
This is a "hash" representation of an Identifier.
const LArVectorProxy ADC2MEV(const HWIdentifier &id, int gain) const
Definition LArADC2MeV.h:32
float tQThr(const HWIdentifier chid) const
Container class for LArDigit.
Liquid Argon digit base class.
Definition LArDigit.h:25
int barrel_ec(const Identifier id) const
return barrel_ec according to :
int sampling(const Identifier id) const
return sampling according to :
Helper class for LArEM offline identifiers.
Definition LArEM_ID.h:111
Helper class for LArFCAL offline identifiers.
Definition LArFCAL_ID.h:49
int sampling(const Identifier id) const
return sampling [0,3] (only 0 for supercells)
Helper class for LArHEC offline identifiers.
Definition LArHEC_ID.h:76
Gaudi::Property< std::map< std::string, int > > m_nPulseByLayer
static constexpr size_t s_nSlots
Five regions x at most four samplings.
std::vector< double > pulseResponse(const ILArShape::ShapeRef_t &shape, const ILArOFC::OFCRef_t &ofc) const
Filter output for a unit-amplitude pulse, tabulated over every offset of the shape against the OFC wi...
SG::WriteHandleKey< LArRawChannelContainer > m_rawChannelKey
Gaudi::Property< bool > m_useDBFortQ
static std::string keyOfSlot(size_t slot)
SG::ReadCondHandleKey< ILArShape > m_shapeKey
Gaudi::Property< double > m_belowThreshold
The OFFC extends optimal filtering by finding pulses in the preceding samples and subtracting their e...
Gaudi::Property< bool > m_absECutFortQ
Gaudi::Property< int > m_nPulse
Maximum number of pulse corrections in flight at once.
SG::ReadCondHandleKey< LArADC2MeV > m_adc2MeVKey
std::vector< LayerParams > m_layerParams
Gaudi::Property< int > m_firstSample
Index of the digit sample the OFC window starts at, i.e.
SG::ReadCondHandleKey< LArOnOffIdMapping > m_cablingKey
double computeOFFC(const std::vector< short > &samples, int firstSample, const ILArOFC::OFCRef_t &ofc, const ILArShape::ShapeRef_t &shape, double pedestal, const LayerParams &par) const
Gaudi::Property< std::map< std::string, double > > m_q3OffsetByLayer
static size_t slotOf(int region, int layer)
Slot for a region code (0=EMB..4=FCAL) and sampling/module; s_nSlots if out of range.
SG::ReadCondHandleKey< ILArPedestal > m_pedestalKey
Gaudi::Property< float > m_eCutFortQ
SG::ReadHandleKey< LArDigitContainer > m_digitKey
StatusCode buildLayerMap(const EventContext &ctx) const
Gaudi::Property< double > m_filterThreshold
Minimum pile-up corrected amplitude required to accept a pulse peak.
static size_t slotOfKey(const std::string &key)
Parse "<REGION>/<LAYER>" into a slot. Returns s_nSlots if unparseable.
Gaudi::Property< double > m_Q3Offset
Absolute term of the Q3 cut, in ADC.
SG::ReadCondHandleKey< ILArOFC > m_ofcKey
Gaudi::Property< std::map< std::string, double > > m_q3CutByLayer
Gaudi::Property< double > m_Q3cut
Quality cut for pulse acceptance.
Gaudi::Property< bool > m_useShapeDer
std::atomic< unsigned long > m_nDropped
StatusCode execute(const EventContext &ctx) const override
SG::ReadCondHandleKey< AthenaAttributeList > m_run2DSPThresholdsKey
SG::ReadCondHandleKey< LArDSPThresholdsComplete > m_run1DSPThresholdsKey
Gaudi::Property< int > m_belowTillReset
Number of consecutive below-threshold samples after which the pending corrections are dropped,...
Gaudi::Property< std::vector< std::string > > m_enabledLayers
Layers the correction may run in; empty means all.
std::atomic< unsigned long > m_nSubtracted
Accepted pulses, and those NPulse left no room to subtract.
Gaudi::Property< std::map< std::string, double > > m_filterThresholdByLayer
const_pointer_type cptr()
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
int r
Definition globals.cxx:22
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
void rotate(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > mid, typename DataModel_detail::iterator< DVL > end)
Specialization of rotate for DataVector/List.
#define unlikely(x)
hold the test vectors and ease the comparison
Resolved parameters for one layer.