Version: SMASH-3.1
quantumsampling.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2020,2022
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #include "smash/quantumsampling.h"
11 
12 #include <cstdlib>
13 #include <iostream>
14 #include <random>
15 
16 #include "gsl/gsl_integration.h"
17 #include "gsl/gsl_multiroots.h"
18 
20 #include "smash/constants.h"
21 #include "smash/distributions.h"
22 #include "smash/logging.h"
23 #include "smash/particletype.h"
24 
25 namespace smash {
26 
27 /*
28  * Root equations and GSL procedure for finding the momentum for which the
29  * maximum of a given Juttner distribution occurs. This is needed for a method
30  * of sampling the distribution function in which one samples uniformly below
31  * the maximum of the distribution.
32  */
33 
34 double QuantumSampling::p_max_root_equation(double p, double mass,
35  double temperature,
36  double effective_chemical_potential,
37  double statistics) {
38  const double Ekin = std::sqrt(p * p + mass * mass);
39  const double term1 =
40  2 * (1 + statistics * std::exp(-(Ekin - effective_chemical_potential) /
41  temperature));
42  const double term2 = (p * p) / (temperature * Ekin);
43 
44  return term1 - term2;
45 }
46 
47 int QuantumSampling::p_max_root_equation_for_GSL(const gsl_vector *roots_array,
48  void *parameters,
49  gsl_vector *function) {
51  static_cast<struct ParametersForMaximumMomentumRootFinder *>(parameters);
52 
53  const double mass = (par->mass);
54  const double temperature = (par->temperature);
55  const double effective_chemical_potential =
57  const double statistics = (par->statistics);
58 
59  const double p_radial = gsl_vector_get(roots_array, 0);
60 
61  gsl_vector_set(function, 0,
62  p_max_root_equation(p_radial, mass, temperature,
63  effective_chemical_potential, statistics));
64 
65  return GSL_SUCCESS;
66 }
67 
68 void QuantumSampling::print_state_p_max(unsigned int iter,
69  gsl_multiroot_fsolver *solver) {
70  std::printf(
71  "\n***\nfind_p_at_maximum_of_the_distribution(): iter = %3u \t"
72  "x = % .3f \t"
73  "f(x) = % .3e \n",
74  iter, gsl_vector_get(solver->x, 0), gsl_vector_get(solver->f, 0));
75 }
76 
78  double mass, double temperature, double effective_chemical_potential,
79  double statistics, double p_max_initial_guess, double solution_precision,
80  double *p_max) {
81  const gsl_multiroot_fsolver_type *Solver_name;
82  gsl_multiroot_fsolver *Root_finder;
83 
84  int status;
85  size_t iter = 0;
86  size_t initial_guess_update = 0;
87 
88  const size_t problem_dimension = 1;
89 
90  struct ParametersForMaximumMomentumRootFinder parameters = {
91  mass, temperature, effective_chemical_potential, statistics};
92 
93  gsl_multiroot_function MaximumOfDistribution = {
94  &p_max_root_equation_for_GSL, problem_dimension, &parameters};
95 
96  double roots_array_initial[1] = {p_max_initial_guess};
97 
98  gsl_vector *roots_array = gsl_vector_alloc(problem_dimension);
99  gsl_vector_set(roots_array, 0, roots_array_initial[0]);
100 
101  Solver_name = gsl_multiroot_fsolver_hybrids;
102  Root_finder = gsl_multiroot_fsolver_alloc(Solver_name, problem_dimension);
103  gsl_multiroot_fsolver_set(Root_finder, &MaximumOfDistribution, roots_array);
104 
105  // print_state_p_max (iter, Root_finder);
106 
107  do {
108  iter++;
109 
110  /*
111  * gsl_multiroot_fsolver_iterate returns either 0 for a correct behavior,
112  * or an error code (a positive integer) when the solver is stuck
113  */
114  status = gsl_multiroot_fsolver_iterate(Root_finder);
115 
116  // print_state_p_max (iter, Root_finder);
117 
118  /*
119  * Check whether the solver is stuck
120  */
121  if (status) {
122  if (initial_guess_update < 100) {
123  /*
124  * In case the solution is not found for the (somewhat small) default
125  * initial guess of p_max_initial_guess = 0.05 [GeV], the value of the
126  * p_max_initial_guess is increased and the root solving procedure is
127  * restarted. This can take place up to a 100 times, with the largest
128  * possible initial guess of p_max_initial_guess = 5.05 [GeV].
129  */
130  p_max_initial_guess += 0.05;
131  initial_guess_update++;
132  roots_array_initial[0] = p_max_initial_guess;
133  gsl_vector_set(roots_array, 0, roots_array_initial[0]);
134  gsl_multiroot_fsolver_set(Root_finder, &MaximumOfDistribution,
135  roots_array);
136  iter = 0;
137  } else {
138  std::cout << "\n\nGSL error message:\n"
139  << gsl_strerror(status) << std::endl;
140  logg[LogArea::Distributions::id].warn(
141  "\n\nThe GSL solver"
142  "\nfind_p_at_maximum_of_the_distribution\nis stuck!"
143  "\n\nInput parameters:"
144  "\n mass [GeV] = ",
145  mass, "\n temperature [GeV] = ", temperature,
146  "\neffective_chemical_potential = ", effective_chemical_potential,
147  "\n statistics = ", statistics,
148  "\n solution_precision = ", solution_precision,
149  "\n\n"
150  "Initialization cannot sample the momenta without "
151  "calculating the distribution maximum."
152  "\nTry adjusting the initial guess (which is "
153  "looped over in the GSL procedure) or the "
154  "solution precision."
155  "\nUncomment print_state_p_max to check solver progress.\n\n\n");
156  throw std::runtime_error(
157  "QuantumSampling::find_p_at_maximum_of_the_distribution returned "
158  "no result.\n\n");
159  continue;
160  }
161  }
162 
163  status = gsl_multiroot_test_residual(Root_finder->f, solution_precision);
164 
165  if (status == GSL_SUCCESS) {
166  p_max[0] = gsl_vector_get(Root_finder->x, 0);
167  }
168  } while (status == GSL_CONTINUE && iter < 100000);
169 
170  gsl_multiroot_fsolver_free(Root_finder);
171  gsl_vector_free(roots_array);
172 
173  return 0;
174 }
175 
177  double mass, double temperature, double effective_chemical_potential,
178  double statistics, double solution_precision) {
179  /*
180  * Momentum at which the distribution function has its maximum.
181  */
182  double p_max[1];
183  p_max[0] = 0.0;
184 
185  /*
186  * Initial guess for the value of p_max, in GeV. This value is
187  * looped over within the GSL solver, so that many initial guesses
188  * are probed if the solution is not found.
189  */
190  double initial_guess_p_max = 0.050;
191 
192  /*
193  * Calling the GSL distribution maximum finder
194  */
196  mass, temperature, effective_chemical_potential, statistics,
197  initial_guess_p_max, solution_precision, p_max);
198 
199  double distribution_function_maximum =
200  p_max[0] * p_max[0] *
201  juttner_distribution_func(p_max[0], mass, temperature,
202  effective_chemical_potential, statistics);
203 
204  return distribution_function_maximum;
205 }
206 
207 /*
208  * Initializing the QuantumSampling object triggers calculation of the
209  * chemical potential and distribution maximum for all species present.
210  */
212  const std::map<PdgCode, int> &initial_multiplicities, double volume,
213  double temperature)
214  : volume_(volume), temperature_(temperature) {
215  /*
216  * This is the precision which we expect from the solution; note that
217  * solution precision also goes into the precision of calculating the
218  * integrals involved etc. Recommended precision is at least 1e-7.
219  */
220  constexpr double solution_precision = 1e-8;
221 
222  for (const auto &pdg_and_mult : initial_multiplicities) {
223  const PdgCode pdg = pdg_and_mult.first;
224  const int number_of_particles = pdg_and_mult.second;
225  const double V_in_GeV = volume_ / (hbarc * hbarc * hbarc);
226  const double number_density = number_of_particles / V_in_GeV;
227  const double spin_degeneracy = pdg.spin_degeneracy();
228  // '+' for fermions, '-' for bosons
229  const double quantum_statistics = (pdg.spin() % 2 == 0) ? -1.0 : 1.0;
230  const ParticleType &ptype = ParticleType::find(pdg);
231  const double particle_mass = ptype.mass();
232  double chemical_potential = 0.0;
233  ChemicalPotentialSolver mu_solver;
234  // Calling the wrapper for the GSL chemical potential finder
235  chemical_potential = mu_solver.effective_chemical_potential(
236  spin_degeneracy, particle_mass, number_density, temperature_,
237  quantum_statistics, solution_precision);
238  effective_chemical_potentials_[pdg] = chemical_potential;
239  const double distribution_function_maximum = maximum_of_the_distribution(
240  particle_mass, temperature_, chemical_potential, quantum_statistics,
241  solution_precision);
242  distribution_function_maximums_[pdg] = distribution_function_maximum;
243  }
244 }
245 
246 /*
247  * Sampling radial momenta of given particle species from Bose, Boltzmann, or
248  * Fermi distribution. The choice between the distributions is made based on
249  * the <statistics> variable:
250  * -1 for Bose
251  * 0 fot Boltzmann
252  * +1 for Fermi
253  *
254  * This sampler is the simplest implementation of sampling based on sampling
255  * from a uniform distribution.
256  */
257 double QuantumSampling::sample(const PdgCode pdg) {
258  const ParticleType &ptype = ParticleType::find(pdg);
259  const double mass = ptype.mass();
260  const double mu = effective_chemical_potentials_.find(pdg)->second;
261  const double distr_max = distribution_function_maximums_.find(pdg)->second;
262  /*
263  * The variable maximum_momentum denotes the "far right" boundary of the
264  * sampled region; we assume that no particle has momentum larger than 10 GeV
265  */
266  constexpr double maximum_momentum = 10.0; // in [GeV]
267  const double statistics = (pdg.spin() % 2 == 0) ? -1.0 : 1.0;
268  double sampled_momentum = 0.0, sampled_ratio = 0.0;
269 
270  do {
271  sampled_momentum = random::uniform(0.0, maximum_momentum);
272  double distribution_at_sampled_p =
273  sampled_momentum * sampled_momentum *
274  juttner_distribution_func(sampled_momentum, mass, temperature_, mu,
275  statistics);
276  sampled_ratio = distribution_at_sampled_p / distr_max;
277  } while (random::canonical() > sampled_ratio);
278 
279  return sampled_momentum;
280 }
281 
282 } // namespace smash
A class which encapsulates a GSL algorithm for finding the effective chemical potential and supportin...
double effective_chemical_potential(double degeneracy, double mass, double number_density, double temperature, double statistics, double solution_precision)
Convenience wrapper for finding the effective chemical potential for a given particle species and per...
Particle type contains the static properties of a particle species.
Definition: particletype.h:98
static const ParticleType & find(PdgCode pdgcode)
Returns the ParticleType object for the given pdgcode.
Definition: particletype.cc:99
double mass() const
Definition: particletype.h:145
PdgCode stores a Particle Data Group Particle Numbering Scheme particle type number.
Definition: pdgcode.h:111
unsigned int spin() const
Definition: pdgcode.h:608
unsigned int spin_degeneracy() const
Definition: pdgcode.h:644
QuantumSampling(const std::map< PdgCode, int > &initial_multiplicities, double volume, double temperature)
Constructor of a QuantumSampling object.
static double p_max_root_equation(double p, double mass, double temperature, double effective_chemical_potential, double statistics)
Root equation for finding the radial momentum at which the Juttner distribution function has its maxi...
const double temperature_
Temperature [GeV].
static int find_p_at_maximum_of_the_distribution(double mass, double temperature, double effective_chemical_potential, double statistics, double p_max_initial_guess, double solution_precision, double *p_max)
A GSL root solver for finding the radial momentum value at which the maximum of the given Juttner dis...
std::map< PdgCode, double > distribution_function_maximums_
Tabulated distribution function maxima for every particle species.
static double maximum_of_the_distribution(double mass, double temperature, double effective_chemical_potential, double statistics, double solution_precision)
A convenience wrapper for finding the maximum value of the Juttner distribution, returning the value ...
std::map< PdgCode, double > effective_chemical_potentials_
Tabulated effective chemical potentials for every particle species.
const double volume_
Volume [fm^3] in which particles sre sampled.
static void print_state_p_max(unsigned int iter, gsl_multiroot_fsolver *solver)
A GSL utility which allows for printing out the status of the solver during the root finding procedur...
static int p_max_root_equation_for_GSL(const gsl_vector *roots_array, void *parameters, gsl_vector *function)
Root equation for finding the radial momentum at which the Juttner distribution function has its maxi...
double sample(const PdgCode pdg)
Sampling radial momenta of given particle species from Boltzmann, Bose, or Fermi distribution.
Collection of useful constants that are known at compile time.
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
constexpr int p
Proton.
T uniform(T min, T max)
Definition: random.h:88
T canonical()
Definition: random.h:113
Definition: action.h:24
double juttner_distribution_func(double momentum_radial, double mass, double temperature, double effective_chemical_potential, double statistics)
Relativistic Juttner distribution function is just a convenience wrapper for displaying Fermi,...
constexpr double hbarc
GeV <-> fm conversion factor.
Definition: constants.h:25
Struct object that holds the parameters relevant to finding the momentum for which the maximum of the...
double statistics
quantum statistics of the particles species (+1 for Fermi, -1 for Bose, 0 for Boltzmann)
double effective_chemical_potential
effective chemical potential mu^* of the particle species