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