Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
EcoMug.h
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file EcoMug.h
1 
2 // EcoMug: Efficient COsmic MUon Generator //
3 // Copyright (C) 2021 Davide Pagano <davide.pagano@unibs.it> //
4 // EcoMug is based on the following work: //
5 // D. Pagano, G. Bonomi, A. Donzella, A. Zenoni, G. Zumerle, N. Zurlo, //
6 // "EcoMug: an Efficient COsmic MUon Generator for cosmic-ray muons applications", //
7 // doi:10.1016/j.nima.2021.165732 //
8 // //
9 // This program is free software: you can redistribute it and/or modify //
10 // it under the terms of the GNU General Public License as published by //
11 // the Free Software Foundation, either version 3 of the License, or //
12 // (at your option) any later version. //
13 // //
14 // This program is distributed in the hope that it will be useful, //
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
17 // GNU General Public License for more details. //
18 // //
19 // You should have received a copy of the GNU General Public License //
20 // along with this program. If not, see <https://www.gnu.org/licenses/>. //
22 
23 #ifndef EcoMug_H
24 #define EcoMug_H
25 
26 //#include <math.h>
27 #include <array>
28 #include <functional>
29 #include <random>
30 
34 class EMRandom
35 {
36  public:
38  {
39  std::random_device rd;
40  std::mt19937 gen(rd());
41  std::uniform_int_distribution<uint64_t> dis(0, std::numeric_limits<uint64_t>::max());
42  s[0] = 12345.; //dis(gen);
43  s[1] = 12345; //dis(gen);
44  };
45 
46  void SetSeed(uint64_t seed)
47  {
48  s[0] = seed;
49  s[1] = seed;
50  };
51 
53  {
54  uint64_t x = next();
55  return to_double(x);
56  };
57 
58  double GenerateRandomDouble(double x1, double x2)
59  {
60  return (x2 - x1) * GenerateRandomDouble() + x1;
61  };
62 
63  int64_t rotl(const uint64_t x, int k)
64  {
65  return (x << k) | (x >> (64 - k));
66  };
67 
68  uint64_t next()
69  {
70  const uint64_t s0 = s[0];
71  uint64_t s1 = s[1];
72  const uint64_t result = s0 + s1;
73  s1 ^= s0;
74  s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14);
75  s[1] = rotl(s1, 36);
76  return result;
77  };
78 
79  double to_double(uint64_t x)
80  {
81  union U
82  {
83  uint64_t i;
84  double d;
85  };
86  U u = {UINT64_C(0x3FF) << 52 | x >> 12};
87  return u.d - 1.0;
88  };
89 
90  uint64_t s[2];
91 };
94 
98 {
99  private:
101  size_t mPopSize;
102  size_t mNIter;
103  int mGenMethod; // 0 = sky, 1 = cylinder, 2 = hspere
104  double m_a;
105  double m_a2;
106  std::vector<std::vector<double> > mRanges;
107  std::vector<std::vector<double> > mPopulation;
108  double mBestCost;
109  std::vector<double> mBestSolution;
110  std::function<double(double, double)> mFunc;
111 
112  public:
113  EMMaximization(const EMRandom& random, int genMethod)
114  : mRandom(random)
115  , mPopSize(200)
116  , mNIter(500)
117  , mGenMethod(genMethod)
118  , m_a(0.)
119  , m_a2(0.)
120  , mBestCost(-1.)
121  {
122 // cppcheck-suppress [useInitializationList]
123  mFunc = &DefaultJ;
124  };
126 
127  static double DefaultJ(double p, double theta)
128  {
129  double n = std::max(0.1, 2.856 - 0.655 * log(p));
130  return 1600 * pow(p, 0.279) * pow(cos(theta), n);
131  }
133 
134  void SetParameters(double minP, double maxP, double minTheta, double maxTheta)
135  {
136  mRanges.push_back({minP, maxP});
137  mRanges.push_back({minTheta, maxTheta});
138  }
140 
141  void SetParameters(double minP, double maxP, double minTheta, double maxTheta, double minPhi, double maxPhi)
142  {
143  mRanges.push_back({minP, maxP});
144  mRanges.push_back({minTheta, maxTheta});
145  mRanges.push_back({minPhi, maxPhi});
146  mRanges.push_back({0, M_PI / 2.});
147  }
149 
150  void SetFunction(std::function<double(double, double)> func)
151  {
152  mFunc = func;
153  }
155 
156  double SkyFunc(double p, double theta)
157  {
158  return mFunc(p, theta) * cos(theta) * sin(theta);
159  }
161 
162  double CylFunc(double p, double theta)
163  {
164  return mFunc(p, theta) * pow(sin(theta), 2);
165  }
167 
168  double HSFunc(double p, double theta, double phi, double theta0)
169  {
170  return mFunc(p, theta) * (sin(theta0) * sin(theta) * cos(phi) + cos(theta0) * cos(theta)) * sin(theta);
171  }
173 
174  double Evaluate(std::vector<double>& v)
175  {
176  if (mGenMethod == 0)
177  {
178  return SkyFunc(v[0], v[1]);
179  }
180  else if (mGenMethod == 1)
181  {
182  return CylFunc(v[0], v[1]);
183  }
184  else
185  {
186  return HSFunc(v[0], v[1], v[2], v[3]);
187  }
188  return -1;
189  }
191 
192  void Evaluate()
193  {
194  double value;
195  for (size_t i = 0; i < mPopSize; ++i)
196  {
197  value = Evaluate(mPopulation[i]);
198  if (value > mBestCost)
199  {
200  mBestCost = value;
202  }
203  }
204  }
206 
207  void Init()
208  {
209  size_t dim = mRanges.size();
210  mPopulation.resize(mPopSize);
211  for (size_t i = 0; i < mPopSize; ++i)
212  {
213  mPopulation[i].resize(dim);
214  for (size_t j = 0; j < dim; ++j)
215  {
217  }
218  }
219  }
221 
222  void UpdateParameters(size_t t)
223  {
224  m_a = 2. - t * (2. / mNIter);
225  m_a2 = -1. + t * ((-1.) / mNIter);
226  }
228 
229  void Move()
230  {
231  double r1, r2, A, C, b, l, rw, p, D_tmp, D_best, distance;
232  std::vector<double> tmp;
233  for (size_t i = 0; i < mPopulation.size(); ++i)
234  {
237  A = 2 * m_a * r1 - m_a;
238  C = 2 * r2;
239  b = 1.;
240  l = (m_a2 - 1) * mRandom.GenerateRandomDouble() + 1;
242 
243  for (size_t j = 0; j < mPopulation[0].size(); ++j)
244  {
245  if (p < 0.5)
246  {
247  if (fabs(A) >= 1)
248  {
249  rw = floor(mRandom.GenerateRandomDouble() * mPopulation.size());
250  tmp = mPopulation[rw];
251  D_tmp = fabs(C * tmp[j] - mPopulation[i][j]);
252  mPopulation[i][j] = tmp[j] - A * D_tmp;
253  }
254  else
255  {
256  D_best = fabs(C * mBestSolution[j] - mPopulation[i][j]);
257  mPopulation[i][j] = mBestSolution[j] - A * D_best;
258  }
259  }
260  else
261  {
262  distance = fabs(mBestSolution[j] - mPopulation[i][j]);
263  mPopulation[i][j] = distance * exp(b * l) * cos(l * 2 * M_PI) + mBestSolution[j];
264  }
265  if (mPopulation[i][j] < mRanges[j][0]) mPopulation[i][j] = mRanges[j][0];
266  if (mPopulation[i][j] > mRanges[j][1]) mPopulation[i][j] = mRanges[j][1];
267  }
268  }
269  }
271 
272  double Maximize()
273  {
274  Init();
275  Evaluate();
276  for (size_t iter = 1; iter < mNIter; ++iter)
277  {
278  UpdateParameters(iter);
279  Move();
280  Evaluate();
281  }
282  return mBestCost;
283  }
284 };
287 
289 class EcoMug
290 {
291  friend class EMRandom;
292 
293  public:
296  {
297  Sky,
300  };
301 
302  private:
304  std::array<double, 3> mGenerationPosition;
312  double mMinimumPhi;
313  double mMaximumPhi;
314  int mCharge;
323  double mJPrime;
324  double mN;
325  double mRandAccRej;
326  double mPhi0;
327  double mTheta0;
328  bool mAccepted;
329  std::array<double, 2> mSkySize;
330  std::array<double, 3> mSkyCenterPosition;
333  std::array<double, 3> mCylinderCenterPosition;
335  // double mMaxFuncSkyCylinder;
336  std::array<double, 3> mHSphereCenterPosition;
338  std::array<double, 3> mMaxJ;
339  std::array<double, 3> mMaxCustomJ;
340  std::function<double(double, double)> mJ;
341 
342  public:
343  // Default constructor
345  : mGenMethod(Sky)
346  , mGenerationPosition({{0., 0., 0.}})
347  , mGenerationTheta(0.)
348  , mGenerationPhi(0.)
349  , mGenerationMomentum(0.)
350  , mMinimumMomentum(0.01)
351  , mMaximumMomentum(1000.)
352  , mMinimumTheta(0.)
353  , mMaximumTheta(M_PI / 2.)
354  , mMinimumPhi(0.)
355  , mMaximumPhi(2. * M_PI)
356  , mCharge(1)
358  , mCylinderMaxPositionPhi(2. * M_PI)
360  , mHSphereMaxPositionPhi(2. * M_PI)
362  , mHSphereMaxPositionTheta(M_PI / 2.)
365  , mJPrime(0.)
366  , mN(0.)
367  , mRandAccRej(0.)
368  , mPhi0(0.)
369  , mTheta0(0.)
370  , mAccepted(false)
371  , mSkySize({{0., 0.}})
372  , mSkyCenterPosition({{0., 0., 0.}})
373  , mCylinderHeight(0.)
374  , mCylinderRadius(0.)
375  , mCylinderCenterPosition({{0., 0., 0.}})
376  , mHSphereRadius(0.)
377  ,
378  /* mMaxFuncSkyCylinder(5.3176), */ mHSphereCenterPosition({{0., 0., 0.}})
379  {
380 // cppcheck-suppress [useInitializationList]
381  mMaxJ = {-1., -1., -1.};
382 // cppcheck-suppress [useInitializationList]
383  mMaxCustomJ = {-1., -1., -1.};
384  };
385 
387  // Methods to access the parameters of the generated muon
390  const std::array<double, 3>& GetGenerationPosition() const
391  {
392  return mGenerationPosition;
393  };
395  double GetGenerationMomentum() const
396  {
397  return mGenerationMomentum;
398  };
400  void GetGenerationMomentum(std::array<double, 3>& momentum) const
401  {
402  momentum = {
406  };
408  double GetGenerationTheta() const
409  {
410  return mGenerationTheta;
411  };
413  double GetGenerationPhi() const
414  {
415  return mGenerationPhi;
416  };
418  int GetCharge() const
419  {
420  return mCharge;
421  };
423 
425  // Methods for the geometry of the generation
428  void SetUseSky()
429  {
430  mGenMethod = Sky;
431  };
434  {
436  };
439  {
441  };
444  {
445  mGenMethod = genM;
446  };
447 
450  {
451  return mGenMethod;
452  };
454 
456  // Common methods to all geometries
461  void SetDifferentialFlux(std::function<double(double, double)> J)
462  {
463  mJ = J;
464  };
466  void SetSeed(uint64_t seed)
467  {
468  if (seed > 0) mRandom.SetSeed(seed);
469  };
472  {
474  };
477  {
479  };
481  void SetMinimumTheta(double theta)
482  {
484  };
486  void SetMaximumTheta(double theta)
487  {
489  };
491  void SetMinimumPhi(double phi)
492  {
493  mMinimumPhi = phi;
494  };
496  void SetMaximumPhi(double phi)
497  {
498  mMaximumPhi = phi;
499  };
500 
502  double GetMinimumMomentum() const
503  {
504  return mMinimumMomentum;
505  };
507  double GetMaximumMomentum() const
508  {
509  return mMaximumMomentum;
510  };
512  double GetMinimumTheta() const
513  {
514  return mMinimumTheta;
515  };
517  double GetMaximumTheta() const
518  {
519  return mMaximumTheta;
520  };
522  double GetMinimumPhi() const
523  {
524  return mMinimumPhi;
525  };
527  double GetMaximumPhi() const
528  {
529  return mMaximumPhi;
530  };
532 
534  // Methods for the plane-based generation
537  void SetSkySize(const std::array<double, 2>& size)
538  {
539  mSkySize = size;
540  };
541 
543  void SetSkyCenterPosition(const std::array<double, 3>& position)
544  {
546  };
548 
550  // Methods for the cylinder-based generation
553  void SetCylinderRadius(double radius)
554  {
555  mCylinderRadius = radius;
556  };
558  void SetCylinderHeight(double height)
559  {
560  mCylinderHeight = height;
561  };
563  void SetCylinderCenterPosition(const std::array<double, 3>& position)
564  {
566  };
568  {
570  };
572  {
574  };
576  double GetCylinderRadius() const
577  {
578  return mCylinderRadius;
579  };
581  double GetCylinderHeight() const
582  {
583  return mCylinderHeight;
584  };
586  const std::array<double, 3>& GetCylinderCenterPosition() const
587  {
589  };
591 
593  // Methods for the half sphere-based generation
596  void SetHSphereRadius(double radius)
597  {
598  mHSphereRadius = radius;
599  };
601  void SetHSphereCenterPosition(const std::array<double, 3>& position)
602  {
604  };
606  {
608  };
610  {
612  };
614  {
617  };
619  {
622  };
624  double GetHSphereRadius() const
625  {
626  return mHSphereRadius;
627  };
629  const std::array<double, 3>& GetHSphereCenterPosition() const
630  {
631  return mHSphereCenterPosition;
632  };
634 
635  private:
636  double F1Cumulative(double x)
637  {
638  return 1. - 8.534790171171021 / pow(x + 2.68, 87. / 40.);
639  };
640 
641  double F1Inverse(double x)
642  {
643  return (2.68 - 2.68 * pow(1. - x, 40. / 87.)) / pow(1. - x, 40. / 87.);
644  };
645 
646  double maxSkyJFunc()
647  {
648  return 1600 * pow(mMaximumMomentum, 0.279) * pow(cos(0.76158), 1.1) * sin(0.76158);
649  };
650 
651  double maxCylJFunc()
652  {
653  return 1600 * pow(mMaximumMomentum, 0.279) * pow(cos(1.35081), 0.1) * pow(sin(1.35081), 2);
654  };
655 
656  double maxHSJFunc()
657  {
658  return 1600 * pow(mMaximumMomentum, 0.279) * pow(cos(1.26452), 0.1) * (sin(1.26452) * sin(1.26452) + cos(1.26452) * cos(1.26452)) * sin(1.26452);
659  };
660 
662  {
664  return F1Inverse(z);
665  };
666 
668  {
672  };
673 
675  {
680  };
681 
683  {
684  EMMaximization maximizer(mRandom, mGenMethod);
685  maximizer.SetFunction(mJ);
686  if (mGenMethod == 0 || mGenMethod == 1)
687  {
689  }
690  else
691  {
693  }
694  mMaxCustomJ[mGenMethod] = maximizer.Maximize();
695  };
696 
698  {
699  EMMaximization maximizer(mRandom, mGenMethod);
700  if (mGenMethod == 0 || mGenMethod == 1)
701  {
703  }
704  else
705  {
707  }
708  mMaxJ[mGenMethod] = maximizer.Maximize();
709  };
710 
711  public:
715  void Generate()
716  {
717  mAccepted = false;
718 
719  if (mMaxJ[mGenMethod] < 0) ComputeMaximum();
720 
721  // Sky or cylinder generation
722  if (mGenMethod == Sky || mGenMethod == Cylinder)
723  {
724  // Generation of the momentum and theta angle
725  while (!mAccepted)
726  {
730  mN = 2.856 - 0.655 * log(mGenerationMomentum);
731  if (mN < 0.1) mN = 0.1;
732 
733  if (mGenMethod == Sky)
734  {
735  mJPrime = 1600 * pow(mGenerationMomentum, 0.279) * pow(cos(mGenerationTheta), mN + 1) * sin(mGenerationTheta);
736  if (mMaxJ[mGenMethod] * mRandAccRej < mJPrime) mAccepted = true;
737  }
738 
739  if (mGenMethod == Cylinder)
740  {
741  mJPrime = 1600 * pow(mGenerationMomentum, 0.279) * pow(cos(mGenerationTheta), mN) * pow(sin(mGenerationTheta), 2);
742  if (mMaxJ[mGenMethod] * mRandAccRej < mJPrime) mAccepted = true;
743  }
744  }
746 
747  // Generation of the position and phi angle
748  if (mGenMethod == Sky)
749  {
752  }
753  if (mGenMethod == Cylinder)
754  {
755  mAccepted = false;
757  while (!mAccepted)
758  {
761  if (mRandAccRej < fabs(cos(mGenerationPhi))) mAccepted = true;
762  }
764  if (mGenerationPhi >= 2. * M_PI) mGenerationPhi -= 2. * M_PI;
765 
766  // Check if the muon is inward
767  if (sin(mGenerationTheta) * cos(mGenerationPhi) * mGenerationPosition[0] + sin(mGenerationTheta) * sin(mGenerationPhi) * mGenerationPosition[1] > 0) Generate();
768  }
769  }
770 
771  // Half-sphere generation
772  if (mGenMethod == HSphere)
773  {
774  // Generation point on the half-sphere
776  while (!mAccepted)
777  {
783  mN = 2.856 - 0.655 * log(mGenerationMomentum);
784  if (mN < 0.1) mN = 0.1;
785 
786  mJPrime = 1600 * pow(mGenerationMomentum, 0.279) * pow(cos(mGenerationTheta), mN) * (sin(mGenerationTheta) * sin(mTheta0) * cos(mGenerationPhi) + cos(mGenerationTheta) * cos(mTheta0)) * sin(mGenerationTheta);
787  if (mMaxJ[mGenMethod] * mRandAccRej < mJPrime) mAccepted = true;
788  }
789 
791  mGenerationPosition[1] = mHSphereRadius * sin(mTheta0) * sin(mPhi0) + mHSphereCenterPosition[1];
792  mGenerationPosition[2] = mHSphereRadius * cos(mTheta0) + mHSphereCenterPosition[2];
793 
796  if (mGenerationPhi >= 2 * M_PI) mGenerationPhi -= 2 * M_PI;
797 
798  mGenerationPhi += M_PI;
799  if (mGenerationPhi >= 2 * M_PI) mGenerationPhi -= 2 * M_PI;
800  }
801 
802  // Generate the charge
803  if (mRandom.GenerateRandomDouble(-100,128)>=0) mCharge = 1;
804  else mCharge = -1;
805  };
807 
812  {
813  mAccepted = false;
814 
816 
817  // Sky or cylinder generation
818  if (mGenMethod == Sky || mGenMethod == Cylinder)
819  {
820  // Generation of the momentum and theta angle
821  while (!mAccepted)
822  {
826 
827  if (mGenMethod == Sky)
828  {
831  }
832 
833  if (mGenMethod == Cylinder)
834  {
837  }
838  }
840 
841  // Generation of the position and phi angle
842  if (mGenMethod == Sky)
843  {
846  }
847  if (mGenMethod == Cylinder)
848  {
849  mAccepted = false;
851  while (!mAccepted)
852  {
855  if (mRandAccRej < fabs(cos(mGenerationPhi))) mAccepted = true;
856  }
858  if (mGenerationPhi >= 2. * M_PI) mGenerationPhi -= 2. * M_PI;
859 
860  // Check if the muon is inward
861  if (sin(mGenerationTheta) * cos(mGenerationPhi) * mGenerationPosition[0] + sin(mGenerationTheta) * sin(mGenerationPhi) * mGenerationPosition[1] > 0) Generate();
862  }
863  }
864 
865  // Half-sphere generation
866  if (mGenMethod == HSphere)
867  {
868  // Generation point on the half-sphere
870  while (!mAccepted)
871  {
877 
878  mJPrime = mJ(mGenerationMomentum, mGenerationTheta) * (sin(mTheta0) * sin(mGenerationTheta) * cos(mGenerationPhi) + cos(mTheta0) * cos(mGenerationTheta)) * sin(mGenerationTheta);
880  }
881 
883  mGenerationPosition[1] = mHSphereRadius * sin(mTheta0) * sin(mPhi0) + mHSphereCenterPosition[1];
884  mGenerationPosition[2] = mHSphereRadius * cos(mTheta0) + mHSphereCenterPosition[2];
885 
888  if (mGenerationPhi >= 2 * M_PI) mGenerationPhi -= 2 * M_PI;
889 
890  mGenerationPhi += M_PI;
891  if (mGenerationPhi >= 2 * M_PI) mGenerationPhi -= 2 * M_PI;
892  }
893 
894  // Generate the charge
895  if (mRandom.GenerateRandomDouble(-100,128)>=0) mCharge = 1;
896  else mCharge = -1;
897  };
899 };
902 
903 #endif