BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
BmnParticleEqualizer1D.cxx
Go to the documentation of this file.
1// -------------------------------------------------------------------------
2// ----- BmnParticleEqualizer1D source file -----
3// ----- Created 01/05/2024 by K. Gertsenberger -----
4// -------------------------------------------------------------------------
5
7
8#include "BmnGlobalTrack.h"
9#include "BmnMCInfoDst.h"
10#include "BmnTrackMatch.h"
11#include "CbmMCTrack.h"
12#include "CbmStack.h"
13#include "FairLogger.h"
14#include "FairMCEventHeader.h"
15#include "FairRootManager.h"
16#include "FairTrackParam.h"
18#include "TCanvas.h"
19#include "TChain.h"
20#include "TClonesArray.h"
21#include "TDatabasePDG.h"
22#include "TFile.h"
23#include "TFitResult.h"
24#include "THashList.h"
25#include "TLegend.h"
26#include "TMath.h"
27#include "TParticlePDG.h"
28#include "TRandom.h"
29#include "TSystem.h"
30#include "TTreeReader.h"
31#include "TTreeReaderValue.h"
32#include "TVectorD.h"
33#include "TVirtualMC.h"
34
35// name and size of the object with the momentum grid (min P, max P, interval count)
36// written to the file with the sample histograms to verify the generator configuration while reading them
37static const char* kMomentumGridName = "MomentumGrid";
38static const int kMomentumGridSize = 3;
39
40// prefix of the name of the object with the relative reconstruction efficiency of a particle type
41// (a TVectorD with one element per momentum interval), see ProduceEfficiencyMap
42static const char* kEfficiencyNamePrefix = "Efficiency_pdg";
43
44// binning of the Phi (azimuth angle) sample histograms of the momentum intervals. The whole azimuth range is covered
45// by a small number of bins on purpose: an interval has much less statistics than the whole momentum range
46static const int kIntervalPhiBinCount = 32;
47
48///< Cut applied to the tracks of the sample (see SetSampleTrackCut)
50Double_t BmnParticleEqualizer1D::fPMin = 0.4; ///< Min value of momentum
51Double_t BmnParticleEqualizer1D::fPMax = 5.4; ///< Max value of momentum
52///< Interval count for momentum to equalize particles at all the intervals
54// particle PDG codes to be equalized: p, pi+, pi-, K+, K- , e-, e+
55vector<Int_t> BmnParticleEqualizer1D::fParticles({2212, 211, -211, 321, -321}); //, 11, -11});
56// their masses in MeV: 938.272,139.6,139.6,493.7,493.7
57
58// ranges of the sample histograms; they are internal to the translation unit ('static'), otherwise these common names
59// would be exported by the library and could collide with the same ones of another translation unit
60static Double_t fPtMin = 0, fPtMax = 3, fYMin = 0, fYMax = 3, fEtaMin = -4, fEtaMax = 8, fThetaMin = 0, fThetaMax = 3.2,
61 fPhiMin = -3.2, fPhiMax = 3.2;
62
63// Default 'stParticleInfo' constructor for storing particle distrubutons
65 Double_t min_p,
66 Double_t max_p,
67 Int_t interval_count)
68 : fY0(0)
69 , fSigma(0.72)
70 , fT(0.223)
71{
72 // construct and initialize vector for particle numbers
73 fParticleNumber.resize(interval_count);
74 fill(fParticleNumber.begin(), fParticleNumber.end(), 0);
75
76 // nothing has been added yet and the efficiency is not taken into account until its map is read
77 fParticleDebt.resize(interval_count, 0.);
78 fEfficiency.resize(interval_count, 1.);
79
80 // construct vector with P histograms divided by momentum intervals
81 fPHistVector.resize(interval_count);
82 fEtaHistVector.resize(interval_count);
83 fPhiHistVector.resize(interval_count);
84
85 // the way to sample the values is defined after the sample histograms have been read
86 fPSampling.resize(interval_count, EPSampling::kNone);
87 fEtaHistIndex.resize(interval_count, -1);
88 fPhiHistIndex.resize(interval_count, -1);
89}
90
91// 'BmnParticleEqualizer1D' constructor with input histograms
95{
99 fMinEfficiency = 0.05;
101
103 fill(fMaxParticles.begin(), fMaxParticles.end(), 0);
104
105 // initialize map with particle information
106 for (auto pdg_key : fParticles)
107 fParticleInfos.emplace(pdg_key, make_unique<stParticleInfo>(pdg_key, fPMin, fPMax, fIntervalCount));
108
109 isActive = ReadSampleHistograms(hist_file_name);
110 if (!isActive)
111 LOG(error) << "Errors occurred while initializing the BmnParticleEqualizer1D generator, "
112 "the simulation run will be stopped";
113}
114
115// Constructor with specified particle types
117 vector<Int_t> pdg_codes,
118 Double_t min_p,
119 Double_t max_p,
120 Int_t intervals)
121 : FairGenerator()
122{
123 fPMin = min_p;
124 fPMax = max_p;
125 fIntervalCount = intervals;
126 fIntervalStep = (max_p - min_p) / intervals;
127
128 // set fParticles to given pdg_codes, dropping the duplicates: the map of the particle information keeps one
129 // entry per PDG code, while the loops over 'fParticles' would process a duplicated code (and add its particles
130 // to the event) as many times as it is listed
131 fParticles.clear();
132 for (auto pdg_key : pdg_codes) {
133 if (find(fParticles.begin(), fParticles.end(), pdg_key) != fParticles.end()) {
134 LOG(warning) << "The PDG code " << pdg_key
135 << " is listed more than once for the equalizing generator, "
136 "the extra entries are ignored";
137 continue;
138 }
139 fParticles.push_back(pdg_key);
140 }
141
145 fMinEfficiency = 0.05;
147
149 fill(fMaxParticles.begin(), fMaxParticles.end(), 0);
150
151 // initialize map with particle information
152 for (auto pdg_key : fParticles)
153 fParticleInfos.emplace(pdg_key, make_unique<stParticleInfo>(pdg_key, fPMin, fPMax, fIntervalCount));
154
155 isActive = ReadSampleHistograms(hist_file_name);
156 if (!isActive)
157 LOG(error) << "Errors occurred while initializing the BmnParticleEqualizer1D generator, "
158 "the simulation run will be stopped";
159}
160
161// Initialize generator
166
167// fit the full momentum histogram of a particle type and attach the function to it (as 'func_name'). The bulk of the
168// distribution is described by two Gaussians and a Landau distribution, with the limits and the start values of the
169// parameters defined by the histogram itself. The power law tail of the Landau term is much flatter than the steeply
170// falling tails of the measured distributions, so above the momentum where the histogram falls below a small fraction
171// of its peak ('p_tail') the bulk is replaced by an exponential fitted to the tail
172// return kTRUE if the fits are valid
173static Bool_t FitMomentumFunction(TH1D* hP, const TString& func_name, Double_t p_min, Double_t p_max)
174{
175 TF1* p_distribution =
176 new TF1(func_name, "(x<[9])*(gaus(0)+gaus(3)+landau(6))+(x>=[9])*[10]*TMath::Exp(-[11]*(x-[9]))", p_min, p_max);
177 Double_t p_avg = (p_min + p_max) / 2;
178 Int_t max_bin = hP->GetMaximumBin();
179 Double_t x_peak = hP->GetXaxis()->GetBinCenter(max_bin);
180 Double_t x_max_value = hP->GetXaxis()->GetBinUpEdge(max_bin);
181 Double_t y_peak = hP->GetBinContent(max_bin);
182 // the spread of the distribution gives the scale of the start widths
183 Double_t rms = TMath::Max(hP->GetRMS(), 0.2);
184
185 // Every start value is put strictly inside the limits of its parameter. Minuit maps a limited parameter onto an
186 // unlimited one, and the derivative of that mapping is zero exactly at a limit, so a parameter started there
187 // cannot leave it and the fit ends as invalid
188 auto set_parameter = [p_distribution](Int_t index, Double_t min_value, Double_t max_value, Double_t start_value) {
189 Double_t margin = 0.01 * (max_value - min_value);
190 p_distribution->SetParLimits(index, min_value, max_value);
191 p_distribution->SetParameter(index, TMath::Range(min_value + margin, max_value - margin, start_value));
192 };
193
194 // The amplitudes are kept non-negative: the function is used to sample the momentum, so it must not be negative
195 // anywhere, while terms of opposite signs compensating each other make it negative in the tails. The amplitude of
196 // the Landau term is not its height: TMath::Landau() is about 0.18 at the most probable value
197 set_parameter(0, 0., 10. * y_peak, 0.3 * y_peak);
198 set_parameter(1, p_min, p_avg, x_peak - 0.5 * rms);
199 set_parameter(2, 0.1, p_avg, 0.5 * rms);
200 set_parameter(3, 0., 10. * y_peak, 0.5 * y_peak);
201 set_parameter(4, x_max_value - 0.1, p_max, x_peak + 0.5 * rms);
202 set_parameter(5, 0.1, p_avg, rms);
203 set_parameter(6, 0., 100. * y_peak, 2. * y_peak);
204 set_parameter(7, p_min, TMath::Min(x_max_value, p_avg), x_peak - 0.1);
205 set_parameter(8, 0.1, p_avg, 0.3 * rms);
206
207 // the start of the tail: the first bin to the right of the peak below the given fraction of the peak height. The
208 // tail needs a few bins and some statistics to be fitted, otherwise the bulk covers the whole range (the step is
209 // then put beyond the range, so that the function is the bulk one up to its upper limit inclusive)
210 const Double_t tail_fraction = 0.05, min_tail_entries = 30;
211 Double_t p_tail = p_max;
212 for (Int_t bin = max_bin + 1; bin <= hP->GetNbinsX(); bin++) {
213 if (hP->GetBinContent(bin) < tail_fraction * y_peak) {
214 p_tail = hP->GetXaxis()->GetBinLowEdge(bin);
215 break;
216 }
217 }
218 Bool_t is_tail = (p_tail <= p_max - 3 * hP->GetBinWidth(max_bin))
219 && (hP->Integral(hP->FindBin(p_tail + 1.e-9), hP->GetNbinsX()) >= min_tail_entries);
220 if (!is_tail)
221 p_tail = p_max + 1.;
222 p_distribution->FixParameter(9, p_tail);
223 p_distribution->FixParameter(10, 0.);
224 p_distribution->FixParameter(11, 0.);
225
226 // The binned likelihood ('L') is used instead of the chi-square: the latter ignores the empty bins and is biased in
227 // the bins with a few entries, i.e. exactly in the tails of the distribution this function is needed for
228 // 1) the bulk below the tail
229 p_distribution->SetRange(p_min, TMath::Min(p_tail, p_max));
230 Bool_t is_valid = hP->Fit(p_distribution, "SQ0LR")->IsValid();
231 // 2) the exponential tail with its own amplitude and slope. The amplitude is not tied to the value of the bulk at
232 // 'p_tail': the Landau term already deviates from the histogram there, and the tail would inherit the deviation
233 if (is_tail) {
234 for (Int_t index = 0; index <= 8; index++)
235 p_distribution->FixParameter(index, p_distribution->GetParameter(index));
236 // the value of the bulk at the start of the tail (the function is still the bulk one just below the step)
237 Double_t bulk_value = p_distribution->Eval(p_tail * (1. - 1.e-12));
238 p_distribution->ReleaseParameter(10);
239 set_parameter(10, 0., 100. * TMath::Max(bulk_value, 1.), bulk_value);
240 p_distribution->ReleaseParameter(11);
241 set_parameter(11, 0., 50., 2.);
242 p_distribution->SetRange(p_tail, p_max);
243 is_valid = hP->Fit(p_distribution, "SQ0LR")->IsValid() && is_valid;
244 }
245 // the fits are made in the subranges, while the function is used over the whole momentum range
246 p_distribution->SetRange(p_min, p_max);
247 TF1* fitted_function = hP->GetFunction(func_name);
248 if (fitted_function != nullptr)
249 fitted_function->SetRange(p_min, p_max);
250
251 return is_valid;
252}
253
254// fit P histogram to generate the values according to the analytical functions
255int BmnParticleEqualizer1D::FitPHistogram(unique_ptr<TH1D>& hP, int pdg_key, Double_t p_min, Double_t p_max)
256{
257 if (hP->GetEntries() == 0) {
258 LOG(warning) << TString::Format("P (momentum) histogram is empty for PDG = %d", pdg_key);
259 return -1;
260 }
261
262 // fit P distribution for the particle type by gaus + landau with an exponential tail
263 if (!FitMomentumFunction(hP.get(), TString::Format("distP_pdg%d", pdg_key), p_min, p_max)) {
264 LOG(error) << TString::Format("Fit of the P (momentum) histogram failed for PDG = %d", pdg_key);
265 // return -2;
266 }
267
268 return 0;
269}
270
271// fit Pt histogram to generate the values according to the analytical functions
273 int pdg_key,
274 Double_t pdg_mass,
275 Double_t pt_min,
276 Double_t pt_max,
277 Double_t& t_output)
278{
279 if (hPt->GetEntries() == 0) {
280 LOG(warning) << TString::Format("Pt (transverse momentum) histogram is empty for PDG = %d", pdg_key);
281 return -1;
282 }
283
284 // fit Pt distribution for the particle type by expo
285 // the squared mass is formatted with a high precision on purpose: the default one (6 decimal digits) turns the
286 // squared mass of the light particles into zero (2.6e-7 for e+/e-) and so removes the mass from the formula
287 TString dist_func_name = TString::Format("distPt_pdg%d", pdg_key);
288 TF1* pt_distribution = new TF1(
289 dist_func_name, TString::Format("[0]*x*exp(-sqrt(x*x+%.10g)/[1])", pdg_mass * pdg_mass), pt_min, pt_max);
290 pt_distribution->SetParameter(1, t_output);
291 // the temperature is a positive value by definition, an unlimited parameter can be driven negative by the fit
292 pt_distribution->SetParLimits(1, 1.e-3, 1.);
293
294 TFitResultPtr r = hPt->Fit(pt_distribution, "SQ0");
295 if (!r->IsValid()) {
296 LOG(error) << TString::Format(
297 "Fit of the Pt (transverse momentum) histogram failed for PDG = %d, the previous temperature value "
298 "(%6.4f) is kept",
299 pdg_key, t_output);
300 return 0;
301 }
302 t_output = r->Parameter(1);
303
304 return 0;
305}
306
307// fit Y histogram to generate the values according to the analytical functions
309 int pdg_key,
310 Double_t& y0_output,
311 Double_t& sigma_putput)
312{
313 if (hY->GetEntries() == 0) {
314 LOG(warning) << TString::Format("Rapidity histogram is empty for %d PDG code", pdg_key);
315 return -1;
316 }
317
318 // fit rapidity distribution for the particle type by gauss
319 if (pdg_key != 2212) {
320 TFitResultPtr r = hY->Fit("gaus", "SQ0");
321 y0_output = r->Parameter(1);
322 sigma_putput = r->Parameter(2);
323 // if proton (PDG == 2212)
324 } else {
325 Double_t max_bin_value = hY->GetMaximum();
326 const string minimizer_type = ROOT::Math::MinimizerOptions::DefaultMinimizerType();
328
329 // FITTING ONLY MEDIUM PEAK with background
330 // TF1* fProtonYDistribution = new TF1("distProtonY", "gaus(0)+pol0(3)", 0.3, 2);
331 // fProtonYDistribution->SetParLimits(0, 0, max_bin_value);
332 // fProtonYDistribution->SetParLimits(1, 0.3, 2);
333 // fProtonYDistribution->SetParName(1,"proton_y0");
334 // fProtonYDistribution->SetParLimits(2, 0.05, 1);
335 // fProtonYDistribution->SetParName(2,"proton_sigma");
336 // fProtonYDistribution->SetParLimits(3, 0, max_bin_value);
337 // TFitResultPtr r = hY->Fit(fProtonYDistribution,"SQ0","", 0.3, 2);
338 // FITTING THREE PEAKS: TARGET FRAGMENTATION + PARTICIPANTS + SPECTATORS
339 unique_ptr<TF1> fProtonYDistribution =
340 make_unique<TF1>("distProtonY", "gaus(0)+gaus(3)+gaus(6)", 0.2, 5); // 0, 5);
341 fProtonYDistribution->SetParLimits(0, 0, max_bin_value);
342 fProtonYDistribution->SetParLimits(1, 0, 0.05);
343 fProtonYDistribution->SetParLimits(2, 0.01, 0.4);
344 fProtonYDistribution->SetParameter(2, 0.2);
345 fProtonYDistribution->SetParLimits(3, 0, max_bin_value);
346 fProtonYDistribution->SetParName(4, "proton_y0");
347 fProtonYDistribution->SetParLimits(4, 0.3, 2);
348 fProtonYDistribution->SetParameter(4, 1);
349 fProtonYDistribution->SetParName(5, "proton_sigma");
350 fProtonYDistribution->SetParLimits(5, 0.05, 1);
351 fProtonYDistribution->SetParameter(5, 0.5);
352 fProtonYDistribution->SetParLimits(6, 0, max_bin_value);
353 fProtonYDistribution->SetParLimits(7, 1.5, 3);
354 fProtonYDistribution->SetParameter(7, 2.25);
355 fProtonYDistribution->SetParLimits(8, 0.05, 1);
356 fProtonYDistribution->SetParameter(8, 0.5);
357
358 TFitResultPtr r = hY->Fit(fProtonYDistribution.get(), "SQ0", "", 0, 5);
359 y0_output = r->Parameter(r->Index("proton_y0"));
360 sigma_putput = r->Parameter(r->Index("proton_sigma"));
361
363 }
364
365 return 0;
366}
367
368// fit Eta (pseudorapidity) histogram to generate the values according to the analytical functions
369int BmnParticleEqualizer1D::FitEtaHistogram(unique_ptr<TH1D>& hEta, int pdg_key, Double_t eta_min, Double_t eta_max)
370{
371 if (hEta->GetEntries() == 0) {
372 LOG(warning) << TString::Format("Eta (pseudorapidity) histogram is empty for PDG = %d", pdg_key);
373 return -1;
374 }
375
376 // fit P distribution for the particle type by gaus + landau
377 TString dist_func_name = TString::Format("distEta_pdg%d", pdg_key);
378 TF1* eta_distribution = new TF1(dist_func_name, "crystalball", eta_min, eta_max);
379 eta_distribution->SetParLimits(1, 1, 4);
380 eta_distribution->SetParameter(1, 2.5);
381 eta_distribution->SetParLimits(2, 0.1, 1.5);
382 eta_distribution->SetParameter(2, 0.5);
383 eta_distribution->SetParLimits(3, 0.1, 2);
384 eta_distribution->SetParameter(3, 1);
385 eta_distribution->SetParLimits(4, 1, 10);
386 eta_distribution->SetParameter(4, 3);
387
388 TFitResultPtr r = hEta->Fit(eta_distribution, "SQ0");
389 if (!r->IsValid()) {
390 LOG(error) << TString::Format("Fit of the Eta (pseudorapidity) histogram failed for PDG = %d", pdg_key);
391 // return -2;
392 }
393
394 return 0;
395}
396
398{
399 gSystem->ExpandPathName(input_list_file);
400
401 // get file list from the input text file
402 list<string> file_list;
403 ifstream list_file(input_list_file.Data());
404 string s;
405 while (getline(list_file, s))
406 file_list.push_back(s);
407 if (file_list.empty()) {
408 cout << "ERROR: There are no files in the list of the input text file: " << input_list_file << endl;
409 return -14;
410 }
411
412 // form ROOT chain for the file list
413 TChain dataChain("bmndata");
414 for (auto const& cur_file_path : file_list) {
415 TString strFilePath(cur_file_path);
416 gSystem->ExpandPathName(strFilePath);
417
418 dataChain.AddFile(strFilePath);
419 }
420
421 if (dataChain.GetEntries() < 1) {
422 cout << "ERROR: There are no correct input files in the text list stored at: \"" << input_list_file << "\""
423 << endl;
424 return -15;
425 }
426
427 // flag whether the input files have SIM or DST format
428 bool isDST = false;
429 TClonesArray* mcTracks = nullptr;
430 BmnMCInfoDst* mcInfo = nullptr;
431 if (dataChain.GetBranch("MCTrack") == nullptr) {
432 LOG(info) << "'MCTrack' branch was not found, searching for 'BmnMCInfo->MCTrack'...";
433 if (dataChain.GetBranch("BmnMCInfo.") == nullptr) {
434 LOG(error) << "'BmnMCInfo.' branch was not found too, please, check the structure of the files, exiting...";
435 return -1;
436 }
437 dataChain.SetBranchAddress("BmnMCInfo.", &mcInfo);
438 if (mcInfo == nullptr) {
439 LOG(error)
440 << "'BmnMCInfo.' branch was not read correctly, please, check the structure of the files, exiting...";
441 return -2;
442 }
443 isDST = true;
444 LOG(info) << "'BmnMCInfo->MCTrack' has been found";
445 dataChain.SetBranchStatus("*", 0);
446 dataChain.SetBranchStatus("BmnMCInfo*", 1);
447 } else {
448 dataChain.SetBranchAddress("MCTrack", &mcTracks);
449 if (mcTracks == nullptr) {
450 LOG(error)
451 << "MCTrack' branch was not read correctly, please, check the structure of the files, exiting...";
452 return -3;
453 }
454 dataChain.SetBranchStatus("*", 0);
455 dataChain.SetBranchStatus("MCTrack*", 1);
456 }
457
458 int iEventNumber = dataChain.GetEntries();
459 if (iEventNumber < 1) {
460 cout << "ERROR: There are no correct entries in the file list stored at: \"" << input_list_file << "\"" << endl;
461 return -16;
462 }
463
464 // the file is owned by the unique pointer so that it is closed and deleted on every exit from the function
465 unique_ptr<TFile> hist_file(TFile::Open(output_histo_file, "RECREATE"));
466 if ((!hist_file) || (!hist_file->IsOpen())) {
467 cout << "ERROR: unable to create ROOT file for the sample histograms: " << output_histo_file << endl;
468 return -1;
469 }
470
471 // create map with particle infos
472 map<Int_t, unique_ptr<stParticleInfo>> particle_infos;
473 for (auto pdg_key : fParticles)
474 particle_infos.emplace(pdg_key, make_unique<stParticleInfo>(pdg_key, fPMin, fPMax, fIntervalCount));
475
476 Double_t interval_width = (fPMax - fPMin) / fIntervalCount;
477 // create all the histograms using make_unique
478 for (auto pdg_key : fParticles) {
479 TString histogram_name = TString::Format("hP_pdg%d", pdg_key);
480 particle_infos[pdg_key]->fPHist = make_unique<TH1D>(histogram_name, histogram_name, 80, fPMin, fPMax);
481 histogram_name = TString::Format("hPt_pdg%d", pdg_key);
482 particle_infos[pdg_key]->fPtHist = make_unique<TH1D>(histogram_name, histogram_name, 60, fPtMin, fPtMax);
483 histogram_name = TString::Format("hY_pdg%d", pdg_key);
484 particle_infos[pdg_key]->fYHist = make_unique<TH1D>(histogram_name, histogram_name, 60, fYMin, fYMax);
485 histogram_name = TString::Format("hEta_pdg%d", pdg_key);
486 particle_infos[pdg_key]->fEtaHist = make_unique<TH1D>(histogram_name, histogram_name, 60, fEtaMin, fEtaMax);
487 histogram_name = TString::Format("hTheta_pdg%d", pdg_key);
488 particle_infos[pdg_key]->fThetaHist =
489 make_unique<TH1D>(histogram_name, histogram_name, 61, fThetaMin, fThetaMax);
490 histogram_name = TString::Format("hPhi_pdg%d", pdg_key);
491 particle_infos[pdg_key]->fPhiHist = make_unique<TH1D>(histogram_name, histogram_name, 64, fPhiMin, fPhiMax);
492 // detach the histograms from the output file (they are created while it is the current ROOT directory) so they
493 // are owned only by the unique pointers - otherwise closing the file below would delete them and their
494 // destructors would cause a double free
495 particle_infos[pdg_key]->fPHist->SetDirectory(nullptr);
496 particle_infos[pdg_key]->fPtHist->SetDirectory(nullptr);
497 particle_infos[pdg_key]->fYHist->SetDirectory(nullptr);
498 particle_infos[pdg_key]->fEtaHist->SetDirectory(nullptr);
499 particle_infos[pdg_key]->fThetaHist->SetDirectory(nullptr);
500 particle_infos[pdg_key]->fPhiHist->SetDirectory(nullptr);
501 int hist_counter = 0;
502 for (auto& p_hist : particle_infos[pdg_key]->fPHistVector) {
503 histogram_name = TString::Format("hP%d_pdg%d", ++hist_counter, pdg_key);
504 p_hist = make_unique<TH1D>(histogram_name, histogram_name, 40, fPMin + (hist_counter - 1) * interval_width,
505 fPMin + hist_counter * interval_width);
506 p_hist->SetDirectory(nullptr);
507 }
508 hist_counter = 0;
509 for (auto& eta_hist : particle_infos[pdg_key]->fEtaHistVector) {
510 histogram_name = TString::Format("hEta%d_pdg%d", ++hist_counter, pdg_key);
511 eta_hist = make_unique<TH1D>(histogram_name, histogram_name, 60, fEtaMin, fEtaMax);
512 eta_hist->SetDirectory(nullptr);
513 }
514 hist_counter = 0;
515 for (auto& phi_hist : particle_infos[pdg_key]->fPhiHistVector) {
516 histogram_name = TString::Format("hPhi%d_pdg%d", ++hist_counter, pdg_key);
517 phi_hist = make_unique<TH1D>(histogram_name, histogram_name, kIntervalPhiBinCount, fPhiMin, fPhiMax);
518 phi_hist->SetDirectory(nullptr);
519 }
520 }
521
522 // fill all the histograms for specified particles by the PDG code
523 int iCurrentEvent = 0;
524 LOG(info) << "Processing events...";
525 for (Int_t iEvent = 0; iEvent < iEventNumber; iEvent++) {
526 // Next Event
527 iCurrentEvent++;
528 if (iCurrentEvent % 1000 == 0)
529 LOG(info) << "Processing event #" << iCurrentEvent << " of " << iEventNumber;
530 dataChain.GetEntry(iEvent);
531
532 TClonesArray* mc_tracks = nullptr;
533 if (isDST)
534 mc_tracks = mcInfo->GetMCTracks();
535 else
536 mc_tracks = mcTracks;
537
538 for (int i = 0; i < mc_tracks->GetEntries(); i++) {
539 CbmMCTrack* pTrack = (CbmMCTrack*)mc_tracks->At(i);
540 Int_t part_code = pTrack->GetPdgCode();
541
542 // exclude secondary particles
543 if (pTrack->GetMotherId() >= 0)
544 continue;
545 // filter particles
546 if (fSampleTrackCut > 0) {
547 // exclude tracks without points in Silicon and GEMs
548 if ((pTrack->GetNPoints(kGEM) < 1) || (pTrack->GetNPoints(kSILICON) < 1))
549 continue;
550 if (fSampleTrackCut > 1) {
551 // exclude tracks without points in ToF detectors. kTOF701 is not checked: CbmMCTrack keeps the
552 // points of TOF700 in a single bit field, returned by GetNPoints() both for kTOF and for kTOF701
553 if ((pTrack->GetNPoints(kTOF) < 1) && (pTrack->GetNPoints(kTOF1) < 1))
554 continue;
555 }
556 }
557
558 if (auto part_iter = particle_infos.find(part_code); part_iter != particle_infos.end()) {
559 int interval_number = (pTrack->GetP() - fPMin) / interval_width;
560 Double_t track_p = pTrack->GetP();
561 Double_t track_eta = 0.5 * TMath::Log((track_p + pTrack->GetPz()) / (track_p - pTrack->GetPz()));
562 // the same definition of the azimuth angle has to be used while reading the histograms in ReadEvent()
563 Double_t track_phi = TMath::ATan2(pTrack->GetPy(), pTrack->GetPx());
564 part_iter->second->fPHist->Fill(track_p);
565 part_iter->second->fPtHist->Fill(pTrack->GetPt());
566 part_iter->second->fYHist->Fill(pTrack->GetRapidity());
567 part_iter->second->fEtaHist->Fill(track_eta);
568 part_iter->second->fThetaHist->Fill(TMath::ACos(pTrack->GetPz() / track_p)); // *TMath::RadToDeg()
569 part_iter->second->fPhiHist->Fill(track_phi); // *TMath::RadToDeg()
570 // compare doubles: abs(min(a,b))*numeric_limits<double>::epsilon()*error_factor
571 if ((track_p >= fPMin) && (isless(track_p, fPMax))) {
572 part_iter->second->fPHistVector[interval_number]->Fill(track_p);
573 part_iter->second->fEtaHistVector[interval_number]->Fill(track_eta);
574 part_iter->second->fPhiHistVector[interval_number]->Fill(track_phi);
575 }
576 }
577 } // for (int i = 0; i < mc_tracks->GetEntries(); i++) {
578 } // while (sim_reader.Next())
579
580 // check correctness of all the histograms
581 for (auto pdg_key : fParticles) {
582 if (particle_infos[pdg_key]->fPHist->GetEntries() == 0) {
583 LOG(error) << "Momentum histogram is empty for pdg=" << pdg_key;
584 return -4;
585 }
586 if (particle_infos[pdg_key]->fPtHist->GetEntries() == 0) {
587 LOG(error) << "Transverse momentum histogram is empty for pdg=" << pdg_key;
588 return -5;
589 }
590 if (particle_infos[pdg_key]->fYHist->GetEntries() == 0) {
591 LOG(error) << "Rapidity histogram is empty for pdg=" << pdg_key;
592 return -6;
593 }
594 if (particle_infos[pdg_key]->fEtaHist->GetEntries() == 0) {
595 LOG(error) << "Pseudorapidity histogram is empty for pdg=" << pdg_key;
596 return -7;
597 }
598 if (particle_infos[pdg_key]->fThetaHist->GetEntries() == 0) {
599 LOG(error) << "Theta angle histogram is empty for pdg=" << pdg_key;
600 return -8;
601 }
602 if (particle_infos[pdg_key]->fPhiHist->GetEntries() == 0) {
603 LOG(error) << "Phi angle histogram is empty for pdg=" << pdg_key;
604 return -9;
605 }
606 }
607
608 // fit histograms and calculate fY0, fSigma, fT for all the specified particle types
609 for (auto pdg_key : fParticles) {
610 // the start values are set inside the loop on purpose: being declared outside it, they would enter the fit of
611 // every next particle type as the values fitted for the previous one, making the result depend on the order
612 Double_t fY0 = 0, fSigma = 0.72, fT = 0.223;
614 TParticlePDG* particle = pdgBase->GetParticle(pdg_key);
615 if (!particle) {
616 LOG(fatal) << "PDG code " << pdg_key << "is not defined. exiting...";
617 return -10;
618 }
619 Double_t fPDGMass = particle->Mass();
620
621 // fit full particle momentum histograms
622 if (FitPHistogram(particle_infos[pdg_key]->fPHist, pdg_key, fPMin, fPMax) != 0)
623 return -11;
624
625 // fit Pt momentum histograms
626 if (FitPtHistogram(particle_infos[pdg_key]->fPtHist, pdg_key, fPDGMass, fPtMin, fPtMax, fT) != 0)
627 return -12;
628
629 // fit Y (rapidity) histograms
630 if (FitYHistogram(particle_infos[pdg_key]->fYHist, pdg_key, fY0, fSigma) != 0)
631 return -13;
632 TVector3 Y0SigmaT(fY0, fSigma, fT);
633 TString vector_name = TString::Format("Y0SigmaT_pdg%d", pdg_key);
634 hist_file->WriteObject(&Y0SigmaT, vector_name);
635
636 // fit Eta (pseudorapidity) histograms
637 // if (FitEtaHistogram(particle_infos[pdg_key]->fEtaHist, pdg_key, eta_min, eta_max) != 0)
638 // return -5;
639
640 LOG(info) << TString::Format("pdg=%i y0=%4.2f sigma_y=%4.2f T_pt=%6.4f", pdg_key, fY0, fSigma, fT);
641 }
642
643 // write the momentum grid the histograms were produced with, so that a generator reading this file can verify
644 // that its own grid is exactly the same: the interval histograms are read by name only and a shifted grid would
645 // not be detected by itself (see ReadSampleHistograms)
646 TVectorD momentum_grid(kMomentumGridSize);
647 momentum_grid[0] = fPMin;
648 momentum_grid[1] = fPMax;
649 momentum_grid[2] = fIntervalCount;
650 hist_file->WriteObject(&momentum_grid, kMomentumGridName);
651
652 // write the histograms to the output ROOT file: they are detached from it, so the file is specified explicitly
653 for (auto pdg_key : fParticles) {
654 hist_file->WriteTObject(particle_infos[pdg_key]->fPHist.get());
655 hist_file->WriteTObject(particle_infos[pdg_key]->fPtHist.get());
656 hist_file->WriteTObject(particle_infos[pdg_key]->fYHist.get());
657 hist_file->WriteTObject(particle_infos[pdg_key]->fEtaHist.get());
658 hist_file->WriteTObject(particle_infos[pdg_key]->fThetaHist.get());
659 hist_file->WriteTObject(particle_infos[pdg_key]->fPhiHist.get());
660 for (auto& p_hist : particle_infos[pdg_key]->fPHistVector)
661 hist_file->WriteTObject(p_hist.get());
662 for (auto& eta_hist : particle_infos[pdg_key]->fEtaHistVector)
663 hist_file->WriteTObject(eta_hist.get());
664 for (auto& phi_hist : particle_infos[pdg_key]->fPhiHistVector)
665 hist_file->WriteTObject(phi_hist.get());
666 // hist_file->WriteObject(&particle_infos[pdg_key]->fPHistVector, "PHistVector");
667 // hist_file->WriteObject(&particle_infos[pdg_key]->fEtaHistVector, "EtaHistVector");
668 }
669
670 hist_file->Close();
671 return 0;
672}
673
674// switch on the correction of the number of the added particles by the reconstruction efficiency
676{
677 if (is_used && (!fIsEfficiencyMapRead)) {
678 LOG(error) << "The efficiency map is absent in the file with the sample histograms for at least one of the "
679 "particle types, so the correction by the efficiency cannot be switched on. Build the map with "
680 "BmnParticleEqualizer1D::ProduceEfficiencyMap() from the reconstructed (DST) files first";
681 return kFALSE;
682 }
683 if (min_efficiency <= 0.) {
684 LOG(error) << "The lower limit of the efficiency has to be positive, otherwise the correction factor is "
685 "unlimited; the correction is not switched on";
686 return kFALSE;
687 }
688
689 fUseEfficiencyCorrection = is_used;
690 fMinEfficiency = min_efficiency;
691 return kTRUE;
692}
693
694// verify that the momentum grid of the sample histograms is the one this class is configured for
696{
697 if ((hist_file == nullptr) || (!hist_file->IsOpen())) {
698 LOG(error) << "The file with the sample histograms is not open";
699 return kFALSE;
700 }
701
702 unique_ptr<TVectorD> momentum_grid((TVectorD*)hist_file->Get(kMomentumGridName));
703 if ((momentum_grid == nullptr) || (momentum_grid->GetNoElements() != kMomentumGridSize)) {
704 LOG(error) << "The momentum grid ('" << kMomentumGridName
705 << "') is absent in the file with the sample histograms: " << hist_file->GetName()
706 << ". The file was produced by an older version of the equalizing generator, please, recreate it "
707 "with ProduceSampleHistograms()";
708 return kFALSE;
709 }
710 // the momenta are compared with an absolute tolerance, because a zero value is a valid lower momentum limit
711 if ((!TMath::AreEqualAbs((*momentum_grid)[0], fPMin, 1.e-9))
712 || (!TMath::AreEqualAbs((*momentum_grid)[1], fPMax, 1.e-9))
713 || (TMath::Nint((*momentum_grid)[2]) != fIntervalCount))
714 {
715 LOG(error) << TString::Format(
716 "The sample histograms were produced for %d momentum intervals in [%g, %g] GeV, but the generator is "
717 "configured for %d intervals in [%g, %g] GeV. Please, recreate the file '%s' with "
718 "ProduceSampleHistograms() or configure the generator accordingly",
719 TMath::Nint((*momentum_grid)[2]), (*momentum_grid)[0], (*momentum_grid)[1], fIntervalCount, fPMin, fPMax,
720 hist_file->GetName());
721 return kFALSE;
722 }
723
724 return kTRUE;
725}
726
727// build the map of the reconstruction efficiency from the reconstructed (DST) files
729{
730 gSystem->ExpandPathName(input_list_file);
731
732 // get file list from the input text file
733 list<string> file_list;
734 ifstream list_file(input_list_file.Data());
735 string s;
736 while (getline(list_file, s))
737 file_list.push_back(s);
738 if (file_list.empty()) {
739 LOG(error) << "There are no files in the list of the input text file: " << input_list_file;
740 return -1;
741 }
742
743 // form ROOT chain for the file list
744 TChain dataChain("bmndata");
745 for (auto const& cur_file_path : file_list) {
746 TString strFilePath(cur_file_path);
747 gSystem->ExpandPathName(strFilePath);
748 dataChain.AddFile(strFilePath);
749 }
750 if (dataChain.GetEntries() < 1) {
751 LOG(error) << "There are no correct input files in the text list stored at: " << input_list_file;
752 return -2;
753 }
754
755 // the sample histograms give the number of the generated tracks the efficiency is referred to, so their
756 // momentum grid has to be verified before the (long) loop over the reconstructed files is started
757 unique_ptr<TFile> hist_file(TFile::Open(histo_file, "UPDATE"));
758 if ((!hist_file) || (!hist_file->IsOpen())) {
759 LOG(error) << "Unable to open the file with the sample histograms for updating: " << histo_file;
760 return -4;
761 }
762 if (!CheckMomentumGrid(hist_file.get()))
763 return -7;
764
765 // the reconstructed tracks are needed, so the input files have to be DST ones
766 TClonesArray* globalTracks = nullptr;
767 BmnMCInfoDst* mcInfo = nullptr;
768 if (dataChain.GetBranch("BmnGlobalTrack") == nullptr) {
769 LOG(error) << "'BmnGlobalTrack' branch was not found: the efficiency map is built from the reconstructed "
770 "(DST) files, please, check the structure of the given files";
771 return -3;
772 }
773 dataChain.SetBranchAddress("BmnGlobalTrack", &globalTracks);
774 // the MC information is present in the simulated files only: for the experimental ones the particle type is
775 // defined by the mass measured by the ToF detectors. A reconstructed track is related to its MC track by the index
776 // stored in the track itself (BmnGlobalTrack::GetRefIndex(), which always returns -1 for now) or by the separate
777 // matching branch
778 TClonesArray* globalMatches = nullptr;
779 Bool_t isMC = (dataChain.GetBranch("BmnMCInfo.") != nullptr);
780 if (isMC)
781 dataChain.SetBranchAddress("BmnMCInfo.", &mcInfo);
782 Bool_t isMatched = isMC && (dataChain.GetBranch("BmnGlobalTrackMatch") != nullptr);
783 if (isMatched)
784 dataChain.SetBranchAddress("BmnGlobalTrackMatch", &globalMatches);
785 LOG(info) << "Building the efficiency map from the " << (isMC ? "simulated" : "experimental") << " DST files";
786 if (isMC && (!isMatched))
787 LOG(warning) << "'BmnGlobalTrackMatch' branch was not found, the particle types of the reconstructed tracks "
788 "without an MC index are defined by the mass measured by the ToF detectors";
789
790 // count the reconstructed tracks of every particle type in every momentum interval
791 map<Int_t, vector<Double_t>> reconstructed_count;
792 for (auto pdg_key : fParticles)
793 reconstructed_count[pdg_key].assign(fIntervalCount, 0.);
794
795 Double_t interval_width = (fPMax - fPMin) / fIntervalCount;
796 int iEventNumber = dataChain.GetEntries();
797 for (int iEvent = 0; iEvent < iEventNumber; iEvent++) {
798 if ((iEvent + 1) % 1000 == 0)
799 LOG(info) << "Processing event #" << iEvent + 1 << " of " << iEventNumber;
800 dataChain.GetEntry(iEvent);
801 if (globalTracks == nullptr)
802 continue;
803 TClonesArray* mc_tracks = (isMC && (mcInfo != nullptr)) ? mcInfo->GetMCTracks() : nullptr;
804
805 for (int iTrack = 0; iTrack < globalTracks->GetEntries(); iTrack++) {
806 BmnGlobalTrack* global_track = (BmnGlobalTrack*)globalTracks->UncheckedAt(iTrack);
807 if (global_track == nullptr)
808 continue;
809 // the reconstructed tracks are counted in the same acceptance as the sample ones (see SetSampleTrackCut):
810 // with the default cut only the tracks matched to a ToF hit
811 Bool_t has_tof = (global_track->GetTof1HitIndex() > -1) || (global_track->GetTof2HitIndex() > -1);
812 if ((fSampleTrackCut > 1) && (!has_tof))
813 continue;
814
815 // define the particle type: by the related MC track for the simulated data, by the ToF mass and the charge
816 // for the experimental ones
817 BmnTrackMatch* global_match = ((globalMatches != nullptr) && (iTrack < globalMatches->GetEntriesFast()))
818 ? (BmnTrackMatch*)globalMatches->UncheckedAt(iTrack)
819 : nullptr;
820 Double_t tof_mass = -1.;
821 Int_t track_pdg = GetGlobalTrackPDG(global_track, global_match, mc_tracks, tof_mass);
822 if (reconstructed_count.find(track_pdg) == reconstructed_count.end())
823 continue;
824
825 FairTrackParam* parFirst = global_track->GetParamFirst();
826 if (parFirst == nullptr)
827 continue;
828 Double_t track_qp = parFirst->GetQp();
829 if (TMath::Abs(track_qp) < 1.e-4)
830 continue;
831 Double_t track_p = 1. / TMath::Abs(track_qp);
832 if ((track_p < fPMin) || (track_p >= fPMax))
833 continue;
834 int interval_number = (track_p - fPMin) / interval_width;
835 if ((interval_number < 0) || (interval_number >= fIntervalCount))
836 continue;
837
838 reconstructed_count[track_pdg][interval_number] += 1.;
839 }
840 }
841
842 for (auto pdg_key : fParticles) {
843 TVectorD efficiency(fIntervalCount);
844 Double_t max_efficiency = 0.;
845 for (int cur_interval = 0; cur_interval < fIntervalCount; cur_interval++) {
846 TString histogram_name = TString::Format("hP%d_pdg%d", cur_interval + 1, pdg_key);
847 unique_ptr<TH1D> p_hist((TH1D*)hist_file->Get(histogram_name));
848 if (p_hist == nullptr) {
849 LOG(error) << "The momentum histogram '" << histogram_name
850 << "' does not exist in the file with the sample histograms: " << histo_file;
851 return -5;
852 }
853 p_hist->SetDirectory(nullptr);
854 Double_t sample_count = p_hist->GetEntries();
855 efficiency[cur_interval] =
856 (sample_count > 0) ? (reconstructed_count[pdg_key][cur_interval] / sample_count) : 0.;
857 max_efficiency = TMath::Max(max_efficiency, efficiency[cur_interval]);
858 }
859
860 if (max_efficiency <= 0.) {
861 LOG(error) << "No track of pdg=" << pdg_key << " was reconstructed in the given files, "
862 << "the efficiency map cannot be built";
863 return -6;
864 }
865 // the numbers of the reconstructed and of the sample tracks come from different data sets, so only the shape
866 // of the ratio is meaningful: it is normalized to its maximum, giving a RELATIVE efficiency
867 efficiency *= 1. / max_efficiency;
868 // 'overwrite' replaces the map of a previous run instead of adding one more cycle of the same key
869 hist_file->WriteObject(&efficiency, TString::Format("%s%d", kEfficiencyNamePrefix, pdg_key), "overwrite");
870
871 TString efficiency_values;
872 for (int cur_interval = 0; cur_interval < fIntervalCount; cur_interval++)
873 efficiency_values += TString::Format(" %4.2f", efficiency[cur_interval]);
874 LOG(info) << "Relative efficiency for pdg=" << pdg_key << ":" << efficiency_values;
875 }
876
877 hist_file->Close();
878 return 0;
879}
880
881// Calculate parameters for all the particle types
882// according to the given histogram stored in the 'input_histo_file' file
884{
885 // open file with histograms
886 TFile* hist_file = TFile::Open(input_histo_file);
887 if ((hist_file == nullptr) || (hist_file->IsZombie())) {
888 LOG(error) << "The file with the sample histograms was not opened: " << input_histo_file;
889 return kFALSE;
890 }
891
892 // the interval histograms are read by name only, so a grid different from the one of this generator would
893 // silently refer them to the wrong momentum ranges
894 if (!CheckMomentumGrid(hist_file))
895 return kFALSE;
896
897 // get histograms from the file and estimate parameters for embedding new particles of this type
898 // an empty list of the particle types would leave the flag set without a single map having been read
899 Bool_t is_efficiency_map_read = (!fParticles.empty());
900 for (auto pdg_key : fParticles) {
901 TString histogram_name = TString::Format("hP_pdg%d", pdg_key);
902 fParticleInfos[pdg_key]->fPHist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
903 if (fParticleInfos[pdg_key]->fPHist.get() == nullptr) {
904 LOG(error) << "Momentum histogram does not exist for pdg=" << pdg_key;
905 return kFALSE;
906 }
907 // detach the histogram from the file so it (and its fitted function) survives after the file is closed below,
908 // and is owned only by the unique_ptr - otherwise closing the file would delete it and cause a double free
909 fParticleInfos[pdg_key]->fPHist->SetDirectory(nullptr);
910 if (fParticleInfos[pdg_key]->fPHist->GetEntries() == 0) {
911 LOG(error) << "Momentum histogram is empty for pdg=" << pdg_key;
912 return kFALSE;
913 }
914 // the fallback momentum sampling for intervals with too little statistics uses the fitted function of this full
915 // histogram; if the histogram file was produced without the fit, create it now so the fallback always works
916 if (fParticleInfos[pdg_key]->fPHist->GetFunction(TString::Format("distP_pdg%d", pdg_key)) == nullptr) {
917 LOG(info) << "The fitted momentum function is absent for pdg=" << pdg_key << ", fitting it now";
918 FitPHistogram(fParticleInfos[pdg_key]->fPHist, pdg_key, fPMin, fPMax);
919 }
920 histogram_name = TString::Format("hPt_pdg%d", pdg_key);
921 fParticleInfos[pdg_key]->fPtHist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
922 if (fParticleInfos[pdg_key]->fPtHist.get() == nullptr) {
923 LOG(error) << "Transverse momentum histogram does not exist for pdg=" << pdg_key;
924 return kFALSE;
925 }
926 fParticleInfos[pdg_key]->fPtHist->SetDirectory(nullptr);
927 if (fParticleInfos[pdg_key]->fPtHist->GetEntries() == 0) {
928 LOG(error) << "Transverse momentum histogram is empty for pdg=" << pdg_key;
929 return kFALSE;
930 }
931 histogram_name = TString::Format("hY_pdg%d", pdg_key);
932 fParticleInfos[pdg_key]->fYHist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
933 if (fParticleInfos[pdg_key]->fYHist.get() == nullptr) {
934 LOG(error) << "Rapidity histogram does not exist for pdg=" << pdg_key;
935 return kFALSE;
936 }
937 fParticleInfos[pdg_key]->fYHist->SetDirectory(nullptr);
938 if (fParticleInfos[pdg_key]->fYHist->GetEntries() == 0) {
939 LOG(error) << "Rapidity histogram is empty for pdg=" << pdg_key;
940 return kFALSE;
941 }
942 histogram_name = TString::Format("hEta_pdg%d", pdg_key);
943 fParticleInfos[pdg_key]->fEtaHist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
944 if (fParticleInfos[pdg_key]->fEtaHist.get() == nullptr) {
945 LOG(error) << "Pseudorapidity histogram does not exist for pdg=" << pdg_key;
946 return kFALSE;
947 }
948 fParticleInfos[pdg_key]->fEtaHist->SetDirectory(nullptr);
949 if (fParticleInfos[pdg_key]->fEtaHist->GetEntries() == 0) {
950 LOG(error) << "Pseudorapidity histogram is empty for pdg=" << pdg_key;
951 return kFALSE;
952 }
953 histogram_name = TString::Format("hTheta_pdg%d", pdg_key);
954 fParticleInfos[pdg_key]->fThetaHist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
955 if (fParticleInfos[pdg_key]->fThetaHist.get() == nullptr) {
956 LOG(error) << "Theta angle histogram does not exist for pdg=" << pdg_key;
957 return kFALSE;
958 }
959 fParticleInfos[pdg_key]->fThetaHist->SetDirectory(nullptr);
960 if (fParticleInfos[pdg_key]->fThetaHist->GetEntries() == 0) {
961 LOG(error) << "Theta angle histogram is empty for pdg=" << pdg_key;
962 return kFALSE;
963 }
964 histogram_name = TString::Format("hPhi_pdg%d", pdg_key);
965 fParticleInfos[pdg_key]->fPhiHist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
966 if (fParticleInfos[pdg_key]->fPhiHist.get() == nullptr) {
967 LOG(error) << "Phi angle histogram does not exist for pdg=" << pdg_key;
968 return kFALSE;
969 }
970 fParticleInfos[pdg_key]->fPhiHist->SetDirectory(nullptr);
971 if (fParticleInfos[pdg_key]->fPhiHist->GetEntries() == 0) {
972 LOG(error) << "Phi angle histogram is empty for pdg=" << pdg_key;
973 return kFALSE;
974 }
975 int hist_counter = 0;
976 for (auto& p_hist : fParticleInfos[pdg_key]->fPHistVector) {
977 histogram_name = TString::Format("hP%d_pdg%d", ++hist_counter, pdg_key);
978 p_hist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
979 if (p_hist.get() == nullptr) {
980 LOG(error) << "Momentum histogram does not exist for pdg=" << pdg_key
981 << " in interval=" << hist_counter;
982 return kFALSE;
983 }
984 p_hist->SetDirectory(nullptr);
985 }
986 hist_counter = 0;
987 for (auto& eta_hist : fParticleInfos[pdg_key]->fEtaHistVector) {
988 histogram_name = TString::Format("hEta%d_pdg%d", ++hist_counter, pdg_key);
989 eta_hist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
990 if (eta_hist.get() == nullptr) {
991 LOG(error) << "Pseudorapidity histogram does not exist for pdg=" << pdg_key
992 << " in interval=" << hist_counter;
993 return kFALSE;
994 }
995 eta_hist->SetDirectory(nullptr);
996 }
997 hist_counter = 0;
998 for (auto& phi_hist : fParticleInfos[pdg_key]->fPhiHistVector) {
999 histogram_name = TString::Format("hPhi%d_pdg%d", ++hist_counter, pdg_key);
1000 phi_hist = unique_ptr<TH1D>((TH1D*)hist_file->Get(histogram_name));
1001 if (phi_hist.get() == nullptr) {
1002 LOG(error) << "Phi angle histogram does not exist for pdg=" << pdg_key
1003 << " in interval=" << hist_counter
1004 << ". The file with the sample histograms was produced by an older version of the "
1005 "equalizing generator, please, recreate it with ProduceSampleHistograms()";
1006 return kFALSE;
1007 }
1008 phi_hist->SetDirectory(nullptr);
1009 }
1010
1011 // the pseudorapidity and the azimuth angle of a new particle are taken from the histograms of its momentum
1012 // interval, or from the nearest interval having statistics if the interval itself has none: this way the values
1013 // stay tied to a close momentum region instead of being taken from the whole momentum range
1014 DefineNearestFilledIntervals(fParticleInfos[pdg_key]->fEtaHistVector, fParticleInfos[pdg_key]->fEtaHistIndex);
1015 DefineNearestFilledIntervals(fParticleInfos[pdg_key]->fPhiHistVector, fParticleInfos[pdg_key]->fPhiHistIndex);
1016
1017 // define how the momentum of a new particle will be sampled in every momentum interval: the P histogram of the
1018 // interval itself is used if the sample has enough statistics there, otherwise the fitted function of the full
1019 // P histogram is used as a fallback. It does not depend on the event, so it is defined once here
1020 TF1* p_dist_func = fParticleInfos[pdg_key]->fPHist->GetFunction(TString::Format("distP_pdg%d", pdg_key));
1021 if (p_dist_func != nullptr) {
1022 // TF1::GetRandom() draws a value from the cumulative distribution tabulated on a grid of GetNpx() points
1023 // over the whole function range and then rejects the values outside the requested momentum interval, so
1024 // the grid has to be fine enough for a single interval to keep the sampling unbiased and fast
1025 p_dist_func->SetNpx(TMath::Max(p_dist_func->GetNpx(), 40 * fIntervalCount));
1026 }
1027 // The lowest value of the fitted function an interval can be sampled at. A function negligible relative to its
1028 // maximum is as unusable as a non-positive one: the cumulative distribution TF1::GetRandom() tabulates cannot
1029 // resolve such an interval in double precision and undefined values are returned (it was observed below a
1030 // relative level of about 1e-13). It is the case for the tails of a function fitted to a small sample
1031 Double_t min_func_value =
1032 (p_dist_func != nullptr) ? TMath::Max(0., 1.e-9 * p_dist_func->GetMaximum(fPMin, fPMax)) : 0.;
1033 Double_t interval_p = fPMin;
1034 for (int cur_interval = 0; cur_interval < fIntervalCount; cur_interval++, interval_p += fIntervalStep) {
1035 if (fParticleInfos[pdg_key]->fPHistVector[cur_interval]->GetEntries() > 2)
1036 fParticleInfos[pdg_key]->fPSampling[cur_interval] = stParticleInfo::EPSampling::kIntervalHist;
1037 // the rejection sampling inside TF1::GetRandom() never returns if the fitted function has no positive
1038 // values in the requested interval, so such intervals have to be excluded here
1039 else if ((p_dist_func != nullptr)
1040 && (p_dist_func->GetMaximum(interval_p, interval_p + fIntervalStep) > min_func_value))
1041 fParticleInfos[pdg_key]->fPSampling[cur_interval] = stParticleInfo::EPSampling::kFitFunction;
1042 else {
1043 fParticleInfos[pdg_key]->fPSampling[cur_interval] = stParticleInfo::EPSampling::kNone;
1044 LOG(warning) << TString::Format(
1045 "There is neither sample statistics nor a significant fitted momentum function for pdg=%d in the "
1046 "momentum interval [%4.2f, %4.2f] GeV: no particles will be added there",
1047 pdg_key, interval_p, interval_p + fIntervalStep);
1048 }
1049 }
1050
1051 if (TDatabasePDG::Instance()->GetParticle(pdg_key) == nullptr) {
1052 LOG(error) << "PDG code " << pdg_key << " is not defined in the PDG database";
1053 return kFALSE;
1054 }
1055
1056 // the mean rapidity, its sigma and the temperature of the Pt distribution were obtained by fitting the sample
1057 // histograms while producing them, so they are simply read back here instead of repeating the fits at every
1058 // start of the simulation (the rapidity fit of the protons uses the slow "Genetic" minimizer)
1059 TString vector_name = TString::Format("Y0SigmaT_pdg%d", pdg_key);
1060 unique_ptr<TVector3> y0_sigma_t((TVector3*)hist_file->Get(vector_name));
1061 if (y0_sigma_t == nullptr) {
1062 LOG(error) << "The fitted distribution parameters ('" << vector_name
1063 << "') do not exist in the file with the sample histograms. It was produced by an older "
1064 "version of the equalizing generator, please, recreate it with ProduceSampleHistograms()";
1065 return kFALSE;
1066 }
1067 fParticleInfos[pdg_key]->fY0 = y0_sigma_t->X();
1068 fParticleInfos[pdg_key]->fSigma = y0_sigma_t->Y();
1069 fParticleInfos[pdg_key]->fT = y0_sigma_t->Z();
1070
1071 LOG(info) << TString::Format("pdg=%i y0=%4.2f sigma_y=%4.2f T_pt=%6.4f", pdg_key, fParticleInfos[pdg_key]->fY0,
1072 fParticleInfos[pdg_key]->fSigma, fParticleInfos[pdg_key]->fT);
1073
1074 // the map of the reconstruction efficiency is optional: it is only used if the correction by it is switched
1075 // on by SetEfficiencyCorrection(), which refuses to do it when the map has not been read here
1076 unique_ptr<TVectorD> efficiency(
1077 (TVectorD*)hist_file->Get(TString::Format("%s%d", kEfficiencyNamePrefix, pdg_key)));
1078 if ((efficiency != nullptr) && (efficiency->GetNoElements() == fIntervalCount)) {
1079 for (int cur_interval = 0; cur_interval < fIntervalCount; cur_interval++)
1080 fParticleInfos[pdg_key]->fEfficiency[cur_interval] = (*efficiency)[cur_interval];
1081 } else {
1082 if (efficiency != nullptr)
1083 LOG(error) << "The efficiency map of pdg=" << pdg_key << " has " << efficiency->GetNoElements()
1084 << " elements instead of " << fIntervalCount << " momentum intervals, it is ignored";
1085 is_efficiency_map_read = kFALSE;
1086 }
1087 }
1088 fIsEfficiencyMapRead = is_efficiency_map_read;
1089
1090 // all the histograms have been detached from the file above, so it can be safely closed and deleted
1091 hist_file->Close();
1092 delete hist_file;
1093
1094 return kTRUE;
1095}
1096
1097// Creates an event with equal number of particles at all momentum intervals
1098Bool_t BmnParticleEqualizer1D::ReadEvent(FairPrimaryGenerator* primGen)
1099{
1100 if (!isActive)
1101 return kFALSE;
1102
1103 // the generator equalizes the particles put on the stack by the generators called before it, so it has to be the
1104 // last one in the list of FairPrimaryGenerator: being registered earlier it would silently miss the particles of
1105 // the generators registered after it and equalize an incomplete event
1106 TObjArray* generator_list = primGen->GetListOfGenerators();
1107 if ((generator_list == nullptr) || (generator_list->Last() != this)) {
1108 LOG(error) << "The BmnParticleEqualizer1D generator is not the last one registered in FairPrimaryGenerator: "
1109 "it has to be added by AddGenerator() after all the other generators of the event";
1110 return kFALSE;
1111 }
1112
1113 // the particles of the event are taken from the stack filled by the previous generators
1114 CbmStack* stack = (gMC == nullptr) ? nullptr : dynamic_cast<CbmStack*>(gMC->GetStack());
1115 if (stack == nullptr) {
1116 LOG(error) << "The BmnParticleEqualizer1D generator requires the particle stack of the simulation (CbmStack) "
1117 "to count the particles of the event, but it is not available";
1118 return kFALSE;
1119 }
1120
1121 /** CLEARING **/
1122 // reset particle count for all the particle types
1123 for (auto part_iter = fParticleInfos.begin(); part_iter != fParticleInfos.end(); part_iter++)
1124 fill(part_iter->second->fParticleNumber.begin(), part_iter->second->fParticleNumber.end(), 0);
1125 // clear maximum number of a particle type among all particles (divided by momentum intervals)
1126 fill(fMaxParticles.begin(), fMaxParticles.end(), 0);
1127
1128 /** FORM EVENT DISTRIBUTION FOR GENERATION PARTICLES TO EQUALIZE THEIR NUMBERS **/
1129 // form particle distributions for the current event (obtained by the previous generators)
1130 TClonesArray* arrParticles = stack->GetListOfParticles();
1131 for (int i = 0; i < arrParticles->GetEntries(); i++) {
1132 // get particle information
1133 // exclude the secondary particles before anything is computed for them: TParticle::Eta() of a track with a
1134 // zero transverse momentum is infinite, and the values of a secondary particle are not needed at all
1135 TParticle* part = (TParticle*)arrParticles->At(i);
1136 if ((part == nullptr) || (!part->IsPrimary()))
1137 continue;
1138
1139 Int_t part_code = part->GetPdgCode();
1140 Double_t part_p = part->P();
1141 Double_t part_eta = part->Eta();
1142 // TParticle::Phi() returns the angle in [0, 2pi), while the sample histograms are filled with ATan2(py, px),
1143 // i.e. in [-pi, pi], so the very same definition is used here
1144 Double_t part_phi = TMath::ATan2(part->Py(), part->Px());
1145
1146 // check particle momentum is inside the momentum range: a momentum below fPMin would be truncated to interval 0
1147 // by the int conversion (not to a negative index), so the range is verified on the momentum value itself first
1148 if ((part_p < fPMin) || (part_p >= fPMax))
1149 continue;
1150 int interval_number = (part_p - fPMin) / fIntervalStep;
1151 if ((interval_number < 0) || (interval_number >= fIntervalCount))
1152 continue;
1153
1154 // add found particle according to the momentum to the "PDG -> Particle Count" map (increase by 1)
1155 LOG(debug) << TString::Format("Found particle: PDG=%i P=%4.2f Eta=%4.2f", part_code, part_p, part_eta);
1156 if (auto part_iter = fParticleInfos.find(part_code); part_iter != fParticleInfos.end()) {
1157 // Skip the particles the generator cannot reproduce: those whose momentum, pseudorapidity or azimuth angle
1158 // falls into a bin where the sample of the interval has no statistics at all. The values are checked
1159 // separately, as they are also sampled separately, see BmnParticleEqualizer2D for the joint check
1161 TH1D* p_hist = fParticleInfos[part_code]->fPHistVector[interval_number].get();
1162 if (p_hist->GetBinContent(p_hist->FindBin(part_p)) < 1)
1163 continue;
1164 TH1D* eta_hist = fParticleInfos[part_code]->fEtaHistVector[interval_number].get();
1165 if (eta_hist->GetBinContent(eta_hist->FindBin(part_eta)) < 1)
1166 continue;
1167 TH1D* phi_hist = fParticleInfos[part_code]->fPhiHistVector[interval_number].get();
1168 if (phi_hist->GetBinContent(phi_hist->FindBin(part_phi)) < 1)
1169 continue;
1170 }
1171
1172 part_iter->second->fParticleNumber[interval_number] += 1;
1173 // update maximum number among all the particle if greater (for the current momentum interval)
1174 if (part_iter->second->fParticleNumber[interval_number] > fMaxParticles[interval_number])
1175 fMaxParticles[interval_number] = part_iter->second->fParticleNumber[interval_number];
1176 }
1177 } // for (int i = 0; i < arrParticles->GetEntries(); i++)
1178
1179 // The sample histograms are filled with the momenta of the simulated particles, i.e. in the laboratory frame, so
1180 // the values sampled from them already include the beam angle. However FairPrimaryGenerator::AddTrack() rotates
1181 // every added track by the beam angle of the event once again, so the rotation is undone here to keep the added
1182 // particles in the very same frame as the sampled distributions. The angles are taken from the event header, where
1183 // they are written by FairPrimaryGenerator::MakeBeamAngle() and are left zero if the beam angle smearing is
1184 // switched off, so the compensation is an identity transformation in that case.
1185 // AddTrack() also rotates the track around the z-axis by its own event plane angle, but only if the rotation was
1186 // switched on by FairPrimaryGenerator::SetEventPlane(), which is not used in BmnRoot: the SetEventPlane() methods
1187 // called in the simulation macros belong to the generators themselves (MpdUnigenGenerator, MpdUrqmdGenerator, ...)
1188 // and those rotate their own particles, not the ones added here. That rotation cannot be compensated anyway: it is
1189 // applied before the beam angle one, and its angle is not available (the header field RotZ is not it, because the
1190 // generators listed above overwrite RotZ with their own reaction plane angle).
1191 // The beam angle rotation is TVector3::RotateUz(), the inverse of it is the transposed matrix, i.e. the projections
1192 // onto the images of the basis vectors
1193 FairMCEventHeader* event_header = primGen->GetEvent();
1194 TVector3 beam_x(1., 0., 0.), beam_y(0., 1., 0.), beam_z(0., 0., 1.);
1195 if ((event_header != nullptr) && ((event_header->GetRotX() != 0.) || (event_header->GetRotY() != 0.))) {
1196 TVector3 beam_direction(TMath::Tan(event_header->GetRotX()), TMath::Tan(event_header->GetRotY()), 1.);
1197 beam_direction = beam_direction.Unit();
1198 beam_x.RotateUz(beam_direction);
1199 beam_y.RotateUz(beam_direction);
1200 beam_z.RotateUz(beam_direction);
1201 }
1202
1203 // generate new particles to equalize their numbers for different types
1204 for (auto pdg_key : fParticles) {
1205 LOG(debug) << TString::Format("Current particle type to be equalized: pdg=%i y0=%4.2f sigma_y=%4.2f T_pt=%6.4f",
1206 pdg_key, fParticleInfos[pdg_key]->fY0, fParticleInfos[pdg_key]->fSigma,
1207 fParticleInfos[pdg_key]->fT);
1208 // the fitted momentum function is the same for the whole particle type and is used as a fallback for the
1209 // intervals with too little sample statistics (the way to sample the momentum in every momentum interval was
1210 // defined in ReadSampleHistograms); fetch it once here to avoid searching for it for every added particle
1211 TF1* p_dist_func = fParticleInfos[pdg_key]->fPHist->GetFunction(TString::Format("distP_pdg%d", pdg_key));
1212
1213 Double_t cur_p = fPMin;
1214 // add a number of particles for all momentum intervals to equalize their count
1215 LOG(debug) << "Adding paticle to equalize the numbers...";
1216 for (int cur_interval = 0; cur_interval < fIntervalCount; cur_interval++, cur_p += fIntervalStep) {
1217 // the momentum cannot be sampled in this interval at all (it was reported while reading the histograms)
1218 if (fParticleInfos[pdg_key]->fPSampling[cur_interval] == stParticleInfo::EPSampling::kNone)
1219 continue;
1220
1221 // the number of the particles of this type to be reached in the interval: the target given by
1222 // SetTargetPerInterval() if it is set, the maximum number among the particle types found in this interval
1223 // of this event otherwise (the default behaviour, which equalizes the types but keeps the momentum
1224 // spectrum of the input events)
1225 Double_t target_number =
1227 Double_t particle_number = target_number - fParticleInfos[pdg_key]->fParticleNumber[cur_interval];
1228 // the added particles are reconstructed with the efficiency of the interval, so more of them are needed
1229 // where it is low; the limit keeps the factor finite in the intervals with an almost zero efficiency.
1230 // A negative value means the event is already richer than the target and nothing is to be added, so it
1231 // must not be scaled: dividing a surplus by the efficiency would make it suppress the next events
1232 if ((fUseEfficiencyCorrection) && (particle_number > 0.))
1233 particle_number /= TMath::Max(fParticleInfos[pdg_key]->fEfficiency[cur_interval], fMinEfficiency);
1234
1235 // The target may be fractional, so the part below one particle is accumulated between the events: without
1236 // it a target of, say, 0.3 particles per event would always be rounded down to nothing. With the default
1237 // target the increment is an integer and the accumulated value stays zero, i.e. nothing changes
1238 Double_t& particle_debt = fParticleInfos[pdg_key]->fParticleDebt[cur_interval];
1239 particle_debt += particle_number;
1240 int add_particle = (int)TMath::Floor(particle_debt);
1241 // the event already has more particles than the target: nothing is added and the surplus is kept, so that
1242 // fewer particles are added to this interval in the next events
1243 if (add_particle < 0)
1244 add_particle = 0;
1245 particle_debt -= add_particle;
1246 // Note: a particle skipped inside the loop below (its kinematics could not be sampled) is not returned to
1247 // the debt on purpose. All such skips are excluded by the checks made while reading the sample histograms,
1248 // so they should not happen at all; and were a skip systematic for some interval, returning the particles
1249 // would make the debt of that interval grow with every event and 'add_particle' grow together with it
1250
1251 LOG(debug) << endl
1252 << " " << add_particle << " particle with PDG = " << pdg_key << " (" << cur_p << " GeV)";
1253 // the pseudorapidity and the azimuth angle are taken from the interval defined while reading the histograms
1254 // (the current one or the nearest one having statistics), so they are fetched once per interval here
1255 Int_t eta_interval = fParticleInfos[pdg_key]->fEtaHistIndex[cur_interval];
1256 Int_t phi_interval = fParticleInfos[pdg_key]->fPhiHistIndex[cur_interval];
1257 if ((eta_interval < 0) || (phi_interval < 0)) {
1258 LOG(warning) << "There is no sample statistics of the pseudorapidity or of the azimuth angle for pdg="
1259 << pdg_key << " in any momentum interval, no particles are added";
1260 continue;
1261 }
1262 for (Int_t k = 0; k < add_particle; k++) {
1263 // sample the momentum either from the P histogram of the interval itself or, if the interval has too
1264 // little sample statistics, from the fitted momentum function of the full histogram (fetched above)
1265 Double_t p =
1266 (fParticleInfos[pdg_key]->fPSampling[cur_interval] == stParticleInfo::EPSampling::kIntervalHist)
1267 ? fParticleInfos[pdg_key]->fPHistVector[cur_interval]->GetRandom()
1268 : p_dist_func->GetRandom(cur_p, cur_p + fIntervalStep);
1269 // Double_t pt = fParticleInfos[pdg_key]->fPtDistribution->GetRandom(cur_pt, cur_pt + fIntervalStep);
1270 // Double_t y = gRandom->Gaus(particle_info->fY0, particle_info->fSigma);
1271 // Double_t mt = TMath::Sqrt(fPDGMass * fPDGMass + pt * pt);
1272 // Double_t pz = mt * TMath::SinH(y);
1273 Double_t eta = fParticleInfos[pdg_key]->fEtaHistVector[eta_interval]->GetRandom();
1274 // the azimuth angle is sampled from the histogram of the sample as well, so the added particles are
1275 // distributed as the ones of the sample, i.e. inside the detector acceptance, and not uniformly
1276 Double_t phi = fParticleInfos[pdg_key]->fPhiHistVector[phi_interval]->GetRandom();
1277 // the fitted momentum function is not required to be positive over the whole interval and
1278 // TF1::GetRandom() takes the absolute values of its negative parts, so the sampled values are
1279 // verified before a particle with a zero or undefined momentum is put on the stack
1280 if ((!TMath::Finite(p)) || (p <= 0) || (!TMath::Finite(eta))) {
1281 LOG(warning) << "An incorrect momentum (" << p << ") or pseudorapidity (" << eta
1282 << ") was sampled for pdg=" << pdg_key << ", the particle is skipped";
1283 continue;
1284 }
1285 Double_t pt = p / TMath::CosH(eta);
1286 TVector3 mom(pt * TMath::Cos(phi), pt * TMath::Sin(phi), pt * TMath::SinH(eta));
1287
1288 // undo the beam angle rotation that AddTrack() applies below (see the comment before the loop)
1289 mom.SetXYZ(mom * beam_x, mom * beam_y, mom * beam_z);
1290
1291 LOG(debug) << "Particle generated: pdg=" << pdg_key << " p=" << p << " eta=" << eta;
1292 // AddTrack parameters: pdgid, px, py, pz, vx, vy, vz
1293 // the vertex (vx, vy, vz) is given relative to the event vertex: FairPrimaryGenerator::AddTrack adds
1294 // the event vertex itself, so (0, 0, 0) means "at the primary vertex"
1295 Int_t particle_count = arrParticles->GetEntriesFast();
1296 primGen->AddTrack(pdg_key, mom.X(), mom.Y(), mom.Z(), 0., 0., 0.);
1297 // mark the particle just put on the stack (the bit is carried over to CbmMCTrack)
1298 // the second possible way: use weight parameter of AddTrack(), then parse in CbmMCTrack
1299 // AddTrack() discards a particle whose PDG code is absent in the database,
1300 // hence the check that the stack has grown
1301 if (arrParticles->GetEntriesFast() > particle_count) {
1302 TParticle* added_particle = (TParticle*)arrParticles->At(arrParticles->GetEntriesFast() - 1);
1303 if (added_particle != nullptr)
1304 added_particle->SetBit(kAddedParticleBit);
1305 }
1306 }
1307 }
1308 }
1309
1310 return kTRUE;
1311}
1312
1313// for every momentum interval define the index of the interval histogram to be used to sample a value
1314void BmnParticleEqualizer1D::DefineNearestFilledIntervals(const vector<unique_ptr<TH1D>>& hist_vector,
1315 vector<Int_t>& index_vector)
1316{
1317 const Int_t interval_count = (Int_t)hist_vector.size();
1318 index_vector.assign(interval_count, -1);
1319
1320 for (Int_t cur_interval = 0; cur_interval < interval_count; cur_interval++) {
1321 // look for the nearest interval having statistics, starting from the current one; at an equal distance the
1322 // lower momentum interval is preferred, as it usually has more statistics
1323 for (Int_t shift = 0; shift < interval_count; shift++) {
1324 const Int_t lower_interval = cur_interval - shift, upper_interval = cur_interval + shift;
1325 if ((lower_interval >= 0) && (hist_vector[lower_interval] != nullptr)
1326 && (hist_vector[lower_interval]->GetEntries() > 2))
1327 {
1328 index_vector[cur_interval] = lower_interval;
1329 break;
1330 }
1331 if ((upper_interval < interval_count) && (hist_vector[upper_interval] != nullptr)
1332 && (hist_vector[upper_interval]->GetEntries() > 2))
1333 {
1334 index_vector[cur_interval] = upper_interval;
1335 break;
1336 }
1337 }
1338 }
1339}
1340
1341// static function to fit the obtained sample histograms (to show whether the results are appropriate)
1343{
1344 TFile* f = TFile::Open(input_histo_file);
1345 if ((!f) || (!f->IsOpen())) {
1346 cout << "ERROR: unable to open ROOT file: " << input_histo_file << endl;
1347 return;
1348 }
1349 for (int particle_number : fParticles) {
1350 /*TCanvas* c = */ new TCanvas(TString::Format("c_%d", particle_number),
1351 TString::Format("Distributions for particle PDG = %d", particle_number), 1000,
1352 500);
1353 TH1D* h1 = (TH1D*)f->Get(TString::Format("hP_pdg%d", particle_number));
1354 if (h1 == nullptr) {
1355 cout << "ERROR: the momentum histogram is absent for PDG = " << particle_number << endl;
1356 continue;
1357 }
1358
1359 Double_t x_max_value = h1->GetXaxis()->GetBinCenter(h1->GetMaximumBin());
1360 Double_t y_max_value = h1->GetBinContent(h1->GetMaximumBin());
1361 Double_t p_avg = (fPMin + fPMax) / 2;
1362 cout << "avg_value = " << p_avg << endl;
1363 cout << "x_max_value = " << x_max_value << endl;
1364 cout << "y_max_value = " << y_max_value << endl;
1365 // the very same function and fit as in FitPHistogram(), so what is shown is what the generator uses
1366 FitMomentumFunction(h1, "distFit", fPMin, fPMax);
1367 // the function is attached without drawing ('0' option of the fit), the flag is reset to show it here
1368 TF1* p_distribution = h1->GetFunction("distFit");
1369 if (p_distribution != nullptr)
1370 p_distribution->ResetBit(TF1::kNotDraw);
1371 h1->Draw();
1372 }
1373}
1374
1375// static function to show the obtained sample histograms with obtained fit (to check whether the results are
1376// appropriate)
1378{
1379 TFile* f = TFile::Open(input_histo_file);
1380 if ((!f) || (!f->IsOpen())) {
1381 cout << "ERROR: unable to open ROOT file: " << input_histo_file << endl;
1382 return -1;
1383 }
1384
1385 for (int particle_number : fParticles) {
1386 /*TCanvas* c = */ new TCanvas(TString::Format("c_%d", particle_number),
1387 TString::Format("Distributions for particle PDG = %d", particle_number), 1000,
1388 500);
1389 TH1D* h1 = (TH1D*)f->Get(TString::Format("hP_pdg%d", particle_number));
1390 if (h1 == nullptr) {
1391 cout << "ERROR: the momentum histogram is absent for PDG = " << particle_number << endl;
1392 continue;
1393 }
1394 TF1* f1 = h1->GetFunction(TString::Format("distP_pdg%d", particle_number));
1395 if (f1 == nullptr) {
1396 cout << "ERROR: the fitted momentum function is absent for PDG = " << particle_number << endl;
1397 continue;
1398 }
1399 f1->Draw();
1400 h1->Draw("SAME");
1401 }
1402
1403 return 0;
1404}
1405
1406// define PDG code of a particle by the given mass
1407// among particle types set by 'vecParticlePDG' vector or in the TDatabasePDG if the vector is empty
1408// tolerance: relative if tolerance > 0, absolute (in GeV) in case of tolerance <= 0
1409// return PDG code or 0 if no corresponding particle was found
1410Int_t BmnParticleEqualizer1D::GetPDGCodeByMass(Double_t mass, Double_t tolerance, const vector<int>& vecParticlePDG)
1411{
1413 if (!pdg_base) {
1414 cout << "ERROR: Could not get TDatabasePDG instance" << endl;
1415 return 0;
1416 }
1417
1418 if (mass < 0)
1419 mass *= -1;
1420
1421 if (vecParticlePDG.empty()) {
1422 TIter next(pdg_base->ParticleList());
1423 while (TParticlePDG* particle = (TParticlePDG*)next()) {
1424 if (TMath::Abs(particle->Mass() - mass) < (tolerance > 0 ? tolerance * mass : -1 * tolerance))
1425 return particle->PdgCode();
1426 }
1427 } else {
1428 for (int particle_number : vecParticlePDG) {
1429 TParticlePDG* particle = pdg_base->GetParticle(particle_number);
1430 if (particle == nullptr)
1431 continue;
1432 if (TMath::Abs(particle->Mass() - mass) < (tolerance > 0 ? tolerance * mass : -1 * tolerance))
1433 return particle->PdgCode();
1434 }
1435 }
1436
1437 return 0;
1438}
1439
1440// define the particle type of a reconstructed (DST) track: by the matched MC track or by the ToF mass and the charge
1442 BmnTrackMatch* global_match,
1443 TClonesArray* mc_tracks,
1444 Double_t& tof_mass)
1445{
1446 tof_mass = -1.;
1447 if (global_track == nullptr)
1448 return 0;
1449
1450 // simulated data: the type of the related MC track. The index stored in the global track itself is taken first:
1451 // BmnGlobalTrack::GetRefIndex() always returns -1 for now (it is kept for compatibility), but it is planned to be
1452 // filled again; until then the MC track is given by the matching
1453 if (mc_tracks != nullptr) {
1454 Int_t mc_index = global_track->GetRefIndex();
1455 if ((mc_index < 0) && (global_match != nullptr) && (global_match->GetNofLinks() > 0))
1456 mc_index = global_match->GetMatchedLink().GetIndex();
1457 if (mc_index > -1) {
1458 if (mc_index >= mc_tracks->GetEntriesFast())
1459 return 0;
1460 CbmMCTrack* mc_track = (CbmMCTrack*)mc_tracks->UncheckedAt(mc_index);
1461 return (mc_track == nullptr) ? 0 : mc_track->GetPdgCode();
1462 }
1463 // the track has the matching information, but no MC track is matched to it
1464 if (global_match != nullptr)
1465 return 0;
1466 // there is no information on the related MC track at all: the type is defined by the ToF mass below
1467 }
1468
1469 // experimental data: the mass measured by ToF-400 or, if it is absent, by ToF-700
1470 Double_t mass_tof2 = global_track->GetMass2(1);
1471 if (mass_tof2 == -1000.0)
1472 mass_tof2 = global_track->GetMass2(2);
1473 // GetMass2 returns the squared mass, a negative value is caused by the ToF resolution
1474 if ((mass_tof2 == -1000.0) || (mass_tof2 < 0))
1475 return 0;
1476 FairTrackParam* par_first = global_track->GetParamFirst();
1477 if ((par_first == nullptr) || (par_first->GetQp() == 0))
1478 return 0;
1479
1480 // the mass does not distinguish a particle from its antiparticle, so only the types with the charge of the track
1481 // (the sign of q/p) are the candidates. An empty list must not be passed on: GetPDGCodeByMass() would search the
1482 // whole PDG database then
1483 vector<Int_t> candidates;
1484 for (Int_t pdg_code : fParticles) {
1485 TParticlePDG* particle = TDatabasePDG::Instance()->GetParticle(pdg_code);
1486 if ((particle != nullptr) && (particle->Charge() * par_first->GetQp() > 0))
1487 candidates.push_back(pdg_code);
1488 }
1489 if (candidates.empty())
1490 return 0;
1491
1492 tof_mass = TMath::Sqrt(mass_tof2);
1493 return GetPDGCodeByMass(tof_mass, -0.03, candidates);
1494}
1495
1496// draw histogram with multiplicity of different particle types for all the momentum intervals
1497// as well as P, Pt, Y, Eta, Theta, Phi distributions after the equalizing
1498// it work both for SIM and DST files listed in the given text file ('input_list_file' var)
1500{
1501 gSystem->ExpandPathName(input_list_file);
1502
1503 // get file list from the input text file
1504 list<string> file_list;
1505 ifstream list_file(input_list_file.Data());
1506 string s;
1507 while (getline(list_file, s))
1508 file_list.push_back(s);
1509 if (file_list.empty()) {
1510 cout << "ERROR: There are no files in the list of the input text file: " << input_list_file << endl;
1511 return -5;
1512 }
1513
1514 // form ROOT chain for the file list
1515 TChain dataChain("bmndata");
1516 for (auto const& cur_file_path : file_list) {
1517 TString strFilePath(cur_file_path);
1518 gSystem->ExpandPathName(strFilePath);
1519
1520 dataChain.Add(strFilePath);
1521 }
1522
1523 // flag whether the input files have SIM or DST format
1524 bool isDST = false;
1525 TClonesArray *mcTracks = nullptr, *globalTracks = nullptr, *globalMatches = nullptr;
1526 BmnMCInfoDst* mcInfo = nullptr;
1527 if (dataChain.GetBranch("MCTrack") == nullptr) {
1528 if (dataChain.GetBranch("BmnMCInfo.") == nullptr) {
1529 LOG(error) << "'MCTrack' and 'BmnMCInfo.' branches were not found, please, check the structure of the "
1530 "files, exiting...";
1531 return -1;
1532 }
1533 dataChain.SetBranchAddress("BmnMCInfo.", &mcInfo);
1534 if (mcInfo == nullptr) {
1535 LOG(error)
1536 << "'BmnMCInfo.' branch was not read correctly, please, check the structure of the files, exiting...";
1537 return -2;
1538 }
1539 dataChain.SetBranchAddress("BmnGlobalTrack", &globalTracks);
1540 if (globalTracks == nullptr) {
1541 LOG(error) << "'BmnGlobalTrack' branch was not read correctly, please, check the structure of the files, "
1542 "exiting...";
1543 return -4;
1544 }
1545 // a reconstructed track is related to its MC track by the index stored in the track itself
1546 // (BmnGlobalTrack::GetRefIndex(), which always returns -1 for now) or by the separate matching branch
1547 if (dataChain.GetBranch("BmnGlobalTrackMatch") != nullptr)
1548 dataChain.SetBranchAddress("BmnGlobalTrackMatch", &globalMatches);
1549 else
1550 LOG(warning) << "'BmnGlobalTrackMatch' branch was not found, the particle types of the reconstructed "
1551 "tracks without an MC index are defined by the mass measured by the ToF detectors";
1552 isDST = true;
1553 // dataChain.SetBranchStatus("*", false);
1554 // dataChain.SetBranchStatus("BmnMCInfo*", true);
1555 // dataChain.SetBranchStatus("BmnGlobalTrack", true);
1556 } else {
1557 dataChain.SetBranchAddress("MCTrack", &mcTracks);
1558 if (mcTracks == nullptr) {
1559 LOG(error)
1560 << "MCTrack' branch was not read correctly, please, check the structure of the files, exiting...";
1561 return -3;
1562 }
1563 // dataChain.SetBranchStatus("*", false);
1564 // dataChain.SetBranchStatus("MCTrack*", true);
1565 }
1566
1567 // create histogram with bars
1568 TCanvas* c_number =
1569 new TCanvas("c_number", "Multiplicity of different particle types for all the momentum intervals", 1000, 500);
1570 gPad->SetGrid();
1571
1572 int bin_count = fIntervalCount, cur_hist = 0;
1573 // create histograms for particle multiplicities
1574 map<int, TH1I*> mapParticleNumberHists;
1575 for (int particle_number : fParticles) {
1576 TH1I* hParticleNumber =
1577 new TH1I(TString::Format("hParticleNumber_%d", particle_number),
1578 TString::Format("hParticleNumber_%d", particle_number), bin_count, fPMin, fPMax);
1579 // gStyle->SetTitleFontSize(0.03); // only one approach to change histogram title size
1580 hParticleNumber->SetStats(kFALSE);
1581 hParticleNumber->LabelsDeflate();
1582 hParticleNumber->SetLabelFont(62);
1583 hParticleNumber->SetMarkerSize(0.6);
1584 // hParticleNumber->LabelsOption("v");
1585 hParticleNumber->SetTitle("");
1586 hParticleNumber->GetXaxis()->SetTitle("momentum interval");
1587 hParticleNumber->GetXaxis()->SetTitleSize(0.03);
1588 hParticleNumber->GetXaxis()->CenterTitle();
1589 hParticleNumber->GetXaxis()->SetLabelSize(0.02 - 0.00017 * bin_count);
1590 // hParticleNumber->GetXaxis()->CenterLabels();
1591 hParticleNumber->GetXaxis()->SetNdivisions(bin_count);
1592 hParticleNumber->GetYaxis()->SetTitle("particle multiplicity");
1593 // hParticleNumber->GetYaxis()->SetTitleSize(0.03);
1594 // hParticleNumber->GetYaxis()->SetNdivisions(1007);
1595 hParticleNumber->GetYaxis()->SetLabelSize(0.02);
1596 hParticleNumber->SetBarWidth(0.18);
1597 hParticleNumber->SetBarOffset(0.05 + cur_hist * 0.18);
1598 switch (cur_hist) {
1599 case 0:
1600 hParticleNumber->SetFillColor(49);
1601 break;
1602 case 1:
1603 hParticleNumber->SetFillColor(50);
1604 break;
1605 case 2:
1606 hParticleNumber->SetFillColor(42);
1607 break;
1608 case 3:
1609 hParticleNumber->SetFillColor(29);
1610 break;
1611 case 4:
1612 hParticleNumber->SetFillColor(36);
1613 break;
1614 default:
1615 break;
1616 }
1617
1618 mapParticleNumberHists[particle_number] = hParticleNumber;
1619 cur_hist++;
1620 }
1621
1622 // create histograms for P, Pt, Y, Eta, Theta, Phi
1623 map<int, TH1D*> mapPHists, mapPtHists, mapYHists, mapEtaHists, mapThetaHists, mapPhiHists;
1624 for (int particle_number : fParticles) {
1625 TH1D* hP = new TH1D(TString::Format("hP_%d", particle_number), TString::Format("hP_%d", particle_number), 80,
1626 fPMin, fPMax);
1627 mapPHists[particle_number] = hP;
1628 TH1D* hPt = new TH1D(TString::Format("hPt_%d", particle_number), TString::Format("hPt_%d", particle_number), 60,
1629 fPtMin, fPtMax);
1630 mapPtHists[particle_number] = hPt;
1631 TH1D* hY = new TH1D(TString::Format("hY_%d", particle_number), TString::Format("hY_%d", particle_number), 60,
1632 fYMin, fYMax);
1633 mapYHists[particle_number] = hY;
1634 TH1D* hEta = new TH1D(TString::Format("hEta_%d", particle_number), TString::Format("hEta_%d", particle_number),
1635 60, fEtaMin, fEtaMax);
1636 mapEtaHists[particle_number] = hEta;
1637 TH1D* hTheta = new TH1D(TString::Format("hTheta_%d", particle_number),
1638 TString::Format("hTheta_%d", particle_number), 61, fThetaMin, fThetaMax);
1639 mapThetaHists[particle_number] = hTheta;
1640 TH1D* hPhi = new TH1D(TString::Format("hPhi_%d", particle_number), TString::Format("hPhi_%d", particle_number),
1641 64, fPhiMin, fPhiMax);
1642 mapPhiHists[particle_number] = hPhi;
1643 }
1644
1645 int iEventNumber = dataChain.GetEntries(), iCurrentEvent = 0;
1646 if (iEventNumber < 1) {
1647 cout << "ERROR: There are no correct entries in the file list stored at: \"" << input_list_file << "\"" << endl;
1648 return -6;
1649 }
1650
1651 // fill all the histograms
1652 for (iCurrentEvent = 0; iCurrentEvent < iEventNumber; iCurrentEvent++) {
1653 // Next Event
1654 if ((iCurrentEvent + 1) % 1000 == 0)
1655 LOG(info) << "Processing event #" << iCurrentEvent + 1 << " of " << iEventNumber;
1656 dataChain.GetEntry(iCurrentEvent);
1657
1658 Int_t track_count = 0;
1659 TClonesArray* mc_tracks = nullptr;
1660 if (isDST) {
1661 mc_tracks = mcInfo->GetMCTracks();
1662 track_count = globalTracks->GetEntries();
1663 } else {
1664 mc_tracks = mcTracks;
1665 track_count = mc_tracks->GetEntries();
1666 }
1667
1668 // Getting tracks of the event and their corresponding components
1669 for (Int_t iTrack = 0; iTrack < track_count; iTrack++) {
1670 Int_t track_pdg = -1;
1671 Double_t track_mass = -1; // real mass in GeV (an integer would truncate it to 0 and spoil the rapidity)
1672 Double_t track_p = 0, track_pt = 0, track_y = 0, track_eta = 0, track_theta = 0, track_phi = 0;
1673 CbmMCTrack* mc_track;
1674 BmnGlobalTrack* global_track;
1675
1676 if (isDST) {
1677 // cout<<"iTrack = "<<iTrack<<", track_count = "<<track_count<<endl;
1678 global_track = (BmnGlobalTrack*)globalTracks->UncheckedAt(iTrack);
1679 // the reconstructed tracks are taken in the same acceptance as the sample ones (see SetSampleTrackCut):
1680 // with the default cut only the tracks matched to a ToF hit, i.e. the ones identified by ToF
1681 Bool_t has_tof = (global_track->GetTof1HitIndex() > -1) || (global_track->GetTof2HitIndex() > -1);
1682 if ((fSampleTrackCut > 1) && (!has_tof))
1683 continue;
1684
1685 // define the particle type: by the related MC track, or by the ToF mass and the charge if the track has
1686 // no information on it
1687 BmnTrackMatch* global_match = ((globalMatches != nullptr) && (iTrack < globalMatches->GetEntriesFast()))
1688 ? (BmnTrackMatch*)globalMatches->UncheckedAt(iTrack)
1689 : nullptr;
1690 Double_t tof_mass = -1.;
1691 track_pdg = GetGlobalTrackPDG(global_track, global_match, mc_tracks, tof_mass);
1692 if (find(fParticles.begin(), fParticles.end(), track_pdg) == fParticles.end())
1693 continue;
1694 // the mass for the rapidity below: the measured one if the type is defined by it, the nominal otherwise
1695 if (tof_mass > 0)
1696 track_mass = tof_mass;
1697 else {
1698 TParticlePDG* particle = TDatabasePDG::Instance()->GetParticle(track_pdg);
1699 if (particle == nullptr)
1700 continue;
1701 track_mass = particle->Mass();
1702 }
1703
1704 // Getting track params predicted by Kalman filter ...
1705 FairTrackParam* parFirst = global_track->GetParamFirst();
1706 // FairTrackParam* parLast = track->GetParamLast();
1707 if (parFirst == nullptr)
1708 continue;
1709 Double_t track_qp = parFirst->GetQp();
1710 track_p = (TMath::Abs(track_qp) > 1.e-4) ? 1. / TMath::Abs(track_qp) : 1.e4;
1711 TVector3 vecMom;
1712 parFirst->Momentum(vecMom);
1713 track_pt = TMath::Sqrt(vecMom.x() * vecMom.x() + vecMom.y() * vecMom.y());
1714 Double_t e = TMath::Sqrt(track_p * track_p + track_mass * track_mass);
1715 track_y = 0.5 * TMath::Log((e + vecMom.z()) / (e - vecMom.z()));
1716 track_eta = 0.5 * TMath::Log((track_p + vecMom.z()) / (track_p - vecMom.z()));
1717 Double_t track_tx = parFirst->GetTx(), track_ty = parFirst->GetTy();
1718 track_phi = TMath::ATan2(track_ty, track_tx); // phi = arctan(tgy/tgx)
1719 // theta = arctan(sqrt(tgx^2 + tgy^2)): the equivalent 'arctan(tgx/cos(phi))' returns a negative angle
1720 // when cos(phi) is negative, and such values fall into the underflow bin of the Theta histogram
1721 track_theta = TMath::ATan(TMath::Sqrt(track_tx * track_tx + track_ty * track_ty));
1722 } // if (isDST)
1723 else
1724 {
1725 mc_track = (CbmMCTrack*)mc_tracks->At(iTrack);
1726 if (mc_track == nullptr)
1727 continue;
1728 if (mc_track->GetMotherId() >= 0)
1729 continue;
1730
1731 // filter particles
1732 if (fSampleTrackCut > 0) {
1733 // exclude tracks without points in Silicon and GEMs
1734 if ((mc_track->GetNPoints(kGEM) < 1) || (mc_track->GetNPoints(kSILICON) < 1))
1735 continue;
1736 if (fSampleTrackCut > 1) {
1737 // exclude tracks without points in ToF detectors. kTOF701 is not checked: CbmMCTrack keeps the
1738 // points of TOF700 in a single bit field, returned by GetNPoints() both for kTOF and for
1739 // kTOF701
1740 if ((mc_track->GetNPoints(kTOF) < 1) && (mc_track->GetNPoints(kTOF1) < 1))
1741 continue;
1742 }
1743 }
1744
1745 track_pdg = mc_track->GetPdgCode();
1746 track_p = mc_track->GetP();
1747 track_pt = mc_track->GetPt();
1748 track_y = mc_track->GetRapidity();
1749 track_eta = 0.5 * TMath::Log((track_p + mc_track->GetPz()) / (track_p - mc_track->GetPz()));
1750 track_theta = TMath::ATan2(track_pt, mc_track->GetPz());
1751 track_phi = TMath::ATan2(mc_track->GetPy(), mc_track->GetPx());
1752 } // if not (isDST)
1753
1754 auto iter_pdg = find(fParticles.begin(), fParticles.end(), track_pdg);
1755 if (iter_pdg == fParticles.end())
1756 continue;
1757
1758 if ((track_p >= fPMin) && (track_p < fPMax)) {
1759 mapParticleNumberHists[*iter_pdg]->Fill(track_p);
1760 mapPHists[*iter_pdg]->Fill(track_p);
1761 mapPtHists[*iter_pdg]->Fill(track_pt);
1762 mapYHists[*iter_pdg]->Fill(track_y);
1763 mapEtaHists[*iter_pdg]->Fill(track_eta);
1764 mapThetaHists[*iter_pdg]->Fill(track_theta);
1765 mapPhiHists[*iter_pdg]->Fill(track_phi);
1766 }
1767 } // for (Int_t iTrack = 0; iTrack < track_count; iTrack++)
1768 } // for (Int_t iEvent = 0; iEvent < iEventNumber; iEvent++) {
1769
1770 // if no required branches in the tree or events then clear memory and exit
1771 if (iCurrentEvent == 0) {
1772 cout << "ERROR: There are no necessary tree branches or DST events in the files from the list: "
1773 << input_list_file << endl;
1774 for (const auto& pair : mapPHists)
1775 delete pair.second;
1776 for (const auto& pair : mapPtHists)
1777 delete pair.second;
1778 for (const auto& pair : mapYHists)
1779 delete pair.second;
1780 for (const auto& pair : mapEtaHists)
1781 delete pair.second;
1782 for (const auto& pair : mapThetaHists)
1783 delete pair.second;
1784 for (const auto& pair : mapPhiHists)
1785 delete pair.second;
1786 delete c_number;
1787 return -7;
1788 }
1789
1790 // find max Y-axis value
1791 Double_t max_y_value = 0;
1792 for (const auto& pair_hist : mapParticleNumberHists) {
1793 if (max_y_value < pair_hist.second->GetMaximum())
1794 max_y_value = pair_hist.second->GetMaximum();
1795 }
1796
1797 // draw bar histogram with particle multiplicity for all the particle types
1798 TLegend* legend = new TLegend(0.7, 0.74, 0.85, 0.88);
1799 TString hist_options = "bar2,hist,text"; // 'text' option for vertical captions with event count
1800 cur_hist = 0;
1801 for (const auto& pair_hist : mapParticleNumberHists) {
1802 if (cur_hist == 0) {
1803 if (pair_hist.second->GetMaximum() < max_y_value)
1804 pair_hist.second->SetMaximum(max_y_value + 10);
1805 pair_hist.second->DrawCopy(hist_options);
1806 } else {
1807 pair_hist.second->DrawCopy(hist_options + ",same");
1808 }
1809
1810 legend->AddEntry(pair_hist.second, TString::Format("Multiplitcity for %d", pair_hist.first), "f");
1811 legend->Draw();
1812 cur_hist++;
1813 }
1814
1815 // draw histograms (P, Pt, Y, Eta, Theta, Phi) for different particle types
1816 TCanvas* c_p = new TCanvas("c_p", "Particle Momentum", 1000, 500);
1817 c_p->Divide((fParticles.size() / 3) + 1, 3);
1818 cur_hist = 1;
1819 for (int particle_number : fParticles) {
1820 c_p->cd(cur_hist);
1821 mapPHists[particle_number]->Draw();
1822 cur_hist++;
1823 }
1824 TCanvas* c_pt = new TCanvas("c_pt", "Transverse Momentum", 1000, 500);
1825 c_pt->Divide((fParticles.size() / 3) + 1, 3);
1826 cur_hist = 1;
1827 for (int particle_number : fParticles) {
1828 c_pt->cd(cur_hist);
1829 mapPtHists[particle_number]->Draw();
1830 cur_hist++;
1831 }
1832 TCanvas* c_y = new TCanvas("c_y", "Rapidity", 1000, 500);
1833 c_y->Divide((fParticles.size() / 3) + 1, 3);
1834 cur_hist = 1;
1835 for (int particle_number : fParticles) {
1836 c_y->cd(cur_hist);
1837 mapYHists[particle_number]->Draw();
1838 cur_hist++;
1839 }
1840 TCanvas* c_eta = new TCanvas("c_eta", "Pseudorapidity", 1000, 500);
1841 c_eta->Divide((fParticles.size() / 3) + 1, 3);
1842 cur_hist = 1;
1843 for (int particle_number : fParticles) {
1844 c_eta->cd(cur_hist);
1845 mapEtaHists[particle_number]->Draw();
1846 cur_hist++;
1847 }
1848 TCanvas* c_theta = new TCanvas("c_theta", "Theta angle", 1000, 500);
1849 c_theta->Divide((fParticles.size() / 3) + 1, 3);
1850 cur_hist = 1;
1851 for (int particle_number : fParticles) {
1852 c_theta->cd(cur_hist);
1853 mapThetaHists[particle_number]->Draw();
1854 cur_hist++;
1855 }
1856 TCanvas* c_phi = new TCanvas("c_phi", "Phi Angle", 1000, 500);
1857 c_phi->Divide((fParticles.size() / 3) + 1, 3);
1858 cur_hist = 1;
1859 for (int particle_number : fParticles) {
1860 c_phi->cd(cur_hist);
1861 mapPhiHists[particle_number]->Draw();
1862 cur_hist++;
1863 }
1864
1865 return 0;
1866}
static Bool_t FitMomentumFunction(TH1D *hP, const TString &func_name, Double_t p_min, Double_t p_max)
static const int kIntervalPhiBinCount
Cut applied to the tracks of the sample (see SetSampleTrackCut).
static const char * kEfficiencyNamePrefix
static Double_t fThetaMax
static Double_t fYMin
static Double_t fPtMin
static Double_t fYMax
static Double_t fPhiMin
static Double_t fEtaMax
static const char * kMomentumGridName
static Double_t fPtMax
static const int kMomentumGridSize
static Double_t fEtaMin
static Double_t fPhiMax
static Double_t fThetaMin
int i
Definition P4_F32vec4.h:22
bool Bool_t
int Int_t
constexpr Bool_t kFALSE
double Double_t
constexpr Bool_t kTRUE
R__EXTERN TSystem * gSystem
TVectorT< Double_t > TVectorD
#define gPad
@ kSILICON
@ kGEM
@ kTOF
@ kTOF1
BmnGlobalTrack.h.
Int_t GetTof2HitIndex() const
Int_t GetTof1HitIndex() const
Float_t GetMass2(Int_t tofID) const
TClonesArray * GetMCTracks() const
rot. around z-axis [rad]
const BmnLink & GetMatchedLink() const
Definition BmnMatch.h:40
Int_t GetNofLinks() const
Definition BmnMatch.h:41
static int FitPHistogram(unique_ptr< TH1D > &hP, int pdg_key, Double_t p_min, Double_t p_max)
static int ProduceEfficiencyMap(TString input_list_file, TString histo_file="$VMCWORKDIR/macro/recotools/particle_hists.root")
Bool_t fUseSampleStatisticsCut
Whether the efficiency map has been found in the sample histogram file.
static Int_t GetPDGCodeByMass(Double_t mass, Double_t tolerance=1.e-6, const vector< int > &vecParticlePDG={})
static Int_t fSampleTrackCut
Cut applied to the tracks of the sample (see SetSampleTrackCut).
static int FitYHistogram(unique_ptr< TH1D > &hY, int pdg_key, Double_t &y0_output, Double_t &sigma_putput)
Double_t fMinEfficiency
Whether the added particle number is corrected by the efficiency.
static int ShowSampleHistograms(TString input_histo_file="$VMCWORKDIR/macro/recotools/particle_hists.root")
static Int_t GetGlobalTrackPDG(BmnGlobalTrack *global_track, BmnTrackMatch *global_match, TClonesArray *mc_tracks, Double_t &tof_mass)
static int ShowResultDistributions(TString input_list_file)
static Double_t fPMin
Min value of momentum.
Bool_t fUseEfficiencyCorrection
Target particle number per interval, 0 = maximum of the event (see the setter).
static Bool_t CheckMomentumGrid(TFile *hist_file)
Bool_t fIsEfficiencyMapRead
Lower limit of the efficiency used for the correction.
static void FitSampleHistograms(TString input_histo_file="$VMCWORKDIR/macro/recotools/particle_hists.root")
additional functions to check results of fitting and particle equalizing
static void DefineNearestFilledIntervals(const vector< unique_ptr< TH1D > > &hist_vector, vector< Int_t > &index_vector)
static int ProduceSampleHistograms(TString input_list_file, TString output_histo_file="$VMCWORKDIR/macro/recotools/particle_hists.root")
Bool_t ReadSampleHistograms(TString input_histo_file="$VMCWORKDIR/macro/recotools/particle_hists.root")
Bool_t SetEfficiencyCorrection(Bool_t is_used, Double_t min_efficiency=0.05)
Switch on the correction of the number of the added particles by the reconstruction efficiency (off b...
static int FitEtaHistogram(unique_ptr< TH1D > &hEta, int pdg_key, Double_t eta_min, Double_t eta_max)
static Int_t fIntervalCount
Interval count for momentum to equalize particles at all the intervals.
static Double_t fPMax
Max value of momentum.
static vector< Int_t > fParticles
Bool_t isActive
Interval step.
map< Int_t, unique_ptr< stParticleInfo > > fParticleInfos
static int FitPtHistogram(unique_ptr< TH1D > &hPt, int pdg_key, Double_t pdg_mass, Double_t pt_min, Double_t pt_max, Double_t &t_output)
BmnParticleEqualizer1D(TString hist_file_name)
Constructor with input histograms.
virtual Bool_t ReadEvent(FairPrimaryGenerator *primGen)
Creates an event with equal number of particles at all momentum intervals.
FairTrackParam * GetParamFirst()
Definition BmnTrack.h:63
CbmMCTrack.h.
Definition CbmMCTrack.h:26
Double_t GetPy() const
Definition CbmMCTrack.h:59
Long64_t GetNPoints(DetectorId detId) const
Accessors to the number of MCPoints in the detectors.
Double_t GetPt() const
Definition CbmMCTrack.h:67
Double_t GetPz() const
Definition CbmMCTrack.h:60
Double_t GetPx() const
Definition CbmMCTrack.h:58
Double_t GetRapidity() const
Int_t GetMotherId() const
Definition CbmMCTrack.h:57
Double_t GetP() const
Definition CbmMCTrack.h:68
Int_t GetPdgCode() const
Accessors.
Definition CbmMCTrack.h:56
CbmStack.h.
Definition CbmStack.h:43
TClonesArray * GetListOfParticles()
Definition CbmStack.h:191
bool IsValid() const
int Index(const std::string &name) const
double Parameter(unsigned int i) const
static void SetDefaultMinimizer(const char *type, const char *algo=nullptr)
static const std::string & DefaultMinimizerType()
virtual void SetLabelSize(Float_t size=0.04)
virtual void SetTitleSize(Float_t size=0.04)
virtual void SetNdivisions(Int_t n1, Int_t n2, Int_t n3, Bool_t optim=kTRUE)
virtual void SetFillColor(Color_t fcolor)
virtual void SetMarkerSize(Size_t msize=1)
virtual Double_t GetBinCenter(Int_t bin) const
virtual Double_t GetBinLowEdge(Int_t bin) const
void CenterTitle(Bool_t center=kTRUE)
virtual Double_t GetBinUpEdge(Int_t bin) const
TVirtualPad * cd(Int_t subpadnumber=0) override
Int_t SetBranchAddress(const char *bname, T **add, TBranch **ptr=nullptr)
virtual Int_t AddFile(const char *name, Long64_t nentries=TTree::kMaxEntries, const char *tname="")
TBranch * GetBranch(const char *name) override
virtual Int_t Add(const char *name, Long64_t nentries=TTree::kMaxEntries)
void SetBranchStatus(const char *bname, bool status=true, UInt_t *found=nullptr) override
Long64_t GetEntries() const override
Int_t GetEntry(Long64_t entry=0, Int_t getall=0) override
static TDatabasePDG * Instance()
const THashList * ParticleList() const
TParticlePDG * GetParticle(const char *name) const
T * Get(const char *namecycle)
virtual void ReleaseParameter(Int_t ipar)
virtual Double_t GetParameter(const TString &name) const
virtual void SetRange(Double_t xmin, Double_t xmax)
virtual void SetNpx(Int_t npx=100)
void Draw(Option_t *option="") override
virtual void SetParLimits(Int_t ipar, Double_t parmin, Double_t parmax)
virtual Double_t GetRandom(Double_t xmin, Double_t xmax, TRandom *rng=nullptr, Option_t *opt=nullptr)
virtual Double_t Eval(Double_t x, Double_t y=0, Double_t z=0, Double_t t=0) const
virtual Double_t GetMaximum(Double_t xmin=0, Double_t xmax=0, Double_t epsilon=1.E-10, Int_t maxiter=100, Bool_t logx=false) const
virtual void SetParameter(const TString &name, Double_t value)
virtual void FixParameter(Int_t ipar, Double_t value)
virtual Int_t GetNpx() const
virtual Bool_t IsOpen() const
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
void Close(Option_t *option="") override
virtual void SetLabelFont(Style_t font=62, Option_t *axis="X")
virtual void SetBarOffset(Float_t offset=0.25)
void SetTitle(const char *title) override
TAxis * GetXaxis()
virtual TFitResultPtr Fit(const char *formula, Option_t *option="", Option_t *goption="", Double_t xmin=0, Double_t xmax=0)
virtual Int_t GetNbinsX() const
TAxis * GetYaxis()
void Draw(Option_t *option="") override
Double_t GetRMS(Int_t axis=1) const
virtual TF1 * GetFunction(const char *name) const
virtual Double_t Integral(Int_t binx1, Int_t binx2, Option_t *option="") const
virtual Int_t GetMaximumBin() const
virtual Double_t GetBinContent(Int_t bin) const
virtual Double_t GetBinWidth(Int_t bin) const
virtual Int_t FindBin(Double_t x, Double_t y=0, Double_t z=0)
virtual void LabelsDeflate(Option_t *axis="X")
virtual void SetBarWidth(Float_t width=0.5)
virtual void SetStats(Bool_t stats=kTRUE)
void Draw(Option_t *option="") override
TLegendEntry * AddEntry(const char *name, const char *label="", Option_t *option="lpf")
virtual void SetTitle(const char *title="")
const char * GetName() const override
Int_t GetEntriesFast() const
TObject * Last() const override
Int_t GetEntries() const override
TObject * At(Int_t idx) const override
TObject * UncheckedAt(Int_t i) const
void SetBit(UInt_t f)
R__ALWAYS_INLINE Bool_t IsZombie() const
void ResetBit(UInt_t f)
void Divide(Int_t nx=1, Int_t ny=1, Float_t xmargin=0.01, Float_t ymargin=0.01, Int_t color=0) override
Int_t PdgCode() const
Double_t Charge() const
Double_t Mass() const
Double_t Px() const
Double_t Py() const
Int_t GetPdgCode() const
Double_t P() const
Bool_t IsPrimary() const
Double_t Eta() const
const char * Data() const
static TString Format(const char *fmt,...)
Double_t Z() const
void SetXYZ(Double_t x, Double_t y, Double_t z)
Double_t Y() const
Double_t X() const
TVector3 Unit() const
Double_t z() const
Double_t x() const
Double_t y() const
void RotateUz(const TVector3 &)
void Draw(Option_t *option="") override=0
Double_t Min(Double_t a, Double_t b)
Double_t CosH(Double_t)
Double_t ACos(Double_t)
Int_t Nint(T x)
Double_t Floor(Double_t x)
Double_t ATan(Double_t)
Int_t Finite(Double_t x)
Double_t ATan2(Double_t y, Double_t x)
Double_t Log(Double_t x)
Double_t Sqrt(Double_t x)
Double_t Cos(Double_t)
Bool_t AreEqualAbs(Double_t af, Double_t bf, Double_t epsilon)
Double_t Abs(Double_t d)
Double_t Sin(Double_t)
Double_t Max(Double_t a, Double_t b)
Double_t Tan(Double_t)
Double_t Range(Double_t lb, Double_t ub, Double_t x)
Double_t SinH(Double_t)
vector< Double_t > fEfficiency
Relative reconstruction efficiency of the particle type, 1 for all the intervals if the efficiency ma...
vector< EPSampling > fPSampling
Momentum sampling way for the particle type (divided by momentum intervals -> vector).
vector< Double_t > fParticleDebt
Part of the target number of the particles that has not been added yet, kept between the events so th...
stParticleInfo(int pdg_key, Double_t min_p, Double_t max_p, Int_t interval_count)
vector< unique_ptr< TH1D > > fEtaHistVector
Eta histograms for the particle type (divided by momentum intervals -> vector).
Double_t fT
Temperature in the P distribution.
vector< Int_t > fParticleNumber
PDG -> Particle Count (divided by momentum intervals -> vector).
Double_t fSigma
Sigma in the rapidity distribution.
vector< Int_t > fEtaHistIndex
Index of the momentum interval whose Eta histogram is used to sample the pseudorapidity for a given i...
@ kNone
the interval cannot be sampled at all, new particles are not added there
@ kIntervalHist
from the P histogram of the interval itself (there is enough sample statistics)
@ kFitFunction
from the fitted function of the full P histogram (too little sample statistics)
vector< unique_ptr< TH1D > > fPHistVector
P histograms for the particle type (divided by momentum intervals -> vector).
vector< Int_t > fPhiHistIndex
The same for the azimuth angle: index of the momentum interval whose Phi histogram is used.
vector< unique_ptr< TH1D > > fPhiHistVector
Phi histograms for the particle type (divided by momentum intervals -> vector).