Project
Loading...
Searching...
No Matches
Digitizer.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14#include "FT0Base/FT0DigParam.h"
18
19#include "TMath.h"
20#include "TRandom.h"
21#include <TH1F.h>
22#include <algorithm>
23#include <cassert>
24#include <iostream>
25#include <optional>
26#include <Vc/Vc>
27
28using namespace o2::ft0;
29
31
32namespace o2::ft0
33{
34// signal shape function
35template <typename Float>
37{
38 using namespace std;
39 Float const a = -0.45458;
40 Float const b = -0.83344945;
41 return x > Float(0) ? -(exp(b * x) - exp(a * x)) / Float(7.8446501) : Float(0);
42};
43
44// integrated signal shape function
45inline float signalForm_integral(float x)
46{
47 using namespace std;
48 double const a = -0.45458;
49 double const b = -0.83344945;
50 if (x < 0) {
51 x = 0;
52 }
53 return -(exp(b * x) / b - exp(a * x) / a) / 7.8446501;
54};
55
56// SIMD version of the integrated signal shape function
57inline Vc::float_v signalForm_integralVc(Vc::float_v x)
58{
59 auto const mask = (x >= 0.0f);
60 Vc::float_v arg(0);
61 arg.assign(x, mask); // branchless if
62 Vc::float_v const a(-0.45458f);
63 Vc::float_v const b(-0.83344945f);
64 Vc::float_v result = -(Vc::exp(b * arg) / b - Vc::exp(a * arg) / a) / 7.8446501f;
65 return result;
66};
67} // namespace o2::ft0
68
69Digitizer::CFDOutput Digitizer::get_time(const std::vector<float>& times, float deadTime)
70{
71 assert(std::is_sorted(std::begin(times), std::end(times)));
72
73 // get a new batch of random values
74 for (float& n : mNoiseSamples) {
75 n = mRndGaus.getNextValue();
76 }
77
78 // this lambda function evaluates the signal shape at a give time
79 auto value_at = [&](float time) {
80 float val = 0;
81 // (1) sum over individual hits
82 Vc::float_v acc(0);
83 Vc::float_v tableVal(0);
84 const float* tp = times.data();
85 size_t m = times.size() / Vc::float_v::size();
86 for (size_t i = 0; i < m; ++i) {
87 tableVal.load(tp);
88 tp += Vc::float_v::size();
89 Vc::prefetchForOneRead(tp);
90 acc += signalFormVc(time - tableVal);
91 }
92 val += acc.sum();
93 // non-SIMD tail
94 for (size_t i = Vc::float_v::size() * m; i < times.size(); ++i, ++tp) {
95 val += signalForm(time - (*tp));
96 }
97 // (2) add noise
98 // find the right indices into the sinc table
99 int timeIndex = std::lround(time / FT0DigParam::Instance().mNoisePeriod * mSincTable.size());
100 int timeOffset = timeIndex / mSincTable.size();
101 timeIndex %= mSincTable.size();
102 if (timeOffset >= mNumNoiseSamples) { // this happens when time >= 25 ns
103 timeOffset = mNumNoiseSamples - 1;
104 LOG(debug) << "timeOffset >= mNumNoiseSamples";
105 }
106 if (timeOffset <= -mNumNoiseSamples) { // this happens when time <= -25 ns
107 timeOffset = -mNumNoiseSamples + 1;
108 LOG(debug) << "timeOffset <= -mNumNoiseSamples";
109 }
110 Vc::float_v noiseVal(0);
111 const float* np = mNoiseSamples.data();
112 tp = mSincTable[timeIndex].data() + mNumNoiseSamples - timeOffset;
113 acc = 0.0f;
114 m = mNumNoiseSamples / Vc::float_v::Size;
115 for (size_t i = 0; i < m; ++i) {
116 tableVal.load(tp);
117 tp += Vc::float_v::Size;
118 Vc::prefetchForOneRead(tp);
119 noiseVal.load(np);
120 np += Vc::float_v::Size;
121 Vc::prefetchForOneRead(np);
122 acc += noiseVal * tableVal;
123 }
124 val += acc.sum(); // horizontal sum
125 // non-SIMD tail
126 for (size_t i = Vc::float_v::Size * m; i < mNumNoiseSamples; ++i, ++tp, ++np) {
127 val += (*np) * (*tp);
128 }
129 return val;
130 };
131 auto const min_time = std::max(deadTime, *std::min_element(std::begin(times),
132 std::end(times)));
133 CFDOutput result{std::nullopt, -0.5f * FT0DigParam::Instance().mBunchWidth};
134 bool is_positive = true;
135
136 // reset the chache
137 std::fill_n(std::begin(mSignalCache), std::size(mSignalCache), -1.0f);
138 const auto& params = FT0DigParam::Instance();
139 // we need double precision for time in order to match previous behaviour
140 for (double time = min_time; time < 0.5 * params.mBunchWidth; time += DP::SIGNAL_CACHE_DT) {
141 float const val = value_at(time);
142 int const index = std::lround((time + 0.5 * params.mBunchWidth) / DP::SIGNAL_CACHE_DT);
143 if (index >= 0 && index < mSignalCache.size()) { // save the value for later use
144 mSignalCache[index] = val;
145 }
146 // look up the time-shifted signal value from the past
147 float val_prev = 0.0f;
148 int const index_prev = std::lround((time - params.mCFDShiftPos + 0.5f * params.mBunchWidth) / DP::SIGNAL_CACHE_DT);
149 val_prev = ((index_prev < 0 || index_prev >= mSignalCache.size() || mSignalCache[index_prev] < 0.0f)
150 ? value_at(time - params.mCFDShiftPos) // was not computed before
151 : mSignalCache[index_prev]); // is available in the cache
152 float const cfd_val = 5.0f * val_prev - val;
153 if (std::abs(val) > params.mCFD_trsh && !is_positive && cfd_val > 0.0f) {
154 if (!result.particle) {
155 result.particle = time;
156 }
157 result.deadTime = time + params.mCFDdeadTime;
158 time += params.mCFDdeadTime - DP::SIGNAL_CACHE_DT;
159 is_positive = true;
160 } else {
161 is_positive = cfd_val > 0.0f;
162 }
163 }
164 return result;
165}
166
167double Digitizer::measure_amplitude(const std::vector<float>& times) const
168{
169 float const from = FT0DigParam::Instance().mAmpRecordLow;
170 float const to = from + FT0DigParam::Instance().mAmpRecordUp;
171 // SIMD version has a negligible effect on the total wall time
172 Vc::float_v acc(0);
173 Vc::float_v tv(0);
174 const float* tp = times.data();
175 size_t const m = times.size() / Vc::float_v::Size;
176 for (size_t i = 0; i < m; ++i) {
177 tv.load(tp);
178 tp += Vc::float_v::Size;
179 Vc::prefetchForOneRead(tp);
180 acc += signalForm_integralVc(to - tv) - signalForm_integralVc(from - tv);
181 }
182 float result = acc.sum(); // horizontal sum
183 // non-SIMD tail
184 for (size_t i = Vc::float_v::Size * m; i < times.size(); ++i, ++tp) {
185 result += signalForm_integral(to - (*tp)) - signalForm_integral(from - (*tp));
186 }
187 return result;
188}
189
190void Digitizer::process(const std::vector<o2::ft0::HitType>* hits,
191 std::vector<o2::ft0::Digit>& digitsBC,
192 std::vector<o2::ft0::ChannelData>& digitsCh,
193 std::vector<o2::ft0::DetTrigInput>& digitsTrig,
195{
196 ;
197 // Calculating signal time, amplitude in mean_time +- time_gate --------------
198 LOG(debug) << " process firstBCinDeque " << firstBCinDeque << " mIntRecord " << mIntRecord;
199 if (firstBCinDeque != mIntRecord) {
200 flush(digitsBC, digitsCh, digitsTrig, label);
201 }
202
203 Int_t parent = -10;
204 for (auto const& hit : *hits) {
205 if (hit.GetEnergyLoss() > 0) {
206 continue;
207 }
208
209 Int_t hit_ch = hit.GetDetectorID();
210
211 // If the dead channel map is used, and the channel with ID 'hit_ch' is dead, don't process this hit.
212 if (mDeadChannelMap && !mDeadChannelMap->isChannelAlive(hit_ch)) {
213 continue;
214 }
215
216 const auto& params = FT0DigParam::Instance();
217
218 Bool_t is_A_side = (hit_ch < 4 * mGeometry.NCellsA);
219
220 // Subtract time-of-flight from hit time
221 const Float_t timeOfFlight = hit.GetPos().R() / o2::constants::physics::LightSpeedCm2NS;
222 const Float_t timeOffset = is_A_side ? params.hitTimeOffsetA : params.hitTimeOffsetC;
223 Double_t hit_time = hit.GetTime() - timeOfFlight + timeOffset;
224
225 if (hit_time > 150) {
226 continue; // not collect very slow particles
227 }
228
229 auto relBC = o2::InteractionRecord{hit_time};
230 if (mCache.size() <= relBC.bc) {
231 mCache.resize(relBC.bc + 1);
232 }
233 mCache[relBC.bc].hits.emplace_back(BCCache::particle{hit_ch, hit_time - relBC.bc2ns()});
234 // charge particles in MCLabel
235 Int_t parentID = hit.GetTrackID();
236 if (parentID != parent) {
237 mCache[relBC.bc].labels.emplace(parentID, mEventID, mSrcID, hit_ch);
238 parent = parentID;
239 }
240 }
241}
242
243void Digitizer::storeBC(BCCache& bc,
244 std::vector<o2::ft0::Digit>& digitsBC,
245 std::vector<o2::ft0::ChannelData>& digitsCh,
246 std::vector<o2::ft0::DetTrigInput>& digitsTrig,
248{
249 if (bc.hits.empty()) {
250 return;
251 }
252 int n_hit_A = 0, n_hit_C = 0, mean_time_A = 0, mean_time_C = 0;
253 int summ_ampl_A = 0, summ_ampl_C = 0;
254 int vertex_time;
255 const auto& params = FT0DigParam::Instance();
256 int first = digitsCh.size(), nStored = 0;
257 auto& particles = bc.hits;
258 std::sort(std::begin(particles), std::end(particles));
259 auto channel_end = particles.begin();
260 std::vector<float> channel_times;
261 for (Int_t ipmt = 0; ipmt < params.mMCPs; ++ipmt) {
262 auto channel_begin = channel_end;
263 channel_end = std::find_if(channel_begin, particles.end(),
264 [ipmt](BCCache::particle const& p) { return p.hit_ch != ipmt; });
265
266 // The hits between 'channel_begin' and 'channel_end' now contains all hits for channel 'ipmt'
267
268 if (channel_end - channel_begin < params.mAmp_trsh) {
269 continue;
270 }
271 channel_times.resize(channel_end - channel_begin);
272 std::transform(channel_begin, channel_end, channel_times.begin(), [](BCCache::particle const& p) { return p.hit_time; });
273 int chain = (std::rand() % 2) ? 1 : 0;
274 auto cfd = get_time(channel_times, mDeadTimes[ipmt].intrec.bc2ns() -
275 firstBCinDeque.bc2ns() +
276 mDeadTimes[ipmt].deadTime);
277 mDeadTimes[ipmt].intrec = firstBCinDeque;
278 mDeadTimes[ipmt].deadTime = cfd.deadTime;
279
280 if (!cfd.particle) {
281 continue;
282 }
283 // miscalibrate CFD with cahnnel offsets
284 int miscalib = 0;
285 if (mCalibOffset) {
286 miscalib = mCalibOffset->mTimeOffsets[ipmt];
287 }
288 int smeared_time = 1000. * (*cfd.particle - params.mCfdShift) * params.mChannelWidthInverse + miscalib + int(1000. * mIntRecord.getTimeOffsetWrtBC() * params.mChannelWidthInverse);
289 bool is_time_in_signal_gate = (smeared_time > -params.mTime_trg_gate && smeared_time < params.mTime_trg_gate);
290 float charge = measure_amplitude(channel_times) * params.mCharge2amp;
291 float amp = is_time_in_signal_gate ? params.mMV_2_Nchannels * charge : 0;
292 if (amp > 4095) {
293 amp = 4095;
294 }
295
296 LOG(debug) << mEventID << " bc " << firstBCinDeque.bc << " orbit " << firstBCinDeque.orbit << ", ipmt " << ipmt << ", smeared_time " << smeared_time << " nStored " << nStored << " offset " << miscalib;
297 if (is_time_in_signal_gate) {
300 }
301 digitsCh.emplace_back(ipmt, smeared_time, int(amp), chain);
302 nStored++;
303
304 // fill triggers
305
306 Bool_t is_A_side = (ipmt < 4 * mGeometry.NCellsA);
307 if (!is_time_in_signal_gate) {
308 continue;
309 }
310
311 if (is_A_side) {
312 n_hit_A++;
313 summ_ampl_A += amp;
314 mean_time_A += smeared_time;
315 } else {
316 n_hit_C++;
317 summ_ampl_C += amp;
318 mean_time_C += smeared_time;
319 }
320 }
321 Bool_t is_A, is_C, isVertex, is_Central, is_SemiCentral = 0;
322 is_A = n_hit_A > 0;
323 is_C = n_hit_C > 0;
324 is_Central = summ_ampl_A + summ_ampl_C >= params.mtrg_central_trh;
325 is_SemiCentral = summ_ampl_A + summ_ampl_C >= params.mtrg_semicentral_trh;
326 uint32_t amplA = is_A ? summ_ampl_A * 0.125 : -5000; // sum amplitude A side / 8 (hardware)
327 uint32_t amplC = is_C ? summ_ampl_C * 0.125 : -5000; // sum amplitude C side / 8 (hardware)
328 int timeA = is_A ? mean_time_A / n_hit_A : -5000; // average time A side
329 int timeC = is_C ? mean_time_C / n_hit_C : -5000; // average time C side
330 vertex_time = (timeC - timeA) * 0.5;
331 isVertex = is_A && is_C && (vertex_time > -params.mTime_trg_gate && vertex_time < params.mTime_trg_gate);
332 LOG(debug) << " A " << is_A << " timeA " << timeA << " mean_time_A " << mean_time_A << " n_hit_A " << n_hit_A << " C " << is_C << " timeC " << timeC << " mean_time_C " << mean_time_C << " n_hit_C " << n_hit_C << " vertex_time " << vertex_time;
333 Triggers triggers;
334 bool isLaser = false;
335 bool isOutputsAreBlocked = false;
336 bool isDataValid = true;
337 if (nStored > 0) {
338 triggers.setTriggers(is_A, is_C, isVertex, is_Central, is_SemiCentral, int8_t(n_hit_A), int8_t(n_hit_C),
339 amplA, amplC, timeA, timeC, isLaser, isOutputsAreBlocked, isDataValid);
340 digitsBC.emplace_back(first, nStored, firstBCinDeque, triggers, mEventID - 1);
341 digitsTrig.emplace_back(firstBCinDeque, is_A, is_C, isVertex, is_Central, is_SemiCentral);
342 size_t const nBC = digitsBC.size();
343 for (auto const& lbl : bc.labels) {
344 labels.addElement(nBC - 1, lbl);
345 }
346 }
347 // Debug output -------------------------------------------------------------
348
349 LOG(debug) << "Event ID: " << mEventID << ", bc " << firstBCinDeque.bc << ", N hit " << bc.hits.size();
350 LOG(debug) << "N hit A: " << int(triggers.getNChanA()) << " N hit C: " << int(triggers.getNChanC()) << " summ ampl A: " << int(triggers.getAmplA())
351 << " summ ampl C: " << int(triggers.getAmplC()) << " mean time A: " << triggers.getTimeA()
352 << " mean time C: " << triggers.getTimeC() << " nStored " << nStored;
353
354 LOG(debug) << "IS A " << triggers.getOrA() << " IsC " << triggers.getOrC() << " vertex " << triggers.getVertex() << " is Central " << triggers.getCen() << " is SemiCentral " << triggers.getSCen();
355}
356
357//------------------------------------------------------------------------
358void Digitizer::flush(std::vector<o2::ft0::Digit>& digitsBC,
359 std::vector<o2::ft0::ChannelData>& digitsCh,
360 std::vector<o2::ft0::DetTrigInput>& digitsTrig,
362{
363
364 assert(firstBCinDeque <= mIntRecord);
365
366 while (firstBCinDeque < mIntRecord && !mCache.empty()) {
367 storeBC(mCache.front(), digitsBC, digitsCh, digitsTrig, labels);
368 mCache.pop_front();
369 ++firstBCinDeque;
370 }
371 firstBCinDeque = mIntRecord;
372}
373
374void Digitizer::flush_all(std::vector<o2::ft0::Digit>& digitsBC,
375 std::vector<o2::ft0::ChannelData>& digitsCh,
376 std::vector<o2::ft0::DetTrigInput>& digitsTrig,
378{
379
380 assert(firstBCinDeque <= mIntRecord);
381 ++mEventID;
382 while (!mCache.empty()) {
383 storeBC(mCache.front(), digitsBC, digitsCh, digitsTrig, labels);
384 mCache.pop_front();
385 ++firstBCinDeque;
386 }
387}
388
390{
391 auto const sinc = [](double x) { x *= TMath::Pi(); return (std::abs(x) < 1e-12) ? 1.0 : std::sin(x) / x; };
392
393 // number of noise samples in one BC
394 const auto& params = FT0DigParam::Instance();
395 mNumNoiseSamples = std::ceil(params.mBunchWidth / params.mNoisePeriod);
396 mNoiseSamples.resize(mNumNoiseSamples);
397
398 // set up tables with sinc function values (times noiseVar)
399 for (size_t i = 0, n = mSincTable.size(); i < n; ++i) {
400 float const time = i / float(n) * params.mNoisePeriod; // [0 .. 1/params.mNoisePeriod)
401 LOG(debug) << "initParameters " << i << "/" << n << " " << time;
402 // we make a table of sinc values between -num_noise_samples and 2*num_noise_samples
403 mSincTable[i].resize(3 * mNumNoiseSamples);
404 for (int j = -mNumNoiseSamples; j < 2 * mNumNoiseSamples; ++j) {
405 mSincTable[i][mNumNoiseSamples + j] = params.mNoiseVar * sinc((time + 0.5f * params.mBunchWidth) / params.mNoisePeriod - j);
406 }
407 }
408 // set up the lookup table for the signal form
409 for (size_t i = 0; i < DP::SIGNAL_TABLE_SIZE; ++i) {
410 float const x = float(i) / float(DP::SIGNAL_TABLE_SIZE) * params.mBunchWidth;
411 mSignalTable[i] = signalForm_i(x);
412 }
413
414 // cache for signal time series used by the CFD -BC/2 .. +3BC/2
415 mSignalCache.resize(std::lround(params.mBunchWidth / DP::SIGNAL_CACHE_DT));
416}
417//_______________________________________________________________________
419{
420 LOG(info) << " @@@ Digitizer::init " << std::endl;
421 mDeadTimes.fill({InteractionRecord(0), -100.});
423}
424//_______________________________________________________________________
426{
428}
429
430//_______________________________________________________________________
432{
433 const auto& params = FT0DigParam::Instance();
434 LOG(info) << " Run Digitzation with parametrs: \n"
435 << " CFD amplitude threshold \n " << params.mCFD_trsh << " CFD signal gate in ps \n"
436 << params.mTime_trg_gate << "shift to have signal around zero after CFD trancformation \n"
437 << params.mCfdShift << "CFD distance between 0.3 of max amplitude to max \n"
438 << params.mCFDShiftPos << "MIP -> mV " << params.mMip_in_V << " Pe in MIP \n"
439 << params.mPe_in_mip << "noise level " << params.mNoiseVar << " noise frequency \n"
440 << params.mNoisePeriod << " mMCPs " << params.mMCPs;
441}
442
std::vector< unsigned long > times
#define O2ParamImpl(classname)
uint64_t exp(uint64_t base, uint8_t exp) noexcept
ClassImp(Digitizer)
int64_t timeC
int16_t charge
Definition RawEventData.h:5
int64_t amplA
int64_t timeA
int64_t amplC
uint64_t bc
Definition RawEventData.h:5
int16_t time
Definition RawEventData.h:4
Configurable digitization parameters.
int32_t i
GPUChain * chain
Definition of a container to keep Monte Carlo truth external to simulation objects.
Header to collect physics constants.
uint32_t j
Definition RawData.h:0
std::ostringstream debug
A container to hold and manage MC truth information/labels.
void addElement(uint32_t dataindex, TruthElement const &element, bool noElement=false)
uint8_t getNChanC() const
Definition Triggers.h:91
int32_t getAmplC() const
Definition Triggers.h:93
bool getCen() const
Definition Triggers.h:79
int32_t getAmplA() const
Definition Triggers.h:92
bool getVertex() const
Definition Triggers.h:81
void setTriggers(uint8_t trgsig, uint8_t chanA, uint8_t chanC, int32_t aamplA, int32_t aamplC, int16_t atimeA, int16_t atimeC)
Definition Triggers.h:97
int16_t getTimeC() const
Definition Triggers.h:95
uint8_t getNChanA() const
Definition Triggers.h:90
bool getOrA() const
Definition Triggers.h:74
bool getOrC() const
Definition Triggers.h:75
int16_t getTimeA() const
Definition Triggers.h:94
bool getSCen() const
Definition Triggers.h:77
void flush(std::vector< o2::ft0::Digit > &digitsBC, std::vector< o2::ft0::ChannelData > &digitsCh, std::vector< o2::ft0::DetTrigInput > &digitsTrig, o2::dataformats::MCTruthContainer< o2::ft0::MCLabel > &label)
void printParameters() const
CFDOutput get_time(const std::vector< float > &times, float deadTime)
Definition Digitizer.cxx:69
VcType signalFormVc(VcType x) const
Definition Digitizer.h:121
void process(const std::vector< o2::ft0::HitType > *hits, std::vector< o2::ft0::Digit > &digitsBC, std::vector< o2::ft0::ChannelData > &digitsCh, std::vector< o2::ft0::DetTrigInput > &digitsTrig, o2::dataformats::MCTruthContainer< o2::ft0::MCLabel > &label)
void flush_all(std::vector< o2::ft0::Digit > &digitsBC, std::vector< o2::ft0::ChannelData > &digitsCh, std::vector< o2::ft0::DetTrigInput > &digitsTrig, o2::dataformats::MCTruthContainer< o2::ft0::MCLabel > &label)
double measure_amplitude(const std::vector< float > &times) const
float signalForm(float x) const
Definition Digitizer.h:106
static constexpr int NCellsA
Definition Geometry.h:52
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
const GLfloat * m
Definition glcorearb.h:4066
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint index
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLenum const GLfloat * params
Definition glcorearb.h:272
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
GLuint GLfloat * val
Definition glcorearb.h:1582
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLint GLuint mask
Definition glcorearb.h:291
constexpr float LightSpeedCm2NS
Float signalForm_i(Float x)
Definition Digitizer.cxx:36
Vc::float_v signalForm_integralVc(Vc::float_v x)
Definition Digitizer.cxx:57
float signalForm_integral(float x)
Definition Digitizer.cxx:45
Defining DataPointCompositeObject explicitly as copiable.
uint32_t orbit
LHC orbit.
uint16_t bc
bunch crossing ID of interaction
static double bc2ns(int bc, unsigned int orbit)
const bool isChannelAlive(const uint8_t &chId) const
static constexpr double SIGNAL_CACHE_DT
std::array< int16_t, o2::ft0::Geometry::Nchannels > mTimeOffsets
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"