R3BROOT
R3B analysis software
Loading...
Searching...
No Matches
R3BDigitizingTamex.cxx
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) 2019 GSI Helmholtzzentrum für Schwerionenforschung GmbH *
3 * Copyright (C) 2019-2025 Members of R3B Collaboration *
4 * *
5 * This software is distributed under the terms of the *
6 * GNU General Public Licence (GPL) version 3, *
7 * copied verbatim in the file "LICENSE". *
8 * *
9 * In applying this license GSI does not waive the privileges and immunities *
10 * granted to it by virtue of its status as an Intergovernmental Organization *
11 * or submit itself to any jurisdiction. *
12 ******************************************************************************/
13
14#include "R3BDigitizingTamex.h"
15
16#include <FairRuntimeDb.h>
17#include <cmath>
18
19#include "R3BException.h"
20#include <FairRunAna.h>
21#include <R3BLogger.h>
22#include <algorithm>
23
25{
26 // some declarations for static functions:
27 namespace
28 {
29 template <class T>
30 auto CheckOverlapping(const T& peak, std::vector<T>& peaks) -> decltype(peaks.begin());
31 template <class T>
32 void ReOverlapping(typename std::vector<T>::iterator v_iter, std::vector<T>& peaks);
33 template <class T>
34 void RemovePeakAt(typename std::vector<T>::iterator v_iter, std::vector<T>& peaks);
35
36 void set_par_with_hit_module_par(Tamex::Params& par,
37 const R3B::Neuland::HitModulePar& module_par,
38 ChannelSide channel_side)
39 {
40 auto side = (channel_side == ChannelSide::right) ? Side::right : Side::left;
41
42 par.fSaturationCoefficient = module_par.pmt_saturation.get(side).value;
43 par.fEnergyGain = module_par.energy_gain.get(side).value;
44 par.fPedestal = module_par.pedestal.get(side).value;
45 par.fPMTThresh = module_par.pmt_threshold.get(side).value;
46 par.fQdcMin = 1 / par.fEnergyGain;
47
48 // TODO: Add other parameters:
49 }
50 } // namespace
51
52 // global variables for default options:
53 const size_t TmxPeaksInitialCapacity = 10;
54
55 Params::Params(TRandom3& rnd)
56 : fRnd{ rnd }
57 {
58 }
59
61 : time_(pmtHit.time)
62 {
63 const auto& par = channel.GetParConstRef();
64 // apply saturation coefficent
65 qdc_ = pmtHit.light / (1. + par.fSaturationCoefficient * pmtHit.light);
66 };
67
68 auto PMTPeak::operator+=(const PMTPeak& other) -> PMTPeak&
69 {
70 qdc_ += other.qdc_;
71 time_ = (time_ < other.time_) ? time_ : other.time_;
72 return *this;
73 }
74
75 FQTPeak::FQTPeak(const PMTPeak& pmtPeak, Channel* channel)
76 : qdc_(pmtPeak.GetQDC())
77 , leading_edge_time_(pmtPeak.GetLETime())
78 , channel_ptr_(channel)
79 {
80 if (channel_ptr_ == nullptr)
81 {
82 LOG(fatal) << "channel is not bound to FQTPeak object!";
83 }
84 const auto& par = channel->GetParConstRef();
85
86 // calculate the time and the width of the signal
87 width_ = QdcToWidth(qdc_, par);
88 trailing_edge_time_ = leading_edge_time_ + width_;
89 }
90
91 auto FQTPeak::operator==(const FQTPeak& other) const -> bool
92 {
93 if (other.leading_edge_time_ == 0 && leading_edge_time_ == 0)
94 {
95 LOG(warn) << "the times of both PMT signals are 0!";
96 }
97 return (leading_edge_time_ <= (other.leading_edge_time_ + other.width_)) &&
98 (other.leading_edge_time_ <= (leading_edge_time_ + width_));
99 }
100
101 void FQTPeak::operator+=(const FQTPeak& other)
102 {
103 if (channel_ptr_ == nullptr)
104 {
105 throw R3B::logic_error("channel is not bound to FQTPeak object!");
106 }
107 leading_edge_time_ =
108 (leading_edge_time_ < other.leading_edge_time_) ? leading_edge_time_ : other.leading_edge_time_;
109 trailing_edge_time_ =
110 (trailing_edge_time_ > other.trailing_edge_time_) ? trailing_edge_time_ : other.trailing_edge_time_;
111 width_ = trailing_edge_time_ - leading_edge_time_;
112 qdc_ = WidthToQdc(width_, channel_ptr_->GetParConstRef());
113 }
114
116 PeakPileUpStrategy strategy,
117 const Params& par,
118 R3B::Neuland::Cal2HitPar* cal_to_hit_par)
119 : Digitizing::Channel{ side }
120 , pileup_strategy_{ strategy }
121 , neuland_hit_par_{ cal_to_hit_par }
122 , par_{ par }
123 {
124 pmt_peaks_.reserve(TmxPeaksInitialCapacity);
125 }
126
127 Channel::Channel(ChannelSide side, PeakPileUpStrategy strategy, TRandom3& rnd)
128 : Channel{ side, strategy, Params{ rnd } }
129 {
130 }
131
133 {
134
135 if (paddle == nullptr)
136 {
137 return;
138 }
139 if (CheckPaddleIDInHitPar())
140 {
141 const auto& module_par = neuland_hit_par_->GetModuleParAt(paddle->GetPaddleID());
142 set_par_with_hit_module_par(par_, module_par, GetSide());
143 }
144 }
145
146 auto Channel::CheckPaddleIDInHitPar() const -> bool
147 {
148 auto is_valid = false;
149 if (neuland_hit_par_ == nullptr)
150 {
151 return false;
152 }
153 if (not neuland_hit_par_->hasChanged())
154 {
155 R3BLOG(warn, "Can't setup parameter in the root file correctly!.");
156 return false;
157 }
158
159 auto PaddleId_max = neuland_hit_par_->GetNumModulePar();
160 if (GetPaddle()->GetPaddleID() > PaddleId_max)
161 {
162 LOG(warn) << "Paddle id " << GetPaddle()->GetPaddleID() << " exceeds the id " << PaddleId_max
163 << " in the parameter file!";
164 is_valid = false;
165 }
166 else
167 {
168 is_valid = true;
169 }
170
171 return is_valid;
172 }
173
174 void Channel::AddHit(Hit newHit)
175 {
176 if (newHit.time < par_.fTimeMin || newHit.time > par_.fTimeMax)
177 {
178 return;
179 }
182 pmt_peaks_.emplace_back(newHit, *this);
183 }
184
185 auto Channel::CreateSignal(const FQTPeak& peak) const -> Signal
186 {
187 auto peakQdc = peak.GetQDC();
188 auto peakTime = peak.GetLETime();
189 auto qdc = ToQdc(peakQdc);
190
191 LOG(debug) << "qdc Signal " << qdc << " and tdc " << peakTime << "\n";
192
193 auto signal = Signal{};
194 signal.qdcUnSat = ToUnSatQdc(qdc);
195 signal.qdc = qdc;
196 signal.tdc = ToTdc(peakTime);
197 signal.side = this->GetSide();
198 LOG(debug) << "R3BDigitizingTamex: Create a signal with qdc " << signal.qdc << " and tdc " << signal.tdc
199 << "\n";
200 return signal;
201 }
202
203 auto Channel::CreateCalSignal(const FQTPeak& peak) const -> CalSignal
204 {
205 auto peakQdc = peak.GetQDC();
206 auto peakTime = peak.GetLETime();
207 auto qdc = ToQdc(peakQdc);
208
209 LOG(debug) << "qdc Cal " << qdc << " and tdc " << peakTime << "\n";
210
211 auto signal = CalSignal{};
212 signal.tot = CalculateTOT(qdc);
213 signal.tle = peakTime;
214 signal.side = this->GetSide();
215 LOG(debug) << "R3BDigitizingTamex: Create a CalSignal with tot " << signal.tot << " and let " << signal.tle
216 << "\n qdc: " << qdc << "\n";
217 return signal;
218 }
219
220 auto Channel::CalculateTOT(const double& qdc) const -> double
221 {
222 // ToDo: Decide if ERROR stuff is needed
223 // if (GetErrorCalculation())
224 // {
225 // ValueError<double> qdc_err_val{ qdc, 0 };
226 // auto par_err_val = GetParErrVal();
227 // auto tot_err_val = qdc_err_val * par_err_val.energyGain + par_err_val.pedestal;
228 // auto randGen = GetDefaultRandomGen();
229 // return randGen.Gaus(tot_err_val.value, tot_err_val.error);
230 // }
231 // else {
232 //
233 // auto par = GetParConstRef();
234 // return (qdc * par.fEnergyGain + par.fPedestal);
235 // }
236
237 auto par = GetParConstRef();
238 return ((qdc * par.fEnergyGain) + par.fPedestal);
239 }
240
241 template <typename Peak>
242 void Channel::PeakPileUp(/* inout */ std::vector<Peak>& peaks)
243 {
244 if (peaks.size() <= 1)
245 {
246 return;
247 }
248
249 std::sort(peaks.begin(), peaks.end(), std::less{});
250 for (auto front_peak = peaks.begin(); front_peak != peaks.end(); ++front_peak)
251 {
252 auto end_peak = std::remove_if(front_peak + 1,
253 peaks.end(),
254 [&front_peak](auto& peak)
255 {
256 if (*front_peak == peak)
257 {
258 (*front_peak) += peak;
259 return true;
260 }
261 return false;
262 });
263 peaks.erase(end_peak, peaks.end());
264 }
265 }
266
267 void Channel::PeakPileUpWithDistance(/* inout */ std::vector<FQTPeak>& peaks, double distance)
268 {
269 if (peaks.empty())
270 {
271 return;
272 }
273 std::sort(peaks.begin(), peaks.end(), std::less{});
274
275 for (auto front_peak = peaks.begin(); front_peak != peaks.end(); ++front_peak)
276 {
277 auto last_leading_time = front_peak->GetLETime();
278 auto end_peak = std::remove_if(front_peak + 1,
279 peaks.end(),
280 [&distance, &front_peak, &last_leading_time](FQTPeak& peak)
281 {
282 if ((peak - last_leading_time) < distance)
283 {
284 front_peak->AddQDC(peak.GetQDC());
285 last_leading_time = peak.GetLETime();
286 return true;
287 }
288 return false;
289 });
290 peaks.erase(end_peak, peaks.end());
291 }
292 }
293
294 void Channel::PeakPileUpInTimeWindow(/* inout */ std::vector<FQTPeak>& peaks, double time_window)
295 {
296 if (peaks.empty())
297 {
298 return;
299 }
300 std::sort(peaks.begin(), peaks.end(), std::less{});
301
302 auto& front_peak = peaks.front();
303 std::for_each(peaks.begin() + 1,
304 peaks.end(),
305 [&front_peak, time_window](FQTPeak& peak)
306 {
307 if ((peak - front_peak) < time_window)
308 {
309 front_peak.AddQDC(peak.GetQDC());
310 }
311 });
312 peaks.erase(peaks.begin() + 1, peaks.end());
313 }
314
315 void Channel::FQTPeakPileUp(/* inout */ std::vector<FQTPeak>& peaks)
316 {
317 switch (pileup_strategy_)
318 {
319 case PeakPileUpStrategy::width:
320 PeakPileUp(peaks);
321 break;
322 case PeakPileUpStrategy::distance:
323 PeakPileUpWithDistance(peaks, par_.fPileUpDistance);
324 break;
325 case PeakPileUpStrategy::time_window:
326 PeakPileUpInTimeWindow(peaks, par_.fPileUpTimeWindow);
327 break;
328 default:
329 break;
330 }
331 }
332
333 template <typename Peak>
334 void Channel::ApplyThreshold(std::vector<Peak>& peaks)
335 {
336 // apply threshold on energy using c++ erase-remove idiom:
337 auto it_end =
338 std::remove_if(peaks.begin(),
339 peaks.end(),
340 [this](const auto& peak) { return peak.GetQDC() < this->GetParConstRef().fPMTThresh; });
341 peaks.erase(it_end, peaks.end());
342 }
343
344 auto Channel::ConstructFQTPeaks(std::vector<PMTPeak>& pmtPeaks) -> std::vector<FQTPeak>
345 {
346 auto FQTPeaks = std::vector<FQTPeak>{};
347 FQTPeaks.reserve(pmtPeaks.size());
348
349 // sorting pmt peaks according to time:
350 std::sort(pmtPeaks.begin(), pmtPeaks.end());
351
352 PeakPileUp(pmtPeaks);
353 ApplyThreshold(pmtPeaks);
354 for (auto const& peak : pmtPeaks)
355 {
356 FQTPeaks.emplace_back(peak, this);
357 }
358 return FQTPeaks;
359 }
360
362 {
363 fqt_peaks_ = ConstructFQTPeaks(pmt_peaks_);
364 // signal pileup:
365 FQTPeakPileUp(fqt_peaks_);
366
367 // construct Channel signals:
368 auto signals = std::vector<Signal>{};
369 signals.reserve(fqt_peaks_.size());
370
371 for (const auto& peak : fqt_peaks_)
372 {
373 signals.emplace_back(CreateSignal(peak));
374 }
375 return signals;
376 }
377
379 {
380 fqt_peaks_ = ConstructFQTPeaks(pmt_peaks_);
381 // signal pileup:
382 FQTPeakPileUp(fqt_peaks_);
383
384 // construct Channel signals:
385 auto cal_signals = std::vector<CalSignal>{};
386 cal_signals.reserve(fqt_peaks_.size());
387
388 for (const auto& peak : fqt_peaks_)
389 {
390 cal_signals.emplace_back(CreateCalSignal(peak));
391 }
392 return cal_signals;
393 }
394
396
397 auto Channel::GetFQTPeaks() -> const std::vector<FQTPeak>&
398 {
399
400 if (!Is_ValidSignals())
401 {
403 }
404 return fqt_peaks_;
405 }
406
407 auto Channel::GetPMTPeaks() -> const std::vector<PMTPeak>&
408 {
409 if (!Is_ValidSignals())
410 {
412 }
413 return pmt_peaks_;
414 }
415
416 auto Channel::ToQdc(double qdc) const -> double
417 {
418 // apply energy smearing
419 qdc = par_.fRnd.get().Gaus(qdc, par_.fEResRel * qdc);
420 return qdc;
421 }
422
423 auto Channel::ToTdc(double time) const -> double { return time + par_.fRnd.get().Gaus(0., par_.fTimeRes); }
424
425 auto Channel::ToUnSatQdc(double qdc) const -> double
426 {
427 // Apply reverse saturation
428 if (par_.fExperimentalDataIsCorrectedForSaturation)
429 {
430 qdc = qdc / (1 - par_.fSaturationCoefficient * qdc);
431 LOG(debug) << "ToUnSatQdc: fSaturationCoefficient = " << par_.fSaturationCoefficient << '\n';
432 }
433 // Apply reverse attenuation
434 return qdc;
435 }
436} // namespace R3B::Digitizing::Neuland::Tamex
#define R3BLOG(severity, x)
Definition R3BLogger.h:35
virtual auto ConstructCalSignals() -> CalSignals
virtual auto GetCalSignals() -> CalSignals
std::vector< Signal > Signals
virtual auto ConstructSignals() -> Signals=0
auto GetSide() const -> ChannelSide
auto GetPaddle() const -> Paddle *
std::vector< CalSignal > CalSignals
auto GetParConstRef() const -> const Tamex::Params &
auto GetFQTPeaks() -> const std::vector< FQTPeak > &
auto GetPMTPeaks() -> const std::vector< PMTPeak > &
void AttachToPaddle(Digitizing::Paddle *paddle) override
auto CreateSignal(const FQTPeak &peak) const -> Signal
auto ConstructCalSignals() -> CalSignals override
Channel(ChannelSide, PeakPileUpStrategy strategy, TRandom3 &)
auto CreateCalSignal(const FQTPeak &peak) const -> CalSignal
FQTPeak(const PMTPeak &pmtPeak, Channel *channel)
static auto QdcToWidth(double qdc, const Par &par) -> double
auto operator==(const FQTPeak &other) const -> bool
static auto WidthToQdc(double width, const Par &par) -> double
auto operator+=(const PMTPeak &other) -> PMTPeak &
auto GetPaddleID() const -> int
std::reference_wrapper< TRandom3 > fRnd
LRPair< ValueError< double > > pedestal
LRPair< ValueError< double > > pmt_threshold
LRPair< ValueError< double > > energy_gain
LRPair< ValueError< double > > pmt_saturation