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