-
Notifications
You must be signed in to change notification settings - Fork 508
Expand file tree
/
Copy pathDigitizer.cxx
More file actions
260 lines (217 loc) · 9.26 KB
/
Copy pathDigitizer.cxx
File metadata and controls
260 lines (217 loc) · 9.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
///
/// \file Digitizer.cxx
/// \brief Implementation of the ALICE3 TOF digitizer
/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT)
/// \since 2026-03-17
///
#include "IOTOFSimulation/Digitizer.h"
#include "DetectorsRaw/HBFUtils.h"
#include <TRandom.h>
#include <vector>
#include <iostream>
#include <numeric>
#include <algorithm>
#include <fairlogger/Logger.h>
namespace o2::iotof
{
o2::iotof::Segmentation* Digitizer::sSegmentation = nullptr;
//_______________________________________________________________________
void Digitizer::init()
{
const int numberOfChips = mGeometry->getSize();
mChips.resize(numberOfChips);
for (int i = numberOfChips; i--;) {
mChips[i].setChipIndex(i);
/// Noise map to be implemented
/// if (mNoiseMap) {
/// mChips[i].setNoiseMap(mNoiseMap);
/// }
/// Dead channel map to be implemented
/// if (mDeadChanMap) {
/// mChips[i].disable(mDeadChanMap->isFullChipMasked(i));
/// mChips[i].setDeadChanMap(mDeadChanMap);
/// }
}
LOG(info) << "Initializing IOTOF digitizer";
LOG(info) << " Time resolution: " << mTimeResolution * 1e3 << " ps";
LOG(info) << " Charge threshold: " << mChargeThreshold << " electrons";
LOG(info) << " Detection efficiency: " << mEfficiency * 100 << " %";
LOG(info) << " Continuous mode: " << (mContinuous ? "ON" : "OFF");
sSegmentation = o2::iotof::Segmentation::Instance();
}
//_______________________________________________________________________
void Digitizer::process(const std::vector<o2::itsmft::Hit>* hits, int evID, int srcID)
{
// Digitize hits from a single event
LOG(debug) << "Digitizing IOTOF hits: " << hits->size() << " hits from event " << evID << " source " << srcID;
if (!hits || hits->empty()) {
return;
}
// Sort hits by detector ID for better cache locality
std::vector<int> hitIdx(hits->size());
std::iota(hitIdx.begin(), hitIdx.end(), 0);
std::sort(hitIdx.begin(), hitIdx.end(),
[hits](int lhs, int rhs) {
return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID();
});
// Process each hit
for (int i : hitIdx) {
processHit((*hits)[i], evID, srcID);
}
// In triggered mode, flush output after each event
if (!mContinuous) {
LOG(debug) << "Inner flushing for non-continuous mode";
fillOutputContainer();
}
}
//_______________________________________________________________________
void Digitizer::processHit(const o2::itsmft::Hit& hit, int evID, int srcID)
{
// Process a single hit and create a digit if it passes all cuts
// Apply efficiency cut
if (!isEfficient()) {
LOG(debug) << "Hit rejected by efficiency cut";
return;
}
// Get detector element ID
int chipID = hit.GetDetectorID();
auto& chip = mChips[chipID];
if (chip.isDisabled()) {
LOG(debug) << "Hit rejected because chip " << chipID << " is disabled";
return;
}
// Convert energy loss to charge (number of electrons)
float energyLoss = hit.GetEnergyLoss(); // in GeV
int charge = energyToCharge(energyLoss);
// Apply charge threshold
if (charge < mChargeThreshold) {
LOG(debug) << "Hit rejected by charge threshold: " << charge << " < " << mChargeThreshold;
return;
}
// Get hit time and apply smearing
// Hit time is in seconds, convert to ns and add event time
double hitTime = hit.GetTime() * sec2ns; // convert to ns
double eventTimeNS = mEventTime.getTimeNS(); // event time since orbit 0
double absoluteTime = hitTime + eventTimeNS; // absolute time
double smearedTime = smearTime(absoluteTime); // apply detector resolution
if (chipID < 0 || chipID >= mGeometry->getSize() || mGeometry->getSize() < 1) {
LOG(debug) << "Invalid detector ID: " << chipID << ", geometry size: " << mGeometry->getSize();
return; // invalid detector ID
}
const auto& matrix = mGeometry->getMatrixL2G(hit.GetDetectorID());
math_utils::Vector3D<float> xyzPositionStart(matrix ^ (hit.GetPosStart())); // start position in sensor frame
// math_utils::Vector3D<float> xyzPositionEnd(matrix ^ (hit.GetPos())); // end position in sensor frame
int row = 0; // Will be determined from start hit position
int col = 0; // Will be determined from start hit position
if (!sSegmentation->localToDetector(xyzPositionStart.X(), xyzPositionStart.Z(), row, col, mGeometry->getIOTOFLayer(chipID))) {
LOG(debug) << "Hit position out of bounds for detector ID " << chipID;
return; // hit is outside the active area
}
// Create the digit with time information
o2::MCCompLabel label(hit.GetTrackID(), evID, srcID, false);
const int roFrameAbs = 0; // For now, we can set this to 0 or calculate based on time if needed
const int nROF = 1; // For now, we can assume the signal is contained in one ROF, this can be extended to multiple ROFs based on the time
registerDigits(chip, roFrameAbs, smearedTime, nROF, static_cast<uint16_t>(row), static_cast<uint16_t>(col), charge, label);
}
//_______________________________________________________________________
double Digitizer::smearTime(double time) const
{
// Apply Gaussian smearing to simulate detector time resolution
if (mTimeResolution > 0) {
return time + gRandom->Gaus(0, mTimeResolution);
}
return time;
}
//_______________________________________________________________________
int Digitizer::energyToCharge(float energyLoss) const
{
// Convert energy loss (GeV) to number of electrons
// Typical value: 3.6 eV per electron-hole pair in silicon
// energyLoss is in GeV, mEnergyToCharge is GeV per electron
return static_cast<int>(energyLoss / mEnergyToCharge);
}
//_______________________________________________________________________
bool Digitizer::isEfficient() const
{
// Apply efficiency cut using random number
return gRandom->Uniform() < mEfficiency;
}
//_______________________________________________________________________
void Digitizer::fillOutputContainer()
{
LOG(info) << "Filling output container with digits from chips";
LOG(debug) << "Number of chips: " << mChips.size();
o2::itsmft::ROFRecord rof;
rof.setFirstEntry(mDigits->size()); // index of the first digit
const auto* extraLabelBuffer = mExtraLabelBuffer.empty() ? nullptr : mExtraLabelBuffer.front().get();
for (auto& chip : mChips) {
if (chip.isDisabled()) {
continue;
}
/// chip.addNoise(...); // to be implemented
if (chip.isEmpty()) {
continue;
}
auto& chipDigits = chip.getDigits();
for (const auto& [key, digit] : chipDigits) {
/// Charge threshold not implemented yet
/// if (digit.getCharge() < mChargeThreshold) {
/// continue; // skip digits below threshold
/// }
int digitID = mDigits->size();
mDigits->emplace_back(digit.getChipIndex(), digit.getRow(), digit.getColumn(), digit.getCharge(), digit.getTime());
if (mMCLabels) {
mMCLabels->addElement(digitID, digit.getLabel().mLabel);
}
auto labelRef = digit.getLabel();
while (mMCLabels && extraLabelBuffer != nullptr && labelRef.mNext >= 0) {
labelRef = (*extraLabelBuffer)[labelRef.mNext];
mMCLabels->addElement(digitID, labelRef.mLabel);
}
}
chipDigits.clear(); // clear chip digits after copying to output
}
rof.setNEntries(mDigits->size() - rof.getFirstEntry()); // number of digits
rof.setBCData(mContinuous ? mROFRecordIR : mEventTime);
mROFRecords->push_back(rof);
LOG(debug) << "Created ROF record with " << mDigits->size() << " digits";
// extraLabelBuffer.clear(); // clear buffer for extra labels
// mExtraLabelBuffer.emplace_back(mExtraLabelBuffer.front().release()); // move current buffer to the end
// mExtraLabelBuffer.pop_front();
}
void Digitizer::registerDigits(Chip& chip, uint32_t roFrame, double time, int nROF,
uint16_t row, uint16_t col, int nElectrons, o2::MCCompLabel& label)
{
(void)nROF;
auto key = o2::iotof::Digit::getOrderingKey(chip.getChipIndex(), row, col);
o2::iotof::LabeledDigit* existingDigit = chip.findDigit(key);
if (!existingDigit) {
// No existing digit, create a new one
chip.addDigit(row, col, nElectrons, time, label);
} else {
// Digit already exists, update charge and labels
const int storedCharge = existingDigit->getCharge();
existingDigit->setCharge(storedCharge + nElectrons);
existingDigit->setTime(std::min(existingDigit->getTime(), time));
if (existingDigit->getLabel().mLabel == label) {
return; // don't store the same label twice
}
std::vector<o2::iotof::McLabelRef>* extra = getExtraLabelBuffer(roFrame);
auto labelRef = existingDigit->getLabel();
const auto next = static_cast<int>(extra->size());
extra->emplace_back(label, labelRef.mNext);
labelRef.mNext = next;
existingDigit->setLabel(labelRef);
}
}
} // namespace o2::iotof