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 <TRandom3.h>
18#include <cmath>
19
21#include "R3BDigitizingPaddle.h"
22#include "R3BException.h"
24#include "R3BShared.h"
25#include <FairRunAna.h>
26#include <R3BLogger.h>
27#include <algorithm>
28#include <cstddef>
29#include <fairlogger/Logger.h>
30#include <functional>
31#include <vector>
32
34{
35 // some declarations for static functions:
36 namespace
37 {
38 template <class T>
39 auto CheckOverlapping(const T& peak, std::vector<T>& peaks) -> decltype(peaks.begin());
40 template <class T>
41 void ReOverlapping(typename std::vector<T>::iterator v_iter, std::vector<T>& peaks);
42 template <class T>
43 void RemovePeakAt(typename std::vector<T>::iterator v_iter, std::vector<T>& peaks);
44
45 void set_par_with_hit_module_par(Tamex::Params& par,
46 const R3B::Neuland::HitModulePar& module_par,
47 Side channel_side)
48 {
49 auto side = (channel_side == Side::right) ? Side::right : Side::left;
50
51 par.saturation_coefficient = module_par.pmt_saturation.get(side).value;
52 par.energy_gain = module_par.energy_gain.get(side).value;
53 par.pedestal = module_par.pedestal.get(side).value;
54 par.pmt_thresh = module_par.pmt_threshold.get(side).value;
55 par.min_energy = 1 / par.energy_gain;
56
57 // TODO: Add other parameters:
58 }
59 } // namespace
60
61 // global variables for default options:
62 const size_t TmxPeaksInitialCapacity = 10;
63
64 Params::Params(TRandom3& rnd)
65 : rnd_gen{ rnd }
66 {
67 }
68
70 : time_(channel_signal.time)
71 {
72 const auto& par = channel.GetPar();
73 // apply saturation coefficent
74 height_ = channel_signal.intensity / (1. + par.saturation_coefficient * channel_signal.intensity);
75 };
76
77 auto PMTPeak::operator+=(const PMTPeak& other) -> PMTPeak&
78 {
79 height_ += other.height_;
80 time_ = (time_ < other.time_) ? time_ : other.time_;
81 return *this;
82 }
83
84 FQTPeak::FQTPeak(const PMTPeak& pmtPeak, Channel* channel)
85 : energy_(pmtPeak.GetHeight())
86 , leading_edge_time_(pmtPeak.GetLETime())
87 , channel_ptr_(channel)
88 {
89 if (channel_ptr_ == nullptr)
90 {
91 LOG(fatal) << "channel is not bound to FQTPeak object!";
92 }
93 const auto& par = channel->GetPar();
94
95 // calculate the time and the width of the signal
98 }
99
100 auto FQTPeak::operator==(const FQTPeak& other) const -> bool
101 {
102 if (other.leading_edge_time_ == 0 && leading_edge_time_ == 0)
103 {
104 LOG(warn) << "the times of both PMT signals are 0!";
105 }
106 return (leading_edge_time_ <= (other.leading_edge_time_ + other.time_over_thresh_)) &&
107 (other.leading_edge_time_ <= (leading_edge_time_ + time_over_thresh_));
108 }
109
110 void FQTPeak::operator+=(const FQTPeak& other)
111 {
112 if (channel_ptr_ == nullptr)
113 {
114 throw R3B::logic_error("channel is not bound to FQTPeak object!");
115 }
122 }
123
125 PeakPileUpStrategy strategy,
126 const Params& par,
127 R3B::Neuland::Cal2HitPar* cal_to_hit_par,
128 bool has_cal_output)
129 : Digitizing::AbstractChannel{ side, has_cal_output }
130 , pileup_strategy_{ strategy }
131 , neuland_hit_par_{ cal_to_hit_par }
132 , par_{ par }
133 {
135 }
136
137 Channel::Channel(Side side, PeakPileUpStrategy strategy, TRandom3& rnd)
138 : Channel{ side, strategy, Params{ rnd } }
139 {
140 }
141
143 {
144 auto is_valid = false;
145 if (neuland_hit_par_ == nullptr)
146 {
147 return false;
148 }
149 if (not neuland_hit_par_->hasChanged())
150 {
151 R3BLOG(warn, "Can't setup parameter in the root file correctly!.");
152 return false;
153 }
154
155 auto PaddleId_max = neuland_hit_par_->GetNumModulePar();
156 if (GetPaddle()->GetPaddleID() > PaddleId_max)
157 {
158 LOG(warn) << "Paddle id " << GetPaddle()->GetPaddleID() << " exceeds the id " << PaddleId_max
159 << " in the parameter file!";
160 is_valid = false;
161 }
162 else
163 {
164 is_valid = true;
165 }
166
167 return is_valid;
168 }
169
171 {
172 if (new_signal.time < par_.min_time || new_signal.time > par_.max_time)
173 {
174 return;
175 }
176 pmt_peaks_.emplace_back(new_signal, *this);
177 }
178
179 auto Channel::CreateHit(const FQTPeak& peak) const -> Hit
180 {
181 auto peakQdc = peak.GetEnergy();
182 auto peakTime = peak.GetLETime();
183 auto qdc = smear_energy(peakQdc);
184
185 auto signal = Hit{};
186 signal.qdcUnSat = to_unsat_energy(qdc);
187 signal.qdc = qdc;
188 signal.tdc = smear_time(peakTime);
189 signal.side = this->GetSide();
190 // R3BLOG(debug3, format("Create a signal {}", signal));
191 return signal;
192 }
193
195 {
196 pmt_peaks_.clear();
197 fqt_peaks_.clear();
198 par_ = Tamex::Params{ par_.rnd_gen };
199 }
200
201 auto Channel::CreateCalSignal(const FQTPeak& peak) const -> CalSignal
202 {
203 auto peak_energy = peak.GetEnergy();
204 auto peakTime = peak.GetLETime();
205 auto smeared_energy = smear_energy(peak_energy);
206
207 auto signal = CalSignal{};
208 signal.tot = calculate_ToT(smeared_energy);
209 signal.tle = peakTime;
210 signal.side = this->GetSide();
211 // R3BLOG(debug2, fmt::format("Creating a cal signal {}", signal));
212 return signal;
213 }
214
215 auto Channel::calculate_ToT(double energy) const -> double
216 {
217 const auto& par = GetPar();
218 return ((energy * par.energy_gain) + par.pedestal);
219 }
220
221 template <typename Peak>
222 void Channel::do_peak_pileup(/* inout */ std::vector<Peak>& peaks)
223 {
224 if (peaks.size() <= 1)
225 {
226 return;
227 }
228
229 std::sort(peaks.begin(), peaks.end(), std::less{});
230 for (auto front_peak = peaks.begin(); front_peak != peaks.end(); ++front_peak)
231 {
232 auto end_peak = std::remove_if(front_peak + 1,
233 peaks.end(),
234 [&front_peak](auto& peak)
235 {
236 if (*front_peak == peak)
237 {
238 (*front_peak) += peak;
239 return true;
240 }
241 return false;
242 });
243 peaks.erase(end_peak, peaks.end());
244 }
245 }
246
247 void Channel::peak_pileup_with_distance(/* inout */ std::vector<FQTPeak>& peaks, double distance)
248 {
249 if (peaks.empty())
250 {
251 return;
252 }
253 std::sort(peaks.begin(), peaks.end(), std::less{});
254
255 for (auto front_peak = peaks.begin(); front_peak != peaks.end(); ++front_peak)
256 {
257 auto last_leading_time = front_peak->GetLETime();
258 auto end_peak = std::remove_if(front_peak + 1,
259 peaks.end(),
260 [&distance, &front_peak, &last_leading_time](FQTPeak& peak)
261 {
262 if ((peak - last_leading_time) < distance)
263 {
264 front_peak->AddEnergy(peak.GetEnergy());
265 last_leading_time = peak.GetLETime();
266 return true;
267 }
268 return false;
269 });
270 peaks.erase(end_peak, peaks.end());
271 }
272 }
273
274 void Channel::peak_pileup_in_time_window(/* inout */ std::vector<FQTPeak>& peaks, double time_window)
275 {
276 if (peaks.empty())
277 {
278 return;
279 }
280 std::sort(peaks.begin(), peaks.end(), std::less{});
281
282 auto& front_peak = peaks.front();
283 std::for_each(peaks.begin() + 1,
284 peaks.end(),
285 [&front_peak, time_window](FQTPeak& peak)
286 {
287 if ((peak - front_peak) < time_window)
288 {
289 front_peak.AddEnergy(peak.GetEnergy());
290 }
291 });
292 peaks.erase(peaks.begin() + 1, peaks.end());
293 }
294
295 void Channel::fqt_peak_pileup(/* inout */ std::vector<FQTPeak>& peaks)
296 {
297 switch (pileup_strategy_)
298 {
300 do_peak_pileup(peaks);
301 break;
303 peak_pileup_with_distance(peaks, par_.pileup_distance);
304 break;
306 peak_pileup_in_time_window(peaks, par_.pileup_time_window);
307 break;
308 default:
309 break;
310 }
311 }
312
313 template <typename Peak>
314 void Channel::apply_threshold(std::vector<Peak>& peaks)
315 {
316 // apply threshold on energy using c++ erase-remove idiom:
317 auto it_end = std::remove_if(peaks.begin(),
318 peaks.end(),
319 [this](const auto& peak) { return peak.GetHeight() < this->GetPar().pmt_thresh; });
320 peaks.erase(it_end, peaks.end());
321 }
322
323 void Channel::construct_FQT_peaks(std::vector<FQTPeak>& FQTPeaks, std::vector<PMTPeak>& pmtPeaks)
324 {
325 FQTPeaks.reserve(pmtPeaks.size());
326
327 // sorting pmt peaks according to time:
328 std::sort(pmtPeaks.begin(), pmtPeaks.end());
329
330 do_peak_pileup(pmtPeaks);
331 apply_threshold(pmtPeaks);
332 for (auto const& peak : pmtPeaks)
333 {
334 FQTPeaks.emplace_back(peak, this);
335 }
336 }
338 {
340 {
341 const auto& module_par = neuland_hit_par_->GetModuleParAt(GetPaddle()->GetPaddleID());
342 set_par_with_hit_module_par(par_, module_par, GetSide());
343 }
344 }
345
347 {
349 // signal pileup:
351
352 // construct Channel signals:
353 hits.reserve(fqt_peaks_.size());
354
355 for (const auto& peak : fqt_peaks_)
356 {
357 hits.emplace_back(CreateHit(peak));
358 }
359 }
360
362 {
363 // construct Channel signals:
364 cal_signals.reserve(fqt_peaks_.size());
365
366 for (const auto& peak : fqt_peaks_)
367 {
368 cal_signals.emplace_back(CreateCalSignal(peak));
369 }
370 }
371
372 auto Channel::smear_energy(double qdc) const -> double
373 {
374 // apply energy smearing
375 qdc = par_.rnd_gen.get().Gaus(qdc, par_.energy_res_rel * qdc);
376 return qdc;
377 }
378
379 auto Channel::smear_time(double time) const -> double { return time + par_.rnd_gen.get().Gaus(0., par_.time_res); }
380
381 auto Channel::to_unsat_energy(double qdc) const -> double
382 {
383 // Apply reverse saturation
384 if (par_.experimental_data_is_corrected_for_saturation)
385 {
386 qdc = qdc / (1 - par_.saturation_coefficient * qdc);
387 }
388 // Apply reverse attenuation
389 return qdc;
390 }
391} // namespace R3B::Digitizing::Neuland::Tamex
#define R3BLOG(severity, x)
Definition R3BLogger.h:32
std::vector< CalSignal > CalSignals
AbstractChannel(R3B::Side side, bool has_cal_output=false)
auto GetPaddle() const -> AbstractPaddle *
auto GetPar() const -> const Tamex::Params &
static void do_peak_pileup(std::vector< Peak > &peaks)
void construct_cal_signals(CalSignals &cal_signals) const override
auto calculate_ToT(double energy) const -> double
auto CreateHit(const FQTPeak &peak) const -> Hit
static void peak_pileup_in_time_window(std::vector< FQTPeak > &peaks, double time_window)
void fqt_peak_pileup(std::vector< FQTPeak > &peaks)
static void peak_pileup_with_distance(std::vector< FQTPeak > &peaks, double distance)
auto CreateCalSignal(const FQTPeak &peak) const -> CalSignal
void construct_FQT_peaks(std::vector< FQTPeak > &FQTPeaks, std::vector< PMTPeak > &pmtPeaks)
Channel(Side, PeakPileUpStrategy strategy, TRandom3 &)
FQTPeak(const PMTPeak &pmtPeak, Channel *channel)
auto operator==(const FQTPeak &other) const -> bool
static auto Energy2ToT(double height, const Params &par) -> double
static auto ToT2Energy(double width, const Params &par) -> double
auto operator+=(const PMTPeak &other) -> PMTPeak &
double time
Time value of the channel signal.
double intensity
Intensity of the channel signal.
std::reference_wrapper< TRandom3 > rnd_gen
Reference to shared random generator.
LRPair< ValueError< double > > pedestal
LRPair< ValueError< double > > pmt_threshold
LRPair< ValueError< double > > energy_gain
LRPair< ValueError< double > > pmt_saturation