Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
event.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file event.cxx
1 // TRENTO: Reduced Thickness Event-by-event Nuclear Topology
2 // Copyright 2015 Jonah E. Bernhard, J. Scott Moreland
3 // TRENTO3D: Three-dimensional extension of TRENTO by Weiyao Ke
4 // MIT License
5 
6 #include "event.h"
7 
8 #include <algorithm>
9 #include <cmath>
10 
11 #include <boost/program_options/variables_map.hpp>
12 #include "nucleus.h"
13 #include <iostream>
14 #include <stdexcept>
15 
16 namespace trento {
17 
18 namespace {
19 
20 constexpr double TINY = 1e-12;
21 
22 // Generalized mean for p > 0.
23 // M_p(a, b) = (1/2*(a^p + b^p))^(1/p)
24 inline double positive_pmean(double p, double a, double b) {
25  return std::pow(.5*(std::pow(a, p) + std::pow(b, p)), 1./p);
26 }
27 
28 // Generalized mean for p < 0.
29 // Same as the positive version, except prevents division by zero.
30 inline double negative_pmean(double p, double a, double b) {
31  if (a < TINY || b < TINY)
32  return 0.;
33  return positive_pmean(p, a, b);
34 }
35 
36 // Generalized mean for p == 0.
37 inline double geometric_mean(double a, double b) {
38  return std::sqrt(a*b);
39 }
40 
41 // Get beam rapidity from beam energy sqrt(s)
42 double beam_rapidity(double beam_energy) {
43  // Proton mass in GeV
44  constexpr double mp = 0.938;
45  return 0.5 * (beam_energy/mp + std::sqrt(std::pow(beam_energy/mp, 2) - 4.));
46 }
47 
48 } // unnamed namespace
49 
50 // Determine the grid parameters like so:
51 // 1. Read and set step size from the configuration.
52 // 2. Read grid max from the config, then set the number of steps as
53 // nsteps = ceil(2*max/step).
54 // 3. Set the actual grid max as max = nsteps*step/2. Hence if the step size
55 // does not evenly divide the config max, the actual max will be marginally
56 // larger (by at most one step size).
57 Event::Event(const VarMap& var_map)
58  : norm_(var_map["normalization"].as<double>()),
59  beam_energy_(var_map["beam-energy"].as<double>()),
60  exp_ybeam_(beam_rapidity(beam_energy_)),
61  mean_coeff_(var_map["mean-coeff"].as<double>()),
62  std_coeff_(var_map["std-coeff"].as<double>()),
63  skew_coeff_(var_map["skew-coeff"].as<double>()),
64  skew_type_(var_map["skew-type"].as<int>()),
65  dxy_(var_map["xy-step"].as<double>()),
66  deta_(var_map["eta-step"].as<double>()),
67  nsteps_(std::ceil(2.*var_map["xy-max"].as<double>()/dxy_)),
68  neta_(std::ceil(2.*var_map["eta-max"].as<double>()/(deta_+1e-15))),
69  xymax_(.5*nsteps_*dxy_),
70  etamax_(var_map["eta-max"].as<double>()),
71  eta2y_(var_map["jacobian"].as<double>(), etamax_, deta_),
72  cgf_(),
73  TA_(boost::extents[nsteps_][nsteps_]),
74  TB_(boost::extents[nsteps_][nsteps_]),
75  TR_(boost::extents[nsteps_][nsteps_][1]),
76  TAB_(boost::extents[nsteps_][nsteps_]),
77  with_ncoll_(var_map["ncoll"].as<bool>()),
78  density_(boost::extents[nsteps_][nsteps_][neta_]) {
79  // Check if the skew parameter is within the applicable range
80  // For 1: relative skew, skew_coeff_ < 10.
81  // 2: absolute skew, skew_coeff_ < 3.
82  try {
83  if ((skew_type_ == 1 && skew_coeff_ > 10.) ||
84  (skew_type_ == 2 && skew_coeff_ > 3.) ){
85  auto info = std::string("Error: skew coefficent too large to be stable.\n")
86  + std::string("Requirements: (1) relative skew, skew_coeff < 10\n")
87  + std::string(" (2) absolute skew, skew_coeff < 3");
88  throw std::invalid_argument(info);
89  }
90  }
91  catch (const std::invalid_argument& error){
92  std::cerr << error.what() << std::endl;
93  exit(1);
94  }
95 
96  // Choose which version of the generalized mean to use based on the
97  // configuration. The possibilities are defined above. See the header for
98  // more information.
99  auto p = var_map["reduced-thickness"].as<double>();
100  if (std::fabs(p) < TINY) {
101  compute_reduced_thickness_ = [this]() {
102  compute_reduced_thickness(geometric_mean);
103  };
104  } else if (p > 0.) {
105  compute_reduced_thickness_ = [this, p]() {
107  [p](double a, double b) { return positive_pmean(p, a, b); });
108  };
109  } else {
110  compute_reduced_thickness_ = [this, p]() {
112  [p](double a, double b) { return negative_pmean(p, a, b); });
113  };
114  }
115 }
116 
117 void Event::compute(const Nucleus& nucleusA, const Nucleus& nucleusB,
119  // Reset npart; compute_nuclear_thickness() increments it.
120  npart_ = 0;
121  compute_nuclear_thickness(nucleusA, profile, TA_);
122  compute_nuclear_thickness(nucleusB, profile, TB_);
125 }
126 
127 bool Event::is3D() const {
128  return etamax_ > TINY;
129 }
130 
131 namespace {
132 
133 // Limit a value to a range.
134 // Used below to constrain grid indices.
135 template <typename T>
136 inline const T& clip(const T& value, const T& min, const T& max) {
137  if (value < min)
138  return min;
139  if (value > max)
140  return max;
141  return value;
142 }
143 
144 } // unnamed namespace
145 
146 // WK: clear Ncoll density table
147 void Event::clear_TAB(void){
148  ncoll_ = 0;
149  for (int iy = 0; iy < nsteps_; ++iy) {
150  for (int ix = 0; ix < nsteps_; ++ix) {
151  TAB_[iy][ix] = 0.;
152  }
153  }
154 }
155 
156 // WK: accumulate a Tpp to Ncoll density table
158  ncoll_ ++;
159  // the loaction of A and B nucleon
160  double xA = A.x() + xymax_, yA = A.y() + xymax_;
161  double xB = B.x() + xymax_, yB = B.y() + xymax_;
162  // impact parameter squared of this binary collision
163  double bpp_sq = std::pow(xA - xB, 2) + std::pow(yA - yB, 2);
164  // the mid point of A and B
165  double x = (xA+xB)/2.;
166  double y = (yA+yB)/2.;
167  // the max radius of Tpp
168  const double r = profile.radius();
169  int ixmin = clip(static_cast<int>((x-r)/dxy_), 0, nsteps_-1);
170  int iymin = clip(static_cast<int>((y-r)/dxy_), 0, nsteps_-1);
171  int ixmax = clip(static_cast<int>((x+r)/dxy_), 0, nsteps_-1);
172  int iymax = clip(static_cast<int>((y+r)/dxy_), 0, nsteps_-1);
173 
174  // Add Tpp to Ncoll density.
175  auto norm_Tpp = profile.norm_Tpp(bpp_sq);
176  for (auto iy = iymin; iy <= iymax; ++iy) {
177  double dysqA = std::pow(yA - (static_cast<double>(iy)+.5)*dxy_, 2);
178  double dysqB = std::pow(yB - (static_cast<double>(iy)+.5)*dxy_, 2);
179  for (auto ix = ixmin; ix <= ixmax; ++ix) {
180  double dxsqA = std::pow(xA - (static_cast<double>(ix)+.5)*dxy_, 2);
181  double dxsqB = std::pow(xB - (static_cast<double>(ix)+.5)*dxy_, 2);
182  // The Ncoll density does not fluctuates, so we use the
183  // deterministic_thickness function
184  // where the Gamma fluctuation are turned off.
185  // since this binary collision already happened, the binary collision
186  // density should be normalized to one.
187  TAB_[iy][ix] += profile.deterministic_thickness(dxsqA + dysqA)
188  * profile.deterministic_thickness(dxsqB + dysqB)
189  / norm_Tpp;
190  }
191  }
192 }
193 
195  const Nucleus& nucleus, NucleonProfile& profile, Grid& TX) {
196  // Construct the thickness grid by looping over participants and adding each
197  // to a small subgrid within its radius. Compared to the other possibility
198  // (grid cells as the outer loop and participants as the inner loop), this
199  // reduces the number of required distance-squared calculations by a factor of
200  // ~20 (depending on the nucleon size). The Event unit test verifies that the
201  // two methods agree.
202 
203  // Wipe grid with zeros.
204  std::fill(TX.origin(), TX.origin() + TX.num_elements(), 0.);
205 
206  const double r = profile.radius();
207 
208  // Deposit each participant onto the grid.
209  for (const auto& nucleon : nucleus) {
210  if (!nucleon.is_participant())
211  continue;
212 
213  ++npart_;
214 
215  // Work in coordinates relative to (-width/2, -width/2).
216  double x = nucleon.x() + xymax_;
217  double y = nucleon.y() + xymax_;
218 
219  // Determine min & max indices of nucleon subgrid.
220  int ixmin = clip(static_cast<int>((x-r)/dxy_), 0, nsteps_-1);
221  int iymin = clip(static_cast<int>((y-r)/dxy_), 0, nsteps_-1);
222  int ixmax = clip(static_cast<int>((x+r)/dxy_), 0, nsteps_-1);
223  int iymax = clip(static_cast<int>((y+r)/dxy_), 0, nsteps_-1);
224 
225  // Prepare profile for new nucleon.
226  profile.fluctuate();
227 
228  // Add profile to grid.
229  for (auto iy = iymin; iy <= iymax; ++iy) {
230  double dysq = std::pow(y - (static_cast<double>(iy)+.5)*dxy_, 2);
231  for (auto ix = ixmin; ix <= ixmax; ++ix) {
232  double dxsq = std::pow(x - (static_cast<double>(ix)+.5)*dxy_, 2);
233  TX[iy][ix] += profile.thickness(dxsq + dysq);
234  }
235  }
236  }
237 }
238 
239 template <typename GenMean>
240 void Event::compute_reduced_thickness(GenMean gen_mean) {
241  double sum = 0.;
242  double ixcm = 0.;
243  double iycm = 0.;
244 
245  for (int iy = 0; iy < nsteps_; ++iy) {
246  for (int ix = 0; ix < nsteps_; ++ix) {
247  auto ta = TA_[iy][ix];
248  auto tb = TB_[iy][ix];
249  auto t = norm_ * gen_mean(ta, tb);
251  TR_[iy][ix][0] = t;
254  if (is3D()) {
255  auto mean = mean_coeff_ * mean_function(ta, tb, exp_ybeam_);
256  auto std = std_coeff_ * std_function(ta, tb);
257  auto skew = skew_coeff_ * skew_function(ta, tb, skew_type_);
258  cgf_.calculate_dsdy(mean, std, skew);
259  auto mid_norm = cgf_.interp_dsdy(0.)*eta2y_.Jacobian(0.);
260  for (int ieta = 0; ieta < neta_; ++ieta) {
261  auto eta = -etamax_ + ieta*deta_;
262  auto rapidity = eta2y_.rapidity(eta);
263  auto jacobian = eta2y_.Jacobian(eta);
264  auto rapidity_dist = cgf_.interp_dsdy(rapidity);
265  density_[iy][ix][ieta] = t * rapidity_dist / mid_norm * jacobian;
266  }
267  }
268 
269  sum += t;
270  // Center of mass grid indices.
271  // No need to multiply by dxy since it would be canceled later.
272  ixcm += t * static_cast<double>(ix);
273  iycm += t * static_cast<double>(iy);
274  }
275  }
276 
277  multiplicity_ = dxy_ * dxy_ * sum;
278  ixcm_ = ixcm / sum;
279  iycm_ = iycm / sum;
280 }
281 
283  // Compute eccentricity at mid rapidity
284 
285  // Simple helper class for use in the following loop.
286  struct EccentricityAccumulator {
287  double re = 0.; // real part
288  double im = 0.; // imaginary part
289  double wt = 0.; // weight
290  double finish() const // compute final eccentricity
291  { return std::sqrt(re*re + im*im) / std::fmax(wt, TINY); }
292  double angle() const // compute event plane angle
293  { return atan2(im, re); }
294  } e2, e3, e4, e5;
295 
296  for (int iy = 0; iy < nsteps_; ++iy) {
297  for (int ix = 0; ix < nsteps_; ++ix) {
298  const auto& t = TR_[iy][ix][0];
299  if (t < TINY)
300  continue;
301 
302  // Compute (x, y) relative to the CM and cache powers of x, y, r.
303  auto x = static_cast<double>(ix) - ixcm_;
304  auto x2 = x*x;
305  auto x3 = x2*x;
306  auto x4 = x2*x2;
307 
308  auto y = static_cast<double>(iy) - iycm_;
309  auto y2 = y*y;
310  auto y3 = y2*y;
311  auto y4 = y2*y2;
312 
313  auto r2 = x2 + y2;
314  auto r = std::sqrt(r2);
315  auto r4 = r2*r2;
316 
317  auto xy = x*y;
318  auto x2y2 = x2*y2;
319 
320  // The eccentricity harmonics are weighted averages of r^n*exp(i*n*phi)
321  // over the entropy profile (reduced thickness). The naive way to compute
322  // exp(i*n*phi) at a given (x, y) point is essentially:
323  //
324  // phi = arctan2(y, x)
325  // real = cos(n*phi)
326  // imag = sin(n*phi)
327  //
328  // However this implementation uses three unnecessary trig functions; a
329  // much faster method is to express the cos and sin directly in terms of x
330  // and y. For example, it is trivial to show (by drawing a triangle and
331  // using rudimentary trig) that
332  //
333  // cos(arctan2(y, x)) = x/r = x/sqrt(x^2 + y^2)
334  // sin(arctan2(y, x)) = y/r = x/sqrt(x^2 + y^2)
335  //
336  // This is easily generalized to cos and sin of (n*phi) by invoking the
337  // multiple angle formula, e.g. sin(2x) = 2sin(x)cos(x), and hence
338  //
339  // sin(2*arctan2(y, x)) = 2*sin(arctan2(y, x))*cos(arctan2(y, x))
340  // = 2*x*y / r^2
341  //
342  // Which not only eliminates the trig functions, but also naturally
343  // cancels the r^2 weight. This cancellation occurs for all n.
344  //
345  // The Event unit test verifies that the two methods agree.
346  e2.re += t * (x2 - y2);
347  e2.im += t * 2.*xy;
348  e2.wt += t * r2;
349 
350  e3.re += t * (x3 - 3.*x*y2);
351  e3.im += t * y*(3. - 4.*y2);
352  e3.wt += t * r2*r;
353 
354  e4.re += t * (x4 + y4 - 6.*x2y2);
355  e4.im += t * 4.*xy*(1. - 2.*y2);
356  e4.wt += t * r4;
357 
358  e5.re += t * x*(x4 - 10.*x2y2 - 5.*y4);
359  e5.im += t * y*(1. - 12.*x2 + 16.*x4);
360  e5.wt += t * r4*r;
361  }
362  }
363 
364  eccentricity_[2] = e2.finish();
365  eccentricity_[3] = e3.finish();
366  eccentricity_[4] = e4.finish();
367  eccentricity_[5] = e5.finish();
368 
369  psi_[2] = e2.angle()/2.;
370  psi_[3] = e3.angle()/3.;
371  psi_[4] = e4.angle()/4.;
372  psi_[5] = e5.angle()/5.;
373 }
374 
375 } // namespace trento