7#include "GaudiKernel/SystemOfUnits.h"
33constexpr std::array<int, 3> q3Lags{-2, -1, 1};
34static_assert(std::ranges::is_sorted(q3Lags) && q3Lags.back() <= 1);
39int anchorIndex(
const std::vector<double>&
response) {
49constexpr std::array<const char*, 5> regionNames{
"EMB",
"EMEC-OW",
"EMEC-IW",
51constexpr int maxLayer = 4;
55 if (region < 0 || region >=
static_cast<int>(regionNames.size()))
57 if (layer < 0 || layer >= maxLayer)
return s_nSlots;
58 return static_cast<size_t>(region) * maxLayer +
static_cast<size_t>(layer);
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);
67 layer = std::stoi(key.substr(slash + 1));
71 for (
size_t r = 0;
r < regionNames.size(); ++
r)
72 if (region == regionNames[
r])
return slotOf(
static_cast<int>(
r), layer);
78 return std::string(regionNames[slot / maxLayer]) +
"/" +
79 std::to_string(slot % maxLayer);
83 const EventContext& ctx)
const {
94 m_slotByHash.assign(
m_onlineId->channelHashMax(),
96 size_t nMapped = 0, nUnmapped = 0;
100 if (!cabling->isOnlineConnected(hw))
continue;
101 const Identifier cid = cabling->cnvToIdentifier(hw);
102 int region = -1, layer = -1;
104 region = std::abs(emId->
barrel_ec(cid)) - 1;
109 }
else if (
m_caloId->is_fcal(cid)) {
111 layer = fcalId->module(cid);
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);
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;
132 const int shapeSize = shape.size();
133 const int ofcSize = ofc.size();
135 auto shapeVal = [&](
int k) {
136 return (k >= 0 && k < shapeSize) ? shape[k] : 0.0f;
142 std::vector<double>
response(std::max(0, shapeSize + ofcSize - 1), 0.0);
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];
166 const int nSamples = samples.size();
167 const int ofcLen = ofc.size();
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 "
180 std::vector<double> samp_no_ped(nSamples);
181 for (
int i = 0; i < nSamples; ++i)
182 samp_no_ped[i] = samples[i] - pedestal;
184 std::vector<double> reco(nSamples, 0.0);
190 const int responseSize =
response.size();
191 if (responseSize == 0) {
193 <<
" OFC size " << ofcLen);
201 const int lagZero = anchorIndex(
response);
203 ATH_MSG_WARNING(
"Pulse response is nowhere positive, cannot subtract");
210 const double peakResponse =
response[lagZero];
213 std::vector<double> cache(responseSize, 0.0);
217 const int correctionLength = responseSize - 2 - lagZero;
220 std::vector<int> slotFreeAt(par.nPulse, 0);
221 int belowCounter = 0;
223 const int loopEnd = std::max(0, nSamples - ofcLen + 1);
225 for (
int i = 0; i < loopEnd; ++i) {
228 if (std::abs(samp_no_ped[i]) < par.belowThreshold)
233 if (par.belowTillReset > 0 && belowCounter >= par.belowTillReset) {
235 std::fill(cache.begin(), cache.end(), 0.0);
238 std::fill(slotFreeAt.begin(), slotFreeAt.end(), 0);
242 double filtered = 0.0;
243 for (
int j = 0; j < ofcLen; ++j)
244 filtered += samp_no_ped[i + j] * ofc[j];
248 const double recoCurrent = filtered + cache[lagZero];
253 const int peak = i - 1;
254 if (peak + q3Lags.front() >= 0) {
255 const double A = reco.at(peak);
260 if (
A > par.filterThreshold &&
A > reco.at(peak - 1) &&
A > recoCurrent) {
262 auto responseVal = [&](
int lag) {
263 const int k = lagZero + lag;
264 return (k >= 0 && k < responseSize) ?
response[k] : 0.0;
268 auto recoVal = [&](
int lag) {
269 return lag <= 0 ? reco.at(peak + lag) : recoCurrent;
273 for (
int lag : q3Lags)
274 Q3 += std::abs(recoVal(lag) -
A * responseVal(lag));
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()) {
289 for (
int k = lagZero; k + 1 < responseSize; ++k)
291 *slot = i + correctionLength;
300 reco[i] = filtered + cache[lagZero];
303 std::rotate(cache.begin(), cache.begin() + 1, cache.end());
309 return reco[firstSample];
326 "useDB requested but neither Run1... nor Run2... initialized.");
327 return StatusCode::FAILURE;
340 auto applyD = [&](
const std::map<std::string, double>& m,
const char* what,
342 for (
const auto& [key, val] : m) {
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;
352 return StatusCode::SUCCESS;
362 ATH_MSG_ERROR(
"NPulseByLayer has unparseable key '" << key <<
"'");
363 return StatusCode::FAILURE;
367 <<
", must be >= 1");
368 return StatusCode::FAILURE;
377 std::vector<bool> on(
s_nSlots + 1,
false);
381 ATH_MSG_ERROR(
"EnabledLayers contains unparseable key '" << key <<
"'");
382 return StatusCode::FAILURE;
387 for (
size_t slot = 0; slot <=
s_nSlots; ++slot) {
388 if (slot <
s_nSlots && on[slot])
continue;
390 std::numeric_limits<double>::max();
395 <<
" slots left at the Optimal Filter");
397 for (
size_t slot = 0; slot <
s_nSlots; ++slot) {
399 if (p.filterThreshold == std::numeric_limits<double>::max())
continue;
401 << p.q3Offset <<
" FilterThreshold="
402 << p.filterThreshold <<
" NPulse="
409 const int minFirstSample = -q3Lags.front() + 1;
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;
420 return StatusCode::FAILURE;
424 "NPulse is 0, no pulse will be subtracted and the OFFC reduces to "
425 "plain optimal filtering");
428 const std::string cutmsg =
m_absECutFortQ.value() ?
"fabs(E)" :
"E";
431 << cutmsg <<
" above the threshold from COOL folder "
435 ATH_MSG_INFO(
"Time and quality computed for " << cutmsg <<
" above "
439 return StatusCode::SUCCESS;
445 << dropped <<
" for want of a free slot (NPulse = "
449 <<
" accepted pulses were not subtracted: their correction "
450 "is missing from the output. Raise NPulse to keep them");
451 return StatusCode::SUCCESS;
463 auto outputContainer = std::make_unique<LArRawChannelContainer>();
481 std::unique_ptr<LArDSPThresholdsFlat> run2DSPThresh;
488 run2DSPThresh = std::make_unique<LArDSPThresholdsFlat>(*dspThrshAttr);
489 if (!run2DSPThresh->good()) [[
unlikely]] {
491 "Failed to initialize LArDSPThresholdFlat from attribute list "
494 return StatusCode::FAILURE;
499 run1DSPThresh = dspThresh.
cptr();
502 return StatusCode::FAILURE;
509 std::call_once(m_slotOnce, [&]() {
514 for (
const LArDigit* digit : *inputContainer) {
520 const bool connected = cabling->isOnlineConnected(
id);
525 const size_t slot = (hash < m_slotByHash.size())
526 ?
static_cast<size_t>(m_slotByHash[hash])
530 const std::vector<short>& samples = digit->samples();
531 const int gain = digit->gain();
532 const float p = peds->
pedestal(
id, gain);
535 const auto& ofca = ofcs->
OFC_a(
id, gain);
536 const auto& adc2mev = adc2MeVs->
ADC2MEV(
id, gain);
537 const size_t nOFC = ofca.size();
543 <<
m_onlineId->channel_name(
id) <<
" gain " << gain);
544 return StatusCode::FAILURE;
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;
562 <<
m_onlineId->channel_name(
id) <<
" gain " << gain);
563 return StatusCode::FAILURE;
566 if (adc2mev.size() < 2) [[
unlikely]] {
570 <<
m_onlineId->channel_name(
id) <<
" gain " << gain);
571 return StatusCode::FAILURE;
578 bool saturated =
false;
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)
584 samp_no_ped[i] = samples[i + firstSample] - p;
587 uint16_t iquaShort = 0;
597 ecut = run2DSPThresh->tQThr(
id);
598 }
else if (run1DSPThresh) {
599 ecut = run1DSPThresh->
tQThr(
id);
602 return StatusCode::FAILURE;
608 const auto& fullShape = shapes->
Shape(
id, gain);
610 double A =
computeOFFC(samples, firstSample, ofca, fullShape, p, par);
612 const float E = adc2mev[0] +
A * adc2mev[1];
619 <<
" above threshold for tQ computation");
623 const auto& ofcb = ofcs->
OFC_b(
id, gain);
625 for (
size_t i = 0; i < nOFC; ++i) {
626 At +=
static_cast<double>(samp_no_ped[i]) * ofcb[i];
630 tau = (std::fabs(
A) > 0.1) ? At /
A : 0.0;
637 const std::vector<double> resp =
pulseResponse(fullShape, ofca);
638 const int shapeShift =
639 resp.empty() ? -1 : anchorIndex(resp) -
static_cast<int>(nOFC) + 1;
641 if (shapeShift < 0 || fullShape.size() < nOFC + shapeShift) [[
unlikely]] {
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;
652 std::span<const float> shape(fullShape.data() + shapeShift,
653 fullShape.size() - shapeShift);
657 const auto& fullshapeDer = shapes->
ShapeDer(
id, gain);
658 if (fullshapeDer.size() < nOFC + shapeShift) [[
unlikely]] {
660 <<
m_onlineId->channel_name(
id) <<
" gain " << gain);
662 <<
", expected at least "
663 << nOFC + shapeShift);
664 return StatusCode::FAILURE;
667 std::span<const float> shapeDer(fullshapeDer.data() + shapeShift,
668 fullshapeDer.size() - shapeShift);
671 for (
size_t i = 0; i < nOFC; ++i) {
672 q += std::pow((
A * (shape[i] - tau * shapeDer[i]) - (samp_no_ped[i])),
678 for (
size_t i = 0; i < nOFC; ++i) {
679 q += std::pow((
A * shape[i] - (samp_no_ped[i])), 2);
684 iquaShort =
static_cast<uint16_t
>(std::min(q, 65535.0));
687 tau *= (Gaudi::Units::nanosecond /
688 Gaudi::Units::picosecond);
691 outputContainer->emplace_back(
id,
static_cast<int>(std::floor(E + 0.5)),
692 static_cast<int>(std::floor(tau + 0.5)),
699 return StatusCode::SUCCESS;
#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,...)
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...
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...
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
float tQThr(const HWIdentifier chid) const
Container class for LArDigit.
Liquid Argon digit base class.
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.
Helper class for LArFCAL offline identifiers.
int sampling(const Identifier id) const
return sampling [0,3] (only 0 for supercells)
Helper class for LArHEC offline identifiers.
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
StatusCode initialize() override
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.
const LArOnlineID * m_onlineId
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.
const CaloCell_ID * m_caloId
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.
StatusCode finalize() override
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.
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.
hold the test vectors and ease the comparison
Resolved parameters for one layer.