Version: SMASH-3.4
hadgas_eos.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2016-2020,2022,2026
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #include "smash/hadgas_eos.h"
11 
12 #include <filesystem>
13 #include <fstream>
14 #include <iomanip>
15 #include <iostream>
16 
17 #include "gsl/gsl_sf_bessel.h"
18 
19 #include "smash/constants.h"
20 #include "smash/integrate.h"
21 #include "smash/interpolation.h"
22 #include "smash/logging.h"
23 #include "smash/random.h"
24 
25 namespace smash {
26 static constexpr int LResonances = LogArea::Resonances::id;
27 
28 EosTable::EosTable(double de, double dnb, double dq, size_t n_e, size_t n_nb,
29  size_t n_q)
30  : de_(de), dnb_(dnb), dq_(dq), n_e_(n_e), n_nb_(n_nb), n_q_(n_q) {
31  table_.resize(n_e_ * n_nb_ * n_q_);
32 }
33 
35  const std::string &eos_savefile_name) {
36  bool table_read_success = false, table_consistency = true;
37  if (std::filesystem::exists(eos_savefile_name)) {
38  std::cout << "Reading table from file " << eos_savefile_name << std::endl;
39  std::ifstream file;
40  file.open(eos_savefile_name, std::ios::in);
41  file >> de_ >> dnb_ >> dq_;
42  file >> n_e_ >> n_nb_ >> n_q_;
43  table_.resize(n_e_ * n_nb_ * n_q_);
44  for (size_t ie = 0; ie < n_e_; ie++) {
45  for (size_t inb = 0; inb < n_nb_; inb++) {
46  for (size_t iq = 0; iq < n_q_; iq++) {
47  double p, T, mub, mus, muq;
48  file >> p >> T >> mub >> mus >> muq;
49  table_[index(ie, inb, iq)] = {p, T, mub, mus, muq};
50  }
51  }
52  }
53  table_read_success = true;
54  std::cout << "Table consumed successfully." << std::endl;
55  }
56 
57  if (table_read_success) {
58  // Check if the saved table is consistent with the current particle table
59  std::cout << "Checking consistency of the table... " << std::endl;
60  constexpr size_t number_of_steps = 50;
61  const size_t ie_step = 1 + n_e_ / number_of_steps;
62  const size_t inb_step = 1 + n_nb_ / number_of_steps;
63  const size_t iq_step = 1 + n_q_ / number_of_steps;
64  for (size_t ie = 0; ie < n_e_; ie += ie_step) {
65  for (size_t inb = 0; inb < n_nb_; inb += inb_step) {
66  for (size_t iq = 0; iq < n_q_; iq += iq_step) {
67  const table_element x = table_[index(ie, inb, iq)];
68  const bool w = eos.account_for_resonance_widths();
69  const double e_comp = eos.energy_density(x.T, x.mub, x.mus, x.muq);
70  const double nb_comp =
71  eos.net_baryon_density(x.T, x.mub, x.mus, x.muq, w);
72  const double ns_comp =
73  eos.net_strange_density(x.T, x.mub, x.mus, x.muq, w);
74  const double p_comp = eos.pressure(x.T, x.mub, x.mus, x.muq, w);
75  const double nq_comp =
76  eos.net_charge_density(x.T, x.mub, x.mus, x.muq, w);
77  // Precision is just 10^-3, this is precision of saved data in the
78  // file
79  const double eps = 1.e-3;
80  // Only check the physical region, hence T > 0 condition
81  if ((std::abs(de_ * ie - e_comp) > eps ||
82  std::abs(dnb_ * inb - nb_comp) > eps ||
83  std::abs(ns_comp) > eps || std::abs(x.p - p_comp) > eps ||
84  std::abs(dq_ * iq - nq_comp) > eps) &&
85  (x.T > 0.0)) {
86  std::cout << "discrepancy: " << de_ * ie << " = " << e_comp << ", "
87  << dnb_ * inb << " = " << nb_comp << ", " << x.p << " = "
88  << p_comp << ", 0 = " << ns_comp << ", " << dq_ * iq
89  << " = " << nq_comp << std::endl;
90  table_consistency = false;
91  goto finish_consistency_check;
92  }
93  }
94  }
95  }
96  }
97 finish_consistency_check:
98 
99  if (!table_read_success || !table_consistency) {
100  std::cout << "Compiling an EoS table..." << std::endl;
101  const double ns = 0.0;
102  for (size_t ie = 0; ie < n_e_; ie++) {
103  std::cout << ie << "/" << n_e_ << "\r" << std::flush;
104  const double e = de_ * ie;
105  for (size_t inb = 0; inb < n_nb_; inb++) {
106  const double nb = dnb_ * inb;
107  for (size_t iq = 0; iq < n_q_; iq++) {
108  const double q = dq_ * iq;
109  // It is physically impossible to have energy density > nucleon
110  // mass*nb, therefore eqns have no solutions.
111  if (nb >= e || q >= e) {
112  table_[index(ie, inb, iq)] = {0.0, 0.0, 0.0, 0.0, 0.0};
113  continue;
114  }
115  // Take extrapolated (T, mub, mus, muq) as initial approximation
116  std::array<double, 4> init_approx;
117  if (inb >= 2) {
118  const table_element y = table_[index(ie, inb - 2, iq)];
119  const table_element x = table_[index(ie, inb - 1, iq)];
120  init_approx = {2.0 * x.T - y.T, 2.0 * x.mub - y.mub,
121  2.0 * x.mus - y.mus, 2.0 * x.muq - y.muq};
122  } else if (iq >= 2) {
123  const table_element y = table_[index(ie, inb, iq - 2)];
124  const table_element x = table_[index(ie, inb, iq - 1)];
125  init_approx = {2.0 * x.T - y.T, 2.0 * x.mub - y.mub,
126  2.0 * x.mus - y.mus, 2.0 * x.muq - y.muq};
127  } else {
128  init_approx = eos.solve_eos_initial_approximation(e, nb, q);
129  }
130  const std::array<double, 4> res =
131  eos.solve_eos(e, nb, ns, q, init_approx);
132  const double T = res[0];
133  const double mub = res[1];
134  const double mus = res[2];
135  const double muq = res[3];
136  const bool w = eos.account_for_resonance_widths();
137  table_[index(ie, inb, iq)] = {eos.pressure(T, mub, mus, muq, w), T,
138  mub, mus, muq};
139  }
140  }
141  }
142  // Save table to file
143  std::cout << "Saving table to file " << eos_savefile_name << std::endl;
144  std::ofstream file;
145  file.open(eos_savefile_name, std::ios::out);
146  file << de_ << " " << dnb_ << " " << dq_ << std::endl;
147  file << n_e_ << " " << n_nb_ << " " << n_q_ << std::endl;
148  file << std::setprecision(7);
149  file << std::fixed;
150  for (size_t ie = 0; ie < n_e_; ie++) {
151  for (size_t inb = 0; inb < n_nb_; inb++) {
152  for (size_t iq = 0; iq < n_q_; iq++) {
153  const EosTable::table_element x = table_[index(ie, inb, iq)];
154  file << x.p << " " << x.T << " " << x.mub << " " << x.mus << " "
155  << x.muq << std::endl;
156  }
157  }
158  }
159  }
160 }
161 
162 void EosTable::get(EosTable::table_element &res, double e, double nb,
163  double q) const {
164  const size_t ie = static_cast<size_t>(std::floor(e / de_));
165  const size_t inb = static_cast<size_t>(std::floor(nb / dnb_));
166  const size_t iq = static_cast<size_t>(std::floor(q / dq_));
167 
168  if (ie >= n_e_ - 1 || inb >= n_nb_ - 1 || iq >= n_q_ - 1) {
169  res = {-1.0, -1.0, -1.0, -1.0, -1.0};
170  } else {
171  // 1st order interpolation
172  const double ae = e / de_ - ie;
173  const double an = nb / dnb_ - inb;
174  const double aq = q / dq_ - iq;
175  const EosTable::table_element s1 = table_[index(ie, inb, iq)];
176  const EosTable::table_element s2 = table_[index(ie + 1, inb, iq)];
177  const EosTable::table_element s3 = table_[index(ie, inb + 1, iq)];
178  const EosTable::table_element s4 = table_[index(ie + 1, inb + 1, iq)];
179  const EosTable::table_element s5 = table_[index(ie, inb, iq + 1)];
180  const EosTable::table_element s6 = table_[index(ie + 1, inb, iq + 1)];
181  const EosTable::table_element s7 = table_[index(ie, inb + 1, iq + 1)];
182  const EosTable::table_element s8 = table_[index(ie + 1, inb + 1, iq + 1)];
183 
184  res.p = detail::interpolate_trilinear(ae, an, aq, s1.p, s2.p, s3.p, s4.p,
185  s5.p, s6.p, s7.p, s8.p);
186  res.T = detail::interpolate_trilinear(ae, an, aq, s1.T, s2.T, s3.T, s4.T,
187  s5.T, s6.T, s7.T, s8.T);
188  res.mub =
189  detail::interpolate_trilinear(ae, an, aq, s1.mub, s2.mub, s3.mub,
190  s4.mub, s5.mub, s6.mub, s7.mub, s8.mub);
191  res.mus =
192  detail::interpolate_trilinear(ae, an, aq, s1.mus, s2.mus, s3.mus,
193  s4.mus, s5.mus, s6.mus, s7.mus, s8.mus);
194  res.muq =
195  detail::interpolate_trilinear(ae, an, aq, s1.muq, s2.muq, s3.muq,
196  s4.muq, s5.muq, s6.muq, s7.muq, s8.muq);
197  }
198 }
199 
200 HadronGasEos::HadronGasEos(bool tabulate, bool account_for_width)
201  : x_(gsl_vector_alloc(n_equations_)),
202  tabulate_(tabulate),
203  account_for_resonance_widths_(account_for_width) {
204  const gsl_multiroot_fsolver_type *solver_type;
205  solver_type = gsl_multiroot_fsolver_hybrid;
206  solver_ = gsl_multiroot_fsolver_alloc(solver_type, n_equations_);
208  logg[LResonances].error(
209  "Compilation of hadron gas EoS table requested with"
210  " account of resonance spectral functions. This is not "
211  "advised, as it will likely take a few days to finish."
212  " Besides, the effect of resonance widths is currently not "
213  "implemented for energy density computation, so the computed"
214  " table will be inconsistent anyways.");
215  }
216  if (tabulate_) {
217  eos_table_.compile_table(*this);
218  }
219 }
220 
222  gsl_multiroot_fsolver_free(solver_);
223  gsl_vector_free(x_);
224 }
225 
227  double mu_over_T) {
228  double x = mu_over_T - m_over_T;
229  if (x < -500.0) {
230  return 0.0;
231  }
232  x = std::exp(x);
233  // In the case of small masses: K_n(z) -> (n-1)!/2 *(2/z)^n, z -> 0,
234  // z*z*K_2(z) -> 2
235  return (m_over_T < really_small)
236  ? 2.0 * x
237  : m_over_T * m_over_T * x * gsl_sf_bessel_Kn_scaled(2, m_over_T);
238 }
239 
241  double beta, double mub, double mus,
242  double muq,
243  bool account_for_width) {
244  const double m_over_T = ptype.mass() * beta;
245  double mu_over_T = beta * (ptype.baryon_number() * mub +
246  ptype.strangeness() * mus + ptype.charge() * muq);
247  const double g = ptype.spin() + 1;
248  if (ptype.is_stable() || !account_for_width) {
249  return g * scaled_partial_density_auxiliary(m_over_T, mu_over_T);
250  } else {
251  // Integral \int_{threshold}^{\infty} A(m) N_{thermal}(m) dm,
252  // where A(m) is the spectral function of the resonance.
253  const double m0 = ptype.mass();
254  const double w0 = ptype.width_at_pole();
255  const double mth = ptype.min_mass_spectral();
256  const double u_min = std::atan(2.0 * (mth - m0) / w0);
257  const double u_max = 0.5 * M_PI;
259  const double result =
260  g * integrate(u_min, u_max, [&](double u) {
261  // One of many possible variable substitutions. Not clear if it has
262  // any advantages, except transforming (m_th, inf) to finite interval.
263  const double tanu = std::tan(u);
264  const double m = m0 + 0.5 * w0 * tanu;
265  const double jacobian = 0.5 * w0 * (1.0 + tanu * tanu);
266  return ptype.full_spectral_function(m) * jacobian *
267  scaled_partial_density_auxiliary(m * beta, mu_over_T);
268  });
269  return result;
270  }
271 }
272 
273 double HadronGasEos::partial_density(const ParticleType &ptype, double T,
274  double mub, double mus, double muq,
275  bool account_for_width) {
276  if (T < really_small) {
277  return 0.0;
278  }
279  return prefactor_ * T * T * T *
280  scaled_partial_density(ptype, 1.0 / T, mub, mus, muq,
281  account_for_width);
282 }
283 
284 double HadronGasEos::energy_density(double T, double mub, double mus,
285  double muq) {
286  if (T < really_small) {
287  return 0.0;
288  }
289  const double beta = 1.0 / T;
290  double e = 0.0;
291  for (const ParticleType &ptype : ParticleType::list_all()) {
292  if (!is_eos_particle(ptype)) {
293  continue;
294  }
295  const double z = ptype.mass() * beta;
296  double x = beta * (mub * ptype.baryon_number() + mus * ptype.strangeness() +
297  muq * ptype.charge() - ptype.mass());
298  if (x < -500.0) {
299  return 0.0;
300  }
301  x = std::exp(x);
302  const size_t g = ptype.spin() + 1;
303  // Small mass case, z*z*K_2(z) -> 2, z*z*z*K_1(z) -> 0 at z->0
304  e += (z < really_small) ? 3.0 * g * x
305  : z * z * g * x *
306  (3.0 * gsl_sf_bessel_Kn_scaled(2, z) +
307  z * gsl_sf_bessel_K1_scaled(z));
308  }
309  e *= prefactor_ * T * T * T * T;
310  return e;
311 }
312 
313 double HadronGasEos::density(double T, double mub, double mus, double muq,
314  bool account_for_width) {
315  if (T < really_small) {
316  return 0.0;
317  }
318  const double beta = 1.0 / T;
319  double rho = 0.0;
320  for (const ParticleType &ptype : ParticleType::list_all()) {
321  if (!is_eos_particle(ptype)) {
322  continue;
323  }
324  rho +=
325  scaled_partial_density(ptype, beta, mub, mus, muq, account_for_width);
326  }
327  rho *= prefactor_ * T * T * T;
328  return rho;
329 }
330 
331 double HadronGasEos::net_baryon_density(double T, double mub, double mus,
332  double muq, bool account_for_width) {
333  if (T < really_small) {
334  return 0.0;
335  }
336  const double beta = 1.0 / T;
337  double rho = 0.0;
338  for (const ParticleType &ptype : ParticleType::list_all()) {
339  if (!ptype.is_baryon() || !is_eos_particle(ptype)) {
340  continue;
341  }
342  rho +=
343  scaled_partial_density(ptype, beta, mub, mus, muq, account_for_width) *
344  ptype.baryon_number();
345  }
346  rho *= prefactor_ * T * T * T;
347  return rho;
348 }
349 
350 double HadronGasEos::net_strange_density(double T, double mub, double mus,
351  double muq, bool account_for_width) {
352  if (T < really_small) {
353  return 0.0;
354  }
355  const double beta = 1.0 / T;
356  double rho = 0.0;
357  for (const ParticleType &ptype : ParticleType::list_all()) {
358  if (ptype.strangeness() == 0 || !is_eos_particle(ptype)) {
359  continue;
360  }
361  rho +=
362  scaled_partial_density(ptype, beta, mub, mus, muq, account_for_width) *
363  ptype.strangeness();
364  }
365  rho *= prefactor_ * T * T * T;
366  return rho;
367 }
368 
369 double HadronGasEos::net_charge_density(double T, double mub, double mus,
370  double muq, bool account_for_width) {
371  if (T < really_small) {
372  return 0.0;
373  }
374  const double beta = 1.0 / T;
375  double rho = 0.0;
376  for (const ParticleType &ptype : ParticleType::list_all()) {
377  if (ptype.charge() == 0 || !is_eos_particle(ptype)) {
378  continue;
379  }
380  rho +=
381  scaled_partial_density(ptype, beta, mub, mus, muq, account_for_width) *
382  ptype.charge();
383  }
384  rho *= prefactor_ * T * T * T;
385  return rho;
386 }
387 
389  double beta) {
390  if (ptype.is_stable()) {
391  return ptype.mass();
392  }
393  // Sampling mass m from A(m) x^2 BesselK_2(x), where x = beta m.
394  // Strategy employs the idea of importance sampling:
395  // -- Sample mass from the simple Breit-Wigner first, then
396  // reject by the ratio.
397  // -- Note that f(x) = x^2 BesselK_2(x) monotonously decreases
398  // and has maximum at x = xmin. That is why instead of f(x)
399  // the ratio f(x)/f(xmin) is used.
400 
401  const double max_mass = 5.0; // GeV
402  double m, q;
403  {
404  // Allow underflows in exponentials
405  DisableFloatTraps guard(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
406  const double w0 = ptype.width_at_pole();
407  const double mth = ptype.min_mass_spectral();
408  const double m0 = ptype.mass();
409  double max_ratio = m0 * m0 * std::exp(-beta * m0) *
410  gsl_sf_bessel_Kn_scaled(2, m0 * beta) *
411  ptype.full_spectral_function(m0) /
413  // Heuristic adaptive maximum search to find max_ratio
414  constexpr int npoints = 31;
415  double m_lower = mth, m_upper = max_mass, m_where_max = m0;
416 
417  for (size_t n_iterations = 0; n_iterations < 2; n_iterations++) {
418  const double dm = (m_upper - m_lower) / npoints;
419  for (size_t i = 1; i < npoints; i++) {
420  m = m_lower + dm * i;
421  const double thermal_factor =
422  m * m * std::exp(-beta * m) * gsl_sf_bessel_Kn_scaled(2, m * beta);
423  q = ptype.full_spectral_function(m) * thermal_factor /
425  if (q > max_ratio) {
426  max_ratio = q;
427  m_where_max = m;
428  }
429  }
430  m_lower = m_where_max - (m_where_max - m_lower) * 0.1;
431  m_upper = m_where_max + (m_upper - m_where_max) * 0.1;
432  }
433  // Safety factor
434  max_ratio *= 1.5;
435 
436  do {
437  // sample mass from A(m)
438  do {
439  m = random::cauchy(m0, 0.5 * w0, mth, max_mass);
440  const double thermal_factor =
441  m * m * std::exp(-beta * m) * gsl_sf_bessel_Kn_scaled(2, m * beta);
442  q = ptype.full_spectral_function(m) * thermal_factor /
444  } while (q < random::uniform(0., max_ratio));
445  if (q > max_ratio) {
446  logg[LResonances].warn(
447  ptype.name(), " - maximum increased in",
448  " sample_mass_thermal from ", max_ratio, " to ", q, ", mass = ", m,
449  " previously assumed maximum at m = ", m_where_max);
450  max_ratio = q;
451  m_where_max = m;
452  } else {
453  break;
454  }
455  } while (true);
456  }
457  return m;
458 }
459 
460 double HadronGasEos::mus_net_strangeness0(double T, double mub, double muq) {
461  // Binary search
462  double mus_u = mub + T;
463  double mus_l = 0.0;
464  double mus, rhos;
465  size_t iteration = 0;
466  // 50 iterations should give precision 2^-50 ~ 10^-15
467  const size_t max_iteration = 50;
468  do {
469  mus = 0.5 * (mus_u + mus_l);
470  rhos = net_strange_density(T, mub, mus, muq);
471  if (rhos > 0.0) {
472  mus_u = mus;
473  } else {
474  mus_l = mus;
475  }
476  iteration++;
477  } while (std::abs(rhos) > tolerance_ && iteration < max_iteration);
478  if (iteration == max_iteration) {
479  throw std::runtime_error("Solving rho_s = 0: too many iterations.");
480  }
481  return mus;
482 }
483 
484 int HadronGasEos::set_eos_solver_equations(const gsl_vector *x, void *params,
485  gsl_vector *f) {
486  double e = reinterpret_cast<struct rparams *>(params)->e;
487  double nb = reinterpret_cast<struct rparams *>(params)->nb;
488  double ns = reinterpret_cast<struct rparams *>(params)->ns;
489  double nq = reinterpret_cast<struct rparams *>(params)->nq;
490  bool w = reinterpret_cast<struct rparams *>(params)->account_for_width;
491 
492  const double T = gsl_vector_get(x, 0);
493  const double mub = gsl_vector_get(x, 1);
494  const double mus = gsl_vector_get(x, 2);
495  const double muq = gsl_vector_get(x, 3);
496 
497  gsl_vector_set(f, 0, energy_density(T, mub, mus, muq) - e);
498  gsl_vector_set(f, 1, net_baryon_density(T, mub, mus, muq, w) - nb);
499  gsl_vector_set(f, 2, net_strange_density(T, mub, mus, muq, w) - ns);
500  gsl_vector_set(f, 3, net_charge_density(T, mub, mus, muq, w) - nq);
501 
502  return GSL_SUCCESS;
503 }
504 
505 double HadronGasEos::e_equation(double T, void *params) {
506  const double edens = reinterpret_cast<struct eparams *>(params)->edens;
507  return edens - energy_density(T, 0.0, 0.0, 0.0);
508 }
509 
510 std::array<double, 4> HadronGasEos::solve_eos_initial_approximation(double e,
511  double nb,
512  double nq) {
513  assert(e >= 0.0);
514  // 1. Get temperature from energy density assuming zero chemical potentials
515  int degeneracies_sum = 0.0;
516  for (const ParticleType &ptype : ParticleType::list_all()) {
517  if (is_eos_particle(ptype)) {
518  degeneracies_sum += ptype.spin() + 1;
519  }
520  }
521  // Temperature in case of massless gas. For massive it should be larger.
522  const double T_min = std::pow(e / prefactor_ / 6 / degeneracies_sum, 1. / 4.);
523  // Simply assume that the temperature is not higher than 2 GeV.
524  const double T_max = 2.0;
525 
526  struct eparams parameters = {e};
527  gsl_function F = {&e_equation, &parameters};
528  const gsl_root_fsolver_type *T = gsl_root_fsolver_brent;
529  gsl_root_fsolver *e_solver;
530  e_solver = gsl_root_fsolver_alloc(T);
531  gsl_root_fsolver_set(e_solver, &F, T_min, T_max);
532 
533  int iter = 0, status, max_iter = 100;
534  double T_init = 0.0;
535 
536  do {
537  iter++;
538  status = gsl_root_fsolver_iterate(e_solver);
539  if (status != GSL_SUCCESS) {
540  break;
541  }
542  T_init = gsl_root_fsolver_root(e_solver);
543  double x_lo = gsl_root_fsolver_x_lower(e_solver);
544  double x_hi = gsl_root_fsolver_x_upper(e_solver);
545  status = gsl_root_test_interval(x_lo, x_hi, 0.0, 0.001);
546  } while (status == GSL_CONTINUE && iter < max_iter);
547 
548  if (status != GSL_SUCCESS) {
549  std::stringstream err_msg;
550  err_msg << "Solver of equation for temperature with e = " << e
551  << " failed to converge. Maybe Tmax = " << T_max << " is too small?"
552  << std::endl;
553  throw std::runtime_error(gsl_strerror(status) + err_msg.str());
554  }
555 
556  gsl_root_fsolver_free(e_solver);
557 
558  // 2. Get the baryon chemical potential for muS = muQ = 0 with previously
559  // obtained T
560  double n_only_baryons = 0.0;
561  for (const ParticleType &ptype : ParticleType::list_all()) {
562  if (is_eos_particle(ptype) && ptype.baryon_number() == 1) {
563  n_only_baryons +=
564  scaled_partial_density(ptype, 1.0 / T_init, 0.0, 0.0, 0.0);
565  }
566  }
567  const double nb_scaled = nb / prefactor_ / (T_init * T_init * T_init);
568  double mub_init = T_init * std::asinh(nb_scaled / n_only_baryons / 2.0);
569 
570  // 3. Get the charge chemical potential assuming muB = muS = 0 with previously
571  // obtained T
572  double n_only_charge_1_particles = 0.0;
573  for (const ParticleType &ptype : ParticleType::list_all()) {
574  if (is_eos_particle(ptype) && ptype.charge() == 1) {
575  n_only_charge_1_particles +=
576  scaled_partial_density(ptype, 1.0 / T_init, 0.0, 0.0, 0.0);
577  }
578  }
579  const double q_scaled = nq / prefactor_ / (T_init * T_init * T_init);
580  double muq_init =
581  T_init * std::asinh(q_scaled / n_only_charge_1_particles / 2.0);
582 
583  // 4. Get the strange chemical potential, where mus = 0 is typically a good
584  // initial approximation
585  std::array<double, 4> initial_approximation = {T_init, mub_init, 0.0,
586  muq_init};
587  return initial_approximation;
588 }
589 
590 std::array<double, 4> HadronGasEos::solve_eos(
591  double e, double nb, double ns, double nq,
592  std::array<double, 4> initial_approximation) {
593  int residual_status = GSL_SUCCESS;
594  size_t iter = 0;
595 
596  struct rparams p = {e, nb, ns, nq, account_for_resonance_widths_};
597  gsl_multiroot_function f = {&HadronGasEos::set_eos_solver_equations,
598  n_equations_, &p};
599 
600  gsl_vector_set(x_, 0, initial_approximation[0]);
601  gsl_vector_set(x_, 1, initial_approximation[1]);
602  gsl_vector_set(x_, 2, initial_approximation[2]);
603  gsl_vector_set(x_, 3, initial_approximation[3]);
604 
605  gsl_multiroot_fsolver_set(solver_, &f, x_);
606  do {
607  iter++;
608  const auto iterate_status = gsl_multiroot_fsolver_iterate(solver_);
609  // std::cout << print_solver_state(iter);
610 
611  // Avoiding too low temperature
612  if (gsl_vector_get(solver_->x, 0) < 0.015) {
613  return {0.0, 0.0, 0.0, 0.0};
614  }
615 
616  // check if solver is stuck
617  if (iterate_status) {
618  break;
619  }
620  residual_status = gsl_multiroot_test_residual(solver_->f, tolerance_);
621  } while (residual_status == GSL_CONTINUE && iter < 1000);
622 
623  if (residual_status != GSL_SUCCESS) {
624  std::stringstream solver_parameters;
625  solver_parameters << "\nSolver run with "
626  << "e = " << e << ", nb = " << nb << ", ns = " << ns
627  << ", nq = " << nq
628  << ", init. approx.: " << initial_approximation[0] << " "
629  << initial_approximation[1] << " "
630  << initial_approximation[2] << " "
631  << initial_approximation[3] << std::endl;
632  logg[LResonances].warn(gsl_strerror(residual_status) +
633  solver_parameters.str() + print_solver_state(iter));
634  }
635 
636  return {gsl_vector_get(solver_->x, 0), gsl_vector_get(solver_->x, 1),
637  gsl_vector_get(solver_->x, 2), gsl_vector_get(solver_->x, 3)};
638 }
639 
640 std::string HadronGasEos::print_solver_state(size_t iter) const {
641  std::stringstream s;
642  // clang-format off
643  s << "iter = " << iter << ","
644  << " x = " << gsl_vector_get(solver_->x, 0) << " "
645  << gsl_vector_get(solver_->x, 1) << " "
646  << gsl_vector_get(solver_->x, 2) << ", "
647  << gsl_vector_get(solver_->x, 3) << ", "
648  << "f(x) = " << gsl_vector_get(solver_->f, 0) << " "
649  << gsl_vector_get(solver_->f, 1) << " "
650  << gsl_vector_get(solver_->f, 2) << " "
651  << gsl_vector_get(solver_->f, 3) << std::endl;
652  // clang-format on
653  return s.str();
654 }
655 
656 namespace detail {
657 
658 double interpolate_trilinear(double ax, double ay, double az, double f1,
659  double f2, double f3, double f4, double f5,
660  double f6, double f7, double f8) {
661  assert(ax >= 0 && ax <= 1);
662  assert(ay >= 0 && ay <= 1);
663  assert(az >= 0 && az <= 1);
664  double res = az * (ax * (ay * f8 + (1.0 - ay) * f6) +
665  (1.0 - ax) * (ay * f7 + (1.0 - ay) * f5)) +
666  (1 - az) * (ax * (ay * f4 + (1.0 - ay) * f2) +
667  (1.0 - ax) * (ay * f3 + (1.0 - ay) * f1));
668  return res;
669 }
670 
671 } // namespace detail
672 
673 } // namespace smash
Guard type that safely disables floating point traps for the scope in which it is placed.
Definition: fpenvironment.h:79
std::vector< table_element > table_
Storage for the tabulated equation of state.
Definition: hadgas_eos.h:96
void compile_table(HadronGasEos &eos, const std::string &eos_savefile_name="hadgas_eos.dat")
Computes the actual content of the table (for EosTable description see documentation of the construct...
Definition: hadgas_eos.cc:34
size_t index(size_t ie, size_t inb, size_t inq) const
proper index in a 1d vector, where the 3d table is stored
Definition: hadgas_eos.h:92
double dnb_
Step in net-baryon density.
Definition: hadgas_eos.h:100
size_t n_q_
Number of steps in net-charge density.
Definition: hadgas_eos.h:108
double de_
Step in energy density.
Definition: hadgas_eos.h:98
EosTable(double de, double dnb, double dq, size_t n_e, size_t n_b, size_t n_q)
Sets up a table p/T/muB/mus/muQ versus (e, nb, nq), where e - energy density, nb - net baryon density...
Definition: hadgas_eos.cc:28
size_t n_e_
Number of steps in energy density.
Definition: hadgas_eos.h:104
double dq_
Step in net-charge density.
Definition: hadgas_eos.h:102
void get(table_element &res, double e, double nb, double nq) const
Obtain interpolated p/T/muB/muS/muQ from the tabulated equation of state given energy density,...
Definition: hadgas_eos.cc:162
size_t n_nb_
Number of steps in net-baryon density.
Definition: hadgas_eos.h:106
Class to handle the equation of state (EoS) of the hadron gas, consisting of all hadrons included in ...
Definition: hadgas_eos.h:124
gsl_multiroot_fsolver * solver_
Definition: hadgas_eos.h:451
static double net_charge_density(double T, double mub, double mus, double muq, bool account_for_resonance_widths=false)
Compute net charge density.
Definition: hadgas_eos.cc:369
static constexpr double prefactor_
Constant factor, that appears in front of many thermodyn. expressions.
Definition: hadgas_eos.h:430
static double partial_density(const ParticleType &ptype, double T, double mub, double mus, double muq, bool account_for_resonance_widths=false)
Compute partial density of one hadron sort.
Definition: hadgas_eos.cc:273
static double sample_mass_thermal(const ParticleType &ptype, double beta)
Sample resonance mass in a thermal medium.
Definition: hadgas_eos.cc:388
std::array< double, 4 > solve_eos(double e, double nb, double ns, double nq, std::array< double, 4 > initial_approximation)
Compute temperature and chemical potentials given energy-, net baryon-, net strangeness- and net char...
Definition: hadgas_eos.cc:590
static double scaled_partial_density_auxiliary(double m_over_T, double mu_over_T)
Function used to avoid duplications in density calculations.
Definition: hadgas_eos.cc:226
EosTable eos_table_
EOS Table to be used.
Definition: hadgas_eos.h:440
gsl_vector * x_
Variables used by gnu equation solver.
Definition: hadgas_eos.h:448
static double mus_net_strangeness0(double T, double mub, double muq)
Compute strangeness chemical potential, requiring that net strangeness = 0.
Definition: hadgas_eos.cc:460
bool account_for_resonance_widths() const
If resonance spectral functions are taken into account.
Definition: hadgas_eos.h:362
static double density(double T, double mub, double mus, double muq, bool account_for_resonance_widths=false)
Compute particle number density.
Definition: hadgas_eos.cc:313
const bool tabulate_
Create an EoS table or not?
Definition: hadgas_eos.h:454
static bool is_eos_particle(const ParticleType &ptype)
Check if a particle belongs to the EoS.
Definition: hadgas_eos.h:354
static int set_eos_solver_equations(const gsl_vector *x, void *params, gsl_vector *f)
Interface EoS equations to be solved to gnu library.
Definition: hadgas_eos.cc:484
std::string print_solver_state(size_t iter) const
Helpful printout, useful for debugging if gnu equation solving goes crazy.
Definition: hadgas_eos.cc:640
static double e_equation(double T, void *params)
Definition: hadgas_eos.cc:505
static double net_baryon_density(double T, double mub, double mus, double muq, bool account_for_resonance_widths=false)
Compute net baryon density.
Definition: hadgas_eos.cc:331
static double energy_density(double T, double mub, double mus, double muq)
Compute energy density.
Definition: hadgas_eos.cc:284
static double net_strange_density(double T, double mub, double mus, double muq, bool account_for_resonance_widths=false)
Compute net strangeness density.
Definition: hadgas_eos.cc:350
const bool account_for_resonance_widths_
Use pole masses of resonances or integrate over spectral functions.
Definition: hadgas_eos.h:457
std::array< double, 4 > solve_eos_initial_approximation(double e, double nb, double nq)
Compute a reasonable initial approximation for solve_eos.
Definition: hadgas_eos.cc:510
static constexpr size_t n_equations_
Number of equations in the system of equations to be solved.
Definition: hadgas_eos.h:437
HadronGasEos(bool tabulate, bool account_for_widths)
Constructor of HadronGasEos.
Definition: hadgas_eos.cc:200
static double pressure(double T, double mub, double mus, double muq, bool account_for_resonance_widths=false)
Compute pressure .
Definition: hadgas_eos.h:194
static constexpr double tolerance_
Precision of equation solving.
Definition: hadgas_eos.h:434
static double scaled_partial_density(const ParticleType &ptype, double beta, double mub, double mus, double muq, bool account_for_width=false)
Compute (unnormalized) density of one hadron sort - helper functions used to reduce code duplication.
Definition: hadgas_eos.cc:240
A C++ interface for numerical integration in one dimension with the GSL CQUAD integration functions.
Definition: integrate.h:106
Particle type contains the static properties of a particle species.
Definition: particletype.h:100
double min_mass_spectral() const
The minimum mass of the resonance, where the spectral function is non-zero.
double breit_wigner_spectral_function(double m) const
This one is the most simple form of the spectral function, using a Cauchy distribution (non-relativis...
int strangeness() const
Definition: particletype.h:215
double full_spectral_function(double m) const
Full spectral function of the resonance (relativistic Breit-Wigner distribution with mass-dependent ...
const std::string & name() const
Definition: particletype.h:144
int32_t charge() const
The charge of the particle.
Definition: particletype.h:191
static const ParticleTypeList & list_all()
Definition: particletype.cc:51
bool is_stable() const
Definition: particletype.h:251
double width_at_pole() const
Definition: particletype.h:153
double mass() const
Definition: particletype.h:147
unsigned int spin() const
Definition: particletype.h:194
int baryon_number() const
Definition: particletype.h:212
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.h:245
double interpolate_trilinear(double ax, double ay, double az, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8)
Perform a trilinear 1st order interpolation.
Definition: hadgas_eos.cc:658
constexpr int p
Proton.
T beta(T a, T b)
Draws a random number from a beta-distribution, where probability density of is .
Definition: random.h:373
T uniform(T min, T max)
Definition: random.h:91
T cauchy(T pole, T width, T min, T max)
Draws a random number from a Cauchy distribution (sometimes also called Lorentz or non-relativistic B...
Definition: random.h:351
Definition: action.h:24
static Integrator integrate
Definition: decaytype.cc:143
static constexpr int LResonances
constexpr double really_small
Numerical error tolerance.
Definition: constants.h:41
Define the data structure for one element of the table.
Definition: hadgas_eos.h:57
double mub
Net baryochemical potential.
Definition: hadgas_eos.h:63
double muq
Net charge chemical potential.
Definition: hadgas_eos.h:67
double T
Temperature.
Definition: hadgas_eos.h:61
double mus
Net strangeness potential.
Definition: hadgas_eos.h:65
Another structure for passing energy density to the gnu library.
Definition: hadgas_eos.h:382
A structure for passing equation parameters to the gnu library.
Definition: hadgas_eos.h:368