BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
BmnGemStripModule.cxx
Go to the documentation of this file.
1#include "BmnGemStripModule.h"
2
3#include "BmnGemStripDigitizer.h" //AZ-110124
4#include "CbmMCTrack.h" //AZ-110124
5#include "CbmStsPoint.h" //AZ-110124
6#include "FairLogger.h"
7#include "FairRunSim.h" //AZ-110124
8#include "TF1.h"
9#include "TH1F.h"
10#include "TRandom.h"
11
12#include <TDatabasePDG.h> //AZ-110124
13#include <TParticlePDG.h> //AZ-110124
14#include <algorithm>
15#include <atomic> // [PARALLEL-STD]
16#include <condition_variable> // [PARALLEL-STD]
17#include <functional> // [PARALLEL-STD]
18#include <limits> // [PARALLEL-STD]
19#include <map>
20#include <mutex>
21#include <random> // [PARALLEL-STD]
22#include <thread> // [PARALLEL-STD]
23
24// ANN models: own inference (MLP) and frugally-deep (fdeep is used only in BmnGemStripFdeepModel.cxx)
26#include "BmnGemStripMLPModel.h"
27//------------------------------------------------------------------------------
28
29// ANN generation of the clusters: static bool for all modules
31
32// Path to an ANN model
34{
35 BmnGemStripMLPModel::SetDefaultPath(path.Data()); // "" -> the default path of the MLP model
36}
37
42
44{
45 Verbosity = false;
46
48
49 DriftGapThickness = 0.3; // cm
50 FirstTransferGapThickness = 0.25; // cm
52 InductionGapThickness = 0.15; // cm
55
57
58 XMinModule = 0.0;
59 XMaxModule = 0.0;
60 YMinModule = 0.0;
61 YMaxModule = 0.0;
62
64 MaxClustersPerCm = 0.0; // no limit
65 FillGenerationData = false; // no generation data in the strip methods (see SetFillGenerationData)
66 AvalancheRadius = 0.10; // cm
67 Gain = 1.0; // gain level (for each electron signal - in RealPointFull or for strip signal - in RealPointFullOne)
68}
69
71 ElectronDriftDirectionInModule edrift_direction,
72 Double_t DriftGap,
73 Double_t FTransferGap,
74 Double_t STransferGap,
75 Double_t InductionGap)
76{
77 Verbosity = false;
78
79 ZStartModulePosition = z_start_pos;
80
81 DriftGapThickness = DriftGap; // cm
82 FirstTransferGapThickness = FTransferGap; // cm
83 SecondTransferGapThickness = STransferGap; // cm
84 InductionGapThickness = InductionGap; // cm
87
88 ElectronDriftDirection = edrift_direction;
89
90 XMinModule = 0.0;
91 XMaxModule = 0.0;
92 YMinModule = 0.0;
93 YMaxModule = 0.0;
94
96 MaxClustersPerCm = 0.0; // no limit
97 FillGenerationData = false; // no generation data in the strip methods (see SetFillGenerationData)
98 AvalancheRadius = 0.10; // cm
99 Gain = 1.0;
100}
101
103
104//------------------------------------------------------------------------------
105
114
116{
117 StripLayers.push_back(strip_layer);
118
120}
121
123{
124 if (layer_num >= 0 && layer_num < (int)StripLayers.size()) {
125 return StripLayers[layer_num].SetStripSignal(strip_num, signal);
126 }
127 return false;
128}
129
131{
132 if (layer_num >= 0 && layer_num < (int)StripLayers.size()) {
133 return StripLayers[layer_num].AddStripSignal(strip_num, signal);
134 }
135 return false;
136}
137
139{
140 if (layer_num >= 0 && layer_num < (int)StripLayers.size()) {
141 return StripLayers[layer_num].SetStripMatch(strip_num, mc_match);
142 }
143 return false;
144}
145
147{
148 if (layer_num >= 0 && layer_num < (int)StripLayers.size()) {
149 return StripLayers[layer_num].SetStripDigitNumberMatch(strip_num, digit_num_match);
150 }
151 return false;
152}
153
155{
156 if (layer_num >= 0 && layer_num < (int)StripLayers.size()) {
157 return StripLayers[layer_num].GetStripSignal(strip_num);
158 }
159 return -1;
160}
161
163{
164 if (layer_num >= 0 && layer_num < (int)StripLayers.size()) {
165 return StripLayers[layer_num].GetStripMatch(strip_num);
166 }
167 return BmnMatch();
168}
169
171{
172 if (layer_num >= 0 && layer_num < (int)StripLayers.size()) {
173 return StripLayers[layer_num].GetStripDigitNumberMatch(strip_num);
174 }
175 return BmnMatch();
176}
177
179{
180 for (size_t ilayer = 0; ilayer < StripLayers.size(); ++ilayer) {
181 StripLayers[ilayer].ResetLayer();
182 }
183
185
189
191}
192
194{
195 for (size_t ilayer = 0; ilayer < StripLayers.size(); ++ilayer) {
196 if (StripLayers[ilayer].IsPointInsideStripLayer(x, y))
197 return true;
198 }
199 return false;
200}
201
203{
204 // old condition: if( z >= ZStartModulePosition && z <= (ZStartModulePosition+ModuleThickness) )
205 Double_t coord_eps = 0.01; // 100 um
206 if (((z > ZStartModulePosition) || (fabs(z - ZStartModulePosition) < coord_eps))
208 || (fabs(z - (ZStartModulePosition + ModuleThickness))) < coord_eps))
209 {
210 if (IsPointInsideModule(x, y))
211 return true;
212 }
213 return false;
214}
215
217{
218 Double_t coord_eps = 0.01; // 100 um
219 if (((z > ZStartModulePosition) || (fabs(z - ZStartModulePosition) < coord_eps))
221 || (fabs(z - (ZStartModulePosition + ModuleThickness))) < coord_eps))
222 {
223 return true;
224 }
225 return false;
226}
227
228// Window size of a strip ANN model: odd (a center strip) and >= 3. Empty string - OK, otherwise - the reason
229static std::string CheckANNStripWindowSize(Int_t n_out)
230{
231 if (n_out < 3 || n_out % 2 == 0)
232 return "model output size " + std::to_string(n_out) + ": the strip window must be odd and >= 3";
233 return "";
234}
235
236// Center point of the ANN strip window: the point of the track segment (entry (x, y, z) -> exit (x_out, y_out, z_out))
237// at the depth of the middle of the drift gap, where most of the signal comes from (the electrons of the drift gap
238// pass all three GEM cascades). The drift gap is at the cathode side: at the module start for the forward drift
239// and at the module end for the backward one. The Lorentz shift is not included (the ANN learns it from bfield).
240// Tracks that do not reach this depth (they start/stop inside the module or leave it through a side): the nearest
241// end of the segment. The same point for pz > 0 and pz < 0 (only the segment matters).
243 Double_t y,
244 Double_t z,
245 Double_t x_out,
246 Double_t y_out,
247 Double_t z_out,
248 Double_t& x_c,
249 Double_t& y_c) const
250{
251 // distance from the middle of the drift gap to the readout plane
252 const Double_t zdist_mid =
255 ? ZStartModulePosition + ModuleThickness - zdist_mid // readout at the module end
256 : ZStartModulePosition + zdist_mid; // readout at the module start
257 Double_t t = (TMath::Abs(z_out - z) > 1e-9) ? (z_mid - z) / (z_out - z) : 0.0;
258 t = TMath::Max(0.0, TMath::Min(1.0, t));
259 x_c = x + t * (x_out - x);
260 y_c = y + t * (y_out - y);
261}
262//------------------------------------------------------------------------------
263
264// Universal method: the standard method or the ANN one (see SetUseANNGeneration())
266 Double_t y,
267 Double_t z,
268 Double_t px,
269 Double_t py,
270 Double_t pz,
271 Double_t signal,
272 Int_t refID)
273{
275 return AddRealPointFull(x, y, z, px, py, pz, signal, refID);
276 else
277 return AddRealPointFull_ANNStripGenerator_MLP_FastStand(x, y, z, px, py, pz, signal, refID, 0, -1);
278}
279//------------------------------------------------------------------------------
280
281// Add point with full realistic behaviour (tracking through the module, avalanche effects)
283 Double_t y,
284 Double_t z,
285 Double_t px,
286 Double_t py,
287 Double_t pz,
288 Double_t signal,
289 Int_t refID)
290{
291
292 // Condition: a start point is inside the module and outside its dead zone (in strip layers)
293 if (IsPointInsideModule(x, y)) {
294
295 // AZ-260322 gRandom->SetSeed(AvalancheGenerationSeed);
296
297 // AZ-110124 - simulation of ionization clusters
298 BmnGemStripDigitizer* digi = (BmnGemStripDigitizer*)FairRunSim::Instance()->GetTask("GemDigitizer");
299 TClonesArray* points = digi->GetMCPoints();
300 TClonesArray* mctracks = digi->GetMCTracks();
301 CbmStsPoint* point = (CbmStsPoint*)points->UncheckedAt(refID);
302 TVector3 mom;
303 point->Momentum(mom);
304 CbmMCTrack* mcTr = (CbmMCTrack*)mctracks->UncheckedAt(point->GetTrackID());
306 Double_t mnoc = 1.0, mass = 3.7283999; // He4
307 if (part)
308 mass = part->Mass();
309 if (mass > 0.0001) {
310 TLorentzVector lorv;
311 lorv.SetVectM(mom, mass);
312 Double_t beta = lorv.Beta();
313 Double_t gamma = lorv.Gamma();
314 Double_t charge = (part) ? part->Charge() / 3 : 1;
315 mnoc = GetNumberOfClusters(beta, gamma, charge, 1.787, 12.33);
316 if (mnoc < 0.5)
317 return kFALSE;
318 // cout << " ***clus*** " << mass << " " << beta << " " << gamma << " " << mnoc << endl;
319
320 // test couts
321 if (0) {
322 cout << "particle, pdg_code = " << part->PdgCode() << "\n";
323 cout << " px:py:pz = ( " << px << " : " << py << " : " << pz << " )\n";
324 cout << " mass = " << mass << "\n";
325 cout << " beta = " << beta << "\n";
326 cout << " gamma = " << gamma << "\n";
327 cout << " charge = " << charge << "\n";
328 cout << " mnoc = " << mnoc << "\n";
329 }
330 }
331 // AZ
332
333 // Distance from entry point (x,y,z) to exit point (along z-axis)
334 Double_t z_distance_from_in_to_out;
335
336 // Condition: track direction along z-axis
337 if (pz > 0.0) {
338 z_distance_from_in_to_out = ModuleThickness - (z - ZStartModulePosition);
339 } else {
340 z_distance_from_in_to_out = z - ZStartModulePosition;
341 }
342
343 // Condition: track distance along z-axis must be not zero
344 if (z_distance_from_in_to_out <= 0.0) {
345 LOG(warn) << "BmnGemStripModule::AddRealPointFull(): Point (" << x << " : " << y << " : " << z
346 << ") has a track with an incorrect length";
347 return false;
348 }
349
350 // Scale coefficient of the track vector (the path from entry point to exit point)
351 Double_t vector_coeff = fabs(z_distance_from_in_to_out / pz);
352
353 // Components of the track vector
354 Double_t x_vec_from_in_to_out = vector_coeff * px;
355 Double_t y_vec_from_in_to_out = vector_coeff * py;
356 Double_t z_vec_from_in_to_out = vector_coeff * pz;
357
358 // Exit point coordinates (x_out, y_out, z_out)
359 Double_t x_out = x + x_vec_from_in_to_out;
360 Double_t y_out = y + y_vec_from_in_to_out;
361 // Double_t z_out = z + z_vec_from_in_to_out;
362
363 // Check if the exit point is outside the module then calculate new x_out, y_out, z_out values
364 // Condition: x-coordinate of the exit point is inside the module
365 if (x_out < XMinModule || x_out > XMaxModule) {
366 if (x_out < XMinModule)
367 x_out = XMinModule;
368 if (x_out > XMaxModule)
369 x_out = XMaxModule;
370
371 x_vec_from_in_to_out = x_out - x;
372 vector_coeff = x_vec_from_in_to_out / px;
373
374 y_vec_from_in_to_out = vector_coeff * py;
375 z_vec_from_in_to_out = vector_coeff * pz;
376
377 y_out = y + y_vec_from_in_to_out;
378 // z_out = z + z_vec_from_in_to_out;
379 }
380 // Condition: y-coordinate of the exit point is inside the module
381 if (y_out < YMinModule || y_out > YMaxModule) {
382 if (y_out < YMinModule)
383 y_out = YMinModule;
384 if (y_out > YMaxModule)
385 y_out = YMaxModule;
386
387 y_vec_from_in_to_out = y_out - y;
388 vector_coeff = y_vec_from_in_to_out / py;
389
390 x_vec_from_in_to_out = vector_coeff * px;
391 z_vec_from_in_to_out = vector_coeff * pz;
392
393 x_out = x + x_vec_from_in_to_out;
394 // z_out = z + z_vec_from_in_to_out;
395 }
396 //----------------------------------------------------------------------
397
398 // Common track length (Fix: a square root!)
399 Double_t track_length =
400 std::sqrt((x_vec_from_in_to_out) * (x_vec_from_in_to_out) + (y_vec_from_in_to_out) * (y_vec_from_in_to_out)
401 + (z_vec_from_in_to_out) * (z_vec_from_in_to_out));
402
403 Double_t current_length = 0.0; // traversed length (counter)
404 Double_t current_length_ratio = 0.0; // ratio of the traversed length to common track length (counter)
405
406 // Int_t collisions_cnt = 0; //total collision counter
407 Double_t current_step = 0.0; // current distance between two collision points
408
409 // Collection of collision points
410 std::vector<CollPoint> collision_points;
411
412 while (current_length < track_length) {
413
414 // Reading the MCD value (mean collision distance = mean free flight path) [cm]
416 MCD = 1 / mnoc; // AZ-110124
417
418 current_step = gRandom->Exp(MCD);
419 current_length += current_step;
420
421 if (current_length > track_length)
422 break;
423
424 current_length_ratio = current_length / track_length;
425
426 Double_t current_x = x + current_length_ratio * x_vec_from_in_to_out;
427 Double_t current_y = y + current_length_ratio * y_vec_from_in_to_out;
428 Double_t current_z = z + current_length_ratio * z_vec_from_in_to_out;
429
430 collision_points.push_back(CollPoint(current_x, current_y, current_z));
431
432 // collisions_cnt++;
433 }
434
435 // Each level - distance to the readout plane
439 // Double_t level4 =
440 // InductionGapThickness+SecondTransferGapThickness+FirstTransferGapThickness+DriftGapThickness; // not used yet
441
442 // Mean electron shift along x-axis (under the influence of the Lorentz force)
443 Double_t xmean; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3)
444
445 // Sigma electron smearing
446 Double_t sigma; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3+ p4*x^4 + p5*x^5)
447
448 // cout << "$$$ BmnGemStripMedium::GetInstance().NameOfCurrentMedium = " <<
449 // BmnGemStripMedium::GetInstance().NameOfCurrentMedium << "\n"; //test cout
450
451 // Reading the medium configuration -------------------------------------
456
463 //----------------------------------------------------------------------
464
465 // Strip cluster in each strip layer
466 Int_t n_strip_layers = StripLayers.size();
467 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
468
469 // Double_t signal_coeff = signal*1E-5;
470
471 // Electron avalanche producing for each collision point at the track
472 for (size_t icoll = 0; icoll < collision_points.size(); ++icoll) {
473
474 Double_t xcoll = collision_points[icoll].x;
475 Double_t ycoll = collision_points[icoll].y;
476 Double_t zcoll = collision_points[icoll].z;
477
478 Double_t zdist; // current z-distance to the readout
479
480 // Find z-distance to the readout depending on the electron drift direction
482 zdist = (ModuleThickness + ZStartModulePosition) - zcoll;
483 } else {
484 zdist = zcoll - ZStartModulePosition;
485 }
486 //------------------------------------------------------------------
487
488 // polynomials
489 xmean = p0_xmean + p1_xmean * zdist + p2_xmean * zdist * zdist + p3_xmean * zdist * zdist * zdist;
490 sigma = p0_sigma + p1_sigma * zdist + p2_sigma * zdist * zdist + p3_sigma * zdist * zdist * zdist
491 + p4_sigma * zdist * zdist * zdist * zdist + p5_sigma * zdist * zdist * zdist * zdist * zdist;
492
493 // Number of electrons in the current collision
494 Int_t n_electrons_cluster = gRandom->Landau(1.027, 0.11);
495 if (n_electrons_cluster < 1)
496 n_electrons_cluster = 1; // min
497 if (n_electrons_cluster > 6)
498 n_electrons_cluster = 6; // max
499
500 for (Int_t ielectron = 0; ielectron < n_electrons_cluster; ++ielectron) {
501
502 // Electron gain in each GEM cascade
503 // Polya distribution is better, but Exponential is good too in our case
504 Double_t gain_gem1 =
505 gRandom->Exp(15); //...->Exp(V), where V is the mean value of the exponential distribution
506 Double_t gain_gem2 = gRandom->Exp(15);
507 Double_t gain_gem3 = gRandom->Exp(15);
508
509 if (gain_gem1 < 1.0)
510 gain_gem1 = 1.0;
511 if (gain_gem2 < 1.0)
512 gain_gem2 = 1.0;
513 if (gain_gem3 < 1.0)
514 gain_gem3 = 1.0;
515
516 int total_gain = 0;
517
518 if (zdist < level1) {
519 total_gain = 1.0;
520 }
521 if (zdist >= level1 && zdist < level2) {
522 total_gain = gain_gem3;
523 }
524 if (zdist >= level2 && zdist < level3) {
525 total_gain = gain_gem3 * gain_gem2;
526 }
527 if (zdist >= level3) {
528 total_gain = gain_gem3 * gain_gem2 * gain_gem1;
529 }
530
531 // Projection of the current electron on the readout (x,y-coordinates)
532 double x_readout, y_readout;
533
534 for (int igain = 0; igain < total_gain; ++igain) {
535
536 // x-shift of the electon depends on the electron drift direction
538 x_readout = gRandom->Gaus(xcoll + xmean, sigma);
539 } else {
540 x_readout = gRandom->Gaus(xcoll - xmean, sigma);
541 }
542
543 y_readout = gRandom->Gaus(ycoll, sigma);
544
545 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
546 // Condition: end electron position must be inside the layer
547 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_readout, y_readout))
548 continue;
549
550 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_readout, y_readout);
551 // AZ-100124 - account for strip width
552 Double_t dstrip = strip_pos - (int)strip_pos;
553 if (TMath::Abs(StripLayers[ilayer].GetAngleDeg()) < 1.0) {
554 // Vertical strips
555 // if (strip_pos - (int)strip_pos > 0.2) continue; // strip width 0.16 mm
556 if (dstrip >= 0.4 && dstrip < 0.6)
557 continue; //
558 } else {
559 // Rotated strips
560 // if (strip_pos - (int)strip_pos > 0.85) continue; // strip width 0.68 mm
561 if (dstrip >= 0.3 && dstrip < 0.7)
562 continue; //
563 }
564 // AZ
565
566 // Add strips with signals to lower and upper clusters
567 // StripLayers[ilayer].AddStripSignal((Int_t)strip_pos, 1.0); //Instead of 1.0 we can use Gain
568 // in future
569 cluster_layers[ilayer].AddStrip((Int_t)strip_pos, 1.0);
570 // cluster_layers[ilayer].AddStrip((Int_t)strip_pos, Gain*signal_coeff);
571 }
572 /*
573 //Electron points on the readout (testing) -------------------------------------
574 XElectronPos.push_back(x_readout);
575 YElectronPos.push_back(y_readout);
576 ElectronSignal.push_back(1.0);
577 //------------------------------------------------------------------------------
578 */
579 }
580 }
581 }
582
583 // Calculate cluster parameters -----------------------------------------
584 vector<Double_t> cluster_mean_position(n_strip_layers, 0.0);
585 vector<Double_t> cluster_total_signal(n_strip_layers, 0.0);
586
587 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
588
589 if (cluster_layers[ilayer].GetClusterSize() > 0) {
590 for (int i = 0; i < cluster_layers[ilayer].GetClusterSize(); ++i) {
591 cluster_mean_position[ilayer] += (cluster_layers[ilayer].Strips[i] + 0.5)
592 * cluster_layers[ilayer].Signals[i]; // as sum of all positions
593 cluster_total_signal[ilayer] += cluster_layers[ilayer].Signals[i];
594 }
595 cluster_mean_position[ilayer] /= cluster_total_signal[ilayer];
596 }
597
598 cluster_layers[ilayer].MeanPosition = cluster_mean_position[ilayer]; // mean cluster position
599 cluster_layers[ilayer].TotalSignal = cluster_total_signal[ilayer]; // total signal of the cluster
600 }
601
603 if (pz > 0.0) {
604 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
605 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
606 }
607 } else {
608 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
609 cluster_layers[ilayer].OriginPosition =
610 StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
611 }
612 }
613 } else {
614 if (pz > 0.0) {
615 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
616 cluster_layers[ilayer].OriginPosition =
617 StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
618 }
619 } else {
620 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
621 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
622 }
623 }
624 }
625
626 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
627 cluster_layers[ilayer].PositionResidual =
628 cluster_layers[ilayer].MeanPosition - cluster_layers[ilayer].OriginPosition;
629 }
630 //----------------------------------------------------------------------
631 /*
632 //Testing ----------------------------------------------------------------------
633 if(AddedClusters.size() == 0) {
634 for(Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
635 AddedClusters.push_back(vector<StripCluster>());
636 }
637 }
638
639 for(Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
640 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
641 }
642 //------------------------------------------------------------------------------
643 */
644 // Add the correct clusters to the strip layers -------------------------
645 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
646 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
647 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
648 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
649 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
650 }
651 }
652 //----------------------------------------------------------------------
653
654 // Fill strip matches ---------------------------------------------------
655 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
656 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
657 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
658 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
659 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
660 refID);
661 }
662 }
663 //----------------------------------------------------------------------
664
665 RealPointsX.push_back(x);
666 RealPointsY.push_back(y);
667 RealPointsXout.push_back(x_out);
668 RealPointsYout.push_back(y_out);
669 RealPointsMC.push_back(refID);
670
671 return true;
672 } else {
673 LOG(warn) << "BmnGemStripModule::AddRealPointFull(): Point (" << x << " : " << y << " : " << z
674 << ") is out of the readout plane or inside a dead zone";
675 return false;
676 }
677}
678// -----------------------------------------------------------------------------
679
680// ANN-based cluster generation: the model returns a 2D image of the signal around the MC point
682 Double_t y,
683 Double_t z,
684 Double_t px,
685 Double_t py,
686 Double_t pz,
687 Double_t signal,
688 Int_t refID,
689 Int_t pdg_code,
690 Double_t bfield,
691 Bool_t onlyStandard,
692 vector<Int_t> segs)
693{
694 Double_t maxThetaAngle = 50.0; // max absolute angle (deg) of track inclination from beam axis
695
696 // Model format: [px, py, pz, drift, bfield, s0..s15] -> image nx_bins x ny_bins in [-1, 1]
697 const BmnGemStripFdeepModel* ann_model = nullptr; // 2D fdeep model (taken below, only if the ANN is used)
698 const Int_t n_track_segments = 64; // track segmentation (SegmentedChargeDistribution)
699 const Int_t n_segs_features = 16; // NOTE: the first 16 of 64 segments (as before)
700 const Int_t n_ann_inputs = 5 + n_segs_features; // total number of input features
701 const Int_t nx_bins = 80, ny_bins = 80; // model output: image size
702 const Double_t dx_area = 1.25, dy_area = 1.25; // half-size of the image area (cm)
703
705
706 SegmentedChargeDistribution.assign(n_track_segments, 0);
707
708 // gRandom is not reseeded (as in AddRealPointFull): the seed of the run is set by the caller
709
710 // Condition: a start point is inside the module and outside its dead zone (in strip layers)
711 if (!IsPointInsideModule(x, y)) {
712 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
713 << ") is out of the readout plane or inside a dead zone";
714 return false;
715 }
716
717 TVector3 mom(px, py, pz);
719
720 Double_t mnoc = 1.0, mass = 3.7283999; // He4
721 if (part)
722 mass = part->Mass();
723 if (mass > 0.0001) {
724 TLorentzVector lorv;
725 lorv.SetVectM(mom, mass);
726 Double_t beta = lorv.Beta();
727 Double_t gamma = lorv.Gamma();
728 Double_t charge = (part) ? part->Charge() / 3 : 1;
729 mnoc = GetNumberOfClusters(beta, gamma, charge, 1.787, 12.33);
730 if (mnoc < 0.5)
731 return kFALSE;
732 if (MaxClustersPerCm > 0.0 && mnoc > MaxClustersPerCm)
733 mnoc = MaxClustersPerCm;
734 }
735
736 // Distance from entry point (x,y,z) to exit point (along z-axis)
737 Double_t z_distance_from_in_to_out;
738
739 // Condition: track direction along z-axis
740 if (pz > 0.0) {
741 z_distance_from_in_to_out = ModuleThickness - (z - ZStartModulePosition);
742 } else {
743 z_distance_from_in_to_out = z - ZStartModulePosition;
744 }
745
746 // Condition: track distance along z-axis must be not zero
747 if (z_distance_from_in_to_out <= 0.0) {
748 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
749 << ") has a track with an incorrect length";
750 return false;
751 }
752
753 // Scale coefficient of the track vector (the path from entry point to exit point)
754 Double_t vector_coeff = fabs(z_distance_from_in_to_out / pz);
755
756 // Components of the track vector
757 Double_t x_vec_from_in_to_out = vector_coeff * px;
758 Double_t y_vec_from_in_to_out = vector_coeff * py;
759 Double_t z_vec_from_in_to_out = vector_coeff * pz;
760
761 // Exit point coordinates (x_out, y_out, z_out)
762 Double_t x_out = x + x_vec_from_in_to_out;
763 Double_t y_out = y + y_vec_from_in_to_out;
764 Double_t z_out = z + z_vec_from_in_to_out;
765
766 // Check if the exit point is outside the module then calculate new x_out, y_out, z_out values
767 // Condition: x-coordinate of the exit point is inside the module
768 if (x_out < XMinModule || x_out > XMaxModule) {
769 if (x_out < XMinModule)
770 x_out = XMinModule;
771 if (x_out > XMaxModule)
772 x_out = XMaxModule;
773
774 x_vec_from_in_to_out = x_out - x;
775 vector_coeff = x_vec_from_in_to_out / px;
776
777 y_vec_from_in_to_out = vector_coeff * py;
778 z_vec_from_in_to_out = vector_coeff * pz;
779
780 y_out = y + y_vec_from_in_to_out;
781 z_out = z + z_vec_from_in_to_out;
782 }
783 // Condition: y-coordinate of the exit point is inside the module
784 if (y_out < YMinModule || y_out > YMaxModule) {
785 if (y_out < YMinModule)
786 y_out = YMinModule;
787 if (y_out > YMaxModule)
788 y_out = YMaxModule;
789
790 y_vec_from_in_to_out = y_out - y;
791 vector_coeff = y_vec_from_in_to_out / py;
792
793 x_vec_from_in_to_out = vector_coeff * px;
794 z_vec_from_in_to_out = vector_coeff * pz;
795
796 x_out = x + x_vec_from_in_to_out;
797 z_out = z + z_vec_from_in_to_out;
798 }
799 //----------------------------------------------------------------------
800
801 // Common track length
802 Double_t track_length =
803 std::sqrt((x_vec_from_in_to_out) * (x_vec_from_in_to_out) + (y_vec_from_in_to_out) * (y_vec_from_in_to_out)
804 + (z_vec_from_in_to_out) * (z_vec_from_in_to_out));
805
806 Double_t current_length = 0.0; // traversed length (counter)
807 Double_t current_length_ratio = 0.0; // ratio of the traversed length to common track length (counter)
808
809 Int_t collisions_cnt = 0; // total collision counter
810 Double_t current_step = 0.0; // current distance between two collision points
811
812 // Collection of collision points
813 std::vector<CollPoint> collision_points;
814
815 while (current_length < track_length) {
816
817 // Reading the MCD value (mean collision distance = mean free flight path) [cm]
819 MCD = 1 / mnoc; // AZ-110124
820
821 current_step = gRandom->Exp(MCD);
822 current_length += current_step;
823
824 if (current_length > track_length)
825 break;
826
827 current_length_ratio = current_length / track_length;
828
829 Double_t current_x = x + current_length_ratio * x_vec_from_in_to_out;
830 Double_t current_y = y + current_length_ratio * y_vec_from_in_to_out;
831 Double_t current_z = z + current_length_ratio * z_vec_from_in_to_out;
832
833 collision_points.push_back(CollPoint(current_x, current_y, current_z));
834
835 collisions_cnt++;
836 }
837
838 // Each level - distance to the readout plane
842
843 // Mean electron shift along x-axis (under the influence of the Lorentz force)
844 Double_t xmean; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3)
845
846 // Sigma electron smearing
847 Double_t sigma; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3+ p4*x^4 + p5*x^5)
848
849 // Reading the medium configuration -------------------------------------
854
861 //----------------------------------------------------------------------
862
863 Double_t r_vec = TMath::Sqrt(px * px + py * py + pz * pz);
864 Double_t theta_deg_abs = TMath::ACos(TMath::Abs(pz) / r_vec) * TMath::RadToDeg();
865
866 // fdeep model: loaded on the first request (not needed with onlyStandard), shared between modules;
867 // its input / output sizes are checked once for each model
868 std::shared_ptr<const BmnGemStripFdeepModel> ann_holder;
869 if (!onlyStandard) {
871 ann_model = ann_holder.get();
872
873 static std::mutex checked_mutex;
874 static std::map<const BmnGemStripFdeepModel*,
875 std::pair<std::shared_ptr<const BmnGemStripFdeepModel>, std::string>>
876 checked; // model -> (holder, why it is not suitable: empty if suitable)
877
878 std::lock_guard<std::mutex> lock(checked_mutex);
879 if (!ann_model) {
880 LOG(warn) << "BmnGemStripModule::" << __func__
881 << "(): fdeep model is not available, the standard method is used";
882 } else {
883 auto it = checked.find(ann_model);
884 if (it == checked.end()) {
885 const Int_t expected_out = nx_bins * ny_bins;
886 std::string why;
887 if ((ann_model->GetInputDim() != n_ann_inputs) || (ann_model->GetOutputDim() != expected_out))
888 why = "input " + std::to_string(ann_model->GetInputDim()) + ", expected "
889 + std::to_string(n_ann_inputs) + "; output " + std::to_string(ann_model->GetOutputDim())
890 + ", expected " + std::to_string(expected_out);
891 else
892 LOG(info) << "BmnGemStripModule::" << __func__ << "(): fdeep model " << ann_model->GetFilePath()
893 << " is used";
894 it = checked.emplace(ann_model, std::make_pair(ann_holder, why)).first;
895 }
896 if (!it->second.second.empty()) {
897 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): the model " << ann_model->GetFilePath()
898 << " is not suitable (" << it->second.second << "), the standard method is used";
899 ann_model = nullptr;
900 }
901 }
902 }
903
904 // Which method is used for this point: standard (electrons -> strips) or ANN (model -> strips)
905 // (without a loaded model - e.g. built without fdeep - the standard method is used)
906 const Bool_t useANN = (theta_deg_abs < maxThetaAngle) && !onlyStandard && ann_model;
907
908 // Initialization of a strip cluster in each strip layer
909 Int_t n_strip_layers = StripLayers.size(); // number of layers in the module
910 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
911
912 // Electron avalanche producing for each collision point at the track
913 // (in the ANN mode only the segmented charge distribution is filled here)
914 for (size_t icoll = 0; icoll < collision_points.size(); ++icoll) {
915
916 Double_t xcoll = collision_points[icoll].x;
917 Double_t ycoll = collision_points[icoll].y;
918 Double_t zcoll = collision_points[icoll].z;
919
920 Double_t zdist; // current z-distance to the readout
921
922 // Find z-distance to the readout depending on the electron drift direction
924 zdist = (ModuleThickness + ZStartModulePosition) - zcoll;
925 } else {
926 zdist = zcoll - ZStartModulePosition;
927 }
928 //------------------------------------------------------------------
929
930 // polynomials
931 xmean = p0_xmean + p1_xmean * zdist + p2_xmean * zdist * zdist + p3_xmean * zdist * zdist * zdist;
932 sigma = p0_sigma + p1_sigma * zdist + p2_sigma * zdist * zdist + p3_sigma * zdist * zdist * zdist
933 + p4_sigma * zdist * zdist * zdist * zdist + p5_sigma * zdist * zdist * zdist * zdist * zdist;
934
935 // Number of electrons in the current collision
936 Int_t n_electrons_cluster = gRandom->Landau(1.027, 0.11);
937 if (n_electrons_cluster < 1)
938 n_electrons_cluster = 1; // min
939 if (n_electrons_cluster > 6)
940 n_electrons_cluster = 6; // max
941
942 // Filling charge distribution -------------------------------------
943 Double_t dist_from_start = 0.0;
944 if (pz > 0.0) {
945 dist_from_start =
946 std::sqrt((xcoll - x) * (xcoll - x) + (ycoll - y) * (ycoll - y) + (zcoll - z) * (zcoll - z));
947 } else {
948 dist_from_start = std::sqrt((xcoll - x_out) * (xcoll - x_out) + (ycoll - y_out) * (ycoll - y_out)
949 + (zcoll - z_out) * (zcoll - z_out));
950 }
951 Int_t segment = (Int_t)(dist_from_start / track_length * n_track_segments);
952 if (segment >= n_track_segments)
953 segment = n_track_segments - 1;
954 //------------------------------------------------------------------
955
956 for (Int_t ielectron = 0; ielectron < n_electrons_cluster; ++ielectron) {
957
958 // Electron gain in each GEM cascade
959 Double_t gain_gem1 = gRandom->Exp(15);
960 Double_t gain_gem2 = gRandom->Exp(15);
961 Double_t gain_gem3 = gRandom->Exp(15);
962
963 if (gain_gem1 < 1.0)
964 gain_gem1 = 1.0;
965 if (gain_gem2 < 1.0)
966 gain_gem2 = 1.0;
967 if (gain_gem3 < 1.0)
968 gain_gem3 = 1.0;
969
970 int total_gain = 0;
971
972 if (zdist < level1) {
973 total_gain = 1.0;
974 }
975 if (zdist >= level1 && zdist < level2) {
976 total_gain = gain_gem3;
977 }
978 if (zdist >= level2 && zdist < level3) {
979 total_gain = gain_gem3 * gain_gem2;
980 }
981 if (zdist >= level3) {
982 total_gain = gain_gem3 * gain_gem2 * gain_gem1;
983 }
984
985 SegmentedChargeDistribution[segment] += total_gain;
986
987 // ANN mode: electrons are not projected on the readout, strips are taken from the model
988 if (useANN)
989 continue;
990
991 // Standard method ---------------------------------------------------
992 // Projection of the current electron on the readout (x,y-coordinates)
993 double x_readout, y_readout;
994
995 for (int igain = 0; igain < total_gain; ++igain) {
996
997 // x-shift of the electon depends on the electron drift direction
999 x_readout = gRandom->Gaus(xcoll + xmean, sigma);
1000 } else {
1001 x_readout = gRandom->Gaus(xcoll - xmean, sigma);
1002 }
1003
1004 y_readout = gRandom->Gaus(ycoll, sigma);
1005
1006 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1007 // Condition: end electron position must be inside the layer
1008 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_readout, y_readout))
1009 continue;
1010
1011 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_readout, y_readout);
1012 // AZ-100124 - account for strip width
1013 Double_t dstrip = strip_pos - (int)strip_pos;
1014 if (TMath::Abs(StripLayers[ilayer].GetAngleDeg()) < 1.0) {
1015 // Vertical strips
1016 if (dstrip >= 0.4 && dstrip < 0.6)
1017 continue;
1018 } else {
1019 // Rotated strips
1020 if (dstrip >= 0.3 && dstrip < 0.7)
1021 continue;
1022 }
1023 // AZ
1024
1025 cluster_layers[ilayer].AddStrip((Int_t)strip_pos, 1.0);
1026 }
1027
1028 // Electron points on the readout (testing) -------------
1029 XElectronPos.push_back(x_readout);
1030 YElectronPos.push_back(y_readout);
1031 ElectronSignal.push_back(1.0);
1032 //------------------------------------------------------
1033 }
1034 }
1035 }
1036 //----------------------------------------------------------------------
1037
1038 // ANN generation ------------------------------------------------------
1039 if (useANN) {
1040
1041 // Normalization (px,py,pz), where r_vec must be equal to 1
1042 Double_t px_norm = px / r_vec;
1043 Double_t py_norm = py / r_vec;
1044 Double_t pz_norm = pz / r_vec;
1045
1046 // Inversion for z-reverse direction particles: the same track segment passed from (x_out, y_out)
1047 if (pz < 0.0) {
1048 px_norm = -px_norm;
1049 py_norm = -py_norm;
1050 pz_norm = -pz_norm;
1051 }
1052
1053 Double_t drift_direction = 0; // 0 is ForwardZAxisEDrift
1055 drift_direction = 1;
1056
1057 // Set seg. vector explicitly from parameter segs if it is not empty
1058 if (!segs.empty()) {
1060 }
1061
1062 if (SegmentedChargeDistribution.size() < static_cast<size_t>(n_segs_features)) {
1063 LOG(error) << "BmnGemStripModule::" << __func__
1064 << "(): segment vector size = " << SegmentedChargeDistribution.size() << ", the model requires "
1065 << n_segs_features;
1066 return false;
1067 }
1068
1069 // Normalize seg. vector: [0, max] -> [0, 1] (max over the whole vector)
1070 Double_t max_segment_val =
1071 *(std::max_element(SegmentedChargeDistribution.begin(), SegmentedChargeDistribution.end()));
1072 vector<Float_t> segs_norm(n_segs_features, 0.0f);
1073 if (max_segment_val > 0.0) {
1074 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
1075 segs_norm[iseg] = static_cast<Float_t>(SegmentedChargeDistribution[iseg] / max_segment_val);
1076 }
1077
1078 // Total charge in segment vector
1079 Double_t total_charge = 0.0;
1080 for (size_t i = 0; i < SegmentedChargeDistribution.size(); ++i)
1081 total_charge += SegmentedChargeDistribution[i];
1082
1083 // MC point: the track start point along the (inverted) momentum
1084 Double_t x_mc = x;
1085 Double_t y_mc = y;
1086 if (pz < 0.0) {
1087 x_mc = x_out;
1088 y_mc = y_out;
1089 }
1090
1091 // Common part of the model input: [px, py, pz, drift, bfield, s0..s{n_segs_features-1}]
1092 std::vector<float> input_data;
1093 input_data.reserve(n_ann_inputs);
1094 input_data.push_back(static_cast<float>(px_norm));
1095 input_data.push_back(static_cast<float>(py_norm));
1096 input_data.push_back(static_cast<float>(pz_norm));
1097 input_data.push_back(static_cast<float>(drift_direction));
1098 input_data.push_back(static_cast<float>(bfield));
1099 input_data.insert(input_data.end(), segs_norm.begin(), segs_norm.end());
1100
1101 if (total_charge > 0.0) {
1102 // 2D model: image of the signal around the MC point -> random points in its bins -> strips
1103 const auto img = ann_model->Predict(input_data);
1104 if (img.size() != static_cast<std::size_t>(nx_bins * ny_bins)) {
1105 LOG(error) << "BmnGemStripModule::" << __func__ << "(): model output size = " << img.size()
1106 << ", expected " << nx_bins * ny_bins;
1107 } else {
1108 Double_t x_bin_size = 2 * dx_area / nx_bins;
1109 Double_t y_bin_size = 2 * dy_area / ny_bins;
1110 Double_t x_min_area = x_mc - dx_area;
1111 Double_t y_min_area = y_mc - dy_area;
1112
1113 // Normalize content of img: [-1,1] -> [0,1]
1114 std::vector<Double_t> norm_content(nx_bins * ny_bins, 0.0);
1115 Double_t sum_norm = 0.0;
1116 for (Int_t idx = 0; idx < nx_bins * ny_bins; ++idx) {
1117 norm_content[idx] = (img.at(idx) - (-1.0)) / (1.0 - (-1.0));
1118 sum_norm += norm_content[idx];
1119 }
1120
1121 // Scale coeff for total signal distribution
1122 const Double_t ann_correction = 0.8; // Set to 1.0 to disable correction
1123 Double_t signal_scale = 0.0;
1124 if (sum_norm > 0.0)
1125 signal_scale = (total_charge / sum_norm) * ann_correction;
1126
1127 Double_t threshold = 0.0001 * total_charge;
1128
1129 // Image rows go from the top (iy = ny_bins - 1) to the bottom
1130 Int_t idx = 0;
1131 for (Int_t iy = ny_bins - 1; iy >= 0; --iy) {
1132 for (Int_t ix = 0; ix < nx_bins; ++ix, ++idx) {
1133 Double_t content = norm_content[idx] * signal_scale;
1134 if (content <= threshold)
1135 continue;
1136
1137 Double_t x_bin_coord = ix * x_bin_size + x_bin_size * 0.5 + x_min_area;
1138 Double_t y_bin_coord = iy * y_bin_size + y_bin_size * 0.5 + y_min_area;
1139
1140 Int_t n_rand_points = 10;
1141 Double_t distr_content = content / n_rand_points;
1142 for (Int_t irp = 0; irp < n_rand_points; ++irp) {
1143 Double_t x_distr =
1144 gRandom->Uniform(x_bin_coord - x_bin_size * 0.5, x_bin_coord + x_bin_size * 0.5);
1145 Double_t y_distr =
1146 gRandom->Uniform(y_bin_coord - y_bin_size * 0.5, y_bin_coord + y_bin_size * 0.5);
1147
1148 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1149 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_distr, y_distr))
1150 continue;
1151 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_distr, y_distr);
1152 cluster_layers[ilayer].AddStrip((Int_t)strip_pos, distr_content);
1153 }
1154
1155 XElectronPos.push_back(x_distr);
1156 YElectronPos.push_back(y_distr);
1157 ElectronSignal.push_back(distr_content);
1158 }
1159 }
1160 }
1161 }
1162 }
1163 }
1164 //----------------------------------------------------------------------
1165
1166 // Calculate cluster parameters --------------------------------------------
1167 vector<Double_t> cluster_mean_position(n_strip_layers, 0.0);
1168 vector<Double_t> cluster_total_signal(n_strip_layers, 0.0);
1169
1170 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1171
1172 if (cluster_layers[ilayer].GetClusterSize() > 0) {
1173
1174 for (int i = 0; i < cluster_layers[ilayer].GetClusterSize(); ++i) {
1175 cluster_mean_position[ilayer] += (cluster_layers[ilayer].Strips[i] + 0.5)
1176 * cluster_layers[ilayer].Signals[i]; // as sum of all positions
1177 cluster_total_signal[ilayer] += cluster_layers[ilayer].Signals[i];
1178 }
1179 cluster_mean_position[ilayer] /= cluster_total_signal[ilayer];
1180 }
1181
1182 cluster_layers[ilayer].MeanPosition = cluster_mean_position[ilayer]; // mean cluster position
1183 cluster_layers[ilayer].TotalSignal = cluster_total_signal[ilayer]; // total signal of the cluster
1184 }
1185
1187 if (pz > 0.0) {
1188 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1189 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
1190 }
1191 } else {
1192 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1193 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
1194 }
1195 }
1196 } else {
1197 if (pz > 0.0) {
1198 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1199 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
1200 }
1201 } else {
1202 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1203 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
1204 }
1205 }
1206 }
1207
1208 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1209 cluster_layers[ilayer].PositionResidual =
1210 cluster_layers[ilayer].MeanPosition - cluster_layers[ilayer].OriginPosition;
1211 cluster_layers[ilayer].SetType(StripLayers[ilayer].GetType());
1212 }
1213 //----------------------------------------------------------------------
1214
1215 // Testing ----------------------------------------------------------------------
1216 if (AddedClusters.size() == 0) {
1217 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1218 AddedClusters.push_back(vector<StripCluster>());
1219 }
1220 }
1221
1222 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1223 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
1224 }
1225 //------------------------------------------------------------------------------
1226
1227 // Add the correct clusters to the strip layers -------------------------
1228 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1229 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
1230 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
1231 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
1232 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
1233 }
1234 }
1235 //----------------------------------------------------------------------
1236
1237 // Fill strip matches ---------------------------------------------------
1238 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1239 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
1240 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
1241 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
1242 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
1243 refID);
1244 }
1245 }
1246 //----------------------------------------------------------------------
1247
1248 RealPointsX.push_back(x);
1249 RealPointsY.push_back(y);
1250 RealPointsXout.push_back(x_out);
1251 RealPointsYout.push_back(y_out);
1252 RealPointsMC.push_back(refID);
1253
1254 return true;
1255}
1256//------------------------------------------------------------------------------
1257
1258// Strip generation with the fdeep model (BmnGemStripFdeepModel): standard method or ANN (|theta| < kANNMaxThetaAngle).
1259// Generation data only with SetFillGenerationData(kTRUE);
1261 Double_t y,
1262 Double_t z,
1263 Double_t px,
1264 Double_t py,
1265 Double_t pz,
1266 Double_t signal,
1267 Int_t refID,
1268 Int_t pdg_code,
1269 Double_t bfield,
1270 Bool_t onlyStandard,
1271 vector<Int_t> segs)
1272{
1273 const Double_t maxThetaAngle = kANNMaxThetaAngle; // max |theta| (deg) of the track for the ANN
1274 const Bool_t kParallelANNLayers = false; // [PARALLEL-ANN] true - ANN prediction for the strip layers in parallel
1275
1276 // Model format: [px, py, pz, drift, bfield, s0..s63, strip_layer, strip_order, center_frac] -> window of N
1277 // strips (N odd, from the model) centered at the middle of the drift gap (GetANNWindowCenterPoint)
1278 const Int_t n_track_segments = 64; // track segmentation (segmented charge distribution)
1279 const Int_t n_segs_features = n_track_segments; // segments passed to the model
1280 const Int_t n_ann_inputs = 5 + n_segs_features + 3; // total number of input features
1281 Int_t n_ann_strips = 0; // model output: strip window size (odd), taken from the model below
1282
1283 // Test outputs are cleared always (cheap if they are empty)
1285
1286 // fdeep model: shared between modules, checked once for each model -------
1287 std::shared_ptr<const BmnGemStripFdeepModel> ann_holder;
1288 const BmnGemStripFdeepModel* ann_model = nullptr;
1289 if (!onlyStandard) {
1290 ann_holder = BmnGemStripFdeepModel::GetShared();
1291 ann_model = ann_holder.get();
1292
1293 static std::mutex checked_mutex;
1294 static std::map<const BmnGemStripFdeepModel*,
1295 std::pair<std::shared_ptr<const BmnGemStripFdeepModel>, std::string>>
1296 checked; // model -> (holder, why it is not suitable: empty if suitable)
1297
1298 std::lock_guard<std::mutex> lock(checked_mutex);
1299 if (!ann_model) {
1300 LOG(warn) << "BmnGemStripModule::" << __func__
1301 << "(): fdeep model is not available, the standard method is used";
1302 } else {
1303 auto it = checked.find(ann_model);
1304 if (it == checked.end()) {
1305 std::string why;
1306 if (ann_model->GetInputDim() != n_ann_inputs)
1307 why = "input " + std::to_string(ann_model->GetInputDim()) + ", expected "
1308 + std::to_string(n_ann_inputs);
1309 else
1310 why = CheckANNStripWindowSize(ann_model->GetOutputDim());
1311 if (why.empty())
1312 LOG(info) << "BmnGemStripModule::" << __func__ << "(): fdeep model " << ann_model->GetFilePath()
1313 << " is used (ANN for |theta| < " << maxThetaAngle << " deg, window "
1314 << ann_model->GetOutputDim() << " strips)";
1315 it = checked.emplace(ann_model, std::make_pair(ann_holder, why)).first;
1316 }
1317 if (!it->second.second.empty()) {
1318 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): the model " << ann_model->GetFilePath()
1319 << " is not suitable (" << it->second.second << "), the standard method is used";
1320 ann_model = nullptr;
1321 }
1322 }
1323 }
1324 if (ann_model)
1325 n_ann_strips = ann_model->GetOutputDim(); // window size of the model (checked above: odd, >= 3)
1326 //--------------------------------------------------------------------------
1327
1328 // Condition: a start point is inside the module and outside its dead zone (in strip layers)
1329 if (!IsPointInsideModule(x, y)) {
1330 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
1331 << ") is out of the readout plane or inside a dead zone";
1332 return false;
1333 }
1334
1335 // Particle: pdg_code = 0 -> from the MC track of the point refID (as in AddRealPointFull) --
1336 Int_t pdg = pdg_code;
1337 if (pdg == 0) {
1338 FairRunSim* run = FairRunSim::Instance();
1339 BmnGemStripDigitizer* digi = run ? (BmnGemStripDigitizer*)run->GetTask("GemDigitizer") : nullptr;
1340 TClonesArray* points = digi ? digi->GetMCPoints() : nullptr;
1341 TClonesArray* mctracks = digi ? digi->GetMCTracks() : nullptr;
1342
1343 CbmMCTrack* mcTr = nullptr;
1344 if (points && mctracks && refID >= 0 && refID < points->GetEntriesFast()) {
1345 CbmStsPoint* point = (CbmStsPoint*)points->UncheckedAt(refID);
1346 if (point && point->GetTrackID() >= 0 && point->GetTrackID() < mctracks->GetEntriesFast())
1347 mcTr = (CbmMCTrack*)mctracks->UncheckedAt(point->GetTrackID());
1348 }
1349
1350 if (mcTr) {
1351 pdg = mcTr->GetPdgCode();
1352 } else {
1353 static std::atomic<bool> no_mc_reported{false};
1354 if (!no_mc_reported.exchange(true))
1355 LOG(warn) << "BmnGemStripModule::" << __func__
1356 << "(): pdg_code = 0 and the MC track is not available (no GemDigitizer or MC point), "
1357 "the He4 mass is used (this warning is printed once)";
1358 }
1359 }
1360
1361 TVector3 mom(px, py, pz);
1362 TParticlePDG* part = (pdg != 0) ? TDatabasePDG::Instance()->GetParticle(pdg) : nullptr;
1363
1364 Double_t mnoc = 1.0, mass = 3.7283999; // He4
1365 if (part)
1366 mass = part->Mass();
1367 if (mass > 0.0001) {
1368 TLorentzVector lorv;
1369 lorv.SetVectM(mom, mass);
1370 Double_t beta = lorv.Beta();
1371 Double_t gamma = lorv.Gamma();
1372 Double_t charge = (part) ? part->Charge() / 3 : 1;
1373 mnoc = GetNumberOfClusters(beta, gamma, charge, 1.787, 12.33);
1374 if (mnoc < 0.5)
1375 return kFALSE;
1376 if (MaxClustersPerCm > 0.0 && mnoc > MaxClustersPerCm)
1377 mnoc = MaxClustersPerCm;
1378 }
1379 //--------------------------------------------------------------------------
1380
1381 // Distance from entry point (x,y,z) to exit point (along z-axis)
1382 Double_t z_distance_from_in_to_out;
1383
1384 // Condition: track direction along z-axis
1385 if (pz > 0.0) {
1386 z_distance_from_in_to_out = ModuleThickness - (z - ZStartModulePosition);
1387 } else {
1388 z_distance_from_in_to_out = z - ZStartModulePosition;
1389 }
1390
1391 // Condition: track distance along z-axis must be not zero
1392 if (z_distance_from_in_to_out <= 0.0) {
1393 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
1394 << ") has a track with an incorrect length";
1395 return false;
1396 }
1397
1398 // Scale coefficient of the track vector (the path from entry point to exit point)
1399 Double_t vector_coeff = fabs(z_distance_from_in_to_out / pz);
1400
1401 // Components of the track vector
1402 Double_t x_vec_from_in_to_out = vector_coeff * px;
1403 Double_t y_vec_from_in_to_out = vector_coeff * py;
1404 Double_t z_vec_from_in_to_out = vector_coeff * pz;
1405
1406 // Exit point coordinates (x_out, y_out, z_out)
1407 Double_t x_out = x + x_vec_from_in_to_out;
1408 Double_t y_out = y + y_vec_from_in_to_out;
1409 Double_t z_out = z + z_vec_from_in_to_out;
1410
1411 // Check if the exit point is outside the module then calculate new x_out, y_out, z_out values
1412 // Condition: x-coordinate of the exit point is inside the module
1413 if (x_out < XMinModule || x_out > XMaxModule) {
1414 if (x_out < XMinModule)
1415 x_out = XMinModule;
1416 if (x_out > XMaxModule)
1417 x_out = XMaxModule;
1418
1419 x_vec_from_in_to_out = x_out - x;
1420 vector_coeff = x_vec_from_in_to_out / px;
1421
1422 y_vec_from_in_to_out = vector_coeff * py;
1423 z_vec_from_in_to_out = vector_coeff * pz;
1424
1425 y_out = y + y_vec_from_in_to_out;
1426 z_out = z + z_vec_from_in_to_out;
1427 }
1428 // Condition: y-coordinate of the exit point is inside the module
1429 if (y_out < YMinModule || y_out > YMaxModule) {
1430 if (y_out < YMinModule)
1431 y_out = YMinModule;
1432 if (y_out > YMaxModule)
1433 y_out = YMaxModule;
1434
1435 y_vec_from_in_to_out = y_out - y;
1436 vector_coeff = y_vec_from_in_to_out / py;
1437
1438 x_vec_from_in_to_out = vector_coeff * px;
1439 z_vec_from_in_to_out = vector_coeff * pz;
1440
1441 x_out = x + x_vec_from_in_to_out;
1442 z_out = z + z_vec_from_in_to_out;
1443 }
1444 //--------------------------------------------------------------------------
1445
1446 // Center point of the ANN strip window: the track at the middle of the drift gap (computed always, also with
1447 // onlyStandard: it is stored with the point, see GetRealPointXcenter(), for the training samples)
1448 Double_t x_c = x, y_c = y;
1449 GetANNWindowCenterPoint(x, y, z, x_out, y_out, z_out, x_c, y_c);
1450
1451 // Common track length
1452 Double_t track_length =
1453 std::sqrt((x_vec_from_in_to_out) * (x_vec_from_in_to_out) + (y_vec_from_in_to_out) * (y_vec_from_in_to_out)
1454 + (z_vec_from_in_to_out) * (z_vec_from_in_to_out));
1455
1456 // Which method is used for this point: standard (electrons -> strips) or ANN (model -> strips)
1457 // (without a suitable model or with onlyStandard the standard method is used)
1458 Double_t r_vec = TMath::Sqrt(px * px + py * py + pz * pz);
1459 Double_t theta_deg_abs = TMath::ACos(TMath::Abs(pz) / r_vec) * TMath::RadToDeg();
1460 const Bool_t useANN = (theta_deg_abs < maxThetaAngle) && ann_model;
1461
1462 // ANN with external segments: the avalanche is not needed
1463 const Bool_t use_external_segs = useANN && !segs.empty();
1464 if (use_external_segs && segs.size() < static_cast<size_t>(n_segs_features)) {
1465 LOG(error) << "BmnGemStripModule::" << __func__ << "(): segment vector size = " << segs.size()
1466 << ", the model requires " << n_segs_features;
1467 return false;
1468 }
1469
1470 // Each level - distance to the readout plane
1474
1475 // Mean electron shift along x-axis (under the influence of the Lorentz force)
1476 Double_t xmean; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3)
1477
1478 // Sigma electron smearing
1479 Double_t sigma; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3+ p4*x^4 + p5*x^5)
1480
1481 // Reading the medium configuration -------------------------------------
1486
1493 //----------------------------------------------------------------------
1494
1495 // Initialization of a strip cluster in each strip layer
1496 Int_t n_strip_layers = StripLayers.size(); // number of layers in the module
1497 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
1498
1499 // Segmented charge distribution of this point (always computed when the avalanche is simulated)
1500 vector<Int_t> seg_local(n_track_segments, 0);
1501
1502 // Standard method: per-layer strip accumulators -------------------------
1503 // Signals are accumulated per strip and added to the cluster with one AddStrip() per strip:
1504 // AddStrip() sums the signals and keeps the strips in ascending order, the signals are integer
1505 // numbers of electrons -> the cluster is bitwise the same as with one AddStrip() per electron.
1506 vector<vector<Double_t>> strip_acc;
1507 vector<Int_t> acc_min, acc_max; // range of the touched strips in each layer
1508 vector<char> layer_vertical; // layer invariant: vertical strips (|angle| < 1 deg)
1509 if (!useANN) {
1510 strip_acc.resize(n_strip_layers);
1511 acc_min.assign(n_strip_layers, std::numeric_limits<Int_t>::max());
1512 acc_max.assign(n_strip_layers, -1);
1513 layer_vertical.assign(n_strip_layers, 0);
1514 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1515 strip_acc[ilayer].assign(std::max(0, StripLayers[ilayer].GetNStrips()), 0.0);
1516 layer_vertical[ilayer] = TMath::Abs(StripLayers[ilayer].GetAngleDeg()) < 1.0;
1517 }
1518 }
1519 //--------------------------------------------------------------------------
1520
1521 if (!use_external_segs) {
1522
1523 Double_t current_length = 0.0; // traversed length (counter)
1524 Double_t current_length_ratio = 0.0; // ratio of the traversed length to common track length (counter)
1525 Double_t current_step = 0.0; // current distance between two collision points
1526
1527 // Collection of collision points
1528 std::vector<CollPoint> collision_points;
1529
1530 // Reading the MCD value (mean collision distance = mean free flight path) [cm]
1532 MCD = 1 / mnoc; // AZ-110124
1533
1534 while (current_length < track_length) {
1535
1536 current_step = gRandom->Exp(MCD);
1537 current_length += current_step;
1538
1539 if (current_length > track_length)
1540 break;
1541
1542 current_length_ratio = current_length / track_length;
1543
1544 Double_t current_x = x + current_length_ratio * x_vec_from_in_to_out;
1545 Double_t current_y = y + current_length_ratio * y_vec_from_in_to_out;
1546 Double_t current_z = z + current_length_ratio * z_vec_from_in_to_out;
1547
1548 collision_points.push_back(CollPoint(current_x, current_y, current_z));
1549 }
1550
1551 // Electron avalanche producing for each collision point at the track
1552 // (in the ANN mode only the segmented charge distribution is filled here)
1553 for (size_t icoll = 0; icoll < collision_points.size(); ++icoll) {
1554
1555 Double_t xcoll = collision_points[icoll].x;
1556 Double_t ycoll = collision_points[icoll].y;
1557 Double_t zcoll = collision_points[icoll].z;
1558
1559 Double_t zdist; // current z-distance to the readout
1560
1561 // Find z-distance to the readout depending on the electron drift direction
1563 zdist = (ModuleThickness + ZStartModulePosition) - zcoll;
1564 } else {
1565 zdist = zcoll - ZStartModulePosition;
1566 }
1567 //------------------------------------------------------------------
1568
1569 // polynomials
1570 xmean = p0_xmean + p1_xmean * zdist + p2_xmean * zdist * zdist + p3_xmean * zdist * zdist * zdist;
1571 sigma = p0_sigma + p1_sigma * zdist + p2_sigma * zdist * zdist + p3_sigma * zdist * zdist * zdist
1572 + p4_sigma * zdist * zdist * zdist * zdist + p5_sigma * zdist * zdist * zdist * zdist * zdist;
1573
1574 // Number of electrons in the current collision
1575 Int_t n_electrons_cluster = gRandom->Landau(1.027, 0.11);
1576 if (n_electrons_cluster < 1)
1577 n_electrons_cluster = 1; // min
1578 if (n_electrons_cluster > 6)
1579 n_electrons_cluster = 6; // max
1580
1581 // Filling charge distribution -------------------------------------
1582 Double_t dist_from_start = 0.0;
1583 if (pz > 0.0) {
1584 dist_from_start =
1585 std::sqrt((xcoll - x) * (xcoll - x) + (ycoll - y) * (ycoll - y) + (zcoll - z) * (zcoll - z));
1586 } else {
1587 dist_from_start = std::sqrt((xcoll - x_out) * (xcoll - x_out) + (ycoll - y_out) * (ycoll - y_out)
1588 + (zcoll - z_out) * (zcoll - z_out));
1589 }
1590 Int_t segment = (Int_t)(dist_from_start / track_length * n_track_segments);
1591 if (segment >= n_track_segments)
1592 segment = n_track_segments - 1;
1593 //------------------------------------------------------------------
1594
1595 for (Int_t ielectron = 0; ielectron < n_electrons_cluster; ++ielectron) {
1596
1597 // Electron gain in each GEM cascade (three draws always, as in the other methods)
1598 Double_t gain_gem1 = gRandom->Exp(15);
1599 Double_t gain_gem2 = gRandom->Exp(15);
1600 Double_t gain_gem3 = gRandom->Exp(15);
1601
1602 if (gain_gem1 < 1.0)
1603 gain_gem1 = 1.0;
1604 if (gain_gem2 < 1.0)
1605 gain_gem2 = 1.0;
1606 if (gain_gem3 < 1.0)
1607 gain_gem3 = 1.0;
1608
1609 int total_gain = 0;
1610
1611 if (zdist < level1) {
1612 total_gain = 1.0;
1613 }
1614 if (zdist >= level1 && zdist < level2) {
1615 total_gain = gain_gem3;
1616 }
1617 if (zdist >= level2 && zdist < level3) {
1618 total_gain = gain_gem3 * gain_gem2;
1619 }
1620 if (zdist >= level3) {
1621 total_gain = gain_gem3 * gain_gem2 * gain_gem1;
1622 }
1623
1624 seg_local[segment] += total_gain;
1625
1626 // ANN mode: electrons are not projected on the readout, strips are taken from the model
1627 if (useANN)
1628 continue;
1629
1630 // Standard method ---------------------------------------------
1631 // Projection of the current electron on the readout (x,y-coordinates)
1632 double x_readout, y_readout;
1633
1634 for (int igain = 0; igain < total_gain; ++igain) {
1635
1636 // x-shift of the electon depends on the electron drift direction
1638 x_readout = gRandom->Gaus(xcoll + xmean, sigma);
1639 } else {
1640 x_readout = gRandom->Gaus(xcoll - xmean, sigma);
1641 }
1642
1643 y_readout = gRandom->Gaus(ycoll, sigma);
1644
1645 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1646 // Condition: end electron position must be inside the layer
1647 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_readout, y_readout))
1648 continue;
1649
1650 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_readout, y_readout);
1651 // AZ-100124 - account for strip width
1652 Double_t dstrip = strip_pos - (int)strip_pos;
1653 if (layer_vertical[ilayer]) {
1654 // Vertical strips
1655 if (dstrip >= 0.4 && dstrip < 0.6)
1656 continue;
1657 } else {
1658 // Rotated strips
1659 if (dstrip >= 0.3 && dstrip < 0.7)
1660 continue;
1661 }
1662 // AZ
1663
1664 const Int_t strip_num = (Int_t)strip_pos;
1665 if (strip_num >= 0 && strip_num < (Int_t)strip_acc[ilayer].size()) {
1666 strip_acc[ilayer][strip_num] += 1.0;
1667 if (strip_num < acc_min[ilayer])
1668 acc_min[ilayer] = strip_num;
1669 if (strip_num > acc_max[ilayer])
1670 acc_max[ilayer] = strip_num;
1671 } else {
1672 // out of the accumulator range: directly, as in the other methods
1673 cluster_layers[ilayer].AddStrip(strip_num, 1.0);
1674 }
1675 }
1676
1677 // Electron points on the readout (generation data) ---------
1678 if (FillGenerationData) {
1679 XElectronPos.push_back(x_readout);
1680 YElectronPos.push_back(y_readout);
1681 ElectronSignal.push_back(1.0);
1682 }
1683 //----------------------------------------------------------
1684 }
1685 }
1686 }
1687 }
1688
1689 // Standard method: accumulated signals -> clusters (one AddStrip per strip)
1690 if (!useANN) {
1691 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1692 for (Int_t istrip = acc_min[ilayer]; istrip <= acc_max[ilayer]; ++istrip) {
1693 if (strip_acc[ilayer][istrip] > 0.0)
1694 cluster_layers[ilayer].AddStrip(istrip, strip_acc[ilayer][istrip]);
1695 }
1696 }
1697 }
1698 //----------------------------------------------------------------------
1699
1700 // ANN generation (fdeep) ---------------------------------------------
1701 if (useANN) {
1702
1703 // Segments for the model: external or from the avalanche of this point
1704 const vector<Int_t>& seg_used = use_external_segs ? segs : seg_local;
1705
1706 // Normalization (px,py,pz), where r_vec must be equal to 1
1707 Double_t px_norm = px / r_vec;
1708 Double_t py_norm = py / r_vec;
1709 Double_t pz_norm = pz / r_vec;
1710
1711 // Inversion for z-reverse direction particles: the same track segment passed from (x_out, y_out)
1712 if (pz < 0.0) {
1713 px_norm = -px_norm;
1714 py_norm = -py_norm;
1715 pz_norm = -pz_norm;
1716 }
1717
1718 Double_t drift_direction = 0; // 0 is ForwardZAxisEDrift
1720 drift_direction = 1;
1721
1722 // Magnetic field: given explicitly or from the current medium configuration
1723 const Double_t bfield_used = (bfield < 0.0) ? BmnGemStripMedium::GetInstance().BField : bfield;
1724
1725 // Normalize seg. vector: [0, max] -> [0, 1] (max over the whole vector)
1726 Double_t max_segment_val = *(std::max_element(seg_used.begin(), seg_used.end()));
1727
1728 // Total charge in segment vector
1729 Double_t total_charge = 0.0;
1730 for (size_t i = 0; i < seg_used.size(); ++i)
1731 total_charge += seg_used[i];
1732
1733 // Window center point (x_c, y_c): computed above with GetANNWindowCenterPoint()
1734
1735 // Common part of the model input: [px, py, pz, drift, bfield, s0..s{n_segs_features-1}]
1736 std::vector<float> input_data;
1737 input_data.reserve(n_ann_inputs);
1738 input_data.push_back(static_cast<float>(px_norm));
1739 input_data.push_back(static_cast<float>(py_norm));
1740 input_data.push_back(static_cast<float>(pz_norm));
1741 input_data.push_back(static_cast<float>(drift_direction));
1742 input_data.push_back(static_cast<float>(bfield_used));
1743 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
1744 input_data.push_back((max_segment_val > 0.0) ? static_cast<float>(seg_used[iseg] / max_segment_val) : 0.0f);
1745
1746 if (total_charge > 0.0) {
1747 // Strip model: a window of n_ann_strips strips around the window center strip in each layer
1748 const Int_t center_strip_index = n_ann_strips / 2; // center strip of the window
1749
1750 // [PARALLEL-ANN] the body of the former loop over layers: one layer, ilayer - its index
1751 auto process_layer = [&](Int_t ilayer) {
1752 // The window is built around the center point (middle of the drift gap): it must be inside this layer
1753 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_c, y_c))
1754 return; // [PARALLEL-ANN] was continue
1755
1756 Int_t n_strips_in_layer = StripLayers[ilayer].GetNStrips();
1757 if (n_strips_in_layer <= 0)
1758 return; // [PARALLEL-ANN] was continue
1759
1760 // Center point position in strips: integer part - window center strip, fractional part - center_frac
1761 Double_t center_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_c, y_c);
1762 Int_t center_strip = (Int_t)TMath::Floor(center_pos);
1763 Double_t center_frac = center_pos - center_strip;
1764
1765 Double_t strip_layer = static_cast<Double_t>(StripLayers[ilayer].GetType()); // 0 - lower, 1 - upper
1766 Double_t strip_order =
1767 static_cast<Double_t>(StripLayers[ilayer].GetStripNumberingOrder()); // 0 - LeftToRight
1768
1769 // Layer part of the model input: [strip_layer, strip_order, center_frac]
1770 std::vector<float> layer_input = input_data;
1771 layer_input.push_back(static_cast<float>(strip_layer));
1772 layer_input.push_back(static_cast<float>(strip_order));
1773 layer_input.push_back(static_cast<float>(center_frac));
1774
1775 // Generate strip signals with AI model: n_ann_strips values in [0, 1] (max ~ 1)
1776 const auto strip_values = ann_model->Predict(layer_input);
1777 if (strip_values.size() != static_cast<std::size_t>(n_ann_strips)) {
1778 LOG(error) << "BmnGemStripModule::"
1779 << "AddRealPointFull_ANNStripGenerator_Fdeep"
1780 << "(): model output size = " << strip_values.size() << ", expected " << n_ann_strips;
1781 return; // [PARALLEL-ANN] was continue
1782 }
1783
1784 // Sum of the model output over all strips of the window
1785 Double_t sum_norm = 0.0;
1786 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip)
1787 sum_norm += strip_values.at(istrip);
1788
1789 // Scale coeff for total signal distribution (separate for lower and upper layers)
1790 const Double_t ann_correction_lower = 0.8; // Set to 1.0 to disable correction
1791 const Double_t ann_correction_upper = 0.6; // Set to 1.0 to disable correction
1792 const Double_t ann_correction =
1793 (StripLayers[ilayer].GetType() == LowerStripLayer) ? ann_correction_lower : ann_correction_upper;
1794
1795 Double_t signal_scale = 0.0;
1796 if (sum_norm > 0.0)
1797 signal_scale = (total_charge / sum_norm) * ann_correction;
1798
1799 Double_t threshold = 0.0001 * total_charge;
1800
1801 // Strips are written directly: window index -> strip number
1802 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip) {
1803 Double_t content = strip_values.at(istrip) * signal_scale;
1804 if (content <= threshold)
1805 continue;
1806
1807 Int_t strip_num = center_strip - center_strip_index + istrip;
1808 if (strip_num < 0 || strip_num >= n_strips_in_layer)
1809 continue; // the window goes out of the layer
1810
1811 cluster_layers[ilayer].AddStrip(strip_num, content);
1812 }
1813 }; // end of process_layer
1814
1815 // [PARALLEL-ANN] layers 1..n-1 in separate threads, layer 0 in the current thread
1816 // (each layer writes only to its own cluster -> the same result as the sequential loop)
1817 if (kParallelANNLayers && n_strip_layers > 1) {
1818 std::vector<std::thread> layer_threads;
1819 for (Int_t ilayer = 1; ilayer < n_strip_layers; ++ilayer)
1820 layer_threads.emplace_back(process_layer, ilayer);
1821 process_layer(0);
1822 for (auto& th : layer_threads)
1823 th.join();
1824 } else {
1825 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer)
1826 process_layer(ilayer);
1827 }
1828 }
1829 }
1830 //----------------------------------------------------------------------
1831
1832 // Segmented charge distribution (generation data) ------------------------
1833 // (seg_used above is not used after this point)
1834 if (FillGenerationData) {
1835 if (use_external_segs)
1836 SegmentedChargeDistribution = std::move(segs);
1837 else
1838 SegmentedChargeDistribution = std::move(seg_local);
1839 }
1840 //----------------------------------------------------------------------
1841
1842 // Calculate cluster parameters --------------------------------------------
1843 vector<Double_t> cluster_mean_position(n_strip_layers, 0.0);
1844 vector<Double_t> cluster_total_signal(n_strip_layers, 0.0);
1845
1846 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1847
1848 if (cluster_layers[ilayer].GetClusterSize() > 0) {
1849
1850 for (int i = 0; i < cluster_layers[ilayer].GetClusterSize(); ++i) {
1851 cluster_mean_position[ilayer] += (cluster_layers[ilayer].Strips[i] + 0.5)
1852 * cluster_layers[ilayer].Signals[i]; // as sum of all positions
1853 cluster_total_signal[ilayer] += cluster_layers[ilayer].Signals[i];
1854 }
1855 cluster_mean_position[ilayer] /= cluster_total_signal[ilayer];
1856 }
1857
1858 cluster_layers[ilayer].MeanPosition = cluster_mean_position[ilayer]; // mean cluster position
1859 cluster_layers[ilayer].TotalSignal = cluster_total_signal[ilayer]; // total signal of the cluster
1860 }
1861
1863 if (pz > 0.0) {
1864 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1865 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
1866 }
1867 } else {
1868 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1869 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
1870 }
1871 }
1872 } else {
1873 if (pz > 0.0) {
1874 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1875 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
1876 }
1877 } else {
1878 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1879 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
1880 }
1881 }
1882 }
1883
1884 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1885 cluster_layers[ilayer].PositionResidual =
1886 cluster_layers[ilayer].MeanPosition - cluster_layers[ilayer].OriginPosition;
1887 cluster_layers[ilayer].SetType(StripLayers[ilayer].GetType());
1888 }
1889 //----------------------------------------------------------------------
1890
1891 // Added clusters (generation data) ----------------------------------------
1892 if (FillGenerationData) {
1893 if (AddedClusters.size() == 0) {
1894 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1895 AddedClusters.push_back(vector<StripCluster>());
1896 }
1897 }
1898
1899 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1900 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
1901 }
1902 }
1903 //------------------------------------------------------------------------------
1904
1905 // Add the correct clusters to the strip layers -------------------------
1906 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1907 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
1908 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
1909 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
1910 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
1911 }
1912 }
1913 //----------------------------------------------------------------------
1914
1915 // Fill strip matches ---------------------------------------------------
1916 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
1917 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
1918 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
1919 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
1920 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
1921 refID);
1922 }
1923 }
1924 //----------------------------------------------------------------------
1925
1926 RealPointsX.push_back(x);
1927 RealPointsY.push_back(y);
1928 RealPointsXout.push_back(x_out);
1929 RealPointsYout.push_back(y_out);
1930 RealPointsMC.push_back(refID);
1931 RealPointsXcenter.push_back(x_c); // ANN window center point
1932 RealPointsYcenter.push_back(y_c);
1933
1934 return true;
1935}
1936//------------------------------------------------------------------------------
1937
1938// The same as AddRealPointFull_ANNStripGenerator_Fdeep, the standard method is parallel
1940 Double_t y,
1941 Double_t z,
1942 Double_t px,
1943 Double_t py,
1944 Double_t pz,
1945 Double_t signal,
1946 Int_t refID,
1947 Int_t pdg_code,
1948 Double_t bfield,
1949 Bool_t onlyStandard,
1950 vector<Int_t> segs)
1951{
1952 const Double_t maxThetaAngle = kANNMaxThetaAngle; // max |theta| (deg) of the track for the ANN
1953 const Bool_t kParallelANNLayers = true; // [PARALLEL-ANN] true - ANN prediction for the strip layers in parallel
1954
1955 // Model format: [px, py, pz, drift, bfield, s0..s63, strip_layer, strip_order, center_frac] -> window of N
1956 // strips (N odd, from the model) centered at the middle of the drift gap (GetANNWindowCenterPoint)
1957 const Int_t n_track_segments = 64; // track segmentation (segmented charge distribution)
1958 const Int_t n_segs_features = n_track_segments; // segments passed to the model
1959 const Int_t n_ann_inputs = 5 + n_segs_features + 3; // total number of input features
1960 Int_t n_ann_strips = 0; // model output: strip window size (odd), taken from the model below
1961
1962 // Test outputs are cleared always (cheap if they are empty)
1964
1965 // fdeep model: shared between modules, checked once for each model -------
1966 std::shared_ptr<const BmnGemStripFdeepModel> ann_holder;
1967 const BmnGemStripFdeepModel* ann_model = nullptr;
1968 if (!onlyStandard) {
1969 ann_holder = BmnGemStripFdeepModel::GetShared();
1970 ann_model = ann_holder.get();
1971
1972 static std::mutex checked_mutex;
1973 static std::map<const BmnGemStripFdeepModel*,
1974 std::pair<std::shared_ptr<const BmnGemStripFdeepModel>, std::string>>
1975 checked; // model -> (holder, why it is not suitable: empty if suitable)
1976
1977 std::lock_guard<std::mutex> lock(checked_mutex);
1978 if (!ann_model) {
1979 LOG(warn) << "BmnGemStripModule::" << __func__
1980 << "(): fdeep model is not available, the standard method is used";
1981 } else {
1982 auto it = checked.find(ann_model);
1983 if (it == checked.end()) {
1984 std::string why;
1985 if (ann_model->GetInputDim() != n_ann_inputs)
1986 why = "input " + std::to_string(ann_model->GetInputDim()) + ", expected "
1987 + std::to_string(n_ann_inputs);
1988 else
1989 why = CheckANNStripWindowSize(ann_model->GetOutputDim());
1990 if (why.empty())
1991 LOG(info) << "BmnGemStripModule::" << __func__ << "(): fdeep model " << ann_model->GetFilePath()
1992 << " is used (ANN for |theta| < " << maxThetaAngle << " deg, window "
1993 << ann_model->GetOutputDim() << " strips)";
1994 it = checked.emplace(ann_model, std::make_pair(ann_holder, why)).first;
1995 }
1996 if (!it->second.second.empty()) {
1997 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): the model " << ann_model->GetFilePath()
1998 << " is not suitable (" << it->second.second << "), the standard method is used";
1999 ann_model = nullptr;
2000 }
2001 }
2002 }
2003 if (ann_model)
2004 n_ann_strips = ann_model->GetOutputDim(); // window size of the model (checked above: odd, >= 3)
2005 //--------------------------------------------------------------------------
2006
2007 // Condition: a start point is inside the module and outside its dead zone (in strip layers)
2008 if (!IsPointInsideModule(x, y)) {
2009 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
2010 << ") is out of the readout plane or inside a dead zone";
2011 return false;
2012 }
2013
2014 // Particle: pdg_code = 0 -> from the MC track of the point refID (as in AddRealPointFull) --
2015 Int_t pdg = pdg_code;
2016 if (pdg == 0) {
2017 FairRunSim* run = FairRunSim::Instance();
2018 BmnGemStripDigitizer* digi = run ? (BmnGemStripDigitizer*)run->GetTask("GemDigitizer") : nullptr;
2019 TClonesArray* points = digi ? digi->GetMCPoints() : nullptr;
2020 TClonesArray* mctracks = digi ? digi->GetMCTracks() : nullptr;
2021
2022 CbmMCTrack* mcTr = nullptr;
2023 if (points && mctracks && refID >= 0 && refID < points->GetEntriesFast()) {
2024 CbmStsPoint* point = (CbmStsPoint*)points->UncheckedAt(refID);
2025 if (point && point->GetTrackID() >= 0 && point->GetTrackID() < mctracks->GetEntriesFast())
2026 mcTr = (CbmMCTrack*)mctracks->UncheckedAt(point->GetTrackID());
2027 }
2028
2029 if (mcTr) {
2030 pdg = mcTr->GetPdgCode();
2031 } else {
2032 static std::atomic<bool> no_mc_reported{false};
2033 if (!no_mc_reported.exchange(true))
2034 LOG(warn) << "BmnGemStripModule::" << __func__
2035 << "(): pdg_code = 0 and the MC track is not available (no GemDigitizer or MC point), "
2036 "the He4 mass is used (this warning is printed once)";
2037 }
2038 }
2039
2040 TVector3 mom(px, py, pz);
2041 TParticlePDG* part = (pdg != 0) ? TDatabasePDG::Instance()->GetParticle(pdg) : nullptr;
2042
2043 Double_t mnoc = 1.0, mass = 3.7283999; // He4
2044 if (part)
2045 mass = part->Mass();
2046 if (mass > 0.0001) {
2047 TLorentzVector lorv;
2048 lorv.SetVectM(mom, mass);
2049 Double_t beta = lorv.Beta();
2050 Double_t gamma = lorv.Gamma();
2051 Double_t charge = (part) ? part->Charge() / 3 : 1;
2052 mnoc = GetNumberOfClusters(beta, gamma, charge, 1.787, 12.33);
2053 if (mnoc < 0.5)
2054 return kFALSE;
2055 if (MaxClustersPerCm > 0.0 && mnoc > MaxClustersPerCm)
2056 mnoc = MaxClustersPerCm;
2057 }
2058 //--------------------------------------------------------------------------
2059
2060 // Distance from entry point (x,y,z) to exit point (along z-axis)
2061 Double_t z_distance_from_in_to_out;
2062
2063 // Condition: track direction along z-axis
2064 if (pz > 0.0) {
2065 z_distance_from_in_to_out = ModuleThickness - (z - ZStartModulePosition);
2066 } else {
2067 z_distance_from_in_to_out = z - ZStartModulePosition;
2068 }
2069
2070 // Condition: track distance along z-axis must be not zero
2071 if (z_distance_from_in_to_out <= 0.0) {
2072 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
2073 << ") has a track with an incorrect length";
2074 return false;
2075 }
2076
2077 // Scale coefficient of the track vector (the path from entry point to exit point)
2078 Double_t vector_coeff = fabs(z_distance_from_in_to_out / pz);
2079
2080 // Components of the track vector
2081 Double_t x_vec_from_in_to_out = vector_coeff * px;
2082 Double_t y_vec_from_in_to_out = vector_coeff * py;
2083 Double_t z_vec_from_in_to_out = vector_coeff * pz;
2084
2085 // Exit point coordinates (x_out, y_out, z_out)
2086 Double_t x_out = x + x_vec_from_in_to_out;
2087 Double_t y_out = y + y_vec_from_in_to_out;
2088 Double_t z_out = z + z_vec_from_in_to_out;
2089
2090 // Check if the exit point is outside the module then calculate new x_out, y_out, z_out values
2091 // Condition: x-coordinate of the exit point is inside the module
2092 if (x_out < XMinModule || x_out > XMaxModule) {
2093 if (x_out < XMinModule)
2094 x_out = XMinModule;
2095 if (x_out > XMaxModule)
2096 x_out = XMaxModule;
2097
2098 x_vec_from_in_to_out = x_out - x;
2099 vector_coeff = x_vec_from_in_to_out / px;
2100
2101 y_vec_from_in_to_out = vector_coeff * py;
2102 z_vec_from_in_to_out = vector_coeff * pz;
2103
2104 y_out = y + y_vec_from_in_to_out;
2105 z_out = z + z_vec_from_in_to_out;
2106 }
2107 // Condition: y-coordinate of the exit point is inside the module
2108 if (y_out < YMinModule || y_out > YMaxModule) {
2109 if (y_out < YMinModule)
2110 y_out = YMinModule;
2111 if (y_out > YMaxModule)
2112 y_out = YMaxModule;
2113
2114 y_vec_from_in_to_out = y_out - y;
2115 vector_coeff = y_vec_from_in_to_out / py;
2116
2117 x_vec_from_in_to_out = vector_coeff * px;
2118 z_vec_from_in_to_out = vector_coeff * pz;
2119
2120 x_out = x + x_vec_from_in_to_out;
2121 z_out = z + z_vec_from_in_to_out;
2122 }
2123 //--------------------------------------------------------------------------
2124
2125 // Center point of the ANN strip window: the track at the middle of the drift gap (computed always, also with
2126 // onlyStandard: it is stored with the point, see GetRealPointXcenter(), for the training samples)
2127 Double_t x_c = x, y_c = y;
2128 GetANNWindowCenterPoint(x, y, z, x_out, y_out, z_out, x_c, y_c);
2129
2130 // Common track length
2131 Double_t track_length =
2132 std::sqrt((x_vec_from_in_to_out) * (x_vec_from_in_to_out) + (y_vec_from_in_to_out) * (y_vec_from_in_to_out)
2133 + (z_vec_from_in_to_out) * (z_vec_from_in_to_out));
2134
2135 // Which method is used for this point: standard (electrons -> strips) or ANN (model -> strips)
2136 // (without a suitable model or with onlyStandard the standard method is used)
2137 Double_t r_vec = TMath::Sqrt(px * px + py * py + pz * pz);
2138 Double_t theta_deg_abs = TMath::ACos(TMath::Abs(pz) / r_vec) * TMath::RadToDeg();
2139 const Bool_t useANN = (theta_deg_abs < maxThetaAngle) && ann_model;
2140
2141 // ANN with external segments: the avalanche is not needed
2142 const Bool_t use_external_segs = useANN && !segs.empty();
2143 if (use_external_segs && segs.size() < static_cast<size_t>(n_segs_features)) {
2144 LOG(error) << "BmnGemStripModule::" << __func__ << "(): segment vector size = " << segs.size()
2145 << ", the model requires " << n_segs_features;
2146 return false;
2147 }
2148
2149 // Each level - distance to the readout plane
2153
2154 // Mean electron shift along x-axis (under the influence of the Lorentz force)
2155 Double_t xmean; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3)
2156
2157 // Sigma electron smearing
2158 Double_t sigma; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3+ p4*x^4 + p5*x^5)
2159
2160 // Reading the medium configuration -------------------------------------
2165
2172 //----------------------------------------------------------------------
2173
2174 // Initialization of a strip cluster in each strip layer
2175 Int_t n_strip_layers = StripLayers.size(); // number of layers in the module
2176 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
2177
2178 // Segmented charge distribution of this point (always computed when the avalanche is simulated)
2179 vector<Int_t> seg_local(n_track_segments, 0);
2180
2181 // [PARALLEL-STD] Standard method: parallel processing of the avalanche electrons -----
2182 // true - electron (x,y) are taken from gRandom sequentially, as in AddRealPointFull_ANNStripGenerator_Fdeep:
2183 // bitwise identical result, only the strip assignment is parallel;
2184 // false - electron (x,y) are generated in parallel by per-task generators (seeded from gRandom):
2185 // faster, statistically equivalent to the sequential methods, bitwise identical to
2186 // AddRealPointFull_ANNStripGenerator_MLP_Parallel (the same scheme of the random numbers).
2187 constexpr Bool_t kExactGRandomSequence = false;
2188 constexpr Long64_t kElectronsPerTask = 4096; // electrons per parallel task
2189 constexpr Long64_t kMinElectronsForParallel = 20000; // below this - sequential execution
2190
2191 struct AvalancheTask // used only if !kExactGRandomSequence
2192 {
2193 Double_t xcenter; // mean x on the readout (with the Lorentz shift)
2194 Double_t ycenter; // mean y on the readout
2195 Double_t sigma; // smearing
2196 Long64_t offset; // index of the first electron of the task (from first_electron)
2197 Long64_t n; // number of electrons in the task
2198 };
2199 std::vector<AvalancheTask> aval_tasks;
2200 Long64_t n_aval_electrons = 0;
2201 std::vector<Double_t> exact_x, exact_y; // electron positions, used only if kExactGRandomSequence
2202
2203 // Persistent thread pool: the threads are created once (on the first call) and wait for work,
2204 // so no threads are created/joined in each call. Lives until the end of the program.
2205 // Work items are taken dynamically (atomic counter) -> load balancing for avalanches of different size.
2206 struct ThreadPool
2207 {
2208 std::vector<std::thread> workers;
2209 std::mutex mtx; // protects the job description and the counters below
2210 std::mutex run_mtx; // one job at a time (in case of calls from several threads)
2211 std::condition_variable cv_start; // workers: a new job is available
2212 std::condition_variable cv_done; // caller: all workers have finished the job
2213 const std::function<void(size_t)>* job = nullptr;
2214 size_t n_items = 0;
2215 std::atomic<size_t> next{0}; // next work item
2216 size_t generation = 0; // job counter
2217 size_t n_busy = 0; // workers that have not finished the current job yet
2218 bool stop = false;
2219
2220 explicit ThreadPool(size_t n_workers)
2221 {
2222 workers.reserve(n_workers);
2223 for (size_t i = 0; i < n_workers; ++i)
2224 workers.emplace_back([this] { Loop(); });
2225 }
2226
2227 ~ThreadPool()
2228 {
2229 {
2230 std::lock_guard<std::mutex> lk(mtx);
2231 stop = true;
2232 }
2233 cv_start.notify_all();
2234 for (auto& w : workers)
2235 w.join();
2236 }
2237
2238 void Work()
2239 {
2240 for (size_t i = next.fetch_add(1); i < n_items; i = next.fetch_add(1))
2241 (*job)(i);
2242 }
2243
2244 void Loop()
2245 {
2246 size_t seen = 0;
2247 for (;;) {
2248 {
2249 std::unique_lock<std::mutex> lk(mtx);
2250 cv_start.wait(lk, [&] { return stop || generation != seen; });
2251 if (stop)
2252 return;
2253 seen = generation;
2254 }
2255 Work();
2256 {
2257 std::lock_guard<std::mutex> lk(mtx);
2258 if (--n_busy == 0)
2259 cv_done.notify_one();
2260 }
2261 }
2262 }
2263
2264 // Runs func(i) for i in [0, n) on the pool threads and the calling thread
2265 void Run(size_t n, const std::function<void(size_t)>& func)
2266 {
2267 if (n == 0)
2268 return;
2269 if (workers.empty() || n == 1) {
2270 for (size_t i = 0; i < n; ++i)
2271 func(i);
2272 return;
2273 }
2274
2275 std::lock_guard<std::mutex> run_lock(run_mtx);
2276 {
2277 std::lock_guard<std::mutex> lk(mtx);
2278 job = &func;
2279 n_items = n;
2280 next.store(0);
2281 n_busy = workers.size();
2282 ++generation;
2283 }
2284 cv_start.notify_all();
2285
2286 Work(); // the calling thread works too
2287
2288 std::unique_lock<std::mutex> lk(mtx);
2289 cv_done.wait(lk, [&] { return n_busy == 0; });
2290 job = nullptr;
2291 }
2292 };
2293
2294 // hardware threads - 1 workers (the calling thread is the last one)
2295 static ThreadPool pool(std::max(1u, std::thread::hardware_concurrency()) - 1);
2296
2297 auto parallel_for = [](size_t n, const std::function<void(size_t)>& func) { pool.Run(n, func); };
2298 //--------------------------------------------------------------------------
2299
2300 if (!use_external_segs) {
2301
2302 Double_t current_length = 0.0; // traversed length (counter)
2303 Double_t current_length_ratio = 0.0; // ratio of the traversed length to common track length (counter)
2304 Double_t current_step = 0.0; // current distance between two collision points
2305
2306 // Collection of collision points
2307 std::vector<CollPoint> collision_points;
2308
2309 // Reading the MCD value (mean collision distance = mean free flight path) [cm]
2311 MCD = 1 / mnoc; // AZ-110124
2312
2313 while (current_length < track_length) {
2314
2315 current_step = gRandom->Exp(MCD);
2316 current_length += current_step;
2317
2318 if (current_length > track_length)
2319 break;
2320
2321 current_length_ratio = current_length / track_length;
2322
2323 Double_t current_x = x + current_length_ratio * x_vec_from_in_to_out;
2324 Double_t current_y = y + current_length_ratio * y_vec_from_in_to_out;
2325 Double_t current_z = z + current_length_ratio * z_vec_from_in_to_out;
2326
2327 collision_points.push_back(CollPoint(current_x, current_y, current_z));
2328 }
2329
2330 // Electron avalanche producing for each collision point at the track
2331 // (in the ANN mode only the segmented charge distribution is filled here)
2332 for (size_t icoll = 0; icoll < collision_points.size(); ++icoll) {
2333
2334 Double_t xcoll = collision_points[icoll].x;
2335 Double_t ycoll = collision_points[icoll].y;
2336 Double_t zcoll = collision_points[icoll].z;
2337
2338 Double_t zdist; // current z-distance to the readout
2339
2340 // Find z-distance to the readout depending on the electron drift direction
2342 zdist = (ModuleThickness + ZStartModulePosition) - zcoll;
2343 } else {
2344 zdist = zcoll - ZStartModulePosition;
2345 }
2346 //------------------------------------------------------------------
2347
2348 // polynomials
2349 xmean = p0_xmean + p1_xmean * zdist + p2_xmean * zdist * zdist + p3_xmean * zdist * zdist * zdist;
2350 sigma = p0_sigma + p1_sigma * zdist + p2_sigma * zdist * zdist + p3_sigma * zdist * zdist * zdist
2351 + p4_sigma * zdist * zdist * zdist * zdist + p5_sigma * zdist * zdist * zdist * zdist * zdist;
2352
2353 // Number of electrons in the current collision
2354 Int_t n_electrons_cluster = gRandom->Landau(1.027, 0.11);
2355 if (n_electrons_cluster < 1)
2356 n_electrons_cluster = 1; // min
2357 if (n_electrons_cluster > 6)
2358 n_electrons_cluster = 6; // max
2359
2360 // Filling charge distribution -------------------------------------
2361 Double_t dist_from_start = 0.0;
2362 if (pz > 0.0) {
2363 dist_from_start =
2364 std::sqrt((xcoll - x) * (xcoll - x) + (ycoll - y) * (ycoll - y) + (zcoll - z) * (zcoll - z));
2365 } else {
2366 dist_from_start = std::sqrt((xcoll - x_out) * (xcoll - x_out) + (ycoll - y_out) * (ycoll - y_out)
2367 + (zcoll - z_out) * (zcoll - z_out));
2368 }
2369 Int_t segment = (Int_t)(dist_from_start / track_length * n_track_segments);
2370 if (segment >= n_track_segments)
2371 segment = n_track_segments - 1;
2372 //------------------------------------------------------------------
2373
2374 for (Int_t ielectron = 0; ielectron < n_electrons_cluster; ++ielectron) {
2375
2376 // Electron gain in each GEM cascade (three draws always, as in the other methods)
2377 Double_t gain_gem1 = gRandom->Exp(15);
2378 Double_t gain_gem2 = gRandom->Exp(15);
2379 Double_t gain_gem3 = gRandom->Exp(15);
2380
2381 if (gain_gem1 < 1.0)
2382 gain_gem1 = 1.0;
2383 if (gain_gem2 < 1.0)
2384 gain_gem2 = 1.0;
2385 if (gain_gem3 < 1.0)
2386 gain_gem3 = 1.0;
2387
2388 int total_gain = 0;
2389
2390 if (zdist < level1) {
2391 total_gain = 1.0;
2392 }
2393 if (zdist >= level1 && zdist < level2) {
2394 total_gain = gain_gem3;
2395 }
2396 if (zdist >= level2 && zdist < level3) {
2397 total_gain = gain_gem3 * gain_gem2;
2398 }
2399 if (zdist >= level3) {
2400 total_gain = gain_gem3 * gain_gem2 * gain_gem1;
2401 }
2402
2403 seg_local[segment] += total_gain;
2404
2405 // ANN mode: electrons are not projected on the readout, strips are taken from the model
2406 if (useANN)
2407 continue;
2408
2409 // Standard method ---------------------------------------------
2410 // [PARALLEL-STD] Only the electron positions (exact mode) or the tasks (fast mode) are produced
2411 // here; strips are assigned later in parallel (see "Standard method: parallel strip assignment")
2412 // x-shift of the electon depends on the electron drift direction
2413 const Double_t x_center =
2414 (ElectronDriftDirection == ForwardZAxisEDrift) ? (xcoll + xmean) : (xcoll - xmean);
2415
2416 if (kExactGRandomSequence) {
2417 // the same gRandom calls in the same order as in AddRealPointFull_ANNStripGenerator_Fdeep
2418 for (int igain = 0; igain < total_gain; ++igain) {
2419 exact_x.push_back(gRandom->Gaus(x_center, sigma));
2420 exact_y.push_back(gRandom->Gaus(ycoll, sigma));
2421 }
2422 } else {
2423 // positions are generated later in parallel, here - only the tasks
2424 for (Long64_t done = 0; done < total_gain; done += kElectronsPerTask) {
2425 aval_tasks.push_back({x_center, ycoll, sigma, n_aval_electrons + done,
2426 std::min<Long64_t>(kElectronsPerTask, total_gain - done)});
2427 }
2428 n_aval_electrons += total_gain;
2429 }
2430 }
2431 }
2432 }
2433
2434 // [PARALLEL-STD] Standard method: parallel strip assignment ------------
2435 // Each task counts its electrons per strip (compact local counters, no per-electron storage).
2436 // The counters of the tasks are summed in the task order and added to the clusters with one AddStrip()
2437 // per strip (as in AddRealPointFull_ANNStripGenerator_MLP): the signals are integer numbers of electrons,
2438 // so the clusters are bitwise the same as with one AddStrip() per electron and do not depend on the threads.
2439 if (!useANN) {
2440
2441 // Number of avalanche electrons of this point
2442 const size_t n_electrons = kExactGRandomSequence ? exact_x.size() : static_cast<size_t>(n_aval_electrons);
2443
2444 if (n_electrons > 0) {
2445
2446 const Bool_t run_parallel = static_cast<Long64_t>(n_electrons) >= kMinElectronsForParallel;
2447 const size_t chunk_size = static_cast<size_t>(kElectronsPerTask);
2448
2449 // Layer invariant (computed once instead of for each electron)
2450 std::vector<char> layer_vertical(n_strip_layers);
2451 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer)
2452 layer_vertical[ilayer] = TMath::Abs(StripLayers[ilayer].GetAngleDeg()) < 1.0;
2453
2454 // Counters of one task: in each layer the strips [lo, lo + cnt.size()) (the range grows if needed)
2455 struct TaskCounts
2456 {
2457 std::vector<Int_t> lo;
2458 std::vector<std::vector<Int_t>> cnt;
2459 };
2460 const size_t n_tasks =
2461 kExactGRandomSequence ? (n_electrons + chunk_size - 1) / chunk_size : aval_tasks.size();
2462 std::vector<TaskCounts> task_counts(n_tasks);
2463
2464 // One electron -> +1 on its strip in each layer (the same conditions as in the old code)
2465 auto count_electron = [&](TaskCounts& tc, Double_t x_readout, Double_t y_readout) {
2466 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2467 // NOTE: these BmnGemStripLayer methods are called from several threads,
2468 // they must not modify the layer
2469 // Condition: end electron position must be inside the layer
2470 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_readout, y_readout))
2471 continue;
2472
2473 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_readout, y_readout);
2474 // AZ-100124 - account for strip width
2475 Double_t dstrip = strip_pos - (int)strip_pos;
2476 if (layer_vertical[ilayer]) {
2477 // Vertical strips
2478 if (dstrip >= 0.4 && dstrip < 0.6)
2479 continue;
2480 } else {
2481 // Rotated strips
2482 if (dstrip >= 0.3 && dstrip < 0.7)
2483 continue;
2484 }
2485 // AZ
2486
2487 const Int_t strip = (Int_t)strip_pos;
2488 std::vector<Int_t>& cnt = tc.cnt[ilayer];
2489 Int_t& lo = tc.lo[ilayer];
2490 if (cnt.empty()) {
2491 lo = strip;
2492 cnt.assign(1, 0);
2493 } else if (strip < lo) {
2494 cnt.insert(cnt.begin(), lo - strip, 0);
2495 lo = strip;
2496 } else if (strip >= lo + static_cast<Int_t>(cnt.size())) {
2497 cnt.resize(strip - lo + 1, 0);
2498 }
2499 ++cnt[strip - lo];
2500 }
2501 };
2502
2503 auto init_counts = [&](TaskCounts& tc) {
2504 tc.lo.assign(n_strip_layers, 0);
2505 tc.cnt.assign(n_strip_layers, std::vector<Int_t>());
2506 };
2507
2508 if (kExactGRandomSequence) {
2509 // (a) Exact mode: positions are already generated (gRandom, sequentially) -> only geometry, by chunks
2510 auto process_chunk = [&](size_t ichunk) {
2511 TaskCounts& tc = task_counts[ichunk];
2512 init_counts(tc);
2513 const size_t begin = ichunk * chunk_size;
2514 const size_t end = std::min(n_electrons, begin + chunk_size);
2515 for (size_t ie = begin; ie < end; ++ie)
2516 count_electron(tc, exact_x[ie], exact_y[ie]);
2517 };
2518
2519 if (run_parallel)
2520 parallel_for(n_tasks, process_chunk);
2521 else
2522 for (size_t ichunk = 0; ichunk < n_tasks; ++ichunk)
2523 process_chunk(ichunk);
2524
2525 // Electron points on the readout (generation data)
2526 if (FillGenerationData) {
2527 XElectronPos.insert(XElectronPos.end(), exact_x.begin(), exact_x.end());
2528 YElectronPos.insert(YElectronPos.end(), exact_y.begin(), exact_y.end());
2529 ElectronSignal.resize(ElectronSignal.size() + n_electrons, 1.0);
2530 }
2531 } else {
2532 // (a) Fast mode: generation + geometry in one pass (one thread launch, one pass over memory)
2533
2534 // base seed from gRandom -> reproducible with the seed of gRandom, independent of the threads
2535 const UInt_t base_seed = static_cast<UInt_t>(gRandom->Integer(0xFFFFFFFFu));
2536
2537 // Electron points on the readout (generation data): each task writes its own range
2538 const size_t first_electron = XElectronPos.size(); // 0 after ResetElectronPointsAndClusters()
2539 if (FillGenerationData) {
2540 XElectronPos.resize(first_electron + n_electrons);
2541 YElectronPos.resize(first_electron + n_electrons);
2542 ElectronSignal.resize(first_electron + n_electrons, 1.0);
2543 }
2544
2545 auto process_task = [&](size_t itask) {
2546 const AvalancheTask& t = aval_tasks[itask];
2547 TaskCounts& tc = task_counts[itask];
2548 init_counts(tc);
2549 std::seed_seq seq{base_seed, static_cast<UInt_t>(itask)};
2550 std::mt19937_64 rng(seq);
2551 std::normal_distribution<Double_t> gaus(0.0, 1.0); // mean + sigma*N(0,1), as TRandom::Gaus
2552
2553 for (Long64_t i = 0; i < t.n; ++i) {
2554 const Double_t x_readout = t.xcenter + t.sigma * gaus(rng);
2555 const Double_t y_readout = t.ycenter + t.sigma * gaus(rng);
2556
2557 if (FillGenerationData) {
2558 const size_t ie = first_electron + static_cast<size_t>(t.offset + i);
2559 XElectronPos[ie] = x_readout;
2560 YElectronPos[ie] = y_readout;
2561 }
2562
2563 count_electron(tc, x_readout, y_readout);
2564 }
2565 };
2566
2567 if (run_parallel)
2568 parallel_for(n_tasks, process_task);
2569 else
2570 for (size_t itask = 0; itask < n_tasks; ++itask)
2571 process_task(itask);
2572 }
2573
2574 // (b) Filling the clusters: the counters of all tasks are summed (in the task order),
2575 // one AddStrip() per strip; layers are independent -> in parallel
2576 auto fill_layer = [&](size_t ilayer) {
2577 StripCluster& cluster = cluster_layers[ilayer];
2578 const Int_t n_strips_in_layer = std::max(0, StripLayers[ilayer].GetNStrips());
2579 std::vector<Double_t> strip_acc(n_strips_in_layer, 0.0);
2580 Int_t acc_min = std::numeric_limits<Int_t>::max();
2581 Int_t acc_max = -1;
2582
2583 for (size_t itask = 0; itask < n_tasks; ++itask) {
2584 const Int_t lo = task_counts[itask].lo[ilayer];
2585 const std::vector<Int_t>& cnt = task_counts[itask].cnt[ilayer];
2586 for (size_t i = 0; i < cnt.size(); ++i) {
2587 if (cnt[i] == 0)
2588 continue;
2589 const Int_t strip = lo + static_cast<Int_t>(i);
2590 if (strip >= 0 && strip < n_strips_in_layer) {
2591 strip_acc[strip] += cnt[i];
2592 if (strip < acc_min)
2593 acc_min = strip;
2594 if (strip > acc_max)
2595 acc_max = strip;
2596 } else {
2597 // out of the accumulator range: directly (the same sum as cnt[i] x AddStrip(strip, 1.0))
2598 cluster.AddStrip(strip, static_cast<Double_t>(cnt[i]));
2599 }
2600 }
2601 }
2602
2603 for (Int_t istrip = acc_min; istrip <= acc_max; ++istrip) {
2604 if (strip_acc[istrip] > 0.0)
2605 cluster.AddStrip(istrip, strip_acc[istrip]);
2606 }
2607 };
2608
2609 if (run_parallel)
2610 parallel_for(static_cast<size_t>(n_strip_layers), fill_layer);
2611 else
2612 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer)
2613 fill_layer(ilayer);
2614 }
2615 }
2616 //----------------------------------------------------------------------
2617
2618 // ANN generation (fdeep) ---------------------------------------------
2619 if (useANN) {
2620
2621 // Segments for the model: external or from the avalanche of this point
2622 const vector<Int_t>& seg_used = use_external_segs ? segs : seg_local;
2623
2624 // Normalization (px,py,pz), where r_vec must be equal to 1
2625 Double_t px_norm = px / r_vec;
2626 Double_t py_norm = py / r_vec;
2627 Double_t pz_norm = pz / r_vec;
2628
2629 // Inversion for z-reverse direction particles: the same track segment passed from (x_out, y_out)
2630 if (pz < 0.0) {
2631 px_norm = -px_norm;
2632 py_norm = -py_norm;
2633 pz_norm = -pz_norm;
2634 }
2635
2636 Double_t drift_direction = 0; // 0 is ForwardZAxisEDrift
2638 drift_direction = 1;
2639
2640 // Magnetic field: given explicitly or from the current medium configuration
2641 const Double_t bfield_used = (bfield < 0.0) ? BmnGemStripMedium::GetInstance().BField : bfield;
2642
2643 // Normalize seg. vector: [0, max] -> [0, 1] (max over the whole vector)
2644 Double_t max_segment_val = *(std::max_element(seg_used.begin(), seg_used.end()));
2645
2646 // Total charge in segment vector
2647 Double_t total_charge = 0.0;
2648 for (size_t i = 0; i < seg_used.size(); ++i)
2649 total_charge += seg_used[i];
2650
2651 // Window center point (x_c, y_c): computed above with GetANNWindowCenterPoint()
2652
2653 // Common part of the model input: [px, py, pz, drift, bfield, s0..s{n_segs_features-1}]
2654 std::vector<float> input_data;
2655 input_data.reserve(n_ann_inputs);
2656 input_data.push_back(static_cast<float>(px_norm));
2657 input_data.push_back(static_cast<float>(py_norm));
2658 input_data.push_back(static_cast<float>(pz_norm));
2659 input_data.push_back(static_cast<float>(drift_direction));
2660 input_data.push_back(static_cast<float>(bfield_used));
2661 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
2662 input_data.push_back((max_segment_val > 0.0) ? static_cast<float>(seg_used[iseg] / max_segment_val) : 0.0f);
2663
2664 if (total_charge > 0.0) {
2665 // Strip model: a window of n_ann_strips strips around the window center strip in each layer
2666 const Int_t center_strip_index = n_ann_strips / 2; // center strip of the window
2667
2668 // [PARALLEL-STD] the body of the former loop over layers: one layer, ilayer - its index
2669 auto process_layer = [&](size_t ilayer) {
2670 // The window is built around the center point (middle of the drift gap): it must be inside this layer
2671 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_c, y_c))
2672 return; // [PARALLEL-STD] was continue
2673
2674 Int_t n_strips_in_layer = StripLayers[ilayer].GetNStrips();
2675 if (n_strips_in_layer <= 0)
2676 return; // [PARALLEL-STD] was continue
2677
2678 // Center point position in strips: integer part - window center strip, fractional part - center_frac
2679 Double_t center_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_c, y_c);
2680 Int_t center_strip = (Int_t)TMath::Floor(center_pos);
2681 Double_t center_frac = center_pos - center_strip;
2682
2683 Double_t strip_layer = static_cast<Double_t>(StripLayers[ilayer].GetType()); // 0 - lower, 1 - upper
2684 Double_t strip_order =
2685 static_cast<Double_t>(StripLayers[ilayer].GetStripNumberingOrder()); // 0 - LeftToRight
2686
2687 // Layer part of the model input: [strip_layer, strip_order, center_frac]
2688 std::vector<float> layer_input = input_data;
2689 layer_input.push_back(static_cast<float>(strip_layer));
2690 layer_input.push_back(static_cast<float>(strip_order));
2691 layer_input.push_back(static_cast<float>(center_frac));
2692
2693 // Generate strip signals with AI model: n_ann_strips values in [0, 1] (max ~ 1)
2694 const auto strip_values = ann_model->Predict(layer_input);
2695 if (strip_values.size() != static_cast<std::size_t>(n_ann_strips)) {
2696 LOG(error) << "BmnGemStripModule::"
2697 << "AddRealPointFull_ANNStripGenerator_Fdeep_Parallel"
2698 << "(): model output size = " << strip_values.size() << ", expected " << n_ann_strips;
2699 return; // [PARALLEL-STD] was continue
2700 }
2701
2702 // Sum of the model output over all strips of the window
2703 Double_t sum_norm = 0.0;
2704 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip)
2705 sum_norm += strip_values.at(istrip);
2706
2707 // Scale coeff for total signal distribution (separate for lower and upper layers)
2708 const Double_t ann_correction_lower = 0.8; // Set to 1.0 to disable correction
2709 const Double_t ann_correction_upper = 0.6; // Set to 1.0 to disable correction
2710 const Double_t ann_correction =
2711 (StripLayers[ilayer].GetType() == LowerStripLayer) ? ann_correction_lower : ann_correction_upper;
2712
2713 Double_t signal_scale = 0.0;
2714 if (sum_norm > 0.0)
2715 signal_scale = (total_charge / sum_norm) * ann_correction;
2716
2717 Double_t threshold = 0.0001 * total_charge;
2718
2719 // Strips are written directly: window index -> strip number
2720 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip) {
2721 Double_t content = strip_values.at(istrip) * signal_scale;
2722 if (content <= threshold)
2723 continue;
2724
2725 Int_t strip_num = center_strip - center_strip_index + istrip;
2726 if (strip_num < 0 || strip_num >= n_strips_in_layer)
2727 continue; // the window goes out of the layer
2728
2729 cluster_layers[ilayer].AddStrip(strip_num, content);
2730 }
2731 };
2732
2733 // [PARALLEL-STD] layers in parallel (each layer writes only to its own cluster) or sequentially
2734 if (kParallelANNLayers)
2735 parallel_for(static_cast<size_t>(n_strip_layers), process_layer);
2736 else
2737 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer)
2738 process_layer(ilayer);
2739 }
2740 }
2741 //----------------------------------------------------------------------
2742
2743 // Segmented charge distribution (generation data) ------------------------
2744 // (seg_used above is not used after this point)
2745 if (FillGenerationData) {
2746 if (use_external_segs)
2747 SegmentedChargeDistribution = std::move(segs);
2748 else
2749 SegmentedChargeDistribution = std::move(seg_local);
2750 }
2751 //----------------------------------------------------------------------
2752
2753 // Calculate cluster parameters --------------------------------------------
2754 vector<Double_t> cluster_mean_position(n_strip_layers, 0.0);
2755 vector<Double_t> cluster_total_signal(n_strip_layers, 0.0);
2756
2757 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2758
2759 if (cluster_layers[ilayer].GetClusterSize() > 0) {
2760
2761 for (int i = 0; i < cluster_layers[ilayer].GetClusterSize(); ++i) {
2762 cluster_mean_position[ilayer] += (cluster_layers[ilayer].Strips[i] + 0.5)
2763 * cluster_layers[ilayer].Signals[i]; // as sum of all positions
2764 cluster_total_signal[ilayer] += cluster_layers[ilayer].Signals[i];
2765 }
2766 cluster_mean_position[ilayer] /= cluster_total_signal[ilayer];
2767 }
2768
2769 cluster_layers[ilayer].MeanPosition = cluster_mean_position[ilayer]; // mean cluster position
2770 cluster_layers[ilayer].TotalSignal = cluster_total_signal[ilayer]; // total signal of the cluster
2771 }
2772
2774 if (pz > 0.0) {
2775 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2776 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
2777 }
2778 } else {
2779 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2780 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
2781 }
2782 }
2783 } else {
2784 if (pz > 0.0) {
2785 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2786 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
2787 }
2788 } else {
2789 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2790 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
2791 }
2792 }
2793 }
2794
2795 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2796 cluster_layers[ilayer].PositionResidual =
2797 cluster_layers[ilayer].MeanPosition - cluster_layers[ilayer].OriginPosition;
2798 cluster_layers[ilayer].SetType(StripLayers[ilayer].GetType());
2799 }
2800 //----------------------------------------------------------------------
2801
2802 // Added clusters (generation data) ----------------------------------------
2803 if (FillGenerationData) {
2804 if (AddedClusters.size() == 0) {
2805 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2806 AddedClusters.push_back(vector<StripCluster>());
2807 }
2808 }
2809
2810 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2811 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
2812 }
2813 }
2814 //------------------------------------------------------------------------------
2815
2816 // Add the correct clusters to the strip layers -------------------------
2817 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2818 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
2819 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
2820 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
2821 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
2822 }
2823 }
2824 //----------------------------------------------------------------------
2825
2826 // Fill strip matches ---------------------------------------------------
2827 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
2828 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
2829 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
2830 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
2831 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
2832 refID);
2833 }
2834 }
2835 //----------------------------------------------------------------------
2836
2837 RealPointsX.push_back(x);
2838 RealPointsY.push_back(y);
2839 RealPointsXout.push_back(x_out);
2840 RealPointsYout.push_back(y_out);
2841 RealPointsMC.push_back(refID);
2842 RealPointsXcenter.push_back(x_c); // ANN window center point
2843 RealPointsYcenter.push_back(y_c);
2844
2845 return true;
2846}
2847//------------------------------------------------------------------------------
2848
2849// Strip generation with the own MLP inference (BmnGemStripMLPModel): standard method or ANN (|theta| <
2850// kANNMaxThetaAngle). pdg_code = 0 -> from MC, bfield < 0 -> from BmnGemStripMedium; ANN with external segs - no
2851// avalanche. Generation data only with SetFillGenerationData(kTRUE)
2853 Double_t y,
2854 Double_t z,
2855 Double_t px,
2856 Double_t py,
2857 Double_t pz,
2858 Double_t signal,
2859 Int_t refID,
2860 Int_t pdg_code,
2861 Double_t bfield,
2862 Bool_t onlyStandard,
2863 vector<Int_t> segs)
2864{
2865 const Double_t maxThetaAngle = kANNMaxThetaAngle; // max |theta| (deg) of the track for the ANN
2866
2867 // Model format: [px, py, pz, drift, bfield, s0..s63, strip_layer, strip_order, center_frac] -> window of N
2868 // strips (N odd, from the model) centered at the middle of the drift gap (GetANNWindowCenterPoint)
2869 const Int_t n_track_segments = 64; // track segmentation (segmented charge distribution)
2870 const Int_t n_segs_features = n_track_segments; // segments passed to the model
2871 const Int_t n_common_inputs = 5 + n_segs_features; // the same for all strip layers of the point
2872 const Int_t n_layer_inputs = 3; // [strip_layer, strip_order, center_frac]
2873 Int_t n_ann_strips = 0; // model output: strip window size (odd), taken from the model below
2874
2875 // Test outputs are cleared always (cheap if they are empty)
2877
2878 // MLP model: shared between modules, checked once for each model ---------
2879 std::shared_ptr<const BmnGemStripMLPModel> mlp_holder;
2880 const BmnGemStripMLPModel* mlp_model = nullptr;
2881 if (!onlyStandard) {
2882 mlp_holder = BmnGemStripMLPModel::GetShared();
2883 mlp_model = mlp_holder.get();
2884
2885 static std::mutex checked_mutex;
2886 static std::map<const BmnGemStripMLPModel*, std::pair<std::shared_ptr<const BmnGemStripMLPModel>, std::string>>
2887 checked; // model -> (holder, why it is not suitable: empty if suitable)
2888
2889 std::lock_guard<std::mutex> lock(checked_mutex);
2890 if (!mlp_model) {
2891 LOG(warn) << "BmnGemStripModule::" << __func__
2892 << "(): MLP model is not available, the standard method is used";
2893 } else {
2894 auto it = checked.find(mlp_model);
2895 if (it == checked.end()) {
2896 std::vector<std::string> expected = {"px", "py", "pz", "drift", "bfield"};
2897 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
2898 expected.push_back("s" + std::to_string(iseg));
2899 expected.insert(expected.end(), {"strip_layer", "strip_order", "center_frac"});
2900
2901 std::string why;
2902 Bool_t ok = mlp_model->CheckFeatureNames(expected, &why);
2903 if (ok) {
2904 why = CheckANNStripWindowSize(mlp_model->GetOutputDim());
2905 ok = why.empty();
2906 }
2907 if (ok) {
2908 why.clear();
2909 LOG(info) << "BmnGemStripModule::" << __func__ << "(): MLP model " << mlp_model->GetFilePath()
2910 << " is used (ANN for |theta| < " << maxThetaAngle << " deg, window "
2911 << mlp_model->GetOutputDim() << " strips)";
2912 } else if (why.empty())
2913 why = "unknown reason";
2914 it = checked.emplace(mlp_model, std::make_pair(mlp_holder, why)).first;
2915 }
2916 if (!it->second.second.empty()) {
2917 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): the model " << mlp_model->GetFilePath()
2918 << " is not suitable (" << it->second.second << "), the standard method is used";
2919 mlp_model = nullptr;
2920 }
2921 }
2922 }
2923 if (mlp_model)
2924 n_ann_strips = mlp_model->GetOutputDim(); // window size of the model (checked above: odd, >= 3)
2925 //--------------------------------------------------------------------------
2926
2927 // Condition: a start point is inside the module and outside its dead zone (in strip layers)
2928 if (!IsPointInsideModule(x, y)) {
2929 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
2930 << ") is out of the readout plane or inside a dead zone";
2931 return false;
2932 }
2933
2934 // Particle: pdg_code = 0 -> from the MC track of the point refID (as in AddRealPointFull) --
2935 Int_t pdg = pdg_code;
2936 if (pdg == 0) {
2937 FairRunSim* run = FairRunSim::Instance();
2938 BmnGemStripDigitizer* digi = run ? (BmnGemStripDigitizer*)run->GetTask("GemDigitizer") : nullptr;
2939 TClonesArray* points = digi ? digi->GetMCPoints() : nullptr;
2940 TClonesArray* mctracks = digi ? digi->GetMCTracks() : nullptr;
2941
2942 CbmMCTrack* mcTr = nullptr;
2943 if (points && mctracks && refID >= 0 && refID < points->GetEntriesFast()) {
2944 CbmStsPoint* point = (CbmStsPoint*)points->UncheckedAt(refID);
2945 if (point && point->GetTrackID() >= 0 && point->GetTrackID() < mctracks->GetEntriesFast())
2946 mcTr = (CbmMCTrack*)mctracks->UncheckedAt(point->GetTrackID());
2947 }
2948
2949 if (mcTr) {
2950 pdg = mcTr->GetPdgCode();
2951 } else {
2952 static std::atomic<bool> no_mc_reported{false};
2953 if (!no_mc_reported.exchange(true))
2954 LOG(warn) << "BmnGemStripModule::" << __func__
2955 << "(): pdg_code = 0 and the MC track is not available (no GemDigitizer or MC point), "
2956 "the He4 mass is used (this warning is printed once)";
2957 }
2958 }
2959
2960 TVector3 mom(px, py, pz);
2961 TParticlePDG* part = (pdg != 0) ? TDatabasePDG::Instance()->GetParticle(pdg) : nullptr;
2962
2963 Double_t mnoc = 1.0, mass = 3.7283999; // He4
2964 if (part)
2965 mass = part->Mass();
2966 if (mass > 0.0001) {
2967 TLorentzVector lorv;
2968 lorv.SetVectM(mom, mass);
2969 Double_t beta = lorv.Beta();
2970 Double_t gamma = lorv.Gamma();
2971 Double_t charge = (part) ? part->Charge() / 3 : 1;
2972 mnoc = GetNumberOfClusters(beta, gamma, charge, 1.787, 12.33);
2973 if (mnoc < 0.5)
2974 return kFALSE;
2975 if (MaxClustersPerCm > 0.0 && mnoc > MaxClustersPerCm)
2976 mnoc = MaxClustersPerCm;
2977 }
2978 //--------------------------------------------------------------------------
2979
2980 // Distance from entry point (x,y,z) to exit point (along z-axis)
2981 Double_t z_distance_from_in_to_out;
2982
2983 // Condition: track direction along z-axis
2984 if (pz > 0.0) {
2985 z_distance_from_in_to_out = ModuleThickness - (z - ZStartModulePosition);
2986 } else {
2987 z_distance_from_in_to_out = z - ZStartModulePosition;
2988 }
2989
2990 // Condition: track distance along z-axis must be not zero
2991 if (z_distance_from_in_to_out <= 0.0) {
2992 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
2993 << ") has a track with an incorrect length";
2994 return false;
2995 }
2996
2997 // Scale coefficient of the track vector (the path from entry point to exit point)
2998 Double_t vector_coeff = fabs(z_distance_from_in_to_out / pz);
2999
3000 // Components of the track vector
3001 Double_t x_vec_from_in_to_out = vector_coeff * px;
3002 Double_t y_vec_from_in_to_out = vector_coeff * py;
3003 Double_t z_vec_from_in_to_out = vector_coeff * pz;
3004
3005 // Exit point coordinates (x_out, y_out, z_out)
3006 Double_t x_out = x + x_vec_from_in_to_out;
3007 Double_t y_out = y + y_vec_from_in_to_out;
3008 Double_t z_out = z + z_vec_from_in_to_out;
3009
3010 // Check if the exit point is outside the module then calculate new x_out, y_out, z_out values
3011 // Condition: x-coordinate of the exit point is inside the module
3012 if (x_out < XMinModule || x_out > XMaxModule) {
3013 if (x_out < XMinModule)
3014 x_out = XMinModule;
3015 if (x_out > XMaxModule)
3016 x_out = XMaxModule;
3017
3018 x_vec_from_in_to_out = x_out - x;
3019 vector_coeff = x_vec_from_in_to_out / px;
3020
3021 y_vec_from_in_to_out = vector_coeff * py;
3022 z_vec_from_in_to_out = vector_coeff * pz;
3023
3024 y_out = y + y_vec_from_in_to_out;
3025 z_out = z + z_vec_from_in_to_out;
3026 }
3027 // Condition: y-coordinate of the exit point is inside the module
3028 if (y_out < YMinModule || y_out > YMaxModule) {
3029 if (y_out < YMinModule)
3030 y_out = YMinModule;
3031 if (y_out > YMaxModule)
3032 y_out = YMaxModule;
3033
3034 y_vec_from_in_to_out = y_out - y;
3035 vector_coeff = y_vec_from_in_to_out / py;
3036
3037 x_vec_from_in_to_out = vector_coeff * px;
3038 z_vec_from_in_to_out = vector_coeff * pz;
3039
3040 x_out = x + x_vec_from_in_to_out;
3041 z_out = z + z_vec_from_in_to_out;
3042 }
3043 //--------------------------------------------------------------------------
3044
3045 // Center point of the ANN strip window: the track at the middle of the drift gap (computed always, also with
3046 // onlyStandard: it is stored with the point, see GetRealPointXcenter(), for the training samples)
3047 Double_t x_c = x, y_c = y;
3048 GetANNWindowCenterPoint(x, y, z, x_out, y_out, z_out, x_c, y_c);
3049
3050 // Common track length
3051 Double_t track_length =
3052 std::sqrt((x_vec_from_in_to_out) * (x_vec_from_in_to_out) + (y_vec_from_in_to_out) * (y_vec_from_in_to_out)
3053 + (z_vec_from_in_to_out) * (z_vec_from_in_to_out));
3054
3055 // Which method is used for this point: standard (electrons -> strips) or ANN (model -> strips)
3056 // (without a suitable model or with onlyStandard the standard method is used)
3057 Double_t r_vec = TMath::Sqrt(px * px + py * py + pz * pz);
3058 Double_t theta_deg_abs = TMath::ACos(TMath::Abs(pz) / r_vec) * TMath::RadToDeg();
3059 const Bool_t useANN = (theta_deg_abs < maxThetaAngle) && mlp_model;
3060
3061 // ANN with external segments: the avalanche is not needed
3062 const Bool_t use_external_segs = useANN && !segs.empty();
3063 if (use_external_segs && segs.size() < static_cast<size_t>(n_segs_features)) {
3064 LOG(error) << "BmnGemStripModule::" << __func__ << "(): segment vector size = " << segs.size()
3065 << ", the model requires " << n_segs_features;
3066 return false;
3067 }
3068
3069 // Each level - distance to the readout plane
3073
3074 // Mean electron shift along x-axis (under the influence of the Lorentz force)
3075 Double_t xmean; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3)
3076
3077 // Sigma electron smearing
3078 Double_t sigma; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3+ p4*x^4 + p5*x^5)
3079
3080 // Reading the medium configuration -------------------------------------
3085
3092 //----------------------------------------------------------------------
3093
3094 // Initialization of a strip cluster in each strip layer
3095 Int_t n_strip_layers = StripLayers.size(); // number of layers in the module
3096 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
3097
3098 // Segmented charge distribution of this point (always computed when the avalanche is simulated)
3099 vector<Int_t> seg_local(n_track_segments, 0);
3100
3101 // Standard method: per-layer strip accumulators -------------------------
3102 // Signals are accumulated per strip and added to the cluster with one AddStrip() per strip:
3103 // AddStrip() sums the signals and keeps the strips in ascending order, the signals are integer
3104 // numbers of electrons -> the cluster is bitwise the same as with one AddStrip() per electron.
3105 vector<vector<Double_t>> strip_acc;
3106 vector<Int_t> acc_min, acc_max; // range of the touched strips in each layer
3107 vector<char> layer_vertical; // layer invariant: vertical strips (|angle| < 1 deg)
3108 if (!useANN) {
3109 strip_acc.resize(n_strip_layers);
3110 acc_min.assign(n_strip_layers, std::numeric_limits<Int_t>::max());
3111 acc_max.assign(n_strip_layers, -1);
3112 layer_vertical.assign(n_strip_layers, 0);
3113 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3114 strip_acc[ilayer].assign(std::max(0, StripLayers[ilayer].GetNStrips()), 0.0);
3115 layer_vertical[ilayer] = TMath::Abs(StripLayers[ilayer].GetAngleDeg()) < 1.0;
3116 }
3117 }
3118 //--------------------------------------------------------------------------
3119
3120 if (!use_external_segs) {
3121
3122 Double_t current_length = 0.0; // traversed length (counter)
3123 Double_t current_length_ratio = 0.0; // ratio of the traversed length to common track length (counter)
3124 Double_t current_step = 0.0; // current distance between two collision points
3125
3126 // Collection of collision points
3127 std::vector<CollPoint> collision_points;
3128
3129 // Reading the MCD value (mean collision distance = mean free flight path) [cm]
3131 MCD = 1 / mnoc; // AZ-110124
3132
3133 while (current_length < track_length) {
3134
3135 current_step = gRandom->Exp(MCD);
3136 current_length += current_step;
3137
3138 if (current_length > track_length)
3139 break;
3140
3141 current_length_ratio = current_length / track_length;
3142
3143 Double_t current_x = x + current_length_ratio * x_vec_from_in_to_out;
3144 Double_t current_y = y + current_length_ratio * y_vec_from_in_to_out;
3145 Double_t current_z = z + current_length_ratio * z_vec_from_in_to_out;
3146
3147 collision_points.push_back(CollPoint(current_x, current_y, current_z));
3148 }
3149
3150 // Electron avalanche producing for each collision point at the track
3151 // (in the ANN mode only the segmented charge distribution is filled here)
3152 for (size_t icoll = 0; icoll < collision_points.size(); ++icoll) {
3153
3154 Double_t xcoll = collision_points[icoll].x;
3155 Double_t ycoll = collision_points[icoll].y;
3156 Double_t zcoll = collision_points[icoll].z;
3157
3158 Double_t zdist; // current z-distance to the readout
3159
3160 // Find z-distance to the readout depending on the electron drift direction
3162 zdist = (ModuleThickness + ZStartModulePosition) - zcoll;
3163 } else {
3164 zdist = zcoll - ZStartModulePosition;
3165 }
3166 //------------------------------------------------------------------
3167
3168 // polynomials
3169 xmean = p0_xmean + p1_xmean * zdist + p2_xmean * zdist * zdist + p3_xmean * zdist * zdist * zdist;
3170 sigma = p0_sigma + p1_sigma * zdist + p2_sigma * zdist * zdist + p3_sigma * zdist * zdist * zdist
3171 + p4_sigma * zdist * zdist * zdist * zdist + p5_sigma * zdist * zdist * zdist * zdist * zdist;
3172
3173 // Number of electrons in the current collision
3174 Int_t n_electrons_cluster = gRandom->Landau(1.027, 0.11);
3175 if (n_electrons_cluster < 1)
3176 n_electrons_cluster = 1; // min
3177 if (n_electrons_cluster > 6)
3178 n_electrons_cluster = 6; // max
3179
3180 // Filling charge distribution -------------------------------------
3181 Double_t dist_from_start = 0.0;
3182 if (pz > 0.0) {
3183 dist_from_start =
3184 std::sqrt((xcoll - x) * (xcoll - x) + (ycoll - y) * (ycoll - y) + (zcoll - z) * (zcoll - z));
3185 } else {
3186 dist_from_start = std::sqrt((xcoll - x_out) * (xcoll - x_out) + (ycoll - y_out) * (ycoll - y_out)
3187 + (zcoll - z_out) * (zcoll - z_out));
3188 }
3189 Int_t segment = (Int_t)(dist_from_start / track_length * n_track_segments);
3190 if (segment >= n_track_segments)
3191 segment = n_track_segments - 1;
3192 //------------------------------------------------------------------
3193
3194 for (Int_t ielectron = 0; ielectron < n_electrons_cluster; ++ielectron) {
3195
3196 // Electron gain in each GEM cascade (three draws always, as in the other methods)
3197 Double_t gain_gem1 = gRandom->Exp(15);
3198 Double_t gain_gem2 = gRandom->Exp(15);
3199 Double_t gain_gem3 = gRandom->Exp(15);
3200
3201 if (gain_gem1 < 1.0)
3202 gain_gem1 = 1.0;
3203 if (gain_gem2 < 1.0)
3204 gain_gem2 = 1.0;
3205 if (gain_gem3 < 1.0)
3206 gain_gem3 = 1.0;
3207
3208 int total_gain = 0;
3209
3210 if (zdist < level1) {
3211 total_gain = 1.0;
3212 }
3213 if (zdist >= level1 && zdist < level2) {
3214 total_gain = gain_gem3;
3215 }
3216 if (zdist >= level2 && zdist < level3) {
3217 total_gain = gain_gem3 * gain_gem2;
3218 }
3219 if (zdist >= level3) {
3220 total_gain = gain_gem3 * gain_gem2 * gain_gem1;
3221 }
3222
3223 seg_local[segment] += total_gain;
3224
3225 // ANN mode: electrons are not projected on the readout, strips are taken from the model
3226 if (useANN)
3227 continue;
3228
3229 // Standard method ---------------------------------------------
3230 // Projection of the current electron on the readout (x,y-coordinates)
3231 double x_readout, y_readout;
3232
3233 for (int igain = 0; igain < total_gain; ++igain) {
3234
3235 // x-shift of the electon depends on the electron drift direction
3237 x_readout = gRandom->Gaus(xcoll + xmean, sigma);
3238 } else {
3239 x_readout = gRandom->Gaus(xcoll - xmean, sigma);
3240 }
3241
3242 y_readout = gRandom->Gaus(ycoll, sigma);
3243
3244 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3245 // Condition: end electron position must be inside the layer
3246 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_readout, y_readout))
3247 continue;
3248
3249 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_readout, y_readout);
3250 // AZ-100124 - account for strip width
3251 Double_t dstrip = strip_pos - (int)strip_pos;
3252 if (layer_vertical[ilayer]) {
3253 // Vertical strips
3254 if (dstrip >= 0.4 && dstrip < 0.6)
3255 continue;
3256 } else {
3257 // Rotated strips
3258 if (dstrip >= 0.3 && dstrip < 0.7)
3259 continue;
3260 }
3261 // AZ
3262
3263 const Int_t strip_num = (Int_t)strip_pos;
3264 if (strip_num >= 0 && strip_num < (Int_t)strip_acc[ilayer].size()) {
3265 strip_acc[ilayer][strip_num] += 1.0;
3266 if (strip_num < acc_min[ilayer])
3267 acc_min[ilayer] = strip_num;
3268 if (strip_num > acc_max[ilayer])
3269 acc_max[ilayer] = strip_num;
3270 } else {
3271 // out of the accumulator range: directly, as in the other methods
3272 cluster_layers[ilayer].AddStrip(strip_num, 1.0);
3273 }
3274 }
3275
3276 // Electron points on the readout (generation data) ---------
3277 if (FillGenerationData) {
3278 XElectronPos.push_back(x_readout);
3279 YElectronPos.push_back(y_readout);
3280 ElectronSignal.push_back(1.0);
3281 }
3282 //----------------------------------------------------------
3283 }
3284 }
3285 }
3286 }
3287
3288 // Standard method: accumulated signals -> clusters (one AddStrip per strip)
3289 if (!useANN) {
3290 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3291 for (Int_t istrip = acc_min[ilayer]; istrip <= acc_max[ilayer]; ++istrip) {
3292 if (strip_acc[ilayer][istrip] > 0.0)
3293 cluster_layers[ilayer].AddStrip(istrip, strip_acc[ilayer][istrip]);
3294 }
3295 }
3296 }
3297 //----------------------------------------------------------------------
3298
3299 // ANN generation (MLP, own inference) ---------------------------------
3300 if (useANN) {
3301
3302 // Segments for the model: external or from the avalanche of this point
3303 const vector<Int_t>& seg_used = use_external_segs ? segs : seg_local;
3304
3305 // Normalization (px,py,pz), where r_vec must be equal to 1
3306 Double_t px_norm = px / r_vec;
3307 Double_t py_norm = py / r_vec;
3308 Double_t pz_norm = pz / r_vec;
3309
3310 // Inversion for z-reverse direction particles: the same track segment passed from (x_out, y_out)
3311 if (pz < 0.0) {
3312 px_norm = -px_norm;
3313 py_norm = -py_norm;
3314 pz_norm = -pz_norm;
3315 }
3316
3317 Double_t drift_direction = 0; // 0 is ForwardZAxisEDrift
3319 drift_direction = 1;
3320
3321 // Magnetic field: given explicitly or from the current medium configuration
3322 const Double_t bfield_used = (bfield < 0.0) ? BmnGemStripMedium::GetInstance().BField : bfield;
3323
3324 // Normalize seg. vector: [0, max] -> [0, 1] (max over the whole vector)
3325 Double_t max_segment_val = *(std::max_element(seg_used.begin(), seg_used.end()));
3326
3327 // Total charge in segment vector
3328 Double_t total_charge = 0.0;
3329 for (size_t i = 0; i < seg_used.size(); ++i)
3330 total_charge += seg_used[i];
3331
3332 // Window center point (x_c, y_c): computed above with GetANNWindowCenterPoint()
3333
3334 // Common part of the model input: [px, py, pz, drift, bfield, s0..s{n_segs_features-1}]
3335 float common_input[n_common_inputs];
3336 common_input[0] = static_cast<float>(px_norm);
3337 common_input[1] = static_cast<float>(py_norm);
3338 common_input[2] = static_cast<float>(pz_norm);
3339 common_input[3] = static_cast<float>(drift_direction);
3340 common_input[4] = static_cast<float>(bfield_used);
3341 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
3342 common_input[5 + iseg] =
3343 (max_segment_val > 0.0) ? static_cast<float>(seg_used[iseg] / max_segment_val) : 0.0f;
3344
3345 if (total_charge > 0.0) {
3346 // Strip model: a window of n_ann_strips strips around the window center strip in each layer
3347 const Int_t center_strip_index = n_ann_strips / 2; // center strip of the window
3348
3349 // Layers with the MC point inside: their part of the input [strip_layer, strip_order, center_frac]
3350 std::vector<Int_t> ann_layers; // layer index
3351 std::vector<Int_t> ann_center_strips; // MC strip (window center)
3352 std::vector<Int_t> ann_n_strips; // number of strips in the layer
3353 std::vector<float> ann_layer_inputs; // [n_ann_layers][n_layer_inputs]
3354 ann_layers.reserve(n_strip_layers);
3355 ann_center_strips.reserve(n_strip_layers);
3356 ann_n_strips.reserve(n_strip_layers);
3357 ann_layer_inputs.reserve(n_strip_layers * n_layer_inputs);
3358
3359 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3360
3361 // The window is built around the center point (middle of the drift gap): it must be inside this layer
3362 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_c, y_c))
3363 continue;
3364
3365 Int_t n_strips_in_layer = StripLayers[ilayer].GetNStrips();
3366 if (n_strips_in_layer <= 0)
3367 continue;
3368
3369 // Center point position in strips: integer part - window center strip, fractional part - center_frac
3370 Double_t center_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_c, y_c);
3371 Int_t center_strip = (Int_t)TMath::Floor(center_pos);
3372 Double_t center_frac = center_pos - center_strip;
3373
3374 Double_t strip_layer = static_cast<Double_t>(StripLayers[ilayer].GetType()); // 0 - lower, 1 - upper
3375 Double_t strip_order =
3376 static_cast<Double_t>(StripLayers[ilayer].GetStripNumberingOrder()); // 0 - LeftToRight
3377
3378 ann_layers.push_back(ilayer);
3379 ann_center_strips.push_back(center_strip);
3380 ann_n_strips.push_back(n_strips_in_layer);
3381 ann_layer_inputs.push_back(static_cast<float>(strip_layer));
3382 ann_layer_inputs.push_back(static_cast<float>(strip_order));
3383 ann_layer_inputs.push_back(static_cast<float>(center_frac));
3384 }
3385
3386 // Strip signals for all these layers in one call: n_ann_strips values in [0, 1] (max ~ 1) per layer
3387 Int_t n_ann_layers = ann_layers.size();
3388 std::vector<float> ann_strip_values(static_cast<size_t>(n_ann_layers) * n_ann_strips);
3389 if (n_ann_layers > 0
3390 && !mlp_model->PredictSplit(common_input, n_common_inputs, ann_layer_inputs.data(), n_ann_layers,
3391 ann_strip_values.data()))
3392 {
3393 LOG(error) << "BmnGemStripModule::" << __func__ << "(): MLP prediction failed";
3394 n_ann_layers = 0;
3395 }
3396
3397 for (Int_t iann = 0; iann < n_ann_layers; ++iann) {
3398 const Int_t ilayer = ann_layers[iann];
3399 const Int_t center_strip = ann_center_strips[iann];
3400 const Int_t n_strips_in_layer = ann_n_strips[iann];
3401 const float* strip_values = ann_strip_values.data() + static_cast<size_t>(iann) * n_ann_strips;
3402
3403 // Sum of the model output over all strips of the window
3404 Double_t sum_norm = 0.0;
3405 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip)
3406 sum_norm += strip_values[istrip];
3407
3408 // Scale coeff for total signal distribution (separate for lower and upper layers)
3409 const Double_t ann_correction_lower = 0.8; // Set to 1.0 to disable correction
3410 const Double_t ann_correction_upper = 0.6; // Set to 1.0 to disable correction
3411 const Double_t ann_correction =
3412 (StripLayers[ilayer].GetType() == LowerStripLayer) ? ann_correction_lower : ann_correction_upper;
3413
3414 Double_t signal_scale = 0.0;
3415 if (sum_norm > 0.0)
3416 signal_scale = (total_charge / sum_norm) * ann_correction;
3417
3418 Double_t threshold = 0.0001 * total_charge;
3419
3420 // Strips are written directly: window index -> strip number
3421 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip) {
3422 Double_t content = strip_values[istrip] * signal_scale;
3423 if (content <= threshold)
3424 continue;
3425
3426 Int_t strip_num = center_strip - center_strip_index + istrip;
3427 if (strip_num < 0 || strip_num >= n_strips_in_layer)
3428 continue; // the window goes out of the layer
3429
3430 cluster_layers[ilayer].AddStrip(strip_num, content);
3431 }
3432 }
3433 }
3434 }
3435 //----------------------------------------------------------------------
3436
3437 // Segmented charge distribution (generation data) ------------------------
3438 // (seg_used above is not used after this point)
3439 if (FillGenerationData) {
3440 if (use_external_segs)
3441 SegmentedChargeDistribution = std::move(segs);
3442 else
3443 SegmentedChargeDistribution = std::move(seg_local);
3444 }
3445 //----------------------------------------------------------------------
3446
3447 // Calculate cluster parameters --------------------------------------------
3448 vector<Double_t> cluster_mean_position(n_strip_layers, 0.0);
3449 vector<Double_t> cluster_total_signal(n_strip_layers, 0.0);
3450
3451 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3452
3453 if (cluster_layers[ilayer].GetClusterSize() > 0) {
3454
3455 for (int i = 0; i < cluster_layers[ilayer].GetClusterSize(); ++i) {
3456 cluster_mean_position[ilayer] += (cluster_layers[ilayer].Strips[i] + 0.5)
3457 * cluster_layers[ilayer].Signals[i]; // as sum of all positions
3458 cluster_total_signal[ilayer] += cluster_layers[ilayer].Signals[i];
3459 }
3460 cluster_mean_position[ilayer] /= cluster_total_signal[ilayer];
3461 }
3462
3463 cluster_layers[ilayer].MeanPosition = cluster_mean_position[ilayer]; // mean cluster position
3464 cluster_layers[ilayer].TotalSignal = cluster_total_signal[ilayer]; // total signal of the cluster
3465 }
3466
3468 if (pz > 0.0) {
3469 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3470 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
3471 }
3472 } else {
3473 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3474 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
3475 }
3476 }
3477 } else {
3478 if (pz > 0.0) {
3479 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3480 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
3481 }
3482 } else {
3483 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3484 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
3485 }
3486 }
3487 }
3488
3489 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3490 cluster_layers[ilayer].PositionResidual =
3491 cluster_layers[ilayer].MeanPosition - cluster_layers[ilayer].OriginPosition;
3492 cluster_layers[ilayer].SetType(StripLayers[ilayer].GetType());
3493 }
3494 //----------------------------------------------------------------------
3495
3496 // Added clusters (generation data) ----------------------------------------
3497 if (FillGenerationData) {
3498 if (AddedClusters.size() == 0) {
3499 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3500 AddedClusters.push_back(vector<StripCluster>());
3501 }
3502 }
3503
3504 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3505 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
3506 }
3507 }
3508 //------------------------------------------------------------------------------
3509
3510 // Add the correct clusters to the strip layers -------------------------
3511 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3512 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
3513 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
3514 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
3515 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
3516 }
3517 }
3518 //----------------------------------------------------------------------
3519
3520 // Fill strip matches ---------------------------------------------------
3521 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
3522 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
3523 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
3524 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
3525 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
3526 refID);
3527 }
3528 }
3529 //----------------------------------------------------------------------
3530
3531 RealPointsX.push_back(x);
3532 RealPointsY.push_back(y);
3533 RealPointsXout.push_back(x_out);
3534 RealPointsYout.push_back(y_out);
3535 RealPointsMC.push_back(refID);
3536 RealPointsXcenter.push_back(x_c); // ANN window center point
3537 RealPointsYcenter.push_back(y_c);
3538
3539 return true;
3540}
3541//------------------------------------------------------------------------------
3542
3543// The same as AddRealPointFull_ANNStripGenerator_MLP, the standard method is parallel (std::thread);
3545 Double_t y,
3546 Double_t z,
3547 Double_t px,
3548 Double_t py,
3549 Double_t pz,
3550 Double_t signal,
3551 Int_t refID,
3552 Int_t pdg_code,
3553 Double_t bfield,
3554 Bool_t onlyStandard,
3555 vector<Int_t> segs)
3556{
3557 const Double_t maxThetaAngle = kANNMaxThetaAngle; // max |theta| (deg) of the track for the ANN
3558
3559 // Model format: [px, py, pz, drift, bfield, s0..s63, strip_layer, strip_order, center_frac] -> window of N
3560 // strips (N odd, from the model) centered at the middle of the drift gap (GetANNWindowCenterPoint)
3561 const Int_t n_track_segments = 64; // track segmentation (segmented charge distribution)
3562 const Int_t n_segs_features = n_track_segments; // segments passed to the model
3563 const Int_t n_common_inputs = 5 + n_segs_features; // the same for all strip layers of the point
3564 const Int_t n_layer_inputs = 3; // [strip_layer, strip_order, center_frac]
3565 Int_t n_ann_strips = 0; // model output: strip window size (odd), taken from the model below
3566
3567 // Test outputs are cleared always (cheap if they are empty)
3569
3570 // MLP model: shared between modules, checked once for each model ---------
3571 std::shared_ptr<const BmnGemStripMLPModel> mlp_holder;
3572 const BmnGemStripMLPModel* mlp_model = nullptr;
3573 if (!onlyStandard) {
3574 mlp_holder = BmnGemStripMLPModel::GetShared();
3575 mlp_model = mlp_holder.get();
3576
3577 static std::mutex checked_mutex;
3578 static std::map<const BmnGemStripMLPModel*, std::pair<std::shared_ptr<const BmnGemStripMLPModel>, std::string>>
3579 checked; // model -> (holder, why it is not suitable: empty if suitable)
3580
3581 std::lock_guard<std::mutex> lock(checked_mutex);
3582 if (!mlp_model) {
3583 LOG(warn) << "BmnGemStripModule::" << __func__
3584 << "(): MLP model is not available, the standard method is used";
3585 } else {
3586 auto it = checked.find(mlp_model);
3587 if (it == checked.end()) {
3588 std::vector<std::string> expected = {"px", "py", "pz", "drift", "bfield"};
3589 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
3590 expected.push_back("s" + std::to_string(iseg));
3591 expected.insert(expected.end(), {"strip_layer", "strip_order", "center_frac"});
3592
3593 std::string why;
3594 Bool_t ok = mlp_model->CheckFeatureNames(expected, &why);
3595 if (ok) {
3596 why = CheckANNStripWindowSize(mlp_model->GetOutputDim());
3597 ok = why.empty();
3598 }
3599 if (ok) {
3600 why.clear();
3601 LOG(info) << "BmnGemStripModule::" << __func__ << "(): MLP model " << mlp_model->GetFilePath()
3602 << " is used (ANN for |theta| < " << maxThetaAngle << " deg, window "
3603 << mlp_model->GetOutputDim() << " strips)";
3604 } else if (why.empty())
3605 why = "unknown reason";
3606 it = checked.emplace(mlp_model, std::make_pair(mlp_holder, why)).first;
3607 }
3608 if (!it->second.second.empty()) {
3609 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): the model " << mlp_model->GetFilePath()
3610 << " is not suitable (" << it->second.second << "), the standard method is used";
3611 mlp_model = nullptr;
3612 }
3613 }
3614 }
3615 if (mlp_model)
3616 n_ann_strips = mlp_model->GetOutputDim(); // window size of the model (checked above: odd, >= 3)
3617 //--------------------------------------------------------------------------
3618
3619 // Condition: a start point is inside the module and outside its dead zone (in strip layers)
3620 if (!IsPointInsideModule(x, y)) {
3621 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
3622 << ") is out of the readout plane or inside a dead zone";
3623 return false;
3624 }
3625
3626 // Particle: pdg_code = 0 -> from the MC track of the point refID (as in AddRealPointFull) --
3627 Int_t pdg = pdg_code;
3628 if (pdg == 0) {
3629 FairRunSim* run = FairRunSim::Instance();
3630 BmnGemStripDigitizer* digi = run ? (BmnGemStripDigitizer*)run->GetTask("GemDigitizer") : nullptr;
3631 TClonesArray* points = digi ? digi->GetMCPoints() : nullptr;
3632 TClonesArray* mctracks = digi ? digi->GetMCTracks() : nullptr;
3633
3634 CbmMCTrack* mcTr = nullptr;
3635 if (points && mctracks && refID >= 0 && refID < points->GetEntriesFast()) {
3636 CbmStsPoint* point = (CbmStsPoint*)points->UncheckedAt(refID);
3637 if (point && point->GetTrackID() >= 0 && point->GetTrackID() < mctracks->GetEntriesFast())
3638 mcTr = (CbmMCTrack*)mctracks->UncheckedAt(point->GetTrackID());
3639 }
3640
3641 if (mcTr) {
3642 pdg = mcTr->GetPdgCode();
3643 } else {
3644 static std::atomic<bool> no_mc_reported{false};
3645 if (!no_mc_reported.exchange(true))
3646 LOG(warn) << "BmnGemStripModule::" << __func__
3647 << "(): pdg_code = 0 and the MC track is not available (no GemDigitizer or MC point), "
3648 "the He4 mass is used (this warning is printed once)";
3649 }
3650 }
3651
3652 TVector3 mom(px, py, pz);
3653 TParticlePDG* part = (pdg != 0) ? TDatabasePDG::Instance()->GetParticle(pdg) : nullptr;
3654
3655 Double_t mnoc = 1.0, mass = 3.7283999; // He4
3656 if (part)
3657 mass = part->Mass();
3658 if (mass > 0.0001) {
3659 TLorentzVector lorv;
3660 lorv.SetVectM(mom, mass);
3661 Double_t beta = lorv.Beta();
3662 Double_t gamma = lorv.Gamma();
3663 Double_t charge = (part) ? part->Charge() / 3 : 1;
3664 mnoc = GetNumberOfClusters(beta, gamma, charge, 1.787, 12.33);
3665 if (mnoc < 0.5)
3666 return kFALSE;
3667 if (MaxClustersPerCm > 0.0 && mnoc > MaxClustersPerCm)
3668 mnoc = MaxClustersPerCm;
3669 }
3670 //--------------------------------------------------------------------------
3671
3672 // Distance from entry point (x,y,z) to exit point (along z-axis)
3673 Double_t z_distance_from_in_to_out;
3674
3675 // Condition: track direction along z-axis
3676 if (pz > 0.0) {
3677 z_distance_from_in_to_out = ModuleThickness - (z - ZStartModulePosition);
3678 } else {
3679 z_distance_from_in_to_out = z - ZStartModulePosition;
3680 }
3681
3682 // Condition: track distance along z-axis must be not zero
3683 if (z_distance_from_in_to_out <= 0.0) {
3684 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
3685 << ") has a track with an incorrect length";
3686 return false;
3687 }
3688
3689 // Scale coefficient of the track vector (the path from entry point to exit point)
3690 Double_t vector_coeff = fabs(z_distance_from_in_to_out / pz);
3691
3692 // Components of the track vector
3693 Double_t x_vec_from_in_to_out = vector_coeff * px;
3694 Double_t y_vec_from_in_to_out = vector_coeff * py;
3695 Double_t z_vec_from_in_to_out = vector_coeff * pz;
3696
3697 // Exit point coordinates (x_out, y_out, z_out)
3698 Double_t x_out = x + x_vec_from_in_to_out;
3699 Double_t y_out = y + y_vec_from_in_to_out;
3700 Double_t z_out = z + z_vec_from_in_to_out;
3701
3702 // Check if the exit point is outside the module then calculate new x_out, y_out, z_out values
3703 // Condition: x-coordinate of the exit point is inside the module
3704 if (x_out < XMinModule || x_out > XMaxModule) {
3705 if (x_out < XMinModule)
3706 x_out = XMinModule;
3707 if (x_out > XMaxModule)
3708 x_out = XMaxModule;
3709
3710 x_vec_from_in_to_out = x_out - x;
3711 vector_coeff = x_vec_from_in_to_out / px;
3712
3713 y_vec_from_in_to_out = vector_coeff * py;
3714 z_vec_from_in_to_out = vector_coeff * pz;
3715
3716 y_out = y + y_vec_from_in_to_out;
3717 z_out = z + z_vec_from_in_to_out;
3718 }
3719 // Condition: y-coordinate of the exit point is inside the module
3720 if (y_out < YMinModule || y_out > YMaxModule) {
3721 if (y_out < YMinModule)
3722 y_out = YMinModule;
3723 if (y_out > YMaxModule)
3724 y_out = YMaxModule;
3725
3726 y_vec_from_in_to_out = y_out - y;
3727 vector_coeff = y_vec_from_in_to_out / py;
3728
3729 x_vec_from_in_to_out = vector_coeff * px;
3730 z_vec_from_in_to_out = vector_coeff * pz;
3731
3732 x_out = x + x_vec_from_in_to_out;
3733 z_out = z + z_vec_from_in_to_out;
3734 }
3735 //--------------------------------------------------------------------------
3736
3737 // Center point of the ANN strip window: the track at the middle of the drift gap (computed always, also with
3738 // onlyStandard: it is stored with the point, see GetRealPointXcenter(), for the training samples)
3739 Double_t x_c = x, y_c = y;
3740 GetANNWindowCenterPoint(x, y, z, x_out, y_out, z_out, x_c, y_c);
3741
3742 // Common track length
3743 Double_t track_length =
3744 std::sqrt((x_vec_from_in_to_out) * (x_vec_from_in_to_out) + (y_vec_from_in_to_out) * (y_vec_from_in_to_out)
3745 + (z_vec_from_in_to_out) * (z_vec_from_in_to_out));
3746
3747 // Which method is used for this point: standard (electrons -> strips) or ANN (model -> strips)
3748 // (without a suitable model or with onlyStandard the standard method is used)
3749 Double_t r_vec = TMath::Sqrt(px * px + py * py + pz * pz);
3750 Double_t theta_deg_abs = TMath::ACos(TMath::Abs(pz) / r_vec) * TMath::RadToDeg();
3751 const Bool_t useANN = (theta_deg_abs < maxThetaAngle) && mlp_model;
3752
3753 // ANN with external segments: the avalanche is not needed
3754 const Bool_t use_external_segs = useANN && !segs.empty();
3755 if (use_external_segs && segs.size() < static_cast<size_t>(n_segs_features)) {
3756 LOG(error) << "BmnGemStripModule::" << __func__ << "(): segment vector size = " << segs.size()
3757 << ", the model requires " << n_segs_features;
3758 return false;
3759 }
3760
3761 // Each level - distance to the readout plane
3765
3766 // Mean electron shift along x-axis (under the influence of the Lorentz force)
3767 Double_t xmean; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3)
3768
3769 // Sigma electron smearing
3770 Double_t sigma; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3+ p4*x^4 + p5*x^5)
3771
3772 // Reading the medium configuration -------------------------------------
3777
3784 //----------------------------------------------------------------------
3785
3786 // Initialization of a strip cluster in each strip layer
3787 Int_t n_strip_layers = StripLayers.size(); // number of layers in the module
3788 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
3789
3790 // Segmented charge distribution of this point (always computed when the avalanche is simulated)
3791 vector<Int_t> seg_local(n_track_segments, 0);
3792
3793 // [PARALLEL-STD] Standard method: parallel processing of the avalanche electrons -----
3794 // true - electron (x,y) are taken from gRandom sequentially, as in AddRealPointFull_ANNStripGenerator_MLP:
3795 // bitwise identical result, only the strip assignment is parallel;
3796 // false - electron (x,y) are generated in parallel by per-task generators (seeded from gRandom):
3797 // faster, statistically equivalent to the sequential methods, bitwise identical to
3798 // AddRealPointFull_ANNStripGenerator_Fdeep_Parallel (the same scheme of the random numbers).
3799 constexpr Bool_t kExactGRandomSequence = false;
3800 constexpr Long64_t kElectronsPerTask = 4096; // electrons per parallel task
3801 constexpr Long64_t kMinElectronsForParallel = 20000; // below this - sequential execution
3802
3803 struct AvalancheTask // used only if !kExactGRandomSequence
3804 {
3805 Double_t xcenter; // mean x on the readout (with the Lorentz shift)
3806 Double_t ycenter; // mean y on the readout
3807 Double_t sigma; // smearing
3808 Long64_t offset; // index of the first electron of the task (from first_electron)
3809 Long64_t n; // number of electrons in the task
3810 };
3811 std::vector<AvalancheTask> aval_tasks;
3812 Long64_t n_aval_electrons = 0;
3813 std::vector<Double_t> exact_x, exact_y; // electron positions, used only if kExactGRandomSequence
3814
3815 // Persistent thread pool: the threads are created once (on the first call) and wait for work,
3816 // so no threads are created/joined in each call. Lives until the end of the program.
3817 // Work items are taken dynamically (atomic counter) -> load balancing for avalanches of different size.
3818 struct ThreadPool
3819 {
3820 std::vector<std::thread> workers;
3821 std::mutex mtx; // protects the job description and the counters below
3822 std::mutex run_mtx; // one job at a time (in case of calls from several threads)
3823 std::condition_variable cv_start; // workers: a new job is available
3824 std::condition_variable cv_done; // caller: all workers have finished the job
3825 const std::function<void(size_t)>* job = nullptr;
3826 size_t n_items = 0;
3827 std::atomic<size_t> next{0}; // next work item
3828 size_t generation = 0; // job counter
3829 size_t n_busy = 0; // workers that have not finished the current job yet
3830 bool stop = false;
3831
3832 explicit ThreadPool(size_t n_workers)
3833 {
3834 workers.reserve(n_workers);
3835 for (size_t i = 0; i < n_workers; ++i)
3836 workers.emplace_back([this] { Loop(); });
3837 }
3838
3839 ~ThreadPool()
3840 {
3841 {
3842 std::lock_guard<std::mutex> lk(mtx);
3843 stop = true;
3844 }
3845 cv_start.notify_all();
3846 for (auto& w : workers)
3847 w.join();
3848 }
3849
3850 void Work()
3851 {
3852 for (size_t i = next.fetch_add(1); i < n_items; i = next.fetch_add(1))
3853 (*job)(i);
3854 }
3855
3856 void Loop()
3857 {
3858 size_t seen = 0;
3859 for (;;) {
3860 {
3861 std::unique_lock<std::mutex> lk(mtx);
3862 cv_start.wait(lk, [&] { return stop || generation != seen; });
3863 if (stop)
3864 return;
3865 seen = generation;
3866 }
3867 Work();
3868 {
3869 std::lock_guard<std::mutex> lk(mtx);
3870 if (--n_busy == 0)
3871 cv_done.notify_one();
3872 }
3873 }
3874 }
3875
3876 // Runs func(i) for i in [0, n) on the pool threads and the calling thread
3877 void Run(size_t n, const std::function<void(size_t)>& func)
3878 {
3879 if (n == 0)
3880 return;
3881 if (workers.empty() || n == 1) {
3882 for (size_t i = 0; i < n; ++i)
3883 func(i);
3884 return;
3885 }
3886
3887 std::lock_guard<std::mutex> run_lock(run_mtx);
3888 {
3889 std::lock_guard<std::mutex> lk(mtx);
3890 job = &func;
3891 n_items = n;
3892 next.store(0);
3893 n_busy = workers.size();
3894 ++generation;
3895 }
3896 cv_start.notify_all();
3897
3898 Work(); // the calling thread works too
3899
3900 std::unique_lock<std::mutex> lk(mtx);
3901 cv_done.wait(lk, [&] { return n_busy == 0; });
3902 job = nullptr;
3903 }
3904 };
3905
3906 // hardware threads - 1 workers (the calling thread is the last one)
3907 static ThreadPool pool(std::max(1u, std::thread::hardware_concurrency()) - 1);
3908
3909 auto parallel_for = [](size_t n, const std::function<void(size_t)>& func) { pool.Run(n, func); };
3910 //--------------------------------------------------------------------------
3911
3912 if (!use_external_segs) {
3913
3914 Double_t current_length = 0.0; // traversed length (counter)
3915 Double_t current_length_ratio = 0.0; // ratio of the traversed length to common track length (counter)
3916 Double_t current_step = 0.0; // current distance between two collision points
3917
3918 // Collection of collision points
3919 std::vector<CollPoint> collision_points;
3920
3921 // Reading the MCD value (mean collision distance = mean free flight path) [cm]
3923 MCD = 1 / mnoc; // AZ-110124
3924
3925 while (current_length < track_length) {
3926
3927 current_step = gRandom->Exp(MCD);
3928 current_length += current_step;
3929
3930 if (current_length > track_length)
3931 break;
3932
3933 current_length_ratio = current_length / track_length;
3934
3935 Double_t current_x = x + current_length_ratio * x_vec_from_in_to_out;
3936 Double_t current_y = y + current_length_ratio * y_vec_from_in_to_out;
3937 Double_t current_z = z + current_length_ratio * z_vec_from_in_to_out;
3938
3939 collision_points.push_back(CollPoint(current_x, current_y, current_z));
3940 }
3941
3942 // Electron avalanche producing for each collision point at the track
3943 // (in the ANN mode only the segmented charge distribution is filled here)
3944 for (size_t icoll = 0; icoll < collision_points.size(); ++icoll) {
3945
3946 Double_t xcoll = collision_points[icoll].x;
3947 Double_t ycoll = collision_points[icoll].y;
3948 Double_t zcoll = collision_points[icoll].z;
3949
3950 Double_t zdist; // current z-distance to the readout
3951
3952 // Find z-distance to the readout depending on the electron drift direction
3954 zdist = (ModuleThickness + ZStartModulePosition) - zcoll;
3955 } else {
3956 zdist = zcoll - ZStartModulePosition;
3957 }
3958 //------------------------------------------------------------------
3959
3960 // polynomials
3961 xmean = p0_xmean + p1_xmean * zdist + p2_xmean * zdist * zdist + p3_xmean * zdist * zdist * zdist;
3962 sigma = p0_sigma + p1_sigma * zdist + p2_sigma * zdist * zdist + p3_sigma * zdist * zdist * zdist
3963 + p4_sigma * zdist * zdist * zdist * zdist + p5_sigma * zdist * zdist * zdist * zdist * zdist;
3964
3965 // Number of electrons in the current collision
3966 Int_t n_electrons_cluster = gRandom->Landau(1.027, 0.11);
3967 if (n_electrons_cluster < 1)
3968 n_electrons_cluster = 1; // min
3969 if (n_electrons_cluster > 6)
3970 n_electrons_cluster = 6; // max
3971
3972 // Filling charge distribution -------------------------------------
3973 Double_t dist_from_start = 0.0;
3974 if (pz > 0.0) {
3975 dist_from_start =
3976 std::sqrt((xcoll - x) * (xcoll - x) + (ycoll - y) * (ycoll - y) + (zcoll - z) * (zcoll - z));
3977 } else {
3978 dist_from_start = std::sqrt((xcoll - x_out) * (xcoll - x_out) + (ycoll - y_out) * (ycoll - y_out)
3979 + (zcoll - z_out) * (zcoll - z_out));
3980 }
3981 Int_t segment = (Int_t)(dist_from_start / track_length * n_track_segments);
3982 if (segment >= n_track_segments)
3983 segment = n_track_segments - 1;
3984 //------------------------------------------------------------------
3985
3986 for (Int_t ielectron = 0; ielectron < n_electrons_cluster; ++ielectron) {
3987
3988 // Electron gain in each GEM cascade (three draws always, as in the other methods)
3989 Double_t gain_gem1 = gRandom->Exp(15);
3990 Double_t gain_gem2 = gRandom->Exp(15);
3991 Double_t gain_gem3 = gRandom->Exp(15);
3992
3993 if (gain_gem1 < 1.0)
3994 gain_gem1 = 1.0;
3995 if (gain_gem2 < 1.0)
3996 gain_gem2 = 1.0;
3997 if (gain_gem3 < 1.0)
3998 gain_gem3 = 1.0;
3999
4000 int total_gain = 0;
4001
4002 if (zdist < level1) {
4003 total_gain = 1.0;
4004 }
4005 if (zdist >= level1 && zdist < level2) {
4006 total_gain = gain_gem3;
4007 }
4008 if (zdist >= level2 && zdist < level3) {
4009 total_gain = gain_gem3 * gain_gem2;
4010 }
4011 if (zdist >= level3) {
4012 total_gain = gain_gem3 * gain_gem2 * gain_gem1;
4013 }
4014
4015 seg_local[segment] += total_gain;
4016
4017 // ANN mode: electrons are not projected on the readout, strips are taken from the model
4018 if (useANN)
4019 continue;
4020
4021 // Standard method ---------------------------------------------
4022 // [PARALLEL-STD] Only the electron positions (exact mode) or the tasks (fast mode) are produced
4023 // here; strips are assigned later in parallel (see "Standard method: parallel strip assignment")
4024 // x-shift of the electon depends on the electron drift direction
4025 const Double_t x_center =
4026 (ElectronDriftDirection == ForwardZAxisEDrift) ? (xcoll + xmean) : (xcoll - xmean);
4027
4028 if (kExactGRandomSequence) {
4029 // the same gRandom calls in the same order as in AddRealPointFull_ANNStripGenerator_MLP
4030 for (int igain = 0; igain < total_gain; ++igain) {
4031 exact_x.push_back(gRandom->Gaus(x_center, sigma));
4032 exact_y.push_back(gRandom->Gaus(ycoll, sigma));
4033 }
4034 } else {
4035 // positions are generated later in parallel, here - only the tasks
4036 for (Long64_t done = 0; done < total_gain; done += kElectronsPerTask) {
4037 aval_tasks.push_back({x_center, ycoll, sigma, n_aval_electrons + done,
4038 std::min<Long64_t>(kElectronsPerTask, total_gain - done)});
4039 }
4040 n_aval_electrons += total_gain;
4041 }
4042 }
4043 }
4044 }
4045
4046 // [PARALLEL-STD] Standard method: parallel strip assignment ------------
4047 // Each task counts its electrons per strip (compact local counters, no per-electron storage).
4048 // The counters of the tasks are summed in the task order and added to the clusters with one AddStrip()
4049 // per strip (as in AddRealPointFull_ANNStripGenerator_MLP): the signals are integer numbers of electrons,
4050 // so the clusters are bitwise the same as with one AddStrip() per electron and do not depend on the threads.
4051 if (!useANN) {
4052
4053 // Number of avalanche electrons of this point
4054 const size_t n_electrons = kExactGRandomSequence ? exact_x.size() : static_cast<size_t>(n_aval_electrons);
4055
4056 if (n_electrons > 0) {
4057
4058 const Bool_t run_parallel = static_cast<Long64_t>(n_electrons) >= kMinElectronsForParallel;
4059 const size_t chunk_size = static_cast<size_t>(kElectronsPerTask);
4060
4061 // Layer invariant (computed once instead of for each electron)
4062 std::vector<char> layer_vertical(n_strip_layers);
4063 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer)
4064 layer_vertical[ilayer] = TMath::Abs(StripLayers[ilayer].GetAngleDeg()) < 1.0;
4065
4066 // Counters of one task: in each layer the strips [lo, lo + cnt.size()) (the range grows if needed)
4067 struct TaskCounts
4068 {
4069 std::vector<Int_t> lo;
4070 std::vector<std::vector<Int_t>> cnt;
4071 };
4072 const size_t n_tasks =
4073 kExactGRandomSequence ? (n_electrons + chunk_size - 1) / chunk_size : aval_tasks.size();
4074 std::vector<TaskCounts> task_counts(n_tasks);
4075
4076 // One electron -> +1 on its strip in each layer (the same conditions as in the old code)
4077 auto count_electron = [&](TaskCounts& tc, Double_t x_readout, Double_t y_readout) {
4078 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4079 // NOTE: these BmnGemStripLayer methods are called from several threads,
4080 // they must not modify the layer
4081 // Condition: end electron position must be inside the layer
4082 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_readout, y_readout))
4083 continue;
4084
4085 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_readout, y_readout);
4086 // AZ-100124 - account for strip width
4087 Double_t dstrip = strip_pos - (int)strip_pos;
4088 if (layer_vertical[ilayer]) {
4089 // Vertical strips
4090 if (dstrip >= 0.4 && dstrip < 0.6)
4091 continue;
4092 } else {
4093 // Rotated strips
4094 if (dstrip >= 0.3 && dstrip < 0.7)
4095 continue;
4096 }
4097 // AZ
4098
4099 const Int_t strip = (Int_t)strip_pos;
4100 std::vector<Int_t>& cnt = tc.cnt[ilayer];
4101 Int_t& lo = tc.lo[ilayer];
4102 if (cnt.empty()) {
4103 lo = strip;
4104 cnt.assign(1, 0);
4105 } else if (strip < lo) {
4106 cnt.insert(cnt.begin(), lo - strip, 0);
4107 lo = strip;
4108 } else if (strip >= lo + static_cast<Int_t>(cnt.size())) {
4109 cnt.resize(strip - lo + 1, 0);
4110 }
4111 ++cnt[strip - lo];
4112 }
4113 };
4114
4115 auto init_counts = [&](TaskCounts& tc) {
4116 tc.lo.assign(n_strip_layers, 0);
4117 tc.cnt.assign(n_strip_layers, std::vector<Int_t>());
4118 };
4119
4120 if (kExactGRandomSequence) {
4121 // (a) Exact mode: positions are already generated (gRandom, sequentially) -> only geometry, by chunks
4122 auto process_chunk = [&](size_t ichunk) {
4123 TaskCounts& tc = task_counts[ichunk];
4124 init_counts(tc);
4125 const size_t begin = ichunk * chunk_size;
4126 const size_t end = std::min(n_electrons, begin + chunk_size);
4127 for (size_t ie = begin; ie < end; ++ie)
4128 count_electron(tc, exact_x[ie], exact_y[ie]);
4129 };
4130
4131 if (run_parallel)
4132 parallel_for(n_tasks, process_chunk);
4133 else
4134 for (size_t ichunk = 0; ichunk < n_tasks; ++ichunk)
4135 process_chunk(ichunk);
4136
4137 // Electron points on the readout (generation data)
4138 if (FillGenerationData) {
4139 XElectronPos.insert(XElectronPos.end(), exact_x.begin(), exact_x.end());
4140 YElectronPos.insert(YElectronPos.end(), exact_y.begin(), exact_y.end());
4141 ElectronSignal.resize(ElectronSignal.size() + n_electrons, 1.0);
4142 }
4143 } else {
4144 // (a) Fast mode: generation + geometry in one pass (one thread launch, one pass over memory)
4145
4146 // base seed from gRandom -> reproducible with the seed of gRandom, independent of the threads
4147 const UInt_t base_seed = static_cast<UInt_t>(gRandom->Integer(0xFFFFFFFFu));
4148
4149 // Electron points on the readout (generation data): each task writes its own range
4150 const size_t first_electron = XElectronPos.size(); // 0 after ResetElectronPointsAndClusters()
4151 if (FillGenerationData) {
4152 XElectronPos.resize(first_electron + n_electrons);
4153 YElectronPos.resize(first_electron + n_electrons);
4154 ElectronSignal.resize(first_electron + n_electrons, 1.0);
4155 }
4156
4157 auto process_task = [&](size_t itask) {
4158 const AvalancheTask& t = aval_tasks[itask];
4159 TaskCounts& tc = task_counts[itask];
4160 init_counts(tc);
4161 std::seed_seq seq{base_seed, static_cast<UInt_t>(itask)};
4162 std::mt19937_64 rng(seq);
4163 std::normal_distribution<Double_t> gaus(0.0, 1.0); // mean + sigma*N(0,1), as TRandom::Gaus
4164
4165 for (Long64_t i = 0; i < t.n; ++i) {
4166 const Double_t x_readout = t.xcenter + t.sigma * gaus(rng);
4167 const Double_t y_readout = t.ycenter + t.sigma * gaus(rng);
4168
4169 if (FillGenerationData) {
4170 const size_t ie = first_electron + static_cast<size_t>(t.offset + i);
4171 XElectronPos[ie] = x_readout;
4172 YElectronPos[ie] = y_readout;
4173 }
4174
4175 count_electron(tc, x_readout, y_readout);
4176 }
4177 };
4178
4179 if (run_parallel)
4180 parallel_for(n_tasks, process_task);
4181 else
4182 for (size_t itask = 0; itask < n_tasks; ++itask)
4183 process_task(itask);
4184 }
4185
4186 // (b) Filling the clusters: the counters of all tasks are summed (in the task order),
4187 // one AddStrip() per strip; layers are independent -> in parallel
4188 auto fill_layer = [&](size_t ilayer) {
4189 StripCluster& cluster = cluster_layers[ilayer];
4190 const Int_t n_strips_in_layer = std::max(0, StripLayers[ilayer].GetNStrips());
4191 std::vector<Double_t> strip_acc(n_strips_in_layer, 0.0);
4192 Int_t acc_min = std::numeric_limits<Int_t>::max();
4193 Int_t acc_max = -1;
4194
4195 for (size_t itask = 0; itask < n_tasks; ++itask) {
4196 const Int_t lo = task_counts[itask].lo[ilayer];
4197 const std::vector<Int_t>& cnt = task_counts[itask].cnt[ilayer];
4198 for (size_t i = 0; i < cnt.size(); ++i) {
4199 if (cnt[i] == 0)
4200 continue;
4201 const Int_t strip = lo + static_cast<Int_t>(i);
4202 if (strip >= 0 && strip < n_strips_in_layer) {
4203 strip_acc[strip] += cnt[i];
4204 if (strip < acc_min)
4205 acc_min = strip;
4206 if (strip > acc_max)
4207 acc_max = strip;
4208 } else {
4209 // out of the accumulator range: directly (the same sum as cnt[i] x AddStrip(strip, 1.0))
4210 cluster.AddStrip(strip, static_cast<Double_t>(cnt[i]));
4211 }
4212 }
4213 }
4214
4215 for (Int_t istrip = acc_min; istrip <= acc_max; ++istrip) {
4216 if (strip_acc[istrip] > 0.0)
4217 cluster.AddStrip(istrip, strip_acc[istrip]);
4218 }
4219 };
4220
4221 if (run_parallel)
4222 parallel_for(static_cast<size_t>(n_strip_layers), fill_layer);
4223 else
4224 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer)
4225 fill_layer(ilayer);
4226 }
4227 }
4228 //----------------------------------------------------------------------
4229
4230 // ANN generation (MLP, own inference) ---------------------------------
4231 if (useANN) {
4232
4233 // Segments for the model: external or from the avalanche of this point
4234 const vector<Int_t>& seg_used = use_external_segs ? segs : seg_local;
4235
4236 // Normalization (px,py,pz), where r_vec must be equal to 1
4237 Double_t px_norm = px / r_vec;
4238 Double_t py_norm = py / r_vec;
4239 Double_t pz_norm = pz / r_vec;
4240
4241 // Inversion for z-reverse direction particles: the same track segment passed from (x_out, y_out)
4242 if (pz < 0.0) {
4243 px_norm = -px_norm;
4244 py_norm = -py_norm;
4245 pz_norm = -pz_norm;
4246 }
4247
4248 Double_t drift_direction = 0; // 0 is ForwardZAxisEDrift
4250 drift_direction = 1;
4251
4252 // Magnetic field: given explicitly or from the current medium configuration
4253 const Double_t bfield_used = (bfield < 0.0) ? BmnGemStripMedium::GetInstance().BField : bfield;
4254
4255 // Normalize seg. vector: [0, max] -> [0, 1] (max over the whole vector)
4256 Double_t max_segment_val = *(std::max_element(seg_used.begin(), seg_used.end()));
4257
4258 // Total charge in segment vector
4259 Double_t total_charge = 0.0;
4260 for (size_t i = 0; i < seg_used.size(); ++i)
4261 total_charge += seg_used[i];
4262
4263 // Window center point (x_c, y_c): computed above with GetANNWindowCenterPoint()
4264
4265 // Common part of the model input: [px, py, pz, drift, bfield, s0..s{n_segs_features-1}]
4266 float common_input[n_common_inputs];
4267 common_input[0] = static_cast<float>(px_norm);
4268 common_input[1] = static_cast<float>(py_norm);
4269 common_input[2] = static_cast<float>(pz_norm);
4270 common_input[3] = static_cast<float>(drift_direction);
4271 common_input[4] = static_cast<float>(bfield_used);
4272 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
4273 common_input[5 + iseg] =
4274 (max_segment_val > 0.0) ? static_cast<float>(seg_used[iseg] / max_segment_val) : 0.0f;
4275
4276 if (total_charge > 0.0) {
4277 // Strip model: a window of n_ann_strips strips around the window center strip in each layer
4278 const Int_t center_strip_index = n_ann_strips / 2; // center strip of the window
4279
4280 // Layers with the MC point inside: their part of the input [strip_layer, strip_order, center_frac]
4281 std::vector<Int_t> ann_layers; // layer index
4282 std::vector<Int_t> ann_center_strips; // MC strip (window center)
4283 std::vector<Int_t> ann_n_strips; // number of strips in the layer
4284 std::vector<float> ann_layer_inputs; // [n_ann_layers][n_layer_inputs]
4285 ann_layers.reserve(n_strip_layers);
4286 ann_center_strips.reserve(n_strip_layers);
4287 ann_n_strips.reserve(n_strip_layers);
4288 ann_layer_inputs.reserve(n_strip_layers * n_layer_inputs);
4289
4290 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4291
4292 // The window is built around the center point (middle of the drift gap): it must be inside this layer
4293 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_c, y_c))
4294 continue;
4295
4296 Int_t n_strips_in_layer = StripLayers[ilayer].GetNStrips();
4297 if (n_strips_in_layer <= 0)
4298 continue;
4299
4300 // Center point position in strips: integer part - window center strip, fractional part - center_frac
4301 Double_t center_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_c, y_c);
4302 Int_t center_strip = (Int_t)TMath::Floor(center_pos);
4303 Double_t center_frac = center_pos - center_strip;
4304
4305 Double_t strip_layer = static_cast<Double_t>(StripLayers[ilayer].GetType()); // 0 - lower, 1 - upper
4306 Double_t strip_order =
4307 static_cast<Double_t>(StripLayers[ilayer].GetStripNumberingOrder()); // 0 - LeftToRight
4308
4309 ann_layers.push_back(ilayer);
4310 ann_center_strips.push_back(center_strip);
4311 ann_n_strips.push_back(n_strips_in_layer);
4312 ann_layer_inputs.push_back(static_cast<float>(strip_layer));
4313 ann_layer_inputs.push_back(static_cast<float>(strip_order));
4314 ann_layer_inputs.push_back(static_cast<float>(center_frac));
4315 }
4316
4317 // Strip signals for all these layers in one call: n_ann_strips values in [0, 1] (max ~ 1) per layer
4318 Int_t n_ann_layers = ann_layers.size();
4319 std::vector<float> ann_strip_values(static_cast<size_t>(n_ann_layers) * n_ann_strips);
4320 if (n_ann_layers > 0
4321 && !mlp_model->PredictSplit(common_input, n_common_inputs, ann_layer_inputs.data(), n_ann_layers,
4322 ann_strip_values.data()))
4323 {
4324 LOG(error) << "BmnGemStripModule::" << __func__ << "(): MLP prediction failed";
4325 n_ann_layers = 0;
4326 }
4327
4328 for (Int_t iann = 0; iann < n_ann_layers; ++iann) {
4329 const Int_t ilayer = ann_layers[iann];
4330 const Int_t center_strip = ann_center_strips[iann];
4331 const Int_t n_strips_in_layer = ann_n_strips[iann];
4332 const float* strip_values = ann_strip_values.data() + static_cast<size_t>(iann) * n_ann_strips;
4333
4334 // Sum of the model output over all strips of the window
4335 Double_t sum_norm = 0.0;
4336 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip)
4337 sum_norm += strip_values[istrip];
4338
4339 // Scale coeff for total signal distribution (separate for lower and upper layers)
4340 const Double_t ann_correction_lower = 0.8; // Set to 1.0 to disable correction
4341 const Double_t ann_correction_upper = 0.6; // Set to 1.0 to disable correction
4342 const Double_t ann_correction =
4343 (StripLayers[ilayer].GetType() == LowerStripLayer) ? ann_correction_lower : ann_correction_upper;
4344
4345 Double_t signal_scale = 0.0;
4346 if (sum_norm > 0.0)
4347 signal_scale = (total_charge / sum_norm) * ann_correction;
4348
4349 Double_t threshold = 0.0001 * total_charge;
4350
4351 // Strips are written directly: window index -> strip number
4352 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip) {
4353 Double_t content = strip_values[istrip] * signal_scale;
4354 if (content <= threshold)
4355 continue;
4356
4357 Int_t strip_num = center_strip - center_strip_index + istrip;
4358 if (strip_num < 0 || strip_num >= n_strips_in_layer)
4359 continue; // the window goes out of the layer
4360
4361 cluster_layers[ilayer].AddStrip(strip_num, content);
4362 }
4363 }
4364 }
4365 }
4366 //----------------------------------------------------------------------
4367
4368 // Segmented charge distribution (generation data) ------------------------
4369 // (seg_used above is not used after this point)
4370 if (FillGenerationData) {
4371 if (use_external_segs)
4372 SegmentedChargeDistribution = std::move(segs);
4373 else
4374 SegmentedChargeDistribution = std::move(seg_local);
4375 }
4376 //----------------------------------------------------------------------
4377
4378 // Calculate cluster parameters --------------------------------------------
4379 vector<Double_t> cluster_mean_position(n_strip_layers, 0.0);
4380 vector<Double_t> cluster_total_signal(n_strip_layers, 0.0);
4381
4382 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4383
4384 if (cluster_layers[ilayer].GetClusterSize() > 0) {
4385
4386 for (int i = 0; i < cluster_layers[ilayer].GetClusterSize(); ++i) {
4387 cluster_mean_position[ilayer] += (cluster_layers[ilayer].Strips[i] + 0.5)
4388 * cluster_layers[ilayer].Signals[i]; // as sum of all positions
4389 cluster_total_signal[ilayer] += cluster_layers[ilayer].Signals[i];
4390 }
4391 cluster_mean_position[ilayer] /= cluster_total_signal[ilayer];
4392 }
4393
4394 cluster_layers[ilayer].MeanPosition = cluster_mean_position[ilayer]; // mean cluster position
4395 cluster_layers[ilayer].TotalSignal = cluster_total_signal[ilayer]; // total signal of the cluster
4396 }
4397
4399 if (pz > 0.0) {
4400 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4401 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
4402 }
4403 } else {
4404 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4405 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
4406 }
4407 }
4408 } else {
4409 if (pz > 0.0) {
4410 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4411 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
4412 }
4413 } else {
4414 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4415 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
4416 }
4417 }
4418 }
4419
4420 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4421 cluster_layers[ilayer].PositionResidual =
4422 cluster_layers[ilayer].MeanPosition - cluster_layers[ilayer].OriginPosition;
4423 cluster_layers[ilayer].SetType(StripLayers[ilayer].GetType());
4424 }
4425 //----------------------------------------------------------------------
4426
4427 // Added clusters (generation data) ----------------------------------------
4428 if (FillGenerationData) {
4429 if (AddedClusters.size() == 0) {
4430 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4431 AddedClusters.push_back(vector<StripCluster>());
4432 }
4433 }
4434
4435 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4436 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
4437 }
4438 }
4439 //------------------------------------------------------------------------------
4440
4441 // Add the correct clusters to the strip layers -------------------------
4442 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4443 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
4444 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
4445 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
4446 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
4447 }
4448 }
4449 //----------------------------------------------------------------------
4450
4451 // Fill strip matches ---------------------------------------------------
4452 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4453 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
4454 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
4455 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
4456 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
4457 refID);
4458 }
4459 }
4460 //----------------------------------------------------------------------
4461
4462 RealPointsX.push_back(x);
4463 RealPointsY.push_back(y);
4464 RealPointsXout.push_back(x_out);
4465 RealPointsYout.push_back(y_out);
4466 RealPointsMC.push_back(refID);
4467 RealPointsXcenter.push_back(x_c); // ANN window center point
4468 RealPointsYcenter.push_back(y_c);
4469
4470 return true;
4471}
4472//------------------------------------------------------------------------------
4473
4474// The same as AddRealPointFull_ANNStripGenerator_MLP (ANN part: identical), but the standard method does not simulate
4475// the avalanche electrons one by one. All electrons of one ionizing collision (sum of the gains of its primary
4476// electrons) start from the same point and are smeared with the same Gaussian (Lorentz shift xmean, sigma).
4477// The strip coordinate of an electron is a linear function of (x, y), so in each strip layer it is Gaussian too,
4478// and the number of electrons in each strip is drawn from the multinomial distribution (a chain of binomials)
4479// with the exact strip probabilities (Gaussian integrals over the strip without its dead band).
4480// Statistically equivalent to the per-electron simulation; the layers are drawn independently (the correlation
4481// "the same electron in both layers" is lost: its effect on the cluster position is ~ sigma / sqrt(N electrons)).
4482// Collisions near a layer border (or at negative strip coordinates) are simulated per electron, as before.
4483// Random numbers: collisions, gains and segments - from gRandom in the same order as in the other strip methods;
4484// strips - from a std::mt19937_64 seeded from gRandom after the collisions (reproducible with the seed of gRandom).
4485// The electron positions (XElectronPos, YElectronPos) are not simulated and not filled.
4487 Double_t y,
4488 Double_t z,
4489 Double_t px,
4490 Double_t py,
4491 Double_t pz,
4492 Double_t signal,
4493 Int_t refID,
4494 Int_t pdg_code,
4495 Double_t bfield,
4496 Bool_t onlyStandard,
4497 vector<Int_t> segs)
4498{
4499 const Double_t maxThetaAngle = kANNMaxThetaAngle; // max |theta| (deg) of the track for the ANN
4500
4501 // Model format: [px, py, pz, drift, bfield, s0..s63, strip_layer, strip_order, center_frac] -> window of N
4502 // strips (N odd, from the model) centered at the middle of the drift gap (GetANNWindowCenterPoint)
4503 const Int_t n_track_segments = 64; // track segmentation (segmented charge distribution)
4504 const Int_t n_segs_features = n_track_segments; // segments passed to the model
4505 const Int_t n_common_inputs = 5 + n_segs_features; // the same for all strip layers of the point
4506 const Int_t n_layer_inputs = 3; // [strip_layer, strip_order, center_frac]
4507 Int_t n_ann_strips = 0; // model output: strip window size (odd), taken from the model below
4508
4509 // Test outputs are cleared always (cheap if they are empty)
4511
4512 // MLP model: shared between modules, checked once for each model ---------
4513 std::shared_ptr<const BmnGemStripMLPModel> mlp_holder;
4514 const BmnGemStripMLPModel* mlp_model = nullptr;
4515 if (!onlyStandard) {
4516 mlp_holder = BmnGemStripMLPModel::GetShared();
4517 mlp_model = mlp_holder.get();
4518
4519 static std::mutex checked_mutex;
4520 static std::map<const BmnGemStripMLPModel*, std::pair<std::shared_ptr<const BmnGemStripMLPModel>, std::string>>
4521 checked; // model -> (holder, why it is not suitable: empty if suitable)
4522
4523 std::lock_guard<std::mutex> lock(checked_mutex);
4524 if (!mlp_model) {
4525 LOG(warn) << "BmnGemStripModule::" << __func__
4526 << "(): MLP model is not available, the standard method is used";
4527 } else {
4528 auto it = checked.find(mlp_model);
4529 if (it == checked.end()) {
4530 std::vector<std::string> expected = {"px", "py", "pz", "drift", "bfield"};
4531 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
4532 expected.push_back("s" + std::to_string(iseg));
4533 expected.insert(expected.end(), {"strip_layer", "strip_order", "center_frac"});
4534
4535 std::string why;
4536 Bool_t ok = mlp_model->CheckFeatureNames(expected, &why);
4537 if (ok) {
4538 why = CheckANNStripWindowSize(mlp_model->GetOutputDim());
4539 ok = why.empty();
4540 }
4541 if (ok) {
4542 why.clear();
4543 LOG(info) << "BmnGemStripModule::" << __func__ << "(): MLP model " << mlp_model->GetFilePath()
4544 << " is used (ANN for |theta| < " << maxThetaAngle << " deg, window "
4545 << mlp_model->GetOutputDim() << " strips)";
4546 } else if (why.empty())
4547 why = "unknown reason";
4548 it = checked.emplace(mlp_model, std::make_pair(mlp_holder, why)).first;
4549 }
4550 if (!it->second.second.empty()) {
4551 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): the model " << mlp_model->GetFilePath()
4552 << " is not suitable (" << it->second.second << "), the standard method is used";
4553 mlp_model = nullptr;
4554 }
4555 }
4556 }
4557 if (mlp_model)
4558 n_ann_strips = mlp_model->GetOutputDim(); // window size of the model (checked above: odd, >= 3)
4559 //--------------------------------------------------------------------------
4560
4561 // Condition: a start point is inside the module and outside its dead zone (in strip layers)
4562 if (!IsPointInsideModule(x, y)) {
4563 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
4564 << ") is out of the readout plane or inside a dead zone";
4565 return false;
4566 }
4567
4568 // Particle: pdg_code = 0 -> from the MC track of the point refID (as in AddRealPointFull) --
4569 Int_t pdg = pdg_code;
4570 if (pdg == 0) {
4571 FairRunSim* run = FairRunSim::Instance();
4572 BmnGemStripDigitizer* digi = run ? (BmnGemStripDigitizer*)run->GetTask("GemDigitizer") : nullptr;
4573 TClonesArray* points = digi ? digi->GetMCPoints() : nullptr;
4574 TClonesArray* mctracks = digi ? digi->GetMCTracks() : nullptr;
4575
4576 CbmMCTrack* mcTr = nullptr;
4577 if (points && mctracks && refID >= 0 && refID < points->GetEntriesFast()) {
4578 CbmStsPoint* point = (CbmStsPoint*)points->UncheckedAt(refID);
4579 if (point && point->GetTrackID() >= 0 && point->GetTrackID() < mctracks->GetEntriesFast())
4580 mcTr = (CbmMCTrack*)mctracks->UncheckedAt(point->GetTrackID());
4581 }
4582
4583 if (mcTr) {
4584 pdg = mcTr->GetPdgCode();
4585 } else {
4586 static std::atomic<bool> no_mc_reported{false};
4587 if (!no_mc_reported.exchange(true))
4588 LOG(warn) << "BmnGemStripModule::" << __func__
4589 << "(): pdg_code = 0 and the MC track is not available (no GemDigitizer or MC point), "
4590 "the He4 mass is used (this warning is printed once)";
4591 }
4592 }
4593
4594 TVector3 mom(px, py, pz);
4595 TParticlePDG* part = (pdg != 0) ? TDatabasePDG::Instance()->GetParticle(pdg) : nullptr;
4596
4597 Double_t mnoc = 1.0, mass = 3.7283999; // He4
4598 if (part)
4599 mass = part->Mass();
4600 if (mass > 0.0001) {
4601 TLorentzVector lorv;
4602 lorv.SetVectM(mom, mass);
4603 Double_t beta = lorv.Beta();
4604 Double_t gamma = lorv.Gamma();
4605 Double_t charge = (part) ? part->Charge() / 3 : 1;
4606 mnoc = GetNumberOfClusters(beta, gamma, charge, 1.787, 12.33);
4607 if (mnoc < 0.5)
4608 return kFALSE;
4609 if (MaxClustersPerCm > 0.0 && mnoc > MaxClustersPerCm)
4610 mnoc = MaxClustersPerCm;
4611 }
4612 //--------------------------------------------------------------------------
4613
4614 // Distance from entry point (x,y,z) to exit point (along z-axis)
4615 Double_t z_distance_from_in_to_out;
4616
4617 // Condition: track direction along z-axis
4618 if (pz > 0.0) {
4619 z_distance_from_in_to_out = ModuleThickness - (z - ZStartModulePosition);
4620 } else {
4621 z_distance_from_in_to_out = z - ZStartModulePosition;
4622 }
4623
4624 // Condition: track distance along z-axis must be not zero
4625 if (z_distance_from_in_to_out <= 0.0) {
4626 LOG(warn) << "BmnGemStripModule::" << __func__ << "(): Point (" << x << " : " << y << " : " << z
4627 << ") has a track with an incorrect length";
4628 return false;
4629 }
4630
4631 // Scale coefficient of the track vector (the path from entry point to exit point)
4632 Double_t vector_coeff = fabs(z_distance_from_in_to_out / pz);
4633
4634 // Components of the track vector
4635 Double_t x_vec_from_in_to_out = vector_coeff * px;
4636 Double_t y_vec_from_in_to_out = vector_coeff * py;
4637 Double_t z_vec_from_in_to_out = vector_coeff * pz;
4638
4639 // Exit point coordinates (x_out, y_out, z_out)
4640 Double_t x_out = x + x_vec_from_in_to_out;
4641 Double_t y_out = y + y_vec_from_in_to_out;
4642 Double_t z_out = z + z_vec_from_in_to_out;
4643
4644 // Check if the exit point is outside the module then calculate new x_out, y_out, z_out values
4645 // Condition: x-coordinate of the exit point is inside the module
4646 if (x_out < XMinModule || x_out > XMaxModule) {
4647 if (x_out < XMinModule)
4648 x_out = XMinModule;
4649 if (x_out > XMaxModule)
4650 x_out = XMaxModule;
4651
4652 x_vec_from_in_to_out = x_out - x;
4653 vector_coeff = x_vec_from_in_to_out / px;
4654
4655 y_vec_from_in_to_out = vector_coeff * py;
4656 z_vec_from_in_to_out = vector_coeff * pz;
4657
4658 y_out = y + y_vec_from_in_to_out;
4659 z_out = z + z_vec_from_in_to_out;
4660 }
4661 // Condition: y-coordinate of the exit point is inside the module
4662 if (y_out < YMinModule || y_out > YMaxModule) {
4663 if (y_out < YMinModule)
4664 y_out = YMinModule;
4665 if (y_out > YMaxModule)
4666 y_out = YMaxModule;
4667
4668 y_vec_from_in_to_out = y_out - y;
4669 vector_coeff = y_vec_from_in_to_out / py;
4670
4671 x_vec_from_in_to_out = vector_coeff * px;
4672 z_vec_from_in_to_out = vector_coeff * pz;
4673
4674 x_out = x + x_vec_from_in_to_out;
4675 z_out = z + z_vec_from_in_to_out;
4676 }
4677 //--------------------------------------------------------------------------
4678
4679 // Center point of the ANN strip window: the track at the middle of the drift gap (computed always, also with
4680 // onlyStandard: it is stored with the point, see GetRealPointXcenter(), for the training samples)
4681 Double_t x_c = x, y_c = y;
4682 GetANNWindowCenterPoint(x, y, z, x_out, y_out, z_out, x_c, y_c);
4683
4684 // Common track length
4685 Double_t track_length =
4686 std::sqrt((x_vec_from_in_to_out) * (x_vec_from_in_to_out) + (y_vec_from_in_to_out) * (y_vec_from_in_to_out)
4687 + (z_vec_from_in_to_out) * (z_vec_from_in_to_out));
4688
4689 // Which method is used for this point: standard (electrons -> strips) or ANN (model -> strips)
4690 // (without a suitable model or with onlyStandard the standard method is used)
4691 Double_t r_vec = TMath::Sqrt(px * px + py * py + pz * pz);
4692 Double_t theta_deg_abs = TMath::ACos(TMath::Abs(pz) / r_vec) * TMath::RadToDeg();
4693 const Bool_t useANN = (theta_deg_abs < maxThetaAngle) && mlp_model;
4694
4695 // ANN with external segments: the avalanche is not needed
4696 const Bool_t use_external_segs = useANN && !segs.empty();
4697 if (use_external_segs && segs.size() < static_cast<size_t>(n_segs_features)) {
4698 LOG(error) << "BmnGemStripModule::" << __func__ << "(): segment vector size = " << segs.size()
4699 << ", the model requires " << n_segs_features;
4700 return false;
4701 }
4702
4703 // Each level - distance to the readout plane
4707
4708 // Mean electron shift along x-axis (under the influence of the Lorentz force)
4709 Double_t xmean; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3)
4710
4711 // Sigma electron smearing
4712 Double_t sigma; // the dependence fitted by polynomial: f(x) = (p0 + p1*x + p2*x^2 + p3*x^3+ p4*x^4 + p5*x^5)
4713
4714 // Reading the medium configuration -------------------------------------
4719
4726 //----------------------------------------------------------------------
4727
4728 // Initialization of a strip cluster in each strip layer
4729 Int_t n_strip_layers = StripLayers.size(); // number of layers in the module
4730 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
4731
4732 // Segmented charge distribution of this point (always computed when the avalanche is simulated)
4733 vector<Int_t> seg_local(n_track_segments, 0);
4734
4735 // [FAST-STAND] Standard method: one task per ionizing collision ----------------
4736 // All electrons of a collision start from the same point -> one Gaussian (mean x_center, y_center; sigma);
4737 // n - sum of the gains of the primary electrons of the collision (number of electrons on the readout)
4738 struct CollisionTask
4739 {
4740 Double_t xcenter; // mean x on the readout (with the Lorentz shift)
4741 Double_t ycenter; // mean y on the readout
4742 Double_t sigma; // smearing
4743 Long64_t n; // number of electrons
4744 };
4745 std::vector<CollisionTask> coll_tasks;
4746 //--------------------------------------------------------------------------
4747
4748 if (!use_external_segs) {
4749
4750 Double_t current_length = 0.0; // traversed length (counter)
4751 Double_t current_length_ratio = 0.0; // ratio of the traversed length to common track length (counter)
4752 Double_t current_step = 0.0; // current distance between two collision points
4753
4754 // Collection of collision points
4755 std::vector<CollPoint> collision_points;
4756
4757 // Reading the MCD value (mean collision distance = mean free flight path) [cm]
4759 MCD = 1 / mnoc; // AZ-110124
4760
4761 while (current_length < track_length) {
4762
4763 current_step = gRandom->Exp(MCD);
4764 current_length += current_step;
4765
4766 if (current_length > track_length)
4767 break;
4768
4769 current_length_ratio = current_length / track_length;
4770
4771 Double_t current_x = x + current_length_ratio * x_vec_from_in_to_out;
4772 Double_t current_y = y + current_length_ratio * y_vec_from_in_to_out;
4773 Double_t current_z = z + current_length_ratio * z_vec_from_in_to_out;
4774
4775 collision_points.push_back(CollPoint(current_x, current_y, current_z));
4776 }
4777
4778 // Electron avalanche producing for each collision point at the track
4779 // (in the ANN mode only the segmented charge distribution is filled here)
4780 for (size_t icoll = 0; icoll < collision_points.size(); ++icoll) {
4781
4782 Double_t xcoll = collision_points[icoll].x;
4783 Double_t ycoll = collision_points[icoll].y;
4784 Double_t zcoll = collision_points[icoll].z;
4785
4786 Double_t zdist; // current z-distance to the readout
4787
4788 // Find z-distance to the readout depending on the electron drift direction
4790 zdist = (ModuleThickness + ZStartModulePosition) - zcoll;
4791 } else {
4792 zdist = zcoll - ZStartModulePosition;
4793 }
4794 //------------------------------------------------------------------
4795
4796 // polynomials
4797 xmean = p0_xmean + p1_xmean * zdist + p2_xmean * zdist * zdist + p3_xmean * zdist * zdist * zdist;
4798 sigma = p0_sigma + p1_sigma * zdist + p2_sigma * zdist * zdist + p3_sigma * zdist * zdist * zdist
4799 + p4_sigma * zdist * zdist * zdist * zdist + p5_sigma * zdist * zdist * zdist * zdist * zdist;
4800
4801 // Number of electrons in the current collision
4802 Int_t n_electrons_cluster = gRandom->Landau(1.027, 0.11);
4803 if (n_electrons_cluster < 1)
4804 n_electrons_cluster = 1; // min
4805 if (n_electrons_cluster > 6)
4806 n_electrons_cluster = 6; // max
4807
4808 // Filling charge distribution -------------------------------------
4809 Double_t dist_from_start = 0.0;
4810 if (pz > 0.0) {
4811 dist_from_start =
4812 std::sqrt((xcoll - x) * (xcoll - x) + (ycoll - y) * (ycoll - y) + (zcoll - z) * (zcoll - z));
4813 } else {
4814 dist_from_start = std::sqrt((xcoll - x_out) * (xcoll - x_out) + (ycoll - y_out) * (ycoll - y_out)
4815 + (zcoll - z_out) * (zcoll - z_out));
4816 }
4817 Int_t segment = (Int_t)(dist_from_start / track_length * n_track_segments);
4818 if (segment >= n_track_segments)
4819 segment = n_track_segments - 1;
4820 //------------------------------------------------------------------
4821
4822 Long64_t coll_electrons = 0; // [FAST-STAND] electrons of this collision on the readout
4823
4824 for (Int_t ielectron = 0; ielectron < n_electrons_cluster; ++ielectron) {
4825
4826 // Electron gain in each GEM cascade (three draws always, as in the other methods)
4827 Double_t gain_gem1 = gRandom->Exp(15);
4828 Double_t gain_gem2 = gRandom->Exp(15);
4829 Double_t gain_gem3 = gRandom->Exp(15);
4830
4831 if (gain_gem1 < 1.0)
4832 gain_gem1 = 1.0;
4833 if (gain_gem2 < 1.0)
4834 gain_gem2 = 1.0;
4835 if (gain_gem3 < 1.0)
4836 gain_gem3 = 1.0;
4837
4838 int total_gain = 0;
4839
4840 if (zdist < level1) {
4841 total_gain = 1.0;
4842 }
4843 if (zdist >= level1 && zdist < level2) {
4844 total_gain = gain_gem3;
4845 }
4846 if (zdist >= level2 && zdist < level3) {
4847 total_gain = gain_gem3 * gain_gem2;
4848 }
4849 if (zdist >= level3) {
4850 total_gain = gain_gem3 * gain_gem2 * gain_gem1;
4851 }
4852
4853 seg_local[segment] += total_gain;
4854
4855 // ANN mode: electrons are not projected on the readout, strips are taken from the model
4856 if (useANN)
4857 continue;
4858
4859 // Standard method: [FAST-STAND] the electrons of the collision are only counted here
4860 coll_electrons += total_gain;
4861 }
4862
4863 // [FAST-STAND] one task per collision (the strips are drawn after the collision loop)
4864 if (!useANN && coll_electrons > 0) {
4865 const Double_t x_center =
4866 (ElectronDriftDirection == ForwardZAxisEDrift) ? (xcoll + xmean) : (xcoll - xmean);
4867 coll_tasks.push_back({x_center, ycoll, sigma, coll_electrons});
4868 }
4869 }
4870 }
4871
4872 // [FAST-STAND] Standard method: electrons -> strips by the multinomial distribution ----------
4873 // For each collision and each layer:
4874 // u(x, y) = ConvertPointToStripPosition(x, y) is linear: u = u0 + a*(x - x0) + b*(y - y0)
4875 // (x, y) ~ N(x0, sigma) x N(y0, sigma) -> u ~ N(u0, sigma_u), sigma_u = sigma * sqrt(a^2 + b^2)
4876 // strip j (u >= 0) gets the electrons with u in [j, j + d1) or [j + d2, j + 1) (d1, d2 - dead band of the strip,
4877 // as in the per-electron code: (0.4, 0.6) for the vertical strips, (0.3, 0.7) for the rotated ones)
4878 // numbers of electrons per strip ~ Multinomial(n, p_j): a chain of binomials, the rest (dead bands, far tails)
4879 // is lost as in the per-electron code.
4880 // Collisions with the +-kNSigma box crossing the layer border, or with negative strip coordinates, are simulated
4881 // per electron (the same conditions as in the per-electron code).
4882 if (!useANN && !coll_tasks.empty()) {
4883
4884 constexpr Double_t kNSigma = 6.0; // range of the Gaussian (lost probability ~ 2e-9)
4885
4886 // Generator of the strips: seeded from gRandom (reproducible with the seed of gRandom)
4887 std::mt19937_64 rng(static_cast<UInt_t>(gRandom->Integer(0xFFFFFFFFu)));
4888 std::normal_distribution<Double_t> gaus01(0.0, 1.0);
4889 std::binomial_distribution<Long64_t> binom;
4890 using BinomParam = std::binomial_distribution<Long64_t>::param_type;
4891
4892 // Standard normal CDF
4893 auto Phi = [](Double_t t) { return 0.5 * TMath::Erfc(-t / TMath::Sqrt2()); };
4894
4895 // Layer invariants: dead band, linear coefficients of the strip coordinate, accumulators
4896 struct LayerSetup
4897 {
4898 Bool_t linear = kFALSE; // u(x, y) is linear (checked), else - per electron always
4899 Double_t d1 = 0.4, d2 = 0.6; // dead band [d1, d2) of a strip (fraction of the strip)
4900 Double_t a = 0.0, b = 0.0; // du/dx, du/dy
4901 Double_t grad = 0.0; // sqrt(a^2 + b^2)
4902 Int_t n_strips = 0;
4903 std::vector<Double_t> acc; // electrons per strip [0, n_strips)
4904 Int_t acc_min = std::numeric_limits<Int_t>::max(), acc_max = -1;
4905 };
4906 std::vector<LayerSetup> ls(n_strip_layers);
4907
4908 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4909 LayerSetup& s = ls[ilayer];
4910 BmnGemStripLayer& layer = StripLayers[ilayer];
4911 if (TMath::Abs(layer.GetAngleDeg()) >= 1.0) {
4912 s.d1 = 0.3; // rotated strips
4913 s.d2 = 0.7;
4914 }
4915 s.n_strips = std::max(0, layer.GetNStrips());
4916 s.acc.assign(s.n_strips, 0.0);
4917
4918 // linear coefficients from the strip coordinate around the window center point, h = 1 mm
4919 const Double_t h = 0.1;
4920 const Double_t u00 = layer.ConvertPointToStripPosition(x_c, y_c);
4921 const Double_t u10 = layer.ConvertPointToStripPosition(x_c + h, y_c);
4922 const Double_t u01 = layer.ConvertPointToStripPosition(x_c, y_c + h);
4923 const Double_t u11 = layer.ConvertPointToStripPosition(x_c + h, y_c + h);
4924 s.a = (u10 - u00) / h;
4925 s.b = (u01 - u00) / h;
4926 s.grad = TMath::Sqrt(s.a * s.a + s.b * s.b);
4927 // linearity check (the cross term must be zero)
4928 s.linear = std::isfinite(s.grad) && TMath::Abs(u11 - u10 - u01 + u00) <= 1e-9 * (1.0 + TMath::Abs(u00));
4929 }
4930
4931 // One electron -> its strip in the layer (the same conditions as in the per-electron code)
4932 auto add_electron = [&](Int_t ilayer, Double_t x_readout, Double_t y_readout) {
4933 BmnGemStripLayer& layer = StripLayers[ilayer];
4934 if (!layer.IsPointInsideStripLayer(x_readout, y_readout))
4935 return;
4936 const Double_t strip_pos = layer.ConvertPointToStripPosition(x_readout, y_readout);
4937 const Double_t dstrip = strip_pos - (int)strip_pos;
4938 if (dstrip >= ls[ilayer].d1 && dstrip < ls[ilayer].d2)
4939 return;
4940 const Int_t strip = (Int_t)strip_pos;
4941 LayerSetup& s = ls[ilayer];
4942 if (strip >= 0 && strip < s.n_strips) {
4943 s.acc[strip] += 1.0;
4944 s.acc_min = std::min(s.acc_min, strip);
4945 s.acc_max = std::max(s.acc_max, strip);
4946 } else {
4947 cluster_layers[ilayer].AddStrip(strip, 1.0); // out of the accumulator range: directly
4948 }
4949 };
4950
4951 // n electrons in the strip -> accumulator
4952 auto add_strip = [&](Int_t ilayer, Int_t strip, Long64_t n) {
4953 LayerSetup& s = ls[ilayer];
4954 if (strip >= 0 && strip < s.n_strips) {
4955 s.acc[strip] += static_cast<Double_t>(n);
4956 s.acc_min = std::min(s.acc_min, strip);
4957 s.acc_max = std::max(s.acc_max, strip);
4958 } else {
4959 cluster_layers[ilayer].AddStrip(strip, static_cast<Double_t>(n));
4960 }
4961 };
4962
4963 for (const CollisionTask& t : coll_tasks) {
4964 const Double_t box = kNSigma * t.sigma; // half-size of the +-kNSigma box in x and y
4965
4966 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
4967 LayerSetup& s = ls[ilayer];
4968 BmnGemStripLayer& layer = StripLayers[ilayer];
4969
4970 // Position of the box relative to the layer: 4 corners + center
4971 Int_t n_inside = 0;
4972 n_inside += layer.IsPointInsideStripLayer(t.xcenter, t.ycenter) ? 1 : 0;
4973 n_inside += layer.IsPointInsideStripLayer(t.xcenter - box, t.ycenter - box) ? 1 : 0;
4974 n_inside += layer.IsPointInsideStripLayer(t.xcenter + box, t.ycenter - box) ? 1 : 0;
4975 n_inside += layer.IsPointInsideStripLayer(t.xcenter - box, t.ycenter + box) ? 1 : 0;
4976 n_inside += layer.IsPointInsideStripLayer(t.xcenter + box, t.ycenter + box) ? 1 : 0;
4977 if (n_inside == 0)
4978 continue; // the whole Gaussian is out of the layer
4979
4980 const Double_t u0 = layer.ConvertPointToStripPosition(t.xcenter, t.ycenter);
4981 const Double_t sigma_u = t.sigma * s.grad;
4982 const Double_t u_lo = u0 - kNSigma * sigma_u;
4983 const Double_t u_hi = u0 + kNSigma * sigma_u;
4984
4985 const Bool_t analytic = s.linear && n_inside == 5 && u_lo >= 0.0 && sigma_u > 1e-12;
4986
4987 if (!analytic) {
4988 // per electron (border of the layer, negative strip coordinates, no smearing)
4989 for (Long64_t ie = 0; ie < t.n; ++ie) {
4990 const Double_t x_readout = t.xcenter + t.sigma * gaus01(rng);
4991 const Double_t y_readout = t.ycenter + t.sigma * gaus01(rng);
4992 add_electron(ilayer, x_readout, y_readout);
4993 }
4994 continue;
4995 }
4996
4997 // Multinomial over the strips j = floor(u_lo) .. floor(u_hi)
4998 const Int_t j_lo = (Int_t)TMath::Floor(u_lo);
4999 const Int_t j_hi = (Int_t)TMath::Floor(u_hi);
5000 Long64_t n_left = t.n;
5001 Double_t mass_left = 1.0; // probability of the categories not drawn yet (incl. lost electrons)
5002
5003 for (Int_t j = j_lo; j <= j_hi && n_left > 0; ++j) {
5004 const Double_t p = (Phi((j + s.d1 - u0) / sigma_u) - Phi((j - u0) / sigma_u))
5005 + (Phi((j + 1 - u0) / sigma_u) - Phi((j + s.d2 - u0) / sigma_u));
5006 if (p <= 0.0)
5007 continue;
5008 if (mass_left <= 0.0)
5009 break;
5010 Double_t q = p / mass_left;
5011 if (q > 1.0)
5012 q = 1.0;
5013 const Long64_t k = binom(rng, BinomParam(n_left, q));
5014 if (k > 0)
5015 add_strip(ilayer, j, k);
5016 n_left -= k;
5017 mass_left -= p;
5018 }
5019 // n_left electrons: in the dead bands or out of +-kNSigma - lost (as in the per-electron code)
5020 }
5021 }
5022
5023 // Accumulators -> clusters (one AddStrip() per strip, ascending order)
5024 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5025 const LayerSetup& s = ls[ilayer];
5026 for (Int_t istrip = s.acc_min; istrip <= s.acc_max; ++istrip) {
5027 if (s.acc[istrip] > 0.0)
5028 cluster_layers[ilayer].AddStrip(istrip, s.acc[istrip]);
5029 }
5030 }
5031 }
5032 //----------------------------------------------------------------------
5033
5034 // ANN generation (MLP, own inference) ---------------------------------
5035 if (useANN) {
5036
5037 // Segments for the model: external or from the avalanche of this point
5038 const vector<Int_t>& seg_used = use_external_segs ? segs : seg_local;
5039
5040 // Normalization (px,py,pz), where r_vec must be equal to 1
5041 Double_t px_norm = px / r_vec;
5042 Double_t py_norm = py / r_vec;
5043 Double_t pz_norm = pz / r_vec;
5044
5045 // Inversion for z-reverse direction particles: the same track segment passed from (x_out, y_out)
5046 if (pz < 0.0) {
5047 px_norm = -px_norm;
5048 py_norm = -py_norm;
5049 pz_norm = -pz_norm;
5050 }
5051
5052 Double_t drift_direction = 0; // 0 is ForwardZAxisEDrift
5054 drift_direction = 1;
5055
5056 // Magnetic field: given explicitly or from the current medium configuration
5057 const Double_t bfield_used = (bfield < 0.0) ? BmnGemStripMedium::GetInstance().BField : bfield;
5058
5059 // Normalize seg. vector: [0, max] -> [0, 1] (max over the whole vector)
5060 Double_t max_segment_val = *(std::max_element(seg_used.begin(), seg_used.end()));
5061
5062 // Total charge in segment vector
5063 Double_t total_charge = 0.0;
5064 for (size_t i = 0; i < seg_used.size(); ++i)
5065 total_charge += seg_used[i];
5066
5067 // Window center point (x_c, y_c): computed above with GetANNWindowCenterPoint()
5068
5069 // Common part of the model input: [px, py, pz, drift, bfield, s0..s{n_segs_features-1}]
5070 float common_input[n_common_inputs];
5071 common_input[0] = static_cast<float>(px_norm);
5072 common_input[1] = static_cast<float>(py_norm);
5073 common_input[2] = static_cast<float>(pz_norm);
5074 common_input[3] = static_cast<float>(drift_direction);
5075 common_input[4] = static_cast<float>(bfield_used);
5076 for (Int_t iseg = 0; iseg < n_segs_features; ++iseg)
5077 common_input[5 + iseg] =
5078 (max_segment_val > 0.0) ? static_cast<float>(seg_used[iseg] / max_segment_val) : 0.0f;
5079
5080 if (total_charge > 0.0) {
5081 // Strip model: a window of n_ann_strips strips around the window center strip in each layer
5082 const Int_t center_strip_index = n_ann_strips / 2; // center strip of the window
5083
5084 // Layers with the MC point inside: their part of the input [strip_layer, strip_order, center_frac]
5085 std::vector<Int_t> ann_layers; // layer index
5086 std::vector<Int_t> ann_center_strips; // MC strip (window center)
5087 std::vector<Int_t> ann_n_strips; // number of strips in the layer
5088 std::vector<float> ann_layer_inputs; // [n_ann_layers][n_layer_inputs]
5089 ann_layers.reserve(n_strip_layers);
5090 ann_center_strips.reserve(n_strip_layers);
5091 ann_n_strips.reserve(n_strip_layers);
5092 ann_layer_inputs.reserve(n_strip_layers * n_layer_inputs);
5093
5094 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5095
5096 // The window is built around the center point (middle of the drift gap): it must be inside this layer
5097 if (!StripLayers[ilayer].IsPointInsideStripLayer(x_c, y_c))
5098 continue;
5099
5100 Int_t n_strips_in_layer = StripLayers[ilayer].GetNStrips();
5101 if (n_strips_in_layer <= 0)
5102 continue;
5103
5104 // Center point position in strips: integer part - window center strip, fractional part - center_frac
5105 Double_t center_pos = StripLayers[ilayer].ConvertPointToStripPosition(x_c, y_c);
5106 Int_t center_strip = (Int_t)TMath::Floor(center_pos);
5107 Double_t center_frac = center_pos - center_strip;
5108
5109 Double_t strip_layer = static_cast<Double_t>(StripLayers[ilayer].GetType()); // 0 - lower, 1 - upper
5110 Double_t strip_order =
5111 static_cast<Double_t>(StripLayers[ilayer].GetStripNumberingOrder()); // 0 - LeftToRight
5112
5113 ann_layers.push_back(ilayer);
5114 ann_center_strips.push_back(center_strip);
5115 ann_n_strips.push_back(n_strips_in_layer);
5116 ann_layer_inputs.push_back(static_cast<float>(strip_layer));
5117 ann_layer_inputs.push_back(static_cast<float>(strip_order));
5118 ann_layer_inputs.push_back(static_cast<float>(center_frac));
5119 }
5120
5121 // Strip signals for all these layers in one call: n_ann_strips values in [0, 1] (max ~ 1) per layer
5122 Int_t n_ann_layers = ann_layers.size();
5123 std::vector<float> ann_strip_values(static_cast<size_t>(n_ann_layers) * n_ann_strips);
5124 if (n_ann_layers > 0
5125 && !mlp_model->PredictSplit(common_input, n_common_inputs, ann_layer_inputs.data(), n_ann_layers,
5126 ann_strip_values.data()))
5127 {
5128 LOG(error) << "BmnGemStripModule::" << __func__ << "(): MLP prediction failed";
5129 n_ann_layers = 0;
5130 }
5131
5132 for (Int_t iann = 0; iann < n_ann_layers; ++iann) {
5133 const Int_t ilayer = ann_layers[iann];
5134 const Int_t center_strip = ann_center_strips[iann];
5135 const Int_t n_strips_in_layer = ann_n_strips[iann];
5136 const float* strip_values = ann_strip_values.data() + static_cast<size_t>(iann) * n_ann_strips;
5137
5138 // Sum of the model output over all strips of the window
5139 Double_t sum_norm = 0.0;
5140 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip)
5141 sum_norm += strip_values[istrip];
5142
5143 // Scale coeff for total signal distribution (separate for lower and upper layers)
5144 const Double_t ann_correction_lower = 0.8; // Set to 1.0 to disable correction
5145 const Double_t ann_correction_upper = 0.6; // Set to 1.0 to disable correction
5146 const Double_t ann_correction =
5147 (StripLayers[ilayer].GetType() == LowerStripLayer) ? ann_correction_lower : ann_correction_upper;
5148
5149 Double_t signal_scale = 0.0;
5150 if (sum_norm > 0.0)
5151 signal_scale = (total_charge / sum_norm) * ann_correction;
5152
5153 Double_t threshold = 0.0001 * total_charge;
5154
5155 // Strips are written directly: window index -> strip number
5156 for (Int_t istrip = 0; istrip < n_ann_strips; ++istrip) {
5157 Double_t content = strip_values[istrip] * signal_scale;
5158 if (content <= threshold)
5159 continue;
5160
5161 Int_t strip_num = center_strip - center_strip_index + istrip;
5162 if (strip_num < 0 || strip_num >= n_strips_in_layer)
5163 continue; // the window goes out of the layer
5164
5165 cluster_layers[ilayer].AddStrip(strip_num, content);
5166 }
5167 }
5168 }
5169 }
5170 //----------------------------------------------------------------------
5171
5172 // Segmented charge distribution (generation data) ------------------------
5173 // (seg_used above is not used after this point)
5174 if (FillGenerationData) {
5175 if (use_external_segs)
5176 SegmentedChargeDistribution = std::move(segs);
5177 else
5178 SegmentedChargeDistribution = std::move(seg_local);
5179 }
5180 //----------------------------------------------------------------------
5181
5182 // Calculate cluster parameters --------------------------------------------
5183 vector<Double_t> cluster_mean_position(n_strip_layers, 0.0);
5184 vector<Double_t> cluster_total_signal(n_strip_layers, 0.0);
5185
5186 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5187
5188 if (cluster_layers[ilayer].GetClusterSize() > 0) {
5189
5190 for (int i = 0; i < cluster_layers[ilayer].GetClusterSize(); ++i) {
5191 cluster_mean_position[ilayer] += (cluster_layers[ilayer].Strips[i] + 0.5)
5192 * cluster_layers[ilayer].Signals[i]; // as sum of all positions
5193 cluster_total_signal[ilayer] += cluster_layers[ilayer].Signals[i];
5194 }
5195 cluster_mean_position[ilayer] /= cluster_total_signal[ilayer];
5196 }
5197
5198 cluster_layers[ilayer].MeanPosition = cluster_mean_position[ilayer]; // mean cluster position
5199 cluster_layers[ilayer].TotalSignal = cluster_total_signal[ilayer]; // total signal of the cluster
5200 }
5201
5203 if (pz > 0.0) {
5204 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5205 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
5206 }
5207 } else {
5208 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5209 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
5210 }
5211 }
5212 } else {
5213 if (pz > 0.0) {
5214 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5215 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x_out, y_out);
5216 }
5217 } else {
5218 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5219 cluster_layers[ilayer].OriginPosition = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
5220 }
5221 }
5222 }
5223
5224 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5225 cluster_layers[ilayer].PositionResidual =
5226 cluster_layers[ilayer].MeanPosition - cluster_layers[ilayer].OriginPosition;
5227 cluster_layers[ilayer].SetType(StripLayers[ilayer].GetType());
5228 }
5229 //----------------------------------------------------------------------
5230
5231 // Added clusters (generation data) ----------------------------------------
5232 if (FillGenerationData) {
5233 if (AddedClusters.size() == 0) {
5234 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5235 AddedClusters.push_back(vector<StripCluster>());
5236 }
5237 }
5238
5239 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5240 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
5241 }
5242 }
5243 //------------------------------------------------------------------------------
5244
5245 // Add the correct clusters to the strip layers -------------------------
5246 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5247 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
5248 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
5249 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
5250 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
5251 }
5252 }
5253 //----------------------------------------------------------------------
5254
5255 // Fill strip matches ---------------------------------------------------
5256 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5257 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
5258 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
5259 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
5260 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
5261 refID);
5262 }
5263 }
5264 //----------------------------------------------------------------------
5265
5266 RealPointsX.push_back(x);
5267 RealPointsY.push_back(y);
5268 RealPointsXout.push_back(x_out);
5269 RealPointsYout.push_back(y_out);
5270 RealPointsMC.push_back(refID);
5271 RealPointsXcenter.push_back(x_c); // ANN window center point
5272 RealPointsYcenter.push_back(y_c);
5273
5274 return true;
5275}
5276//------------------------------------------------------------------------------
5277
5278// Add single point with Gaussian smearing
5280 Double_t y,
5281 Double_t z,
5282 Double_t px,
5283 Double_t py,
5284 Double_t pz,
5285 Double_t signal,
5286 Int_t refID)
5287{
5288
5289 if (IsPointInsideModule(x, y)) {
5290
5291 if (signal <= 0.0)
5292 signal = 1e-16;
5293
5294 Double_t radius = AvalancheRadius;
5295 if (radius <= 0.0)
5296 return false;
5297
5298 Int_t cycle_cnt = 0;
5299 while (true) {
5300 radius = gRandom->Gaus(AvalancheRadius, 0.02);
5301 if (radius > AvalancheRadius / 2.0 && radius < AvalancheRadius * 2.0 && radius > 0.0)
5302 break;
5303 cycle_cnt++;
5304
5305 if (cycle_cnt > 5) {
5306 radius = AvalancheRadius;
5307 break;
5308 }
5309 }
5310
5311 // Make strip cluster in each strip layer -------------------------------
5312 Int_t n_strip_layers = StripLayers.size();
5313 vector<StripCluster> cluster_layers(n_strip_layers, StripCluster());
5314
5315 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5316 if (!StripLayers[ilayer].IsPointInsideStripLayer(x, y))
5317 continue;
5318 cluster_layers[ilayer] = MakeCluster(ilayer, x, y, signal, radius);
5319 }
5320 //----------------------------------------------------------------------
5321
5322 // Testing ----------------------------------------------------------------------
5323 /*if(AddedClusters.size() == 0) {
5324 for(Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5325 AddedClusters.push_back(vector<StripCluster>());
5326 }
5327 }
5328
5329 for(Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5330 AddedClusters[ilayer].push_back(cluster_layers[ilayer]);
5331 }*/
5332 //------------------------------------------------------------------------------
5333
5334 // Add the correct clusters to the strip layers -------------------------
5335 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5336 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
5337 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
5338 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
5339 StripLayers[ilayer].AddStripSignal(strip_num, strip_signal);
5340 }
5341 }
5342 //----------------------------------------------------------------------
5343
5344 // Fill strip matches ---------------------------------------------------
5345 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5346 for (size_t ielement = 0; ielement < cluster_layers[ilayer].Strips.size(); ++ielement) {
5347 Int_t strip_num = cluster_layers[ilayer].Strips.at(ielement);
5348 Double_t strip_signal = cluster_layers[ilayer].Signals.at(ielement);
5349 StripLayers[ilayer].AddLinkToStripMatch(strip_num, strip_signal / cluster_layers[ilayer].TotalSignal,
5350 refID);
5351 }
5352 }
5353 //----------------------------------------------------------------------
5354
5355 RealPointsX.push_back(x);
5356 RealPointsY.push_back(y);
5357 RealPointsXout.push_back(x);
5358 RealPointsYout.push_back(y);
5359 RealPointsMC.push_back(refID);
5360
5361 return true;
5362 } else {
5363 LOG(warn) << "BmnGemStripModule::AddRealPointFullOne(): Point (" << x << " : " << y << " : " << z
5364 << ") is out of the readout plane or inside a dead zone";
5365 return false;
5366 }
5367}
5368
5369// Add single point without smearing and avalanch effects
5371 Double_t y,
5372 Double_t z,
5373 Double_t px,
5374 Double_t py,
5375 Double_t pz,
5376 Double_t signal,
5377 Int_t refID)
5378{
5379
5380 if (IsPointInsideModule(x, y)) {
5381
5382 Int_t n_strip_layers = StripLayers.size();
5383
5384 // Add a signal to the strips layers ------------------------------------
5385 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5386
5387 if (!StripLayers[ilayer].IsPointInsideStripLayer(x, y))
5388 continue;
5389
5390 Double_t strip_pos = StripLayers[ilayer].ConvertPointToStripPosition(x, y);
5391 StripLayers[ilayer].AddStripSignal((Int_t)strip_pos, signal);
5392
5393 // strip match
5394 StripLayers[ilayer].AddLinkToStripMatch((Int_t)strip_pos, 1.0, refID);
5395 }
5396 //----------------------------------------------------------------------
5397
5398 RealPointsX.push_back(x);
5399 RealPointsY.push_back(y);
5400 RealPointsXout.push_back(x);
5401 RealPointsYout.push_back(y);
5402 RealPointsMC.push_back(refID);
5403
5404 return true;
5405 } else {
5406 LOG(warn) << "BmnGemStripModule::AddRealPointSimple(): Point (" << x << " : " << y
5407 << ") is out of the readout plane or inside a dead zone";
5408 return false;
5409 }
5410}
5411
5413 Double_t xcoord,
5414 Double_t ycoord,
5415 Double_t signal,
5416 Double_t radius)
5417{
5418
5419 Double_t ClusterDistortion = 0.0; // signal variation (0.1 is 10%)
5420
5421 StripCluster cluster;
5422
5423 if (radius <= 0.0)
5424 return cluster;
5425
5426 Double_t Pitch = StripLayers[layer_num].GetPitch();
5427
5428 Double_t RadiusInZones = radius / Pitch;
5429 Double_t Sigma = RadiusInZones / 3.33;
5430
5431 TF1 gausF("gausF", "gaus", -4 * Sigma, 4 * Sigma);
5432 gausF.SetParameter(0, 1.0); // constant (altitude)
5433 gausF.SetParameter(1, 0.0); // mean (center position)
5434 gausF.SetParameter(2, Sigma); // sigma
5435
5436 Double_t SCluster = gausF.Integral(-4 * Sigma, 4 * Sigma); // square of the one side distribution (more exactly)
5437
5438 TRandom rand(0);
5439 Double_t var_level = ClusterDistortion; // signal variation (0.1 is 10%)
5440
5441 Int_t NStripsInLayer = StripLayers[layer_num].GetNStrips();
5442
5443 if (NStripsInLayer == 0)
5444 return cluster;
5445
5446 Double_t CenterZonePos = StripLayers[layer_num].ConvertPointToStripPosition(xcoord, ycoord);
5447
5448 if (CenterZonePos < 0.0 || CenterZonePos >= NStripsInLayer)
5449 return cluster;
5450
5451 gausF.SetParameter(1, CenterZonePos);
5452 Double_t total_signal = 0.0;
5453
5454 Double_t LeftZonePos = CenterZonePos - RadiusInZones;
5455 Double_t RightZonePos = CenterZonePos + RadiusInZones;
5456 Double_t OutLeftBorder = 0.0;
5457 Double_t OutRightBorder = 0.0;
5458
5459 if (LeftZonePos < 0.0) {
5460 OutLeftBorder = LeftZonePos;
5461 LeftZonePos = 0.0;
5462 }
5463 if (RightZonePos < 0.0) {
5464 OutRightBorder = RightZonePos;
5465 RightZonePos = 0.0;
5466 }
5467 if (LeftZonePos >= NStripsInLayer) {
5468 OutLeftBorder = LeftZonePos;
5469 LeftZonePos = NStripsInLayer - 0.001;
5470 }
5471 if (RightZonePos >= NStripsInLayer) {
5472 OutRightBorder = RightZonePos;
5473 RightZonePos = NStripsInLayer - 0.001;
5474 }
5475
5476 Double_t h = 0.0;
5477 Double_t dist = 0.0;
5478
5479 // avalanche is inside of the one strip
5480 if ((Int_t)LeftZonePos == (Int_t)RightZonePos) {
5481
5482 Int_t NumCurrentZone = (Int_t)LeftZonePos;
5483
5484 h = RightZonePos - LeftZonePos;
5485 if (h < 0.0)
5486 h = 0.0;
5487
5488 Double_t xp = LeftZonePos + dist;
5489 Double_t S = gausF.Integral(xp, xp + h);
5490 Double_t Energy = (signal * Gain) * (S / SCluster);
5491 Energy += rand.Gaus(0.0, var_level * Energy);
5492 if (Energy < 0.0)
5493 Energy = 0.0;
5494
5495 if (NumCurrentZone >= 0 && NumCurrentZone < NStripsInLayer && Energy > 0.0) {
5496 cluster.AddStrip(NumCurrentZone, Energy);
5497 total_signal += Energy;
5498 }
5499
5500 dist += h;
5501
5502 } else {
5503 // left border strip
5504 Int_t NumCurrentZone = (Int_t)LeftZonePos;
5505
5506 h = ((Int_t)LeftZonePos + 1) - LeftZonePos;
5507 if (h < 0.0)
5508 h = 0.0;
5509
5510 Double_t xp = LeftZonePos + dist;
5511 Double_t S = gausF.Integral(xp, xp + h);
5512 Double_t Energy = (signal * Gain) * (S / SCluster);
5513 Energy += rand.Gaus(0.0, var_level * Energy);
5514 if (Energy < 0.0)
5515 Energy = 0.0;
5516
5517 if (NumCurrentZone >= 0 && NumCurrentZone < NStripsInLayer && Energy > 0.0) {
5518 cluster.AddStrip(NumCurrentZone, Energy);
5519 total_signal += Energy;
5520 }
5521
5522 dist += h;
5523
5524 // inner strips
5525 for (Int_t i = (Int_t)LeftZonePos + 1; i < (Int_t)RightZonePos; ++i) {
5526
5527 NumCurrentZone = i;
5528
5529 h = 1.0;
5530
5531 xp = LeftZonePos + dist;
5532 S = gausF.Integral(xp, xp + h);
5533 Energy = (signal * Gain) * (S / SCluster);
5534 Energy += rand.Gaus(0.0, var_level * Energy);
5535 if (Energy < 0.0)
5536 Energy = 0.0;
5537
5538 if (NumCurrentZone >= 0 && NumCurrentZone < NStripsInLayer && Energy > 0.0) {
5539 cluster.AddStrip(NumCurrentZone, Energy);
5540 total_signal += Energy;
5541 }
5542
5543 dist += h;
5544 }
5545 // right border strip
5546 NumCurrentZone = (Int_t)RightZonePos;
5547
5548 h = RightZonePos - (Int_t)RightZonePos;
5549 if (h < 0.0)
5550 h = 0.0;
5551
5552 xp = LeftZonePos + dist;
5553 S = gausF.Integral(xp, xp + h);
5554 Energy = (signal * Gain) * (S / SCluster);
5555 Energy += rand.Gaus(0.0, var_level * Energy);
5556 if (Energy < 0.0)
5557 Energy = 0.0;
5558
5559 if (NumCurrentZone >= 0 && NumCurrentZone < NStripsInLayer && Energy > 0.0) {
5560 cluster.AddStrip(NumCurrentZone, Energy);
5561 total_signal += Energy;
5562 }
5563
5564 dist += h;
5565 }
5566
5567 if (cluster.GetClusterSize() <= 0) {
5568 return cluster;
5569 }
5570
5571 // find the mean value of the avalanche position (fitting by gaus function)
5572 Double_t mean_fit_pos = 0.0;
5573
5574 Int_t NOutLeftBins = 0;
5575 Int_t NOutRightBins = 0;
5576 if (OutLeftBorder != 0.0) {
5577 NOutLeftBins = (Int_t)(fabs(OutLeftBorder)) + 1;
5578 }
5579 if (OutRightBorder != 0.0) {
5580 NOutRightBins = (Int_t)(OutRightBorder - RightZonePos) + 1;
5581 }
5582
5583 Int_t begin_strip_num = cluster.Strips.at(0);
5584 Int_t last_strip_num = cluster.Strips.at(cluster.GetClusterSize() - 1);
5585 Int_t nstrips = last_strip_num - begin_strip_num + 1;
5586
5587 TH1F hist("hist_for_fit", "hist_for_fit", nstrips + NOutLeftBins + NOutRightBins, begin_strip_num - NOutLeftBins,
5588 last_strip_num + 1 + NOutRightBins);
5589 Int_t hist_index = 0;
5590
5591 for (Int_t i = 0; i < cluster.GetClusterSize(); ++i) {
5592 Double_t value = cluster.Signals.at(i);
5593 hist.SetBinContent(hist_index + 1 + NOutLeftBins, value);
5594 hist_index++;
5595 }
5596
5597 // on the left border
5598 if (NOutLeftBins > 0.0) {
5599 Double_t first = OutLeftBorder;
5600 Double_t last = (Int_t)OutLeftBorder;
5601 Double_t S = gausF.Integral(first, last);
5602 Double_t Energy = (signal * Gain) * (S / SCluster);
5603 Energy += rand.Gaus(0.0, var_level * Energy);
5604 if (Energy < 0.0)
5605 Energy = 0.0;
5606 Double_t value = Energy;
5607 hist.SetBinContent(1, value);
5608 dist = 0.0;
5609
5610 for (Int_t i = 1; i < NOutLeftBins; ++i) {
5611 h = 1.0;
5612 first = (Int_t)OutLeftBorder + dist;
5613 last = first + h;
5614 S = gausF.Integral(first, last);
5615 Energy = (signal * Gain) * (S / SCluster);
5616 Energy += rand.Gaus(0.0, var_level * Energy);
5617 if (Energy < 0.0)
5618 Energy = 0.0;
5619 value = Energy;
5620 hist.SetBinContent(1 + i, value);
5621 dist += h;
5622 }
5623 }
5624
5625 // on the right border
5626 if (NOutRightBins > 0.0) {
5627 dist = 0.0;
5628 for (Int_t i = hist_index; i < hist_index + NOutRightBins - 1; ++i) {
5629 h = 1.0;
5630 Double_t first = NStripsInLayer + dist;
5631 Double_t last = first + h;
5632 Double_t S = gausF.Integral(first, last);
5633 Double_t Energy = (signal * Gain) * (S / SCluster);
5634 Energy += rand.Gaus(0.0, var_level * Energy);
5635 if (Energy < 0.0)
5636 Energy = 0.0;
5637 Double_t value = Energy;
5638 hist.SetBinContent(1 + i, value);
5639 dist += h;
5640 }
5641
5642 Double_t first = (Int_t)OutRightBorder;
5643 Double_t last = first + (OutRightBorder - (Int_t)OutRightBorder);
5644 Double_t S = gausF.Integral(first, last);
5645 Double_t Energy = (signal * Gain) * (S / SCluster);
5646 Energy += rand.Gaus(0.0, var_level * Energy);
5647 if (Energy < 0.0)
5648 Energy = 0.0;
5649 Double_t value = Energy;
5650 hist.SetBinContent(hist_index + NOutRightBins, value);
5651 }
5652
5653 TF1* gausFitFunction = 0;
5654 TString fit_params = "WQ0";
5655
5656#ifdef DRAW_REAL_CLUSTER_HISTOGRAMS
5657 fit_params = "WQ";
5658#endif
5659
5660 if (nstrips > 1) {
5661 hist.Fit("gaus", fit_params); // Q - quit mode (without information on the screen); 0 - not draw
5662 gausFitFunction = hist.GetFunction("gaus");
5663 if (gausFitFunction) {
5664 mean_fit_pos = gausFitFunction->GetParameter(1);
5665 } else {
5666 mean_fit_pos = hist.GetMean();
5667 }
5668 } else {
5669 mean_fit_pos = hist.GetMean();
5670 }
5671
5672 cluster.MeanPosition = mean_fit_pos;
5673 cluster.TotalSignal = total_signal;
5674 cluster.PositionResidual = mean_fit_pos - CenterZonePos; // residual between mean and origin positions (lower
5675 // cluster): residual = finded(current) - orig(average)
5676
5677 cluster.IsCorrect = true; // set correct status of the cluster
5678 return cluster;
5679}
5680
5682{
5683 if (aval_radius >= 0.0)
5684 AvalancheRadius = aval_radius;
5685 else {
5686 LOG(warn) << "BmnGemStripModule::SetAvalancheRadius(): incorrect value for AvalancheRadius! It'll be "
5687 "set by default!";
5688 AvalancheRadius = 0.1; // default
5689 }
5690}
5691
5696
5698{
5699
5702
5703 Int_t n_strip_layers = StripLayers.size();
5704
5705 // Find strip clusters and hits in the strip layers -------------------------
5706 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5707 StripLayers[ilayer].FindClustersAndStripHits();
5708 }
5709 //--------------------------------------------------------------------------
5710
5711 map<Int_t, Bool_t> UsageStatus_LowerClusters;
5712 map<Int_t, Bool_t> UsageStatus_UpperClusters;
5713
5714 // Saving all unique id marks of clusters in a module
5715 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5716 for (Int_t i_strip_hit = 0; i_strip_hit < StripLayers[ilayer].GetNStripHits(); ++i_strip_hit) {
5717 StripCluster cluster = StripLayers[ilayer].GetStripClusters()[i_strip_hit];
5718 if (cluster.GetType() == LowerStripLayer) {
5719 UsageStatus_LowerClusters[cluster.GetUniqueID()] = false;
5720 }
5721 if (cluster.GetType() == UpperStripLayer) {
5722 UsageStatus_UpperClusters[cluster.GetUniqueID()] = false;
5723 }
5724 }
5725 }
5726
5727 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5728 for (Int_t jlayer = ilayer + 1; jlayer < n_strip_layers; ++jlayer) {
5729 // cout << "(i-layer : j-layer) = ( " << ilayer << " : " << jlayer << " )\n";
5730
5731 if (StripLayers[ilayer].GetType() == StripLayers[jlayer].GetType())
5732 continue;
5733
5734 Double_t iAngleRad = StripLayers[ilayer].GetAngleRad();
5735 Double_t jAngleRad = StripLayers[jlayer].GetAngleRad();
5736
5737 if (Abs(iAngleRad - jAngleRad) < 0.01)
5738 continue;
5739
5740 Double_t i_PiOver2MinusAngleRad = PiOver2() - iAngleRad;
5741 Double_t j_PiOver2MinusAngleRad = PiOver2() - jAngleRad;
5742
5743 for (Int_t i_strip_hit = 0; i_strip_hit < StripLayers[ilayer].GetNStripHits(); ++i_strip_hit) {
5744 for (Int_t j_strip_hit = 0; j_strip_hit < StripLayers[jlayer].GetNStripHits(); ++j_strip_hit) {
5745
5746 Double_t iStripHitPos = StripLayers[ilayer].GetStripHitPos(i_strip_hit);
5747 Double_t jStripHitPos = StripLayers[jlayer].GetStripHitPos(j_strip_hit);
5748
5749 Double_t iStripHitError = StripLayers[ilayer].GetStripHitError(i_strip_hit);
5750 Double_t jStripHitError = StripLayers[jlayer].GetStripHitError(j_strip_hit);
5751
5752 Double_t xcoord;
5753 Double_t ycoord;
5754
5755 if (Abs(iAngleRad) >= 0.01 && Abs(jAngleRad) >= 0.01) {
5756
5757 Double_t iB = StripLayers[ilayer].CalculateStripEquationB(iStripHitPos);
5758 Double_t jB = StripLayers[jlayer].CalculateStripEquationB(jStripHitPos);
5759
5760 xcoord = (jB - iB) / (Tan(i_PiOver2MinusAngleRad) - Tan(j_PiOver2MinusAngleRad));
5761 ycoord = Tan(i_PiOver2MinusAngleRad) * xcoord + iB;
5762 } else {
5763
5764 if (Abs(iAngleRad) < 0.01) {
5765
5766 if (StripLayers[ilayer].GetStripNumberingOrder() == LeftToRight) {
5767 xcoord = StripLayers[ilayer].GetXLeftStripBorderPoint()
5768 + iStripHitPos * StripLayers[ilayer].GetPitch();
5769 } else {
5770 xcoord = StripLayers[ilayer].GetXRightStripBorderPoint()
5771 - iStripHitPos * StripLayers[ilayer].GetPitch();
5772 }
5773
5774 Double_t jB = StripLayers[jlayer].CalculateStripEquationB(jStripHitPos);
5775 ycoord = Tan(j_PiOver2MinusAngleRad) * xcoord + jB;
5776 }
5777
5778 if (Abs(jAngleRad) < 0.01) {
5779
5780 if (StripLayers[jlayer].GetStripNumberingOrder() == LeftToRight) {
5781 xcoord = StripLayers[jlayer].GetXLeftStripBorderPoint()
5782 + jStripHitPos * StripLayers[jlayer].GetPitch();
5783 } else {
5784 xcoord = StripLayers[jlayer].GetXRightStripBorderPoint()
5785 - jStripHitPos * StripLayers[jlayer].GetPitch();
5786 }
5787
5788 Double_t iB = StripLayers[ilayer].CalculateStripEquationB(iStripHitPos);
5789 ycoord = Tan(i_PiOver2MinusAngleRad) * xcoord + iB;
5790 }
5791 }
5792
5793 // check: a point belongs to both strip layers together
5794 if (StripLayers[ilayer].IsPointInsideStripLayer(xcoord, ycoord)
5795 && StripLayers[jlayer].IsPointInsideStripLayer(xcoord, ycoord))
5796 {
5797
5798 IntersectionPointsX.push_back(xcoord);
5799 IntersectionPointsY.push_back(ycoord);
5800
5801 // Intersection (x,y)-errors from the strip-errors ------
5802 Double_t AngleIntersecRad = Abs(iAngleRad - jAngleRad);
5803
5804 // a rhomb zone is the intersection of the strip errors
5805 Double_t RhombSide1 = (iStripHitError * StripLayers[ilayer].GetPitch()) / Sin(AngleIntersecRad);
5806 Double_t RhombSide2 = (jStripHitError * StripLayers[jlayer].GetPitch()) / Sin(AngleIntersecRad);
5807
5808 // x-strip error
5809 Double_t xerr1 = Sin(Abs(jAngleRad)) * RhombSide1;
5810 Double_t xerr2 = Sin(Abs(iAngleRad)) * RhombSide2;
5811 Double_t xcoord_err = xerr1 + xerr2;
5812
5813 // y-strip error
5814 Double_t yerr1 = Cos(Abs(jAngleRad)) * RhombSide1;
5815 Double_t yerr2 = Cos(Abs(iAngleRad)) * RhombSide2;
5816 Double_t ycoord_err = yerr1 + yerr2;
5817
5818 IntersectionPointsXErrors.push_back(xcoord_err);
5819 IntersectionPointsYErrors.push_back(ycoord_err);
5820 //------------------------------------------------------
5821
5822 // intersection MC-matching -----------------------------
5823 BmnMatch iStripMatch = StripLayers[ilayer].GetStripMatch((Int_t)iStripHitPos);
5824 BmnMatch jStripMatch = StripLayers[jlayer].GetStripMatch((Int_t)jStripHitPos);
5825
5826 BmnMatch intersection_match;
5827 intersection_match.AddLink(iStripMatch);
5828 intersection_match.AddLink(jStripMatch);
5829
5830 IntersectionPointMatches.push_back(intersection_match);
5831 //------------------------------------------------------
5832
5833 // intersection digit number matching -------------------
5834 BmnMatch iStripDigitNumberMatch =
5835 StripLayers[ilayer].GetStripDigitNumberMatch((Int_t)iStripHitPos);
5836 BmnMatch jStripDigitNumberMatch =
5837 StripLayers[jlayer].GetStripDigitNumberMatch((Int_t)jStripHitPos);
5838
5839 BmnMatch intersection_digit_num_match;
5840 intersection_digit_num_match.AddLink(iStripDigitNumberMatch);
5841 intersection_digit_num_match.AddLink(jStripDigitNumberMatch);
5842
5843 IntersectionPointDigitNumberMatches.push_back(intersection_digit_num_match);
5844 //------------------------------------------------------
5845
5846 // Additional information about the intersection ------------
5847 // cluster size (number strips) in both strip layers for each intersection
5848 Int_t iLayerClusterSize = StripLayers[ilayer].GetStripHitClusterSize(i_strip_hit);
5849 Int_t jLayerClusterSize = StripLayers[jlayer].GetStripHitClusterSize(j_strip_hit);
5850
5851 // strip position in both strip layers for each intersection
5852 Double_t iLayerStripPosition = StripLayers[ilayer].ConvertPointToStripPosition(xcoord, ycoord);
5853 Double_t jLayerStripPosition = StripLayers[jlayer].ConvertPointToStripPosition(xcoord, ycoord);
5854
5855 if (StripLayers[ilayer].GetType() == LowerStripLayer) {
5856 IntersectionPoints_LowerLayerClusterSize.push_back(iLayerClusterSize);
5857 IntersectionPoints_UpperLayerClusterSize.push_back(jLayerClusterSize);
5858 IntersectionPoints_LowerLayerStripPosition.push_back(iLayerStripPosition);
5859 IntersectionPoints_UpperLayerStripPosition.push_back(jLayerStripPosition);
5861 StripLayers[ilayer].GetStripHitTotalSignal(i_strip_hit));
5863 StripLayers[jlayer].GetStripHitTotalSignal(j_strip_hit));
5864 LowerClusters.push_back(StripLayers[ilayer].GetStripClusters().at(i_strip_hit));
5865 UpperClusters.push_back(StripLayers[jlayer].GetStripClusters().at(j_strip_hit));
5866 UsageStatus_LowerClusters
5867 [StripLayers[ilayer].GetStripClusters()[i_strip_hit].GetUniqueID()] = true;
5868 UsageStatus_UpperClusters
5869 [StripLayers[jlayer].GetStripClusters()[j_strip_hit].GetUniqueID()] = true;
5870 } else {
5871 IntersectionPoints_LowerLayerClusterSize.push_back(jLayerClusterSize);
5872 IntersectionPoints_UpperLayerClusterSize.push_back(iLayerClusterSize);
5873 IntersectionPoints_LowerLayerStripPosition.push_back(jLayerStripPosition);
5874 IntersectionPoints_UpperLayerStripPosition.push_back(iLayerStripPosition);
5876 StripLayers[jlayer].GetStripHitTotalSignal(j_strip_hit));
5878 StripLayers[ilayer].GetStripHitTotalSignal(i_strip_hit));
5879 LowerClusters.push_back(StripLayers[jlayer].GetStripClusters().at(j_strip_hit));
5880 UpperClusters.push_back(StripLayers[ilayer].GetStripClusters().at(i_strip_hit));
5881 UsageStatus_LowerClusters
5882 [StripLayers[jlayer].GetStripClusters()[j_strip_hit].GetUniqueID()] = true;
5883 UsageStatus_UpperClusters
5884 [StripLayers[ilayer].GetStripClusters()[i_strip_hit].GetUniqueID()] = true;
5885 }
5886 //------------------------------------------------------
5887 }
5888 }
5889 }
5890 }
5891 }
5892 /*
5893 for(auto it : UsageStatus_UpperClusters) {
5894 if(it.second == false) cout << " upper_cluster[" << it.first << "] = " << it.second << "\n";
5895 }
5896 */
5897 for (auto it : UsageStatus_LowerClusters) {
5898
5899 // if(it.second == false) cout << " lower_cluster[" << it.first << "] = " << it.second << "\n";
5900
5901 if (it.second == false) {
5902
5903 for (Int_t ilayer = 0; ilayer < n_strip_layers; ++ilayer) {
5904
5905 if (StripLayers[ilayer].GetType() != LowerStripLayer)
5906 continue;
5907
5908 for (size_t icluster = 0; icluster < StripLayers[ilayer].GetStripClusters().size(); ++icluster) {
5909 StripCluster cluster = StripLayers[ilayer].GetStripClusters()[icluster];
5910
5911 if ((int)cluster.GetUniqueID() != it.first)
5912 continue;
5913
5914 Double_t xcoord;
5915 Double_t ycoord;
5916
5917 if (StripLayers[ilayer].GetStripNumberingOrder() == LeftToRight) {
5918 xcoord = StripLayers[ilayer].GetXLeftStripBorderPoint()
5919 + cluster.MeanPosition * StripLayers[ilayer].GetPitch();
5920 } else {
5921 xcoord = StripLayers[ilayer].GetXRightStripBorderPoint()
5922 - cluster.MeanPosition * StripLayers[ilayer].GetPitch();
5923 }
5924
5925 ycoord = (StripLayers[ilayer].GetYMinLayer() + StripLayers[ilayer].GetYMaxLayer()) * 0.5;
5926
5927 Double_t xerr = cluster.Error * StripLayers[ilayer].GetPitch();
5928 Double_t yerr = StripLayers[ilayer].GetYSize() * 0.5;
5929
5930 // cout << " low_cluster[" << icluster << "]:\n";
5931 // cout << " ID = " << cluster.GetUniqueID() << "\n";
5932 // cout << " xcoord = " << xcoord << "\n";
5933 // cout << " xmin = " << StripLayers[ilayer].GetXMinLayer() << ", xmax = " <<
5934 // StripLayers[ilayer].GetXMaxLayer() << "\n"; cout << " ycoord = " << ycoord << "\n"; cout << "
5935 // ymin = " << StripLayers[ilayer].GetYMinLayer() << ", ymax = " <<
5936 // StripLayers[ilayer].GetYMaxLayer() << "\n"; cout << " xerr = " << xerr << "\n"; cout << " yerr
5937 // = " << yerr << "\n"; cout << "\n";
5938
5939 PseudoIntersectionsX.push_back(xcoord);
5940 PseudoIntersectionsY.push_back(ycoord);
5941
5942 PseudoIntersectionsXErrors.push_back(xerr);
5943 PseudoIntersectionsYErrors.push_back(yerr);
5944
5947
5949 StripLayers[ilayer].ConvertPointToStripPosition(xcoord, ycoord));
5951
5954
5955 PseudoIntersectionMatches.push_back(
5956 StripLayers[ilayer].GetStripMatch((Int_t)cluster.MeanPosition)); // mc-match
5957 PseudoIntersectionDigitNumberMatches.push_back(StripLayers[ilayer].GetStripDigitNumberMatch(
5958 (Int_t)cluster.MeanPosition)); // digit number matches
5959
5960 LowerClusters_PseudoIntersections.push_back(cluster);
5962 StripCluster()); // empty cluster with default parameters
5963 }
5964 }
5965 }
5966 }
5967}
5968
5969// need for a separated test (find x,y intersection coords from strip positions)
5971 Double_t& y,
5972 Double_t strip_pos_layerA,
5973 Double_t strip_pos_layerB,
5974 Int_t layerA_index,
5975 Int_t layerB_index)
5976{
5977
5978 Int_t ilayer = layerA_index;
5979 Int_t jlayer = layerB_index;
5980
5981 if (StripLayers[ilayer].GetType() == StripLayers[jlayer].GetType())
5982 return false;
5983
5984 Double_t iAngleRad = StripLayers[ilayer].GetAngleRad();
5985 Double_t jAngleRad = StripLayers[jlayer].GetAngleRad();
5986
5987 if (Abs(iAngleRad - jAngleRad) < 0.01)
5988 return false;
5989
5990 Double_t i_PiOver2MinusAngleRad = PiOver2() - iAngleRad;
5991 Double_t j_PiOver2MinusAngleRad = PiOver2() - jAngleRad;
5992
5993 Double_t iStripHitPos = strip_pos_layerA;
5994 Double_t jStripHitPos = strip_pos_layerB;
5995
5996 Double_t xcoord;
5997 Double_t ycoord;
5998
5999 if (Abs(iAngleRad) >= 0.01 && Abs(jAngleRad) >= 0.01) {
6000
6001 Double_t iB = StripLayers[ilayer].CalculateStripEquationB(iStripHitPos);
6002 Double_t jB = StripLayers[jlayer].CalculateStripEquationB(jStripHitPos);
6003
6004 xcoord = (jB - iB) / (Tan(i_PiOver2MinusAngleRad) - Tan(j_PiOver2MinusAngleRad));
6005 ycoord = Tan(i_PiOver2MinusAngleRad) * xcoord + iB;
6006 } else {
6007
6008 if (Abs(iAngleRad) < 0.01) {
6009
6010 if (StripLayers[ilayer].GetStripNumberingOrder() == LeftToRight) {
6011 xcoord = StripLayers[ilayer].GetXLeftStripBorderPoint() + iStripHitPos * StripLayers[ilayer].GetPitch();
6012 } else {
6013 xcoord =
6014 StripLayers[ilayer].GetXRightStripBorderPoint() - iStripHitPos * StripLayers[ilayer].GetPitch();
6015 }
6016
6017 Double_t jB = StripLayers[jlayer].CalculateStripEquationB(jStripHitPos);
6018 ycoord = Tan(j_PiOver2MinusAngleRad) * xcoord + jB;
6019 }
6020
6021 if (Abs(jAngleRad) < 0.01) {
6022
6023 if (StripLayers[jlayer].GetStripNumberingOrder() == LeftToRight) {
6024 xcoord = StripLayers[jlayer].GetXLeftStripBorderPoint() + jStripHitPos * StripLayers[jlayer].GetPitch();
6025 } else {
6026 xcoord =
6027 StripLayers[jlayer].GetXRightStripBorderPoint() - jStripHitPos * StripLayers[jlayer].GetPitch();
6028 }
6029
6030 Double_t iB = StripLayers[ilayer].CalculateStripEquationB(iStripHitPos);
6031 ycoord = Tan(i_PiOver2MinusAngleRad) * xcoord + iB;
6032 }
6033 }
6034
6035 x = xcoord;
6036 y = ycoord;
6037
6038 // check: a point belongs to both strip layers together
6039 if (StripLayers[ilayer].IsPointInsideStripLayer(xcoord, ycoord)
6040 && StripLayers[jlayer].IsPointInsideStripLayer(xcoord, ycoord))
6041 {
6042 return true;
6043 }
6044
6045 return false;
6046}
6047
6065
6083
6085{
6086
6087 if (StripLayers.size() == 0)
6088 return;
6089
6090 XMinModule = 1.0E+10;
6091 XMaxModule = -1.0E+10;
6092 YMinModule = 1.0E+10;
6093 YMaxModule = -1.0E+10;
6094
6095 for (size_t ilayer = 0; ilayer < StripLayers.size(); ++ilayer) {
6096 if (StripLayers[ilayer].GetXMinLayer() < XMinModule)
6097 XMinModule = StripLayers[ilayer].GetXMinLayer();
6098 if (StripLayers[ilayer].GetXMaxLayer() > XMaxModule)
6099 XMaxModule = StripLayers[ilayer].GetXMaxLayer();
6100 if (StripLayers[ilayer].GetYMinLayer() < YMinModule)
6101 YMinModule = StripLayers[ilayer].GetYMinLayer();
6102 if (StripLayers[ilayer].GetYMaxLayer() > YMaxModule)
6103 YMaxModule = StripLayers[ilayer].GetYMaxLayer();
6104 }
6105 return;
6106}
6107
6108// -------------------------------------------------------------------------
6109
6111 Double_t gamma,
6112 Double_t charge,
6113 Double_t p0,
6114 Double_t p1)
6115{
6116 // AZ-110124 - compute number of ionization clusters
6117
6118 Double_t beta2 = beta * beta;
6119 Double_t gamma2 = gamma * gamma;
6120 Double_t val = p0 / beta2 * (p1 + TMath::Log(beta2 * gamma2) - beta2);
6121 return TMath::Min(val * charge * charge, 20000.);
6122}
static std::string CheckANNStripWindowSize(Int_t n_out)
vector< Double_t > dist(vector< Double_t > qp, Double_t mu)
Definition BmnMath.cxx:869
int i
Definition P4_F32vec4.h:22
bool Bool_t
int Int_t
float Float_t
constexpr Bool_t kFALSE
double Double_t
long long Long64_t
R__EXTERN TRandom * gRandom
@ LeftToRight
@ UpperStripLayer
@ LowerStripLayer
TClonesArray * GetMCTracks()
TClonesArray * GetMCPoints()
static std::shared_ptr< const BmnGemStripFdeepModel > GetShared(const std::string &path="")
const std::string & GetFilePath() const
bool Predict(const float *input, float *output) const
Double_t ConvertPointToStripPosition(Double_t x, Double_t y)
Bool_t IsPointInsideStripLayer(Double_t x, Double_t y)
bool CheckFeatureNames(const std::vector< std::string > &expected, std::string *why=nullptr) const
bool PredictSplit(const float *common, int n_common, const float *specific, int n_rows, float *output) const
static std::string GetDefaultPath()
const std::string & GetFilePath() const
static void SetDefaultPath(const std::string &path)
static std::shared_ptr< const BmnGemStripMLPModel > GetShared(const std::string &path="")
static BmnGemStripMedium & GetInstance()
vector< Double_t > IntersectionPointsYErrors
vector< Double_t > RealPointsXcenter
vector< Double_t > IntersectionPointsX
void GetANNWindowCenterPoint(Double_t x, Double_t y, Double_t z, Double_t x_out, Double_t y_out, Double_t z_out, Double_t &x_c, Double_t &y_c) const
Double_t FirstTransferGapThickness
vector< Double_t > PseudoIntersections_LowerLayerStripPosition
vector< Int_t > PseudoIntersections_UpperLayerClusterSize
Bool_t AddRealPointFull_ANNStripGenerator_Fdeep(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID, Int_t pdg_code=0, Double_t bfield=-1, Bool_t onlyStandard=kFALSE, vector< Int_t > segs={})
vector< Double_t > PseudoIntersections_UpperLayerStripPosition
BmnMatch GetStripMatchInLayer(Int_t layer_num, Int_t strip_num)
void SetAvalancheRadius(Double_t aval_radius)
vector< Double_t > XElectronPos
static void SetANNModelPath(TString path)
vector< Double_t > RealPointsXout
vector< Double_t > IntersectionPoints_LowerLayerStripTotalSignal
vector< vector< StripCluster > > AddedClusters
vector< Double_t > RealPointsMC
vector< StripCluster > UpperClusters
vector< StripCluster > LowerClusters
vector< BmnMatch > IntersectionPointDigitNumberMatches
BmnMatch GetStripDigitNumberMatchInLayer(Int_t layer_num, Int_t strip_num)
vector< Double_t > IntersectionPoints_UpperLayerStripTotalSignal
ElectronDriftDirectionInModule ElectronDriftDirection
vector< Int_t > PseudoIntersections_LowerLayerClusterSize
vector< Double_t > PseudoIntersections_UpperLayerStripTotalSignal
vector< Double_t > IntersectionPoints_LowerLayerStripPosition
vector< Double_t > PseudoIntersectionsY
vector< Double_t > PseudoIntersections_LowerLayerStripTotalSignal
vector< Double_t > IntersectionPointsY
Bool_t AddRealPointFull(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID)
Bool_t AddRealPointFull_ANNStripGenerator_MLP_Parallel(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID, Int_t pdg_code=0, Double_t bfield=-1, Bool_t onlyStandard=kFALSE, vector< Int_t > segs={})
Double_t GetStripSignalInLayer(Int_t layer_num, Int_t strip_num)
vector< BmnMatch > PseudoIntersectionMatches
vector< Double_t > YElectronPos
vector< Double_t > RealPointsY
vector< Double_t > IntersectionPoints_UpperLayerStripPosition
Bool_t AddRealPointSimple(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID)
vector< Int_t > SegmentedChargeDistribution
vector< Double_t > PseudoIntersectionsYErrors
Bool_t SetStripSignalInLayer(Int_t layer_num, Int_t strip_num, Double_t signal)
vector< Double_t > ElectronSignal
Bool_t IsPointInsideModule(Double_t x, Double_t y)
vector< Double_t > PseudoIntersectionsX
static TString GetANNModelPath()
vector< StripCluster > UpperClusters_PseudoIntersections
vector< BmnMatch > IntersectionPointMatches
vector< Double_t > RealPointsX
Bool_t AddRealPointFull_ANNGenerator(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID, Int_t pdg_code, Double_t bfield, Bool_t onlyStandard=false, vector< Int_t > segs={})
Bool_t AddStripSignalInLayer(Int_t layer_num, Int_t strip_num, Double_t signal)
Bool_t AddRealPointFullOne(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID)
Double_t GetNumberOfClusters(Double_t beta, Double_t gamma, Double_t charge, Double_t p0, Double_t p1)
Bool_t SetStripMatchInLayer(Int_t layer_num, Int_t strip_num, BmnMatch mc_match)
Bool_t AddRealPointFull_ANNStripGenerator_MLP_FastStand(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID, Int_t pdg_code=0, Double_t bfield=-1, Bool_t onlyStandard=kFALSE, vector< Int_t > segs={})
vector< Int_t > IntersectionPoints_UpperLayerClusterSize
Bool_t AddRealPoint(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID)
vector< Double_t > RealPointsYcenter
static Bool_t fUseANNGeneration
vector< BmnMatch > PseudoIntersectionDigitNumberMatches
vector< Int_t > IntersectionPoints_LowerLayerClusterSize
vector< Double_t > RealPointsYout
static constexpr Double_t kANNMaxThetaAngle
vector< StripCluster > LowerClusters_PseudoIntersections
StripCluster MakeCluster(Int_t layer_num, Double_t xcoord, Double_t ycoord, Double_t signal, Double_t radius)
void ResetElectronPointsAndClusters()
Bool_t IsPointInsideZThickness(Double_t z)
Bool_t SearchIntersectionPoint(Double_t &x, Double_t &y, Double_t strip_pos_layerA, Double_t strip_pos_layerB, Int_t layerA_index, Int_t layerB_index)
Double_t SecondTransferGapThickness
Bool_t AddRealPointFull_ANNStripGenerator_Fdeep_Parallel(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID, Int_t pdg_code=0, Double_t bfield=-1, Bool_t onlyStandard=kFALSE, vector< Int_t > segs={})
vector< Double_t > PseudoIntersectionsXErrors
Bool_t AddRealPointFull_ANNStripGenerator_MLP(Double_t x, Double_t y, Double_t z, Double_t px, Double_t py, Double_t pz, Double_t signal, Int_t refID, Int_t pdg_code=0, Double_t bfield=-1, Bool_t onlyStandard=kFALSE, vector< Int_t > segs={})
Double_t GetZPositionRegistered()
vector< Double_t > IntersectionPointsXErrors
void AddStripLayer(BmnGemStripLayer strip_layer)
vector< BmnGemStripLayer > StripLayers
Bool_t SetStripDigitNumberMatchInLayer(Int_t layer_num, Int_t strip_num, BmnMatch digit_num_match)
void AddLink(const BmnMatch &match)
Definition BmnMatch.cxx:40
CbmMCTrack.h.
Definition CbmMCTrack.h:26
Int_t GetPdgCode() const
Accessors.
Definition CbmMCTrack.h:56
Double_t TotalSignal
Int_t GetClusterSize()
Int_t GetType()
Double_t MeanPosition
Double_t Error
void AddStrip(Int_t strip_num, Double_t strip_signal)
Double_t PositionResidual
vector< Int_t > Strips
vector< Double_t > Signals
static TDatabasePDG * Instance()
TParticlePDG * GetParticle(const char *name) const
virtual Double_t GetParameter(const TString &name) const
virtual Double_t Integral(Double_t a, Double_t b, Double_t epsrel=1.e-12)
virtual void SetParameter(const TString &name, Double_t value)
virtual Double_t GetMean(Int_t axis=1) const
virtual TFitResultPtr Fit(const char *formula, Option_t *option="", Option_t *goption="", Double_t xmin=0, Double_t xmax=0)
virtual void SetBinContent(Int_t bin, Double_t content)
virtual TF1 * GetFunction(const char *name) const
Double_t Beta() const
Double_t Gamma() const
void SetVectM(const TVector3 &spatial, Double_t mass)
Int_t GetEntriesFast() const
TObject * UncheckedAt(Int_t i) const
virtual UInt_t GetUniqueID() const
Int_t PdgCode() const
Double_t Charge() const
Double_t Mass() const
virtual Double_t Gaus(Double_t mean=0, Double_t sigma=1)
double beta(double x, double y)
ElectronDriftDirectionInModule
@ BackwardZAxisEDrift
@ ForwardZAxisEDrift
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:20362
Expr< UnaryOp< Fabs< T >, Expr< A, T, D, D2, R >, T >, T, D, D2, R > fabs(const Expr< A, T, D, D2, R > &rhs)
Double_t Min(Double_t a, Double_t b)
Double_t ACos(Double_t)
Double_t Floor(Double_t x)
constexpr Double_t Sqrt2()
constexpr Double_t PiOver2()
Double_t Log(Double_t x)
Double_t Erfc(Double_t x)
constexpr Double_t Sigma()
Double_t Sqrt(Double_t x)
Double_t Cos(Double_t)
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)
constexpr Double_t RadToDeg()